From 72528b5e2445fc87e9e0c651a2ad2134fe41d71c Mon Sep 17 00:00:00 2001 From: Michael <64731211+neutrinoks@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:47:34 +0400 Subject: [PATCH] feat: upgrade to v2.0.2 (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ScanConfig defaults no longer sort results (#1539) * accounts-db: get rid of const in tests (#1732) get rid of const in tests Co-authored-by: HaoranYi * rpc: add more services to getClusterNodes (#1637) * rpc: add tpu_forwards/tpu_forwards_quic to getClusterNodes * add tvu * order * add tpu_vote * add serve_repair * fix tests * fix name in tests * remove tvu / repair quic * wen_restart: Ignore Gossip messages from my own pubkey. (#1678) * CHANGELOG: `Copy` no longer derived on Rent and EpochSchedule (#1746) * SVM: update spec (#1757) * remove unused build dep `cc` from solana-program (#1659) * remove unused build dep `cc` from solana-program * remove cc from workspace * refactor: clean up geyser-plugin-manager (#1570) * Remove redundant Option return type from accountinfo_from_shared_account_data * Remove redundant Option return type from accountinfo_from_stored_account_meta * add retries to transaction sending in LocalCluster (#1747) * ci: upgrade client-traget macos version (#1765) * ledger-tool: Use helper for AccountsDb args (#1749) There are a handful of commands that specify AccountsDb config. To create consistency between all these commands and remove repeated code, build all of the arguments in a helper function that is passed to the relevant commands. * Simd-118: [HAL-02] add lamports burned to error log (#1768) Add lamports burned to error log * Simd-118: [HAL-01] add log to unreachable macro (#1767) Add log to unreachable macro * Verify elf with active feature set before attempting deployment (#1654) Co-authored-by: Jon C * Simd-118: [HAL-04] simplify assertion that epoch contains enough slots for partitions (#1769) Simplify assertion that epoch contains enough slots for partitions * [docs] Update ZK Token Proof program to ZK ElGamal Proof program (#1758) * docs: update ZK Token Proof program to ZK ElGamal Proof program * remove blank line at eof * Program Runtime: Unify transaction batch program caches (#1399) * local program cache: add `modified_entries` field * use `modified_entries` for modified program cache * invoke context: make `program_cache_for_tx_batch` mutable * invoke context: unify local program cache instances * remove `find_program_in_cache` alias * SVM: Unify different instances of epoch_schedule in SVM and Bank into one (#1736) * Add Shuttle multithreading test infrastructure (#1634) * Add num_partitions to Blockstore rewards (#1601) * Add num_partitions field to Rewards proto definition * Add type to hold rewards plus num_partitions * Add Bank method to get rewards plus num_partitions for recording * Update Blockstore::write_rewards to use num_partitions * Update RewardsRecorderService to handle num_partitions * Populate num_partitions in ReplayStage::record_rewards * Write num_partitions to Bigtable * Reword KeyedRewardsAndNumPartitions method * Clone immediately * Determine epoch boundary by checking parent epoch * Rename UiConfirmedBlock field * nit: fix comment typo * Add test_get_rewards_and_partitions * Add pre-activation test * Add should_record unit test * run full gossip node when running `solana-gossip rpc-url` (#1753) * send actual gossip ip:port when running solana-gossip rpc-url * refactor gossip_addr out from spy and rpc methods * fix race condition on vote count (#1762) * [sdk] Add ZK Elgamal Proof program feature gate (#1679) * add `zk-elgamal-proof-program-enabled` feature gate * reserve account for zk elgamal proof program * add zk elgamal proof as builtin program * cargo lock * cargo sort * replace feature gate key to start with `zk..` * add simd number * update `elgamal_program` to `zk_elgamal_proof_program` * Don't panic when pausing stale unified schedulers (#1761) * ledger-tool: Subfunction for snapshot args (#1773) There are several arguments to control snapshot configuration in the various ledger-tool commands. The inclusion of args in each command is inconsistent, especially for commands outside of main.rs This change consolidates the snapshot related arguments into a single function to help create consistency and reduce duplicate code * ledger-tool: Make joining AccountsBackgroundService optional (#1673) AccountsBackgroundService performs several operations that can take a long time to complete and do not check the exit flag mid-operation. Thus, ledger-tool can get hung up for a while waiting for ABS to finish. However, many ledger-tool command do not ABS to have finished. So, return a handle to the ABS thread and allow the caller to decide whether to join ABS or not. As of right now, create-snapshot is the only command that requires ABS to have finished before continuing. * Extract curve25519 crate from zk-token-sdk (#951) * extract curve25519 crate * remove obsolete comment * fix Cargo.toml files * fix imports * update lock file * remove unused deps from zk-token-sdk * fmt * add solana-curve25519 patch * add missing override to programs/sbf/Cargo.toml * copy over an allow() * move new crate to curves dir * use workspace version * add back missing dev dep * add missing dependencies to programs/sbf * fmt * move dep to the correct dependency table * remove #[cfg(not(target_os = "solana"))] above errors mod * Ensure mapping of callee is updated with direct mapping (#1093) Consider this scenario: - Program increases length of an account - Program start CPI and adds this account as a read-only account - In fn update_callee_account() we resize account, which may change the pointer - Once CPI finishes, the program continues and may read/write from the account. The mapping must be up-to-date else we use stale pointers. Note that we always call callee_account.set_data_length(), which may change the pointer. In testing I found that resizing a vector from 10240 down to 127 sometimes changes its pointer. So, always update the pointer. * [curve25519] Remove `ElGamalError` from curve25519 crate (#1777) * remove `ElGamalError` from curve25519 crate * add `ElGamalError` to zk-token-sdk * sdk: Only compile wasm-bindgen when target_arch = "wasm32" (#1658) * move wasm-bindgen dep under cfg(target_arch = "wasm32") in sdk and program * remove wasm_bindgen_stub (we don't need it where we're going) * put wasm_bindgen usage behind #[cfg(target_arch = "wasm32")] * remove doc comments from skippeed fields * add missing attribute * another missing attribute * add doc comments explaining duplicated structs * fmt * fix wasm comments * Update sdk/program/src/instruction.rs --------- Co-authored-by: Jon C * transaction-status: Use string instead of int for `amount` in `amountToUiAmount` (#1737) * transaction-status: Use string instead of int for `amount` * Add a changelog entry * Update CHANGELOG.md Co-authored-by: Tyera --------- Co-authored-by: Tyera * Adds `diff` to accounts-hash-cache-tool (#1772) * SVM: Move `fee_structure` to environment input (#1771) * SVM: add `fee_structure` to environment arg * runtime: add `fee_structure` to bank * SVM: drop `fee_structure` from global configs * always pack a few newest ancient slots (#1730) * always pack a few newest ancient slots * pr feedback * remove extra () * adds high slot tests --------- Co-authored-by: brooks * remove double-counted metric (#1748) * SVM: Reduce public visibility of sysvar_cache (#1783) * ledger-tool: Deduplicate max-genesis-archive-unpacked-size argument (#1774) The argument is currently declared in multiple places. So, delcare the argument in one central place. * add ancient pack metrics (#1750) * add ancient pack metrics * add missing params --------- Co-authored-by: brooks * Refactor - Avoid host build of SBPF program test crates (#1711) * Removes ProgramTest from simulation tests. * Removes ProgramTest from sysvar syscall tests. * Workaround for rustc crash caused by 16 byte aligned memcpy. * Deduplicates test_program_sbf_sanity. * Moves mem and remaining_compute_units into test_program_sbf_sanity(). * Removes unused dev-dependencies in Cargo.toml. * Removes crate-type = lib from Cargo.tomls. * Adds SBF_OUT_DIR env to CI script. * Adds "sysvar" to build.rs. * ci: ignore curve25519-dalek audit temporarily (#1786) ci: ignore curve25519-dalek audit * build(deps): bump bytemuck from 1.16.0 to 1.16.1 (#1789) * build(deps): bump bytemuck from 1.16.0 to 1.16.1 Bumps [bytemuck](https://github.com/Lokathor/bytemuck) from 1.16.0 to 1.16.1. - [Changelog](https://github.com/Lokathor/bytemuck/blob/main/changelog.md) - [Commits](https://github.com/Lokathor/bytemuck/compare/v1.16.0...v1.16.1) --- updated-dependencies: - dependency-name: bytemuck dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update all Cargo files --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump url from 2.5.1 to 2.5.2 (#1788) * build(deps): bump url from 2.5.1 to 2.5.2 Bumps [url](https://github.com/servo/rust-url) from 2.5.1 to 2.5.2. - [Release notes](https://github.com/servo/rust-url/releases) - [Commits](https://github.com/servo/rust-url/compare/v2.5.1...v2.5.2) --- updated-dependencies: - dependency-name: url dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update all Cargo files --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * program: move itertools to dev-dependencies (#1782) * port join from itertools and use it in program_stubs.rs * move itertools to dev-dependencies of solana-program * add comment to join fn * more concise replacement for join fn Co-authored-by: Jon C * remove join fn --------- Co-authored-by: Jon C * Adjust replay-related metrics for unified scheduler (#1741) * Adjust replay-related metrics for unified schduler * Fix grammar * Don't compute slowest for unified scheduler * Rename to is_unified_scheduler_enabled * Hoist uses to top of file * Conditionally disable replay-slot-end-to-end-stats * Remove the misleading fairly balanced text * bpf_loader: use an explicit thread-local pool for stack and heap memory (#1370) * Rename ComputeBudget::max_invoke_stack_height to max_instruction_stack_depth The new name is consistent with the existing ComputeBudget::max_instruction_trace_length. Also expose compute_budget:MAX_INSTRUCTION_DEPTH. * bpf_loader: use an explicit thread-local pool for stack and heap memory Use a fixed thread-local pool to hold stack and heap memory. This mitigates the long standing issue of jemalloc causing TLB shootdowns to serve such frequent large allocations. Because we need 1 stack and 1 heap region per instruction, and the current max instruction nesting is hardcoded to 5, the pre-allocated size is (MAX_STACK + MAX_HEAP) * 5 * NUM_THREADS. With the current limits that's about 2.5MB per thread. Note that this is memory that would eventually get allocated anyway, we're just pre-allocating it now. * programs/sbf: add test for stack/heap zeroing Add TEST_STACK_HEAP_ZEROED which tests that stack and heap regions are zeroed across reuse from the memory pool. * ledger-tool: Make verify --print-bank-hash support json (#1745) The bank-hash command in ledger-tool was recently deprecated. However, the command is used by some of the scripts that coordinate starting up a fresh cluster. So, the deprecation of bank-hash broke those scripts. This change fixes the scripts by doing the following: - Makes --print-bank-hash support --output json - Updates scripts to install jq on provisioned nodes - Update remote-node.sh to parse the bank hash from json using jq * use bytemuck_derive 1.7.0 explicitly in sdk and program (#1793) * use bytemuck_derive 1.7.0 explicitly in sdk and program * explicitly activate the derive feature of bytemuck in zk-sdk * Make futures crate optional in solana-type-overrides (#1792) * CI - SBPF program build script (#1581) * Replaces the rust build script by a makefile * PrioGraphScheduler::complete_batch remove TransactionAccountLocks allocation (#1759) * Round up correctly when truncating max ancient storages (#1781) * refactor: consolidate fee deduction for failed transactions (#1636) * change match to an if (#726) * typo fixes (#1795) * charging CU for loaded accounts data size (#1356) * Fix SyscallLogPubkey doc comment (#1805) * Fix SyscallLogPubkey doc comment * Update logging.rs * ReadWriteAccountSet: use AHashSet (#1265) * Reuse compute budget processing (#1700) * refactor: reuse compute budget limits * fix tests * Rename tx counts from committed to executed (#1807) * Clean up: remove unused _feature_set (#1803) * ledger-tool: Get shreds from BigTable blocks (#1638) There is often a desire to examine/replay/etc older blocks. If the blocks are recent enough, they can be pulled from an actively running node. Otherwise, the blocks must be pulled down from warehouse node archives. These archives are uploaded on a per-epoch basis so they are quite large, and can take multiple hours to download and decompress. With the addition of Entry data to BigTable, blocks can be recreated from BigTable data. Namely, we can recreate the Entries with proper PoH and transaction data. We can then shred them such that they are the same format as blocks that are produced from the cluster. This change introduces a new command that will read BigTable data and insert shreds into a local Blockstore. The new command is: $ agave-ledger-tool bigtable shreds ... Several important notes about the change: - Shred for some slot S will not be signed by the actual leader for slot S. Instead, shreds will be signed with a "dummy" keypair. The shred signatures does not affect the ability to replay the block. - Entry PoH data does not go back to genesis in BigTable. This data could be extracted and uploaded from the existing rocksdb archives; however, that work is not planned as far as I know. --allow-mock-poh can be passed to generate filler PoH data. Blocks created with this flag are replayable by passing --skip-poh-verify to ledger-tool. - A snapshot will be unpacked to determine items such as the shred version, tick hash rate and ticks per slot. This snapshot must be in the same epoch as the requested slots * [docs] Add ciphertext validity proof docs (#1776) * docs: add ciphertext validity proof docs * update docs link to anza's * remove extra space at the end of line * deps: Use bytemuck_derive explicitly instead of "derive" feature on bytemuck (#1799) * deps: Use bytemuck_derive explicitly * Missed a couple in zk-token-sdk * Fix last few bits * Fixup a re-export * Use re-exports properly * harden sbf realloc tests (#1600) * Improve SchedulerStatus code and test as follow-up (#1797) * Improve SchedulerStatus code and test as follow-up * Don't use wait_timeout_while with magic number * SVM: Refactor program match criteria (#1784) * SVM: hoist `program_modification_slot` up from bank * SVM: add `check_program_modification_slot` to processing config * SVM: hoist `program_match_criteria` up from bank * SVM: drop `get_program_match_critera` from callbacks * SVM: update spec (#1814) * Verify elf locally for write buffer cli command (#1794) Verify elf locally for write buffer command * add stats for ancient bytes_from_newest_storages (#1802) * add stats for ancient bytes_from_smallest_storages * fix else if * remove totally unrelated demo code. ugh. * Update CHANGELOG.md in preparation for branching v2.0 (#1819) * Bump version to v2.0.1 (#1821) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * v2.0: add .github/CODEOWNERS (#1833) * v2.0: chore: add dcou to apply_votes_to_tower (backport of #1831) (#1843) chore: add dcou to apply_votes_to_tower (#1831) * add dcou to apply_votes_to_tower * cargo sort * fix fmt (cherry picked from commit 66bdefd17873e5a8c0bc02c53e8b2eb5904d0ccc) Co-authored-by: Yihau Chen * v2.0: chore: remove publish=false from transaction-metrics-tracker/Cargo.toml (backport of #1828) (#1844) chore: remove publish=false from transaction-metrics-tracker/Cargo.toml (#1828) (cherry picked from commit ac63c0a78bf8ad59e4ca2cfd555abacf8009e633) Co-authored-by: Yihau Chen * v2.0: chore: publish solana-tps-client (backport of #1845) (#1846) chore: publish solana-tps-client (#1845) (cherry picked from commit 758477331271d2ba79d392e36c71e4e98f06761e) Co-authored-by: Yihau Chen * Bump version to v2.0.2 (#1848) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * v2.0: Add partitioned epoch rewards to changelog for v2.0 (backport of #1864) (#1868) Add partitioned epoch rewards to changelog for v2.0 (#1864) Add partitioned epoch rewards to changelog (cherry picked from commit f0641effb515a269a22233402206f715744d62c1) Co-authored-by: Tyera * v2.0: spl: Upgrade all crates to v2-only versions (backport of #1872) (#1880) spl: Upgrade all crates to v2-only versions (#1872) (cherry picked from commit 6aee8454c0a3d8e0b748cae3e21d406ca368357a) Co-authored-by: Jon C * v2.0: Use num_partitions to find specific stake rewards in partitions (backport of #1677) (#1881) Use num_partitions to find specific stake rewards in partitions (#1677) * Add helper to find and filter rewards from a slot * Check feature enabled for desired epoch * Refactor existing rewards code to support vote-rewards after activation * Append stake rewards from partitions * Remove feature deactivation from TestValidator * Improve comments * Add comment about retaining feature activation slot logic * Add custom error and use in getInflationReward * Review nit (cherry picked from commit 0496b06f891e4f1caddf8ce4742a4bed91c5e56b) Co-authored-by: Tyera * v2.0: Make unified scheduler opt-in for block verification (backport of #1668) (#1874) Make unified scheduler opt-in for block verification (#1668) * Enable unified scheduler for block verification * Revert making unified scheduler enabled by default (cherry picked from commit 40508cd03f295fa170ff0f116d1b945972d5c593) Co-authored-by: Ryo Onodera * v2.0: Remove support for deprecated rpc endpoints (backport of #1809) (#1886) * Remove support for deprecated rpc endpoints (#1809) * Remove rpc_obsolete_v1_7 * Remove rpc_deprecated_v1_7 * Remove rpc_deprecated_v1_9 * Add CHANGELOG entry * Add another CHANGELOG entry (cherry picked from commit 83527d95777b21cdd155a6293ca4fdc740eda7a6) # Conflicts: # CHANGELOG.md * Fix conflict --------- Co-authored-by: Tyera * v2.0: Add --block-verification-method=unified-scheduler for v2.0 changelog (backport of #1878) (#1879) Add --block-verification-method=unified-scheduler for v2.0 changelog (#1878) (cherry picked from commit 2b88299585886cdad87c7b9403d2da4324dc8af0) # Conflicts: # CHANGELOG.md Co-authored-by: Ryo Onodera * v2.0: Avoid unneeded start_session() with cleanups (bp: #1815, #1861) (#1854) * Avoid unneeded start_session() when spawning (#1815) * Avoid unneeded start_session() when spawning * Add comments (cherry picked from commit 40a9851c82b4a669150800f84f026214e85921f4) * Apply cosmetic changes to unified scheduler (#1861) * Apply cosmetic changes to unified scheduler * Use first instead of old-fashioned firstly Co-authored-by: Andrew Fitzgerald --------- Co-authored-by: Andrew Fitzgerald --------- Co-authored-by: Ryo Onodera Co-authored-by: Andrew Fitzgerald * v2.0: Add since field to deprecation note (backport of #1905) (#1910) Add since field to deprecation note (#1905) Add to deprecation note (cherry picked from commit 70254b175ebb939c728010aa50e26d3e7e404c77) Co-authored-by: Tyera * v2.0: vote: remove deprecated ixs (backport of #1906) (#1917) vote: remove deprecated ixs (#1906) (cherry picked from commit e42e7fd692a9777f0b3f25b460c2038fabbe4a2c) Co-authored-by: Ashwin Sekar * v2.0: Deprecate --rocksdb-shred-compaction fifo (backport of #1882) (#1907) Deprecate --rocksdb-shred-compaction fifo (#1882) The fifo compaction option was originally added to mitigate write stalls that were occurring with level compaction. Since fifo was introduced, the level compaction implementation has been optimized to reduce I/O amplification. With these improvements, level and fifo are comparable and sticking with level only simplifies things. For now, only a deprecation warning will be printed. In the next release branch (2.1.0), specifying fifo will be an error (cherry picked from commit e15e235ec43e55e1028a3c418dab6c6c621a2a87) Co-authored-by: steviez * v2.0: Refactor and additional metrics for cost tracking (backport of #1888) (#1900) * Refactor and additional metrics for cost tracking (#1888) * Refactor and add metrics: - Combine remove_* and update_* functions to reduce locking on cost-tracker and iteration. - Add method to calculate executed transaction cost by directly using actual execution cost and loaded accounts size; - Wireup histogram to report loaded accounts size; - Report time of block limits checking; - Move account counters from ExecuteDetailsTimings to ExecuteAccountsDetails; * Move committed transactions adjustment into its own function (cherry picked from commit c3fadacf69c4420e4bb024b1a81740ce8cde905a) * rename cost_tracker.account_data_size to better describe its purpose is to tracker per-block new account allocation --------- Co-authored-by: Tao Zhu <82401714+tao-stones@users.noreply.github.com> Co-authored-by: Tao Zhu * v2.0: Remove deprecated SyncClient methods (backport of #1902) (#1909) Remove deprecated SyncClient methods (#1902) * Remove deprecated methods from SyncClient trait * Add line to changelog (cherry picked from commit 3d3faf5dee7a4b730f2bdb63dd850e4258211712) Co-authored-by: Tyera * Revert "v2.0: Refactor and additional metrics for cost tracking (backport of #1888) (#1900) (#1937) Revert "v2.0: Refactor and additional metrics for cost tracking (backport of #1888) (#1900)" This reverts commit 0aef62eac75343eca27b333da43777a00189fadb. * v2.0: Remove deprecated RpcClient methods (backport of #1899) (#1922) Remove deprecated RpcClient methods (#1899) * Remove deprecated RpcClient methods corresponding to #1809 * Remove internal handling for pre-v1.9.0 servers * Remove unused import of deprecated_config module * Remove request mapping (pre-v1.7.0) * Remove dangling allow-deprecated tag * Add line to changelog * Remove deprecated RpcRequest variants * Remove reprecated rpc-client-nonce-utils methods * Remove deprecated TestValidator method * Remove deprecated solana fees command (cherry picked from commit 51af772e63adc4f94ed4f06b20a8ae9d4a986118) Co-authored-by: Tyera * v2.0: Deprecate RpcClient::get_stake_activation (backport of #1895) (#1925) Deprecate RpcClient::get_stake_activation (#1895) * Deprecate RpcClient::get_stake_activation * Fixup redelegation test (cherry picked from commit 114041c46aa3802239eccd34335f9bcc257a0416) Co-authored-by: Tyera * v2.0: Remove rpc endpoint deprecated in v1.18 (backport of #1897) (#1926) Remove rpc endpoint deprecated in v1.18 (#1897) * Remove rpc_deprecated_v1_18 * Update changelog * Fixup redelegation test (cherry picked from commit 99b2d5ad60c1163620befd3340a5a38a084037a9) Co-authored-by: Tyera * v2.0: Remove deprecated_config module (backport of #1951) (#1957) Remove deprecated_config module (#1951) (cherry picked from commit 00ee0acba356b7a82823736732d3f45951fdd132) Co-authored-by: Tyera * v2.0: gossip: do not allow duplicate proofs for incorrect shred versions (backport of #1931) (#1941) gossip: do not allow duplicate proofs for incorrect shred versions (#1931) * gossip: do not allow duplicate proofs for incorrect shred versions * pr feedback: refactor test function to take shred_version (cherry picked from commit 69ea21e947975ea04e6ac31dbe529dcb48e960a4) Co-authored-by: Ashwin Sekar * v2.0: ci: skip spl test when version is too high (backport of #1945) (#1961) ci: skip spl test when version is too high (#1945) (cherry picked from commit 4d9d27f1ff65d34a1ab7fd400ee11c21721edce9) Co-authored-by: Yihau Chen * v2.0: Remove deprecated symbols from solana-sdk (backport of #1953) (#1962) Remove deprecated symbols from solana-sdk (#1953) * Remove deprecated Account methods * Remove deprecated info macro * Remove deprecated Signature method * Remove deprecated native_loader method * Remove deprecated transaction method (deprecated in v1.9) * Remove deprecated entrypoint definitions * Remove deprecated program_stubs macro (cherry picked from commit e2643f3dc765a6f27a452bd37e76665a19ed7246) Co-authored-by: Tyera * v2.0: Remove deprecated BanksClient methods and dependent ProgramTest methods (backport of #1956) (#1963) Remove deprecated BanksClient methods and dependent ProgramTest methods (#1956) * Remove deprecated Banks client/interface methods * Remove dependent deprecated program-test methods (cherry picked from commit 6e4e82eb2ac5ef57f58815ce4d66bfd3b6b1385a) Co-authored-by: Tyera * v2.0: Remove deprecated spl-token conversion methods (backport of #1955) (#1978) Remove deprecated spl-token conversion methods (#1955) (cherry picked from commit 58027a38ba5cda6bd955b8ef89b4270ad1f7bf69) Co-authored-by: Tyera * v2.0: Remove deprecated symbols from solana-program (backport of #1958) (#1977) Remove deprecated symbols from solana-program (#1958) * Remove SLOT_MS * Remove deprecated Pubkey methods * Remove deprecated info! macro * Delete deprecated and dangling borsh 0.9 module * Remove deprecated UpgradeableLoaderState methods * Remove FeeCalculator deprecated method * Remove deprecated Instruction ctor * Remove deprecated legacy-message methods (deprecated in v1.9) (cherry picked from commit 2f8594032695b207ae3d654d38c1fc5c1da87f11) Co-authored-by: Tyera * v2.0: Handle deprecated Instructions sysvar methods (backport of #1959) (#1979) * Handle deprecated Instructions sysvar methods (#1959) * Remove deprecated legacy-message methods (deprecated in v1.9) * Unpub deprecated method load_current_index; dedupe * Unpub deprecated method load_instruction_at; dedupe * Remove allow(deprecated) tags * Make load_instruction_at available to benches (cherry picked from commit adb9d9e3c9ebc1cf5493bff9f8da33e3cf1d7d9c) # Conflicts: # sdk/program/Cargo.toml # sdk/program/src/sysvar/instructions.rs * Fix conflicts --------- Co-authored-by: Tyera * v2.0: Remove deprecated address_lookup_table_account re-export (backport of #1972) (#1990) * Remove deprecated address_lookup_table_account re-export (#1972) * Remove deprecated re-export * Remove deprecated stuff from example_mocks * Remove deprecated programs/alt re-export (cherry picked from commit f681fe87d512f441847b6c1e3f2f78850cbdefcf) # Conflicts: # sdk/program/src/lib.rs * Fix conflict --------- Co-authored-by: Tyera * v2.0: Remove deprecated, unused methods from Bank (backport of #1980) (#1988) Remove deprecated, unused methods from Bank (#1980) Remove deprecated methods from Bank (not in use) (cherry picked from commit 031304704641a8b5bb9b94f6e467641679f61258) Co-authored-by: Tyera * v2.0: Refactor cost tracking (backport of #1954) (#1975) * Refactor cost tracking (#1954) * Refactor and additional metrics for cost tracking (#1888) * Refactor and add metrics: - Combine remove_* and update_* functions to reduce locking on cost-tracker and iteration. - Add method to calculate executed transaction cost by directly using actual execution cost and loaded accounts size; - Wireup histogram to report loaded accounts size; - Report time of block limits checking; - Move account counters from ExecuteDetailsTimings to ExecuteAccountsDetails; * Move committed transactions adjustment into its own function * remove histogram for loaded accounts size due to performance impact (cherry picked from commit f8630a352256e4d31b5b624cb40565f5cfe2e13b) * rename cost_tracker.account_data_size to better describe its purpose is to tracker per-block new account allocation --------- Co-authored-by: Tao Zhu <82401714+tao-stones@users.noreply.github.com> Co-authored-by: Tao Zhu * v2.0: Bump ThinClient since version (backport of #1991) (#2001) Bump ThinClient since version (#1991) (cherry picked from commit 1063be7f1b8a0e73f942a5332609bef23a45ec56) Co-authored-by: Tyera * v2.0: Add suggestion to deprecation message (backport of #1993) (#2000) Add suggestion to deprecation message (#1993) (cherry picked from commit c848645aba522fbe64dec529c95656efb0c3ce64) Co-authored-by: Tyera * v2.0: Remove assorted deprecated symbols (backport of #1995) (#2002) * Remove assorted deprecated symbols (#1995) * Remove deprecated method from program-test * Remove deprecated programs stuff from TestValidator * Remove deprecated method from TestValidator (deprecated in v1.11.0) * Remove deprecated re-export from cluster_info (cherry picked from commit efdbdc00cea2b89338b2e8e1f9ebb6ca77c8b5fa) # Conflicts: # gossip/src/cluster_info.rs * Fix conflict --------- Co-authored-by: Tyera * v2.0: Use node's latest vote for commitment calc. too (backport of #1964) (#1994) Use node's latest vote for commitment calc. too (#1964) * Use node's latest vote for commitment calc. too * Make local_cluster test use finalized * Update core/src/commitment_service.rs Co-authored-by: Tyera * Don't wrap with Option and update tests --------- Co-authored-by: Tyera Eulberg Co-authored-by: Tyera (cherry picked from commit 556298982a2dd83604552137968d493b25e3850a) Co-authored-by: Ryo Onodera * v2.0: [zk-token-sdk] Specify version in deprecations in `zk-token-sdk` (backport of #2011) (#2014) * v2.0: Remove deprecated CommitmentLevel variants (backport of #1903) (#1989) Remove deprecated CommitmentLevel variants (#1903) * Remove deprecated CommitmentLevel variants and CommitmentConfig methods * Remove deprecated variants from BlockCommitmentCache usage * Remove deprecated variants from rpc * Remove maybe_map_commitment from RpcClient (cherry picked from commit 2e3c2c78e94fc7614dbf5899c5dc27a20e60f4c9) Co-authored-by: Tyera * v2.0: Remove deprecated symbols from recent_blockhashes_account (backport of #2004) (#2009) Remove deprecated symbols from recent_blockhashes_account (#2004) * Move recent_blockhashes_account to runtime * Fixup imports * Reduce pub * Remove unused method * Duplicate code to limit pub and dependency complexity * Move test-only fn into tests module (cherry picked from commit f77658b9e4d5b441165d72f8dfafd1490dce1493) Co-authored-by: Tyera * v2.0: Remove rpc methods from mock sender (backport of #1996) (#2008) Remove rpc methods from mock sender (#1996) Remove deleted RPC methods from mock_sender (cherry picked from commit 27988e791faac74bbde9dabedddca66ec990b9b5) Co-authored-by: Tyera * v2.0: Deprecate RpcRequest::GetStakeActivation (backport of #2005) (#2013) Deprecate RpcRequest::GetStakeActivation (#2005) * Correct deprecation note * Deprecate rpc-client-api request (cherry picked from commit 0deb6fb3f2022fd56a4457a2bb03c49146af90d4) Co-authored-by: Tyera * v2.0: Remove deprecated symbols from solana-client (mostly re-exports) (backport of #1992) (#2041) Remove deprecated symbols from solana-client (mostly re-exports) (#1992) * Remove usage of deprecated re-export from SendTransactionService * Remove usage of deprecated re-export from core * Remove usage of deprecated re-export from solana-dos * Remove deprecated ConnectionCache methods * Remove deprecated udp_client re-export * Remove deprecated tpu_connection re-export * Remove deprecated quic_client re-export (cherry picked from commit 3cd7621a81c2e2a8288dd17de35acf8d515c2d5e) Co-authored-by: Tyera * v2.0: Update changlog re: removal of deprecated symbols (backport of #2006) (#2042) Update changlog re: removal of deprecated symbols (#2006) * Format SDK changes as bulleted list, and make deprecation removal generic * Add changelog line for solana-program * Add line for solana-client * Update CHANGELOG.md (cherry picked from commit b61ce735ea149e09ddafba6e3d3043876e279c5d) Co-authored-by: Tyera * v2.0: Clean up disable_fees_sysvar feature (backport of #2003) (#2043) Clean up disable_fees_sysvar feature (#2003) * Clean up disable_fees_sysvar * Remove bank fees test * Remove unused rpc response types * Fixup sysvar_cache tests * Remove fees-sysvar from bootstrap_validator_stake_lamports; no longer included * Update account counts, since fees no longer touched * Update bank hashes in test, since fees sysvar is not populated * Remove test case passing fees sysvar as account (cherry picked from commit 1c34908d03e1aecfd72ffb34acc3221a9faa5798) Co-authored-by: Tyera * v2.0: Remove get_stake_activation methods from RpcClient (backport of #2036) (#2044) Remove get_stake_activation methods from RpcClient (#2036) * Remove dangling JsonRpcRequestProcessor method * Remove get_stake_activation methods from RpcClient * Remove getStakeActivation from mock_sender * Remove unused rpc response type (cherry picked from commit 30791818e0769a252966f5733f0f4f63a9d1a4ca) Co-authored-by: Tyera * v2.0: Update install docs to agave (backport of #2060) (#2061) Update install docs to agave (#2060) * s/SOLANA/AGAVE * s/solana.com/anza.xyz (cherry picked from commit b1bb9ff5763f2cecbd377b053dc9bc47c3034c4a) Co-authored-by: Tyera * v2.0: Removes unused deprecated cli args (backport of #2058) (#2063) Removes unused deprecated cli args (#2058) (cherry picked from commit 05134bed3b8f6cef24d68ad0052a3288bcaeda49) Co-authored-by: Brooks * v2.0: Remove rpc- and pubsub-client version querying (backport of #2045) (#2052) Remove rpc- and pubsub-client version querying (#2045) * Remove filter mapping * Remove unused internal module * Remove unused pubsub-client stuff * Remove unused RpcClient encoding maping, node_version (cherry picked from commit 69d2eb8da87b19838b6d01ff95e90ada6c6de6fd) Co-authored-by: Tyera * v2.0: Remove deprecated Memcmp filter symbols (backport of #2050) (#2069) Remove deprecated Memcmp filter symbols (#2050) * Add test to demonstrate serialization and acceptable json inputs * Remove custom serialization * Unpub deprecated fields * Remove deprecated, unused field * Remove deprecated MemcmpEncodedBytes variant * Remove deprecated error variants * Add back custom deserialization to support missing 'encoding' field * Update tests to demonstrate consistency and changes * Add helper fns to use in rpc spl-token filter checks * Update base64 syntax and remove allow-deprecated (cherry picked from commit 3b3ffbb6c9f5558b819e9921b407d96c7cb55c2d) Co-authored-by: Tyera * finalise removement of solana-metrics --------- Signed-off-by: dependabot[bot] Co-authored-by: galactus <96341601+godmodegalactus@users.noreply.github.com> Co-authored-by: HaoranYi Co-authored-by: HaoranYi Co-authored-by: Kirill Fomichev Co-authored-by: Wen <113942165+wen-coding@users.noreply.github.com> Co-authored-by: Jon C Co-authored-by: Joe C Co-authored-by: Kevin Heavey Co-authored-by: Andrei Silviu Dragnea Co-authored-by: Greg Cusack Co-authored-by: Yihau Chen Co-authored-by: steviez Co-authored-by: Tyera Co-authored-by: Lucas Ste <38472950+LucasSte@users.noreply.github.com> Co-authored-by: samkim-crypto Co-authored-by: Pankaj Garg Co-authored-by: Andrew Fitzgerald Co-authored-by: Ryo Onodera Co-authored-by: Sean Young Co-authored-by: Tyera Co-authored-by: Brooks Co-authored-by: Jeff Washington (jwash) Co-authored-by: Alexander Meißner Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alessandro Decina Co-authored-by: Justin Starry Co-authored-by: knotts Co-authored-by: Tao Zhu <82401714+tao-stones@users.noreply.github.com> Co-authored-by: Sammy Harris <41593264+stegaBOB@users.noreply.github.com> Co-authored-by: Will Hickey Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Ashwin Sekar Co-authored-by: Tao Zhu --- .github/CODEOWNERS | 1 + .../scripts/downstream-project-spl-common.sh | 1 + .github/workflows/client-targets.yml | 2 +- .github/workflows/downstream-project-spl.yml | 10 + CHANGELOG.md | 34 +- Cargo.lock | 748 +++++++----- Cargo.toml | 206 ++-- account-decoder/src/parse_token.rs | 31 - accounts-db/Cargo.toml | 3 + .../accounts-hash-cache-tool/Cargo.toml | 2 +- .../accounts-hash-cache-tool/src/main.rs | 292 ++++- accounts-db/benches/accounts.rs | 1 + accounts-db/src/accounts.rs | 309 +++-- accounts-db/src/accounts_db.rs | 26 +- .../src/accounts_db/geyser_plugin_utils.rs | 2 +- accounts-db/src/accounts_hash.rs | 2 +- accounts-db/src/accounts_index.rs | 19 +- accounts-db/src/ancient_append_vecs.rs | 275 ++++- accounts-db/src/bucket_map_holder.rs | 2 +- accounts-db/src/buffered_reader.rs | 226 ++-- accounts-db/src/cache_hash_data.rs | 2 +- accounts-db/src/file_io.rs | 90 +- accounts-db/src/tiered_storage/file.rs | 4 +- accounts-db/src/tiered_storage/hot.rs | 2 +- accounts-db/src/tiered_storage/index.rs | 2 +- accounts-db/src/tiered_storage/meta.rs | 2 +- banks-client/src/lib.rs | 43 +- banks-interface/src/lib.rs | 8 - banks-server/src/banks_server.rs | 19 - bucket_map/Cargo.toml | 3 +- bucket_map/src/restart.rs | 2 +- ci/bench/part2.sh | 2 + ci/do-audit.sh | 3 + ci/test-bench.sh | 1 + ci/test-stable.sh | 16 +- cli/src/cli.rs | 10 - cli/src/cluster_query.rs | 70 +- cli/src/feature.rs | 2 +- cli/src/program.rs | 235 +++- cli/tests/fixtures/alt_bn128.so | Bin 0 -> 4648 bytes cli/tests/fixtures/build.sh | 1 + cli/tests/program.rs | 440 +++++++ cli/tests/stake.rs | 113 +- client/src/connection_cache.rs | 24 - client/src/lib.rs | 6 - client/src/nonblocking/mod.rs | 3 - client/src/nonblocking/quic_client.rs | 8 - client/src/nonblocking/tpu_connection.rs | 5 - client/src/nonblocking/udp_client.rs | 5 - client/src/quic_client.rs | 5 - client/src/thin_client.rs | 18 - client/src/tpu_connection.rs | 6 - client/src/udp_client.rs | 5 - compute-budget/src/compute_budget.rs | 50 +- .../src/compute_budget_processor.rs | 16 +- core/Cargo.toml | 1 + core/benches/consumer.rs | 36 +- core/src/banking_stage/committer.rs | 10 +- core/src/banking_stage/consume_worker.rs | 11 + core/src/banking_stage/consumer.rs | 40 +- core/src/banking_stage/forwarder.rs | 3 +- .../immutable_deserialized_packet.rs | 4 +- .../banking_stage/latest_unprocessed_votes.rs | 72 +- core/src/banking_stage/qos_service.rs | 126 +- .../banking_stage/read_write_account_set.rs | 6 +- .../prio_graph_scheduler.rs | 18 +- .../scheduler_controller.rs | 2 - .../unprocessed_packet_batches.rs | 8 - .../unprocessed_transaction_storage.rs | 2 - core/src/commitment_service.rs | 100 +- core/src/replay_stage.rs | 51 +- core/src/rewards_recorder_service.rs | 23 +- core/src/tvu.rs | 1 + core/src/warm_quic_cache_service.rs | 6 +- cost-model/src/cost_model.rs | 94 +- cost-model/src/cost_tracker.rs | 45 +- cost-model/src/transaction_cost.rs | 10 +- curves/curve25519/.gitignore | 1 + curves/curve25519/Cargo.toml | 19 + .../curve25519/src}/curve_syscall_traits.rs | 0 .../curve25519/src}/edwards.rs | 9 +- .../curve25519/src}/errors.rs | 0 curves/curve25519/src/lib.rs | 8 + .../curve25519/src}/ristretto.rs | 9 +- .../curve25519/src}/scalar.rs | 18 +- docs/src/cli/install.md | 18 +- .../runtime/zk-docs/ciphertext_validity.pdf | Bin 0 -> 277600 bytes docs/src/runtime/zk-docs/zero_proof.pdf | Bin 261850 -> 262550 bytes ...{zk-token-proof.md => zk-elgamal-proof.md} | 66 +- dos/Cargo.toml | 1 + dos/src/main.rs | 6 +- .../src/accounts_update_notifier.rs | 25 +- gossip/src/cluster_info.rs | 10 +- gossip/src/crds_gossip.rs | 2 + gossip/src/duplicate_shred.rs | 174 ++- gossip/src/duplicate_shred_handler.rs | 25 +- gossip/src/main.rs | 72 +- ledger-tool/Cargo.toml | 1 + ledger-tool/src/args.rs | 158 ++- ledger-tool/src/bigtable.rs | 299 ++++- ledger-tool/src/ledger_utils.rs | 41 +- ledger-tool/src/main.rs | 368 ++---- ledger-tool/src/output.rs | 20 +- ledger-tool/src/program.rs | 42 +- ledger/src/blockstore.rs | 13 +- ledger/src/blockstore_processor.rs | 284 ++++- local-cluster/Cargo.toml | 1 + local-cluster/src/cluster_tests.rs | 68 +- local-cluster/src/local_cluster.rs | 152 ++- local-cluster/tests/local_cluster.rs | 42 +- net/gce.sh | 1 + net/remote/remote-node.sh | 2 +- net/scripts/install-jq.sh | 10 + program-runtime/Cargo.toml | 2 + program-runtime/src/invoke_context.rs | 35 +- program-runtime/src/lib.rs | 1 + program-runtime/src/loaded_programs.rs | 75 +- program-runtime/src/mem_pool.rs | 146 +++ program-runtime/src/sysvar_cache.rs | 2 +- program-runtime/src/timings.rs | 46 +- program-test/src/lib.rs | 44 +- programs/address-lookup-table/src/lib.rs | 10 - programs/bpf_loader/Cargo.toml | 6 +- programs/bpf_loader/src/lib.rs | 100 +- programs/bpf_loader/src/syscalls/cpi.rs | 19 +- programs/bpf_loader/src/syscalls/logging.rs | 2 +- programs/bpf_loader/src/syscalls/mod.rs | 32 +- programs/loader-v4/Cargo.toml | 4 + programs/loader-v4/src/lib.rs | 31 +- programs/sbf/Cargo.lock | 683 ++++------- programs/sbf/Cargo.toml | 76 +- programs/sbf/Makefile | 13 + programs/sbf/benches/bpf_loader.rs | 4 +- programs/sbf/build.rs | 130 -- programs/sbf/c/makefile | 2 - programs/sbf/rust/curve25519/Cargo.toml | 1 + programs/sbf/rust/curve25519/src/lib.rs | 2 +- programs/sbf/rust/invoke/src/lib.rs | 94 ++ programs/sbf/rust/invoke_dep/src/lib.rs | 2 + programs/sbf/rust/mem/Cargo.toml | 2 +- .../rust/remaining_compute_units/Cargo.toml | 2 +- programs/sbf/rust/sanity/Cargo.toml | 2 +- programs/sbf/rust/simulation/Cargo.toml | 2 +- programs/sbf/rust/sysvar/Cargo.toml | 2 +- programs/sbf/rust/sysvar/src/lib.rs | 29 +- programs/sbf/tests/mem.rs | 27 - programs/sbf/tests/programs.rs | 935 +++++++++++---- programs/sbf/tests/remaining_compute_units.rs | 27 - programs/sbf/tests/sanity.rs | 37 - programs/sbf/tests/simulation.rs | 86 +- programs/sbf/tests/simulation_validator.rs | 41 - programs/sbf/tests/sysvar.rs | 105 +- programs/stake/Cargo.toml | 1 + programs/system/Cargo.toml | 1 + programs/system/src/system_processor.rs | 53 +- programs/zk-elgamal-proof/Cargo.toml | 2 +- programs/zk-token-proof-tests/Cargo.toml | 2 +- programs/zk-token-proof/Cargo.toml | 2 +- .../src/nonblocking/pubsub_client.rs | 66 +- pubsub-client/src/pubsub_client.rs | 42 +- rpc-client-api/Cargo.toml | 1 + rpc-client-api/src/config.rs | 1 + rpc-client-api/src/custom_error.rs | 22 + rpc-client-api/src/deprecated_config.rs | 122 -- rpc-client-api/src/filter.rs | 424 +++---- rpc-client-api/src/lib.rs | 2 - rpc-client-api/src/request.rs | 78 +- rpc-client-api/src/response.rs | 44 +- rpc-client-api/src/version_req.rs | 20 - rpc-client-nonce-utils/src/blockhash_query.rs | 147 +-- rpc-client/src/mock_sender.rs | 51 +- rpc-client/src/nonblocking/rpc_client.rs | 861 ++------------ rpc-client/src/rpc_client.rs | 382 +----- rpc-test/tests/rpc.rs | 2 +- rpc/src/rpc.rs | 1047 ++++------------- rpc/src/rpc_service.rs | 13 +- rpc/src/transaction_status_service.rs | 1 - runtime/Cargo.toml | 2 + runtime/src/bank.rs | 346 ++---- runtime/src/bank/address_lookup_table.rs | 4 +- .../bank/builtins/core_bpf_migration/mod.rs | 15 +- .../core_bpf_migration/target_builtin.rs | 4 + runtime/src/bank/builtins/mod.rs | 30 + .../partitioned_epoch_rewards/distribution.rs | 11 +- .../src/bank/partitioned_epoch_rewards/mod.rs | 270 ++++- .../src/bank}/recent_blockhashes_account.rs | 62 +- runtime/src/bank/sysvar_cache.rs | 22 +- runtime/src/bank/tests.rs | 309 +---- runtime/src/bank_client.rs | 52 - runtime/src/bank_forks.rs | 2 +- runtime/src/commitment.rs | 11 +- runtime/src/genesis_utils.rs | 4 +- runtime/src/installed_scheduler_pool.rs | 24 +- runtime/src/loader_utils.rs | 13 +- sdk/Cargo.toml | 6 +- .../tests/crates/fail/Cargo.toml | 4 +- .../tests/crates/noop/Cargo.toml | 4 +- sdk/macro/src/lib.rs | 28 - sdk/program/Cargo.toml | 13 +- sdk/program/src/alt_bn128/mod.rs | 2 +- sdk/program/src/borsh0_9.rs | 44 - sdk/program/src/bpf_loader_upgradeable.rs | 54 - sdk/program/src/clock.rs | 4 - sdk/program/src/example_mocks.rs | 41 +- sdk/program/src/fee_calculator.rs | 91 +- sdk/program/src/hash.rs | 8 +- sdk/program/src/instruction.rs | 29 +- sdk/program/src/lib.rs | 15 +- sdk/program/src/log.rs | 18 - sdk/program/src/message/compiled_keys.rs | 2 +- sdk/program/src/message/legacy.rs | 88 +- sdk/program/src/message/versions/v0/mod.rs | 4 +- sdk/program/src/program_stubs.rs | 7 +- sdk/program/src/pubkey.rs | 23 +- sdk/program/src/sysvar/instructions.rs | 31 +- sdk/program/src/sysvar/slot_hashes.rs | 2 +- sdk/program/src/vote/instruction.rs | 43 - sdk/src/account.rs | 20 - sdk/src/client.rs | 36 - sdk/src/commitment_config.rs | 129 +- sdk/src/ed25519_instruction.rs | 3 +- sdk/src/entrypoint.rs | 27 - sdk/src/entrypoint_deprecated.rs | 20 - sdk/src/feature_set.rs | 5 + sdk/src/lib.rs | 19 +- sdk/src/log.rs | 11 - sdk/src/native_loader.rs | 15 +- sdk/src/reserved_account_keys.rs | 6 + sdk/src/signature.rs | 8 - sdk/src/signer/keypair.rs | 5 +- sdk/src/transaction/mod.rs | 64 +- send-transaction-service/Cargo.toml | 1 + .../src/send_transaction_service.rs | 6 +- storage-bigtable/src/bigtable.rs | 4 + storage-bigtable/src/lib.rs | 2 + storage-proto/proto/confirmed_block.proto | 6 + storage-proto/src/convert.rs | 38 +- svm/Cargo.toml | 7 + svm/doc/spec.md | 165 ++- svm/src/account_loader.rs | 175 +-- svm/src/lib.rs | 1 + svm/src/message_processor.rs | 30 +- svm/src/nonce_info.rs | 160 +-- svm/src/program_loader.rs | 160 ++- svm/src/rollback_accounts.rs | 229 ++++ svm/src/transaction_error_metrics.rs | 7 + svm/src/transaction_processing_callback.rs | 9 +- svm/src/transaction_processor.rs | 427 ++++--- svm/src/transaction_results.rs | 1 - svm/tests/conformance.rs | 24 +- .../example-programs/clock-sysvar/Cargo.toml | 4 +- .../example-programs/hello-solana/Cargo.toml | 4 +- .../simple-transfer/Cargo.toml | 4 +- svm/tests/integration_test.rs | 2 - test-validator/src/lib.rs | 61 +- thin-client/README.md | 2 +- thin-client/src/thin_client.rs | 60 +- tps-client/Cargo.toml | 2 +- transaction-dos/src/main.rs | 1 + transaction-metrics-tracker/Cargo.toml | 1 - transaction-status/src/lib.rs | 14 + transaction-status/src/parse_token.rs | 31 +- type-overrides/Cargo.toml | 19 + type-overrides/src/lib.rs | 50 + unified-scheduler-pool/src/lib.rs | 200 ++-- .../src/sleepless_testing.rs | 124 +- validator/src/bin/solana-test-validator.rs | 5 +- validator/src/cli.rs | 28 - validator/src/main.rs | 31 +- wen-restart/src/heaviest_fork_aggregate.rs | 41 + .../src/last_voted_fork_slots_aggregate.rs | 45 + zk-sdk/Cargo.toml | 1 + zk-sdk/src/encryption/pod/elgamal.rs | 8 +- zk-sdk/src/encryption/pod/grouped_elgamal.rs | 6 +- zk-sdk/src/encryption/pod/pedersen.rs | 2 +- zk-sdk/src/pod.rs | 2 +- zk-sdk/src/sigma_proofs/pod.rs | 4 +- .../handles_2.rs | 2 +- .../handles_3.rs | 2 +- .../batched_range_proof_u128.rs | 2 +- .../batched_range_proof_u256.rs | 2 +- .../batched_range_proof_u64.rs | 2 +- .../proof_data/batched_range_proof/mod.rs | 9 +- .../ciphertext_ciphertext_equality.rs | 2 +- .../ciphertext_commitment_equality.rs | 2 +- .../grouped_ciphertext_validity/handles_2.rs | 2 +- .../grouped_ciphertext_validity/handles_3.rs | 2 +- .../proof_data/percentage_with_cap.rs | 2 +- .../proof_data/pod.rs | 2 +- .../proof_data/pubkey_validity.rs | 2 +- .../proof_data/zero_ciphertext.rs | 2 +- zk-sdk/src/zk_elgamal_proof_program/state.rs | 2 +- zk-token-sdk/Cargo.toml | 5 +- zk-token-sdk/src/curve25519/mod.rs | 11 - zk-token-sdk/src/encryption/elgamal.rs | 12 +- zk-token-sdk/src/errors.rs | 24 +- .../handles_2.rs | 2 +- .../handles_3.rs | 2 +- .../batched_range_proof_u128.rs | 2 +- .../batched_range_proof_u256.rs | 2 +- .../batched_range_proof_u64.rs | 2 +- .../instruction/batched_range_proof/mod.rs | 9 +- .../ciphertext_ciphertext_equality.rs | 2 +- .../ciphertext_commitment_equality.rs | 2 +- zk-token-sdk/src/instruction/fee_sigma.rs | 2 +- .../grouped_ciphertext_validity/handles_2.rs | 2 +- .../grouped_ciphertext_validity/handles_3.rs | 2 +- .../src/instruction/pubkey_validity.rs | 2 +- zk-token-sdk/src/instruction/range_proof.rs | 2 +- .../src/instruction/transfer/with_fee.rs | 21 +- .../src/instruction/transfer/without_fee.rs | 4 +- zk-token-sdk/src/instruction/withdraw.rs | 2 +- zk-token-sdk/src/instruction/zero_balance.rs | 2 +- zk-token-sdk/src/lib.rs | 3 +- zk-token-sdk/src/zk_token_elgamal/convert.rs | 23 +- zk-token-sdk/src/zk_token_elgamal/ops.rs | 6 +- .../zk_token_elgamal/pod/auth_encryption.rs | 3 +- .../src/zk_token_elgamal/pod/elgamal.rs | 9 +- .../zk_token_elgamal/pod/grouped_elgamal.rs | 6 +- .../src/zk_token_elgamal/pod/instruction.rs | 9 +- zk-token-sdk/src/zk_token_elgamal/pod/mod.rs | 14 +- .../src/zk_token_elgamal/pod/pedersen.rs | 10 +- .../src/zk_token_elgamal/pod/range_proof.rs | 6 +- .../src/zk_token_elgamal/pod/sigma_proofs.rs | 6 +- zk-token-sdk/src/zk_token_proof_state.rs | 2 +- 325 files changed, 8349 insertions(+), 8506 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100755 cli/tests/fixtures/alt_bn128.so delete mode 100644 client/src/nonblocking/quic_client.rs delete mode 100644 client/src/nonblocking/tpu_connection.rs delete mode 100644 client/src/nonblocking/udp_client.rs delete mode 100644 client/src/quic_client.rs delete mode 100644 client/src/tpu_connection.rs delete mode 100644 client/src/udp_client.rs create mode 100644 curves/curve25519/.gitignore create mode 100644 curves/curve25519/Cargo.toml rename {zk-token-sdk/src/curve25519 => curves/curve25519/src}/curve_syscall_traits.rs (100%) rename {zk-token-sdk/src/curve25519 => curves/curve25519/src}/edwards.rs (98%) rename {zk-token-sdk/src/curve25519 => curves/curve25519/src}/errors.rs (100%) create mode 100644 curves/curve25519/src/lib.rs rename {zk-token-sdk/src/curve25519 => curves/curve25519/src}/ristretto.rs (98%) rename {zk-token-sdk/src/curve25519 => curves/curve25519/src}/scalar.rs (52%) create mode 100644 docs/src/runtime/zk-docs/ciphertext_validity.pdf rename docs/src/runtime/{zk-token-proof.md => zk-elgamal-proof.md} (65%) create mode 100755 net/scripts/install-jq.sh create mode 100644 program-runtime/src/mem_pool.rs create mode 100755 programs/sbf/Makefile delete mode 100644 programs/sbf/build.rs delete mode 100644 programs/sbf/c/makefile delete mode 100644 programs/sbf/tests/mem.rs delete mode 100644 programs/sbf/tests/remaining_compute_units.rs delete mode 100644 programs/sbf/tests/sanity.rs delete mode 100644 programs/sbf/tests/simulation_validator.rs delete mode 100644 rpc-client-api/src/deprecated_config.rs delete mode 100644 rpc-client-api/src/version_req.rs rename {sdk/src => runtime/src/bank}/recent_blockhashes_account.rs (72%) delete mode 100644 sdk/program/src/borsh0_9.rs create mode 100644 svm/src/rollback_accounts.rs create mode 100644 type-overrides/Cargo.toml create mode 100644 type-overrides/src/lib.rs delete mode 100644 zk-token-sdk/src/curve25519/mod.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000000..e01f5318e40257 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @anza-xyz/backport-reviewers diff --git a/.github/scripts/downstream-project-spl-common.sh b/.github/scripts/downstream-project-spl-common.sh index 0bfff86dcca2df..779af8f2568110 100644 --- a/.github/scripts/downstream-project-spl-common.sh +++ b/.github/scripts/downstream-project-spl-common.sh @@ -19,6 +19,7 @@ project_used_solana_version=$(sed -nE 's/solana-sdk = \"[>=<~]*(.*)\"/\1/p' <"to echo "used solana version: $project_used_solana_version" if semverGT "$project_used_solana_version" "$SOLANA_VER"; then echo "skip" + export SKIP_SPL_DOWNSTREAM_PROJECT_TEST=1 return fi diff --git a/.github/workflows/client-targets.yml b/.github/workflows/client-targets.yml index 848d10f85089e2..1a33d2ae59493c 100644 --- a/.github/workflows/client-targets.yml +++ b/.github/workflows/client-targets.yml @@ -47,7 +47,7 @@ jobs: strategy: matrix: os: - - macos-11 + - macos-12 target: - aarch64-apple-ios - x86_64-apple-ios diff --git a/.github/workflows/downstream-project-spl.yml b/.github/workflows/downstream-project-spl.yml index d875afbd4a9c56..8d3baf25949e99 100644 --- a/.github/workflows/downstream-project-spl.yml +++ b/.github/workflows/downstream-project-spl.yml @@ -55,6 +55,10 @@ jobs: run: | source .github/scripts/downstream-project-spl-install-deps.sh source .github/scripts/downstream-project-spl-common.sh + if [ -n "$SKIP_SPL_DOWNSTREAM_PROJECT_TEST" ]; then + exit 0 + fi + cargo check test: @@ -103,6 +107,9 @@ jobs: run: | source .github/scripts/downstream-project-spl-install-deps.sh source .github/scripts/downstream-project-spl-common.sh + if [ -n "$SKIP_SPL_DOWNSTREAM_PROJECT_TEST" ]; then + exit 0 + fi programStr="${{ tojson(matrix.arrays.required_programs) }}" IFS=', ' read -ra programs <<<"${programStr//[\[\]$'\n'$'\r' ]/}" @@ -154,6 +161,9 @@ jobs: run: | source .github/scripts/downstream-project-spl-install-deps.sh source .github/scripts/downstream-project-spl-common.sh + if [ -n "$SKIP_SPL_DOWNSTREAM_PROJECT_TEST" ]; then + exit 0 + fi programStr="${{ tojson(matrix.programs) }}" IFS=', ' read -ra programs <<<"${programStr//[\[\]$'\n'$'\r' ]/}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 102aa724f7e32d..c8d5c088ca3382 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,28 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm and follows a [Backwards Compatibility Policy](https://docs.solanalabs.com/backwards-compatibility) Release channels have their own copy of this changelog: -* [edge - v2.0](#edge-channel) -* [beta - v1.18](https://github.com/solana-labs/solana/blob/v1.18/CHANGELOG.md) -* [stable - v1.17](https://github.com/solana-labs/solana/blob/v1.17/CHANGELOG.md) +* [edge - v2.1](#edge-channel) +* [beta - v2.0](https://github.com/solana-labs/solana/blob/v2.0/CHANGELOG.md) +* [stable - v1.18](https://github.com/solana-labs/solana/blob/v1.18/CHANGELOG.md) -## [2.0.0] - Unreleased +## [2.1.0] - Unreleased + +## [2.0.0] * Breaking - * SDK: Support for Borsh v0.9 removed, please use v1 or v0.10 (#1440) + * SDK: + * Support for Borsh v0.9 removed, please use v1 or v0.10 (#1440) + * `Copy` is no longer derived on `Rent` and `EpochSchedule`, please switch to using `clone()` (solana-labs#32767) + * `solana-sdk`: deprecated symbols removed + * `solana-program`: deprecated symbols removed + * RPC: obsolete and deprecated v1 endpoints are removed. These endpoints are: + confirmTransaction, getSignatureStatus, getSignatureConfirmation, getTotalSupply, + getConfirmedSignaturesForAddress, getConfirmedBlock, getConfirmedBlocks, getConfirmedBlocksWithLimit, + getConfirmedTransaction, getConfirmedSignaturesForAddress2, getRecentBlockhash, getFees, + getFeeCalculatorForBlockhash, getFeeRateGovernor, getSnapshotSlot getStakeActivation + * `--enable-rpc-obsolete_v1_7` flag removed + * Deprecated methods are removed from `RpcClient` and `RpcClient::nonblocking` + * `solana-client`: deprecated re-exports removed; please import `solana-connection-cache`, `solana-quic-client`, or `solana-udp-client` directly * Changes * `central-scheduler` as default option for `--block-production-method` (#34891) * `solana-rpc-client-api`: `RpcFilterError` depends on `base64` version 0.22, so users may need to upgrade to `base64` version 0.22 @@ -25,6 +39,16 @@ Release channels have their own copy of this changelog: when the `replaceRecentBlockhash` config param is `true` (#380) * SDK: `cargo test-sbf` accepts `--tools-version`, just like `build-sbf` (#1359) * CLI: Can specify `--full-snapshot-archive-path` (#1631) + * transaction-status: The SPL Token `amountToUiAmount` instruction parses the amount into a string instead of a number (#1737) + * Implemented partitioned epoch rewards as per [SIMD-0118](https://github.com/solana-foundation/solana-improvement-documents/blob/fae25d5a950f43bd787f1f5d75897ef1fdd425a7/proposals/0118-partitioned-epoch-reward-distribution.md). Feature gate: #426. Specific changes include: + * EpochRewards sysvar expanded and made persistent (#428, #572) + * Stake Program credits now allowed during distribution (#631) + * Updated type in Bank::epoch_rewards_status (#1277) + * Partitions are recalculated on boot from snapshot (#1159) + * `epoch_rewards_status` removed from snapshot (#1274) + * Added `unified-scheduler` option for `--block-verification-method` (#1668) + * Deprecate the `fifo` option for `--rocksdb-shred-compaction` (#1882) + * `fifo` will remain supported in v2.0 with plans to fully remove in v2.1 ## [1.18.0] * Changes diff --git a/Cargo.lock b/Cargo.lock index e1c869720b7747..af027b270249ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "agave-accounts-hash-cache-tool" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bytemuck", "clap 2.34.0", @@ -75,7 +75,7 @@ dependencies = [ [[package]] name = "agave-cargo-registry" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 2.34.0", "flate2", @@ -105,7 +105,7 @@ dependencies = [ [[package]] name = "agave-geyser-plugin-interface" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "solana-sdk", @@ -115,7 +115,7 @@ dependencies = [ [[package]] name = "agave-install" -version = "2.0.0" +version = "2.0.2" dependencies = [ "atty", "bincode", @@ -151,7 +151,7 @@ dependencies = [ [[package]] name = "agave-ledger-tool" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_cmd", "bs58", @@ -194,6 +194,7 @@ dependencies = [ "solana-storage-bigtable", "solana-streamer", "solana-transaction-status", + "solana-type-overrides", "solana-unified-scheduler-pool", "solana-version", "solana-vote-program", @@ -205,7 +206,7 @@ dependencies = [ [[package]] name = "agave-store-tool" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 2.34.0", "solana-accounts-db", @@ -215,7 +216,7 @@ dependencies = [ [[package]] name = "agave-validator" -version = "2.0.0" +version = "2.0.2" dependencies = [ "agave-geyser-plugin-interface", "chrono", @@ -283,7 +284,7 @@ dependencies = [ [[package]] name = "agave-watchtower" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 2.34.0", "humantime", @@ -626,6 +627,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "assoc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdc70193dadb9d7287fa4b633f15f90c876915b31f6af17da307fc59c9859a8" + [[package]] name = "async-channel" version = "1.9.0" @@ -679,18 +686,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -843,7 +850,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -885,6 +892,18 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.5.1" @@ -967,7 +986,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "syn_derive", ] @@ -1083,7 +1102,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1094,9 +1113,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "bytesize" @@ -1184,13 +1203,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.99" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -1238,7 +1256,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1332,18 +1350,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstyle", "clap_lex 0.7.1", @@ -1519,7 +1537,7 @@ dependencies = [ "anes", "cast 0.3.0", "ciborium", - "clap 4.5.7", + "clap 4.5.9", "criterion-plot", "is-terminal", "itertools 0.10.5", @@ -1675,9 +1693,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -1685,27 +1703,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1776,7 +1794,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1789,7 +1807,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1868,7 +1886,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1891,7 +1909,7 @@ checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -1997,7 +2015,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -2010,7 +2028,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -2192,6 +2210,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -2255,7 +2279,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -2315,7 +2339,7 @@ dependencies = [ [[package]] name = "gen-headers" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "regex", @@ -2323,11 +2347,25 @@ dependencies = [ [[package]] name = "gen-syscall-list" -version = "2.0.0" +version = "2.0.2" dependencies = [ "regex", ] +[[package]] +name = "generator" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186014d53bc231d0090ef8d6f03e0920c54d85a5ed22f4f2f74315ec56cf83fb" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2647,9 +2685,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", @@ -2737,7 +2775,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -2819,9 +2857,9 @@ dependencies = [ [[package]] name = "index_list" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70891286cb8e844fdfcf1178b47569699f9e20b5ecc4b45a6240a64771444638" +checksum = "2cb725b6505e51229de32027e0cfcd9db29da4d89156f9747b0a5195643fa3e1" [[package]] name = "indexmap" @@ -3128,7 +3166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if 1.0.0", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -3342,9 +3380,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -3552,7 +3590,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3625,7 +3663,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3636,9 +3674,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.36.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] @@ -3660,9 +3698,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" @@ -3693,7 +3731,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -3749,6 +3787,12 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "parity-tokio-ipc" version = "0.9.0" @@ -3806,9 +3850,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -3867,9 +3911,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", @@ -3878,9 +3922,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" dependencies = [ "pest", "pest_generator", @@ -3888,22 +3932,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "pest_meta" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" dependencies = [ "once_cell", "pest", @@ -3947,7 +3991,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -4222,7 +4266,7 @@ dependencies = [ [[package]] name = "proto" -version = "2.0.0" +version = "2.0.2" dependencies = [ "protobuf-src", "tonic-build", @@ -4254,7 +4298,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -4320,6 +4364,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.4.6" @@ -4419,6 +4469,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xorshift" version = "0.3.0" @@ -4459,7 +4518,7 @@ dependencies = [ [[package]] name = "rbpf-cli" -version = "2.0.0" +version = "2.0.2" [[package]] name = "rdrand" @@ -4490,9 +4549,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -4812,6 +4871,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -4835,7 +4900,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -4850,9 +4915,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -4863,9 +4928,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -4891,9 +4956,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -4909,20 +4974,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -4969,7 +5034,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -5019,7 +5084,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -5113,6 +5178,25 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shuttle" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9a8db61a44e2b663f169a08206a789bcbd22ba32011e14951562848e7b9c98" +dependencies = [ + "assoc", + "bitvec", + "generator", + "hex", + "owo-colors", + "rand 0.8.5", + "rand_core 0.6.4", + "rand_pcg", + "scoped-tls", + "smallvec", + "tracing", +] + [[package]] name = "signal-hook" version = "0.3.17" @@ -5228,7 +5312,7 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "2.0.0" +version = "2.0.2" dependencies = [ "Inflector", "assert_matches", @@ -5252,7 +5336,7 @@ dependencies = [ [[package]] name = "solana-accounts-bench" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 2.34.0", "log", @@ -5266,7 +5350,7 @@ dependencies = [ [[package]] name = "solana-accounts-cluster-bench" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 2.34.0", "log", @@ -5297,13 +5381,14 @@ dependencies = [ [[package]] name = "solana-accounts-db" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", "blake3", "bv", "bytemuck", + "bytemuck_derive", "criterion", "crossbeam-channel", "dashmap", @@ -5331,6 +5416,7 @@ dependencies = [ "smallvec", "solana-accounts-db", "solana-bucket-map", + "solana-compute-budget", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-inline-spl", @@ -5352,7 +5438,7 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "bytemuck", @@ -5368,7 +5454,7 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program-tests" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -5379,7 +5465,7 @@ dependencies = [ [[package]] name = "solana-banking-bench" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 3.2.25", "crossbeam-channel", @@ -5403,7 +5489,7 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "borsh 1.5.1", "futures 0.3.30", @@ -5420,7 +5506,7 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "2.0.0" +version = "2.0.2" dependencies = [ "serde", "serde_derive", @@ -5430,7 +5516,7 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "crossbeam-channel", @@ -5448,7 +5534,7 @@ dependencies = [ [[package]] name = "solana-bench-streamer" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 3.2.25", "crossbeam-channel", @@ -5459,7 +5545,7 @@ dependencies = [ [[package]] name = "solana-bench-tps" -version = "2.0.0" +version = "2.0.2" dependencies = [ "chrono", "clap 2.34.0", @@ -5505,7 +5591,7 @@ dependencies = [ [[package]] name = "solana-bloom" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bv", "fnv", @@ -5522,7 +5608,7 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -5533,12 +5619,13 @@ dependencies = [ "rand 0.8.5", "scopeguard", "solana-compute-budget", + "solana-curve25519", "solana-measure", "solana-poseidon", "solana-program-runtime", "solana-sdk", + "solana-type-overrides", "solana-vote", - "solana-zk-token-sdk", "solana_rbpf", "test-case", "thiserror", @@ -5546,7 +5633,7 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program-tests" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -5557,10 +5644,11 @@ dependencies = [ [[package]] name = "solana-bucket-map" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bv", "bytemuck", + "bytemuck_derive", "fs_extra", "log", "memmap2", @@ -5576,7 +5664,7 @@ dependencies = [ [[package]] name = "solana-cargo-build-bpf" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "solana-logger", @@ -5584,7 +5672,7 @@ dependencies = [ [[package]] name = "solana-cargo-build-sbf" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_cmd", "bzip2", @@ -5605,11 +5693,11 @@ dependencies = [ [[package]] name = "solana-cargo-test-bpf" -version = "2.0.0" +version = "2.0.2" [[package]] name = "solana-cargo-test-sbf" -version = "2.0.0" +version = "2.0.2" dependencies = [ "cargo_metadata", "clap 3.2.25", @@ -5621,7 +5709,7 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "chrono", @@ -5638,7 +5726,7 @@ dependencies = [ [[package]] name = "solana-clap-v3-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "chrono", @@ -5656,7 +5744,7 @@ dependencies = [ [[package]] name = "solana-cli" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -5715,7 +5803,7 @@ dependencies = [ [[package]] name = "solana-cli-config" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "dirs-next", @@ -5730,7 +5818,7 @@ dependencies = [ [[package]] name = "solana-cli-output" -version = "2.0.0" +version = "2.0.2" dependencies = [ "Inflector", "base64 0.22.1", @@ -5756,7 +5844,7 @@ dependencies = [ [[package]] name = "solana-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "bincode", @@ -5788,7 +5876,7 @@ dependencies = [ [[package]] name = "solana-client-test" -version = "2.0.0" +version = "2.0.2" dependencies = [ "futures-util", "rand 0.8.5", @@ -5818,7 +5906,7 @@ dependencies = [ [[package]] name = "solana-compute-budget" -version = "2.0.0" +version = "2.0.2" dependencies = [ "rustc_version", "solana-frozen-abi", @@ -5827,7 +5915,7 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -5835,7 +5923,7 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "chrono", @@ -5848,7 +5936,7 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "bincode", @@ -5871,7 +5959,7 @@ dependencies = [ [[package]] name = "solana-core" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ahash 0.8.11", "assert_matches", @@ -5910,6 +5998,7 @@ dependencies = [ "solana-bloom", "solana-client", "solana-compute-budget", + "solana-connection-cache", "solana-core", "solana-cost-model", "solana-entry", @@ -5958,7 +6047,7 @@ dependencies = [ [[package]] name = "solana-cost-model" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ahash 0.8.11", "itertools 0.12.1", @@ -5982,9 +6071,20 @@ dependencies = [ "test-case", ] +[[package]] +name = "solana-curve25519" +version = "2.0.2" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "solana-program", + "thiserror", +] + [[package]] name = "solana-dos" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "clap 3.2.25", @@ -5995,6 +6095,7 @@ dependencies = [ "serde", "solana-bench-tps", "solana-client", + "solana-connection-cache", "solana-core", "solana-faucet", "solana-gossip", @@ -6016,7 +6117,7 @@ dependencies = [ [[package]] name = "solana-download-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "console", "indicatif", @@ -6028,7 +6129,7 @@ dependencies = [ [[package]] name = "solana-ed25519-program-tests" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "ed25519-dalek", @@ -6039,7 +6140,7 @@ dependencies = [ [[package]] name = "solana-entry" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -6061,7 +6162,7 @@ dependencies = [ [[package]] name = "solana-faucet" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "byteorder", @@ -6083,7 +6184,7 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bitflags 2.6.0", "bs58", @@ -6104,17 +6205,17 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "2.0.0" +version = "2.0.2" dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "solana-genesis" -version = "2.0.0" +version = "2.0.2" dependencies = [ "base64 0.22.1", "bincode", @@ -6139,7 +6240,7 @@ dependencies = [ [[package]] name = "solana-genesis-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "solana-accounts-db", @@ -6150,7 +6251,7 @@ dependencies = [ [[package]] name = "solana-geyser-plugin-manager" -version = "2.0.0" +version = "2.0.2" dependencies = [ "agave-geyser-plugin-interface", "bs58", @@ -6175,7 +6276,7 @@ dependencies = [ [[package]] name = "solana-gossip" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -6226,7 +6327,7 @@ dependencies = [ [[package]] name = "solana-inline-spl" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bytemuck", "rustc_version", @@ -6235,7 +6336,7 @@ dependencies = [ [[package]] name = "solana-keygen" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bs58", "clap 3.2.25", @@ -6252,7 +6353,7 @@ dependencies = [ [[package]] name = "solana-ledger" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -6324,7 +6425,7 @@ dependencies = [ [[package]] name = "solana-loader-v4-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "log", @@ -6332,12 +6433,13 @@ dependencies = [ "solana-measure", "solana-program-runtime", "solana-sdk", + "solana-type-overrides", "solana_rbpf", ] [[package]] name = "solana-local-cluster" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "crossbeam-channel", @@ -6356,6 +6458,7 @@ dependencies = [ "solana-entry", "solana-gossip", "solana-ledger", + "solana-local-cluster", "solana-logger", "solana-pubsub-client", "solana-quic-client", @@ -6377,7 +6480,7 @@ dependencies = [ [[package]] name = "solana-log-analyzer" -version = "2.0.0" +version = "2.0.2" dependencies = [ "byte-unit", "clap 3.2.25", @@ -6389,7 +6492,7 @@ dependencies = [ [[package]] name = "solana-logger" -version = "2.0.0" +version = "2.0.2" dependencies = [ "env_logger", "lazy_static", @@ -6398,7 +6501,7 @@ dependencies = [ [[package]] name = "solana-measure" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "solana-sdk", @@ -6406,11 +6509,11 @@ dependencies = [ [[package]] name = "solana-memory-management" -version = "2.0.0" +version = "2.0.2" [[package]] name = "solana-merkle-root-bench" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 2.34.0", "log", @@ -6423,7 +6526,7 @@ dependencies = [ [[package]] name = "solana-merkle-tree" -version = "2.0.0" +version = "2.0.2" dependencies = [ "fast-math", "hex", @@ -6432,7 +6535,7 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "2.0.0" +version = "2.0.2" dependencies = [ "crossbeam-channel", "env_logger", @@ -6448,7 +6551,7 @@ dependencies = [ [[package]] name = "solana-net-shaper" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 3.2.25", "rand 0.8.5", @@ -6460,7 +6563,7 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "clap 3.2.25", @@ -6487,7 +6590,7 @@ checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" [[package]] name = "solana-notifier" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "reqwest", @@ -6497,17 +6600,17 @@ dependencies = [ [[package]] name = "solana-package-metadata-macro" -version = "2.0.0" +version = "2.0.2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "toml 0.8.14", ] [[package]] name = "solana-perf" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ahash 0.8.11", "assert_matches", @@ -6537,7 +6640,7 @@ dependencies = [ [[package]] name = "solana-poh" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -6559,7 +6662,7 @@ dependencies = [ [[package]] name = "solana-poh-bench" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 3.2.25", "log", @@ -6575,7 +6678,7 @@ dependencies = [ [[package]] name = "solana-poseidon" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ark-bn254", "light-poseidon", @@ -6584,7 +6687,7 @@ dependencies = [ [[package]] name = "solana-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "arbitrary", @@ -6603,7 +6706,7 @@ dependencies = [ "bs58", "bv", "bytemuck", - "cc", + "bytemuck_derive", "console_error_panic_hook", "console_log", "curve25519-dalek", @@ -6618,6 +6721,7 @@ dependencies = [ "num-derive", "num-traits", "parking_lot 0.12.3", + "qualifier_attr", "rand 0.8.5", "rustc_version", "rustversion", @@ -6638,7 +6742,7 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "base64 0.22.1", @@ -6660,6 +6764,7 @@ dependencies = [ "solana-logger", "solana-measure", "solana-sdk", + "solana-type-overrides", "solana-vote", "solana_rbpf", "test-case", @@ -6668,7 +6773,7 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "async-trait", @@ -6700,7 +6805,7 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "crossbeam-channel", @@ -6724,7 +6829,7 @@ dependencies = [ [[package]] name = "solana-quic-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-mutex", "async-trait", @@ -6751,7 +6856,7 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "2.0.0" +version = "2.0.2" dependencies = [ "lazy_static", "num_cpus", @@ -6759,7 +6864,7 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "console", @@ -6778,7 +6883,7 @@ dependencies = [ [[package]] name = "solana-rpc" -version = "2.0.0" +version = "2.0.2" dependencies = [ "base64 0.22.1", "bincode", @@ -6839,7 +6944,7 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "async-trait", @@ -6869,11 +6974,12 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "base64 0.22.1", "bs58", + "const_format", "jsonrpc-core", "reqwest", "reqwest-middleware", @@ -6891,7 +6997,7 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "clap 2.34.0", @@ -6908,7 +7014,7 @@ dependencies = [ [[package]] name = "solana-rpc-test" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "bs58", @@ -6935,7 +7041,7 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "2.0.0" +version = "2.0.2" dependencies = [ "aquamarine", "arrayref", @@ -7002,6 +7108,8 @@ dependencies = [ "solana-version", "solana-vote", "solana-vote-program", + "solana-zk-elgamal-proof-program", + "solana-zk-sdk", "solana-zk-token-proof-program", "solana-zk-token-sdk", "static_assertions", @@ -7014,7 +7122,7 @@ dependencies = [ [[package]] name = "solana-runtime-transaction" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "log", @@ -7028,7 +7136,7 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "assert_matches", @@ -7037,6 +7145,7 @@ dependencies = [ "borsh 1.5.1", "bs58", "bytemuck", + "bytemuck_derive", "byteorder", "chrono", "curve25519-dalek", @@ -7085,13 +7194,13 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bs58", "proc-macro2", "quote", "rustversion", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7102,11 +7211,12 @@ checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" [[package]] name = "solana-send-transaction-service" -version = "2.0.0" +version = "2.0.2" dependencies = [ "crossbeam-channel", "log", "solana-client", + "solana-connection-cache", "solana-logger", "solana-measure", "solana-metrics", @@ -7117,7 +7227,7 @@ dependencies = [ [[package]] name = "solana-stake-accounts" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 2.34.0", "solana-clap-utils", @@ -7133,7 +7243,7 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -7145,13 +7255,14 @@ dependencies = [ "solana-logger", "solana-program-runtime", "solana-sdk", + "solana-type-overrides", "solana-vote-program", "test-case", ] [[package]] name = "solana-storage-bigtable" -version = "2.0.0" +version = "2.0.2" dependencies = [ "backoff", "bincode", @@ -7183,7 +7294,7 @@ dependencies = [ [[package]] name = "solana-storage-proto" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "bs58", @@ -7199,7 +7310,7 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "async-channel", @@ -7233,7 +7344,7 @@ dependencies = [ [[package]] name = "solana-svm" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "itertools 0.12.1", @@ -7261,12 +7372,13 @@ dependencies = [ "solana-sdk", "solana-svm", "solana-system-program", + "solana-type-overrides", "solana-vote", ] [[package]] name = "solana-system-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -7277,11 +7389,12 @@ dependencies = [ "solana-logger", "solana-program-runtime", "solana-sdk", + "solana-type-overrides", ] [[package]] name = "solana-test-validator" -version = "2.0.0" +version = "2.0.2" dependencies = [ "base64 0.22.1", "bincode", @@ -7311,7 +7424,7 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "log", @@ -7325,7 +7438,7 @@ dependencies = [ [[package]] name = "solana-tokens" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -7359,7 +7472,7 @@ dependencies = [ [[package]] name = "solana-tps-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "serial_test", @@ -7380,7 +7493,7 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "bincode", @@ -7402,7 +7515,7 @@ dependencies = [ [[package]] name = "solana-transaction-dos" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "clap 2.34.0", @@ -7429,7 +7542,7 @@ dependencies = [ [[package]] name = "solana-transaction-metrics-tracker" -version = "2.0.0" +version = "2.0.2" dependencies = [ "Inflector", "base64 0.22.1", @@ -7443,7 +7556,7 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "2.0.0" +version = "2.0.2" dependencies = [ "Inflector", "base64 0.22.1", @@ -7468,7 +7581,7 @@ dependencies = [ [[package]] name = "solana-turbine" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -7503,9 +7616,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "solana-type-overrides" +version = "2.0.2" +dependencies = [ + "futures 0.3.30", + "lazy_static", + "rand 0.8.5", + "shuttle", +] + [[package]] name = "solana-udp-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "solana-connection-cache", @@ -7518,7 +7641,7 @@ dependencies = [ [[package]] name = "solana-unified-scheduler-logic" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "solana-sdk", @@ -7527,7 +7650,7 @@ dependencies = [ [[package]] name = "solana-unified-scheduler-pool" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "crossbeam-channel", @@ -7548,7 +7671,7 @@ dependencies = [ [[package]] name = "solana-upload-perf" -version = "2.0.0" +version = "2.0.2" dependencies = [ "serde_json", "solana-metrics", @@ -7556,7 +7679,7 @@ dependencies = [ [[package]] name = "solana-version" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "rustc_version", @@ -7570,7 +7693,7 @@ dependencies = [ [[package]] name = "solana-vote" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "itertools 0.12.1", @@ -7587,7 +7710,7 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -7609,7 +7732,7 @@ dependencies = [ [[package]] name = "solana-wen-restart" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "assert_matches", @@ -7638,7 +7761,7 @@ dependencies = [ [[package]] name = "solana-zk-elgamal-proof-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bytemuck", "num-derive", @@ -7650,7 +7773,7 @@ dependencies = [ [[package]] name = "solana-zk-keygen" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bs58", "clap 3.2.25", @@ -7669,12 +7792,13 @@ dependencies = [ [[package]] name = "solana-zk-sdk" -version = "2.0.0" +version = "2.0.2" dependencies = [ "aes-gcm-siv", "base64 0.22.1", "bincode", "bytemuck", + "bytemuck_derive", "curve25519-dalek", "itertools 0.12.1", "lazy_static", @@ -7696,7 +7820,7 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bytemuck", "criterion", @@ -7710,7 +7834,7 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program-tests" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bytemuck", "curve25519-dalek", @@ -7722,15 +7846,15 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "2.0.0" +version = "2.0.2" dependencies = [ "aes-gcm-siv", "base64 0.22.1", "bincode", "bytemuck", + "bytemuck_derive", "byteorder", "curve25519-dalek", - "getrandom 0.1.16", "itertools 0.12.1", "lazy_static", "merlin", @@ -7741,6 +7865,7 @@ dependencies = [ "serde_derive", "serde_json", "sha3 0.9.1", + "solana-curve25519", "solana-program", "solana-sdk", "subtle", @@ -7783,9 +7908,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spl-associated-token-account" -version = "3.0.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e688554bac5838217ffd1fab7845c573ff106b6336bf7d290db7c98d5a8efd" +checksum = "68034596cf4804880d265f834af1ff2f821ad5293e41fa0f8f59086c181fc38e" dependencies = [ "assert_matches", "borsh 1.5.1", @@ -7799,9 +7924,9 @@ dependencies = [ [[package]] name = "spl-discriminator" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d1814406e98b08c5cd02c1126f83fd407ad084adce0b05fda5730677822eac" +checksum = "a38ea8b6dedb7065887f12d62ed62c1743aa70749e8558f963609793f6fb12bc" dependencies = [ "bytemuck", "solana-program", @@ -7816,7 +7941,7 @@ checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -7828,15 +7953,15 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.68", + "syn 2.0.71", "thiserror", ] [[package]] name = "spl-instruction-padding" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be3f0c53b6eb2dfccb77b5710bddb04548da338a3f56bed214177f6a577d1ca6" +checksum = "8cdbcd2652240c5b04befd4807c2b0f9412c66b18db398ca955f236a8ff1c378" dependencies = [ "num_enum", "solana-program", @@ -7844,21 +7969,22 @@ dependencies = [ [[package]] name = "spl-memo" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e9bae02de3405079a057fe244c867a08f92d48327d231fc60da831f94caf0a" +checksum = "a0dba2f2bb6419523405d21c301a32c9f9568354d4742552e7972af801f4bdb3" dependencies = [ "solana-program", ] [[package]] name = "spl-pod" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ce669f48cf2eca1ec518916d8725596bfb655beb1c74374cf71dc6cb773c9" +checksum = "e6166a591d93af33afd75bbd8573c5fd95fb1213f1bf254f0508c89fdb5ee156" dependencies = [ "borsh 1.5.1", "bytemuck", + "bytemuck_derive", "solana-program", "solana-zk-token-sdk", "spl-program-error", @@ -7866,9 +7992,9 @@ dependencies = [ [[package]] name = "spl-program-error" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49065093ea91f57b9b2bd81493ff705e2ad4e64507a07dbc02b085778e02770e" +checksum = "d7b28bed65356558133751cc32b48a7a5ddfc59ac4e941314630bbed1ac10532" dependencies = [ "num-derive", "num-traits", @@ -7886,14 +8012,14 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] name = "spl-tlv-account-resolution" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cace91ba08984a41556efe49cbf2edca4db2f577b649da7827d3621161784bf8" +checksum = "37a75a5f0fcc58126693ed78a17042e9dc53f07e357d6be91789f7d62aff61a4" dependencies = [ "bytemuck", "solana-program", @@ -7905,9 +8031,9 @@ dependencies = [ [[package]] name = "spl-token" -version = "4.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ae123223633a389f95d1da9d49c2d0a50d499e7060b9624626a69e536ad2a4" +checksum = "70a0f06ac7f23dc0984931b1fe309468f14ea58e32660439c1cef19456f5d0e3" dependencies = [ "arrayref", "bytemuck", @@ -7920,9 +8046,9 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "3.0.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5412f99ae7ee6e0afde00defaa354e6228e47e30c0e3adf553e2e01e6abb584" +checksum = "d9c10f3483e48679619c76598d4e4aebb955bc49b0a5cc63323afbf44135c9bf" dependencies = [ "arrayref", "bytemuck", @@ -7944,9 +8070,9 @@ dependencies = [ [[package]] name = "spl-token-group-interface" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d419b5cfa3ee8e0f2386fd7e02a33b3ec8a7db4a9c7064a2ea24849dc4a273b6" +checksum = "df8752b85a5ecc1d9f3a43bce3dd9a6a053673aacf5deb513d1cbb88d3534ffd" dependencies = [ "bytemuck", "solana-program", @@ -7957,9 +8083,9 @@ dependencies = [ [[package]] name = "spl-token-metadata-interface" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30179c47e93625680dabb620c6e7931bd12d62af390f447bc7beb4a3a9b5feee" +checksum = "c6c2318ddff97e006ed9b1291ebec0750a78547f870f62a69c56fe3b46a5d8fc" dependencies = [ "borsh 1.5.1", "solana-program", @@ -7971,9 +8097,9 @@ dependencies = [ [[package]] name = "spl-transfer-hook-interface" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a98359769cd988f7b35c02558daa56d496a7e3bd8626e61f90a7c757eedb9b" +checksum = "a110f33d941275d9f868b96daaa993f1e73b6806cc8836e43075b4d3ad8338a7" dependencies = [ "arrayref", "bytemuck", @@ -7987,9 +8113,9 @@ dependencies = [ [[package]] name = "spl-type-length-value" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ce13429dbd41d2cee8a73931c05fda0b0c8ca156a8b0c19445642550bb61a" +checksum = "bdcd73ec187bc409464c60759232e309f83b52a18a9c5610bf281c9c6432918c" dependencies = [ "bytemuck", "solana-program", @@ -8080,9 +8206,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -8098,7 +8224,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8177,6 +8303,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.41" @@ -8277,7 +8409,7 @@ dependencies = [ "cfg-if 1.0.0", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8288,7 +8420,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "test-case-core", ] @@ -8309,22 +8441,22 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8426,9 +8558,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -8475,7 +8607,7 @@ source = "git+https://github.com/anza-xyz/solana-tokio.git?rev=7cf47705faacf7bf0 dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -8587,7 +8719,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.15", ] [[package]] @@ -8612,9 +8744,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" dependencies = [ "indexmap 2.2.6", "serde", @@ -8719,7 +8851,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -9027,7 +9159,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "wasm-bindgen-shared", ] @@ -9061,7 +9193,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -9152,13 +9284,42 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -9176,7 +9337,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -9196,18 +9357,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -9218,9 +9379,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -9230,9 +9391,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -9242,15 +9403,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -9260,9 +9421,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -9272,9 +9433,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -9284,9 +9445,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -9296,9 +9457,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -9328,6 +9489,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x509-parser" version = "0.14.0" @@ -9368,22 +9538,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -9403,7 +9573,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.71", ] [[package]] @@ -9427,9 +9597,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.11+zstd.1.5.6" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index cdea68a673eff1..b1169976cf2434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "connection-cache", "core", "cost-model", + "curves/*", "dos", "download-utils", "entry", @@ -116,6 +117,7 @@ members = [ "transaction-metrics-tracker", "transaction-status", "turbine", + "type-overrides", "udp-client", "unified-scheduler-logic", "unified-scheduler-pool", @@ -135,7 +137,7 @@ exclude = ["programs/sbf", "svm/tests/example-programs"] resolver = "2" [workspace.package] -version = "2.0.0" +version = "2.0.2" authors = ["Anza Maintainers "] repository = "https://github.com/anza-xyz/agave" homepage = "https://anza.xyz/" @@ -171,13 +173,13 @@ bs58 = "0.5.1" bv = "0.11.1" byte-unit = "4.0.19" bytecount = "0.6.8" -bytemuck = "1.16.0" +bytemuck = "1.16.1" +bytemuck_derive = "1.7.0" byteorder = "1.5.0" bytes = "1.6" bzip2 = "0.4.4" caps = "0.5.5" cargo_metadata = "0.15.4" -cc = "1.0.94" chrono = { version = "0.4.38", default-features = false } chrono-humanize = "0.2.3" clap = "2.33.1" @@ -310,110 +312,113 @@ serde_yaml = "0.9.34" serial_test = "2.0.0" sha2 = "0.10.8" sha3 = "0.10.8" +shuttle = "0.7.1" signal-hook = "0.3.17" siphasher = "0.3.11" smallvec = "1.13.2" smpl_jwt = "0.7.1" socket2 = "0.5.7" soketto = "0.7" -solana-account-decoder = { path = "account-decoder", version = "=2.0.0" } -solana-accounts-db = { path = "accounts-db", version = "=2.0.0" } -solana-address-lookup-table-program = { path = "programs/address-lookup-table", version = "=2.0.0" } -solana-banks-client = { path = "banks-client", version = "=2.0.0" } -solana-banks-interface = { path = "banks-interface", version = "=2.0.0" } -solana-banks-server = { path = "banks-server", version = "=2.0.0" } -solana-bench-tps = { path = "bench-tps", version = "=2.0.0" } -solana-bloom = { path = "bloom", version = "=2.0.0" } -solana-bpf-loader-program = { path = "programs/bpf_loader", version = "=2.0.0" } -solana-bucket-map = { path = "bucket_map", version = "=2.0.0" } -agave-cargo-registry = { path = "cargo-registry", version = "=2.0.0" } -solana-clap-utils = { path = "clap-utils", version = "=2.0.0" } -solana-clap-v3-utils = { path = "clap-v3-utils", version = "=2.0.0" } -solana-cli = { path = "cli", version = "=2.0.0" } -solana-cli-config = { path = "cli-config", version = "=2.0.0" } -solana-cli-output = { path = "cli-output", version = "=2.0.0" } -solana-client = { path = "client", version = "=2.0.0" } -solana-compute-budget = { path = "compute-budget", version = "=2.0.0" } -solana-compute-budget-program = { path = "programs/compute-budget", version = "=2.0.0" } -solana-config-program = { path = "programs/config", version = "=2.0.0" } -solana-connection-cache = { path = "connection-cache", version = "=2.0.0", default-features = false } -solana-core = { path = "core", version = "=2.0.0" } -solana-cost-model = { path = "cost-model", version = "=2.0.0" } -solana-download-utils = { path = "download-utils", version = "=2.0.0" } -solana-entry = { path = "entry", version = "=2.0.0" } -solana-faucet = { path = "faucet", version = "=2.0.0" } -solana-frozen-abi = { path = "frozen-abi", version = "=2.0.0" } -solana-frozen-abi-macro = { path = "frozen-abi/macro", version = "=2.0.0" } -solana-tps-client = { path = "tps-client", version = "=2.0.0" } -solana-genesis = { path = "genesis", version = "=2.0.0" } -solana-genesis-utils = { path = "genesis-utils", version = "=2.0.0" } -agave-geyser-plugin-interface = { path = "geyser-plugin-interface", version = "=2.0.0" } -solana-geyser-plugin-manager = { path = "geyser-plugin-manager", version = "=2.0.0" } -solana-gossip = { path = "gossip", version = "=2.0.0" } -solana-inline-spl = { path = "inline-spl", version = "=2.0.0" } -solana-ledger = { path = "ledger", version = "=2.0.0" } -solana-loader-v4-program = { path = "programs/loader-v4", version = "=2.0.0" } -solana-local-cluster = { path = "local-cluster", version = "=2.0.0" } -solana-logger = { path = "logger", version = "=2.0.0" } -solana-measure = { path = "measure", version = "=2.0.0" } -solana-merkle-tree = { path = "merkle-tree", version = "=2.0.0" } -solana-metrics = { path = "metrics", version = "=2.0.0" } -solana-net-utils = { path = "net-utils", version = "=2.0.0" } +solana-account-decoder = { path = "account-decoder", version = "=2.0.2" } +solana-accounts-db = { path = "accounts-db", version = "=2.0.2" } +solana-address-lookup-table-program = { path = "programs/address-lookup-table", version = "=2.0.2" } +solana-banks-client = { path = "banks-client", version = "=2.0.2" } +solana-banks-interface = { path = "banks-interface", version = "=2.0.2" } +solana-banks-server = { path = "banks-server", version = "=2.0.2" } +solana-bench-tps = { path = "bench-tps", version = "=2.0.2" } +solana-bloom = { path = "bloom", version = "=2.0.2" } +solana-bpf-loader-program = { path = "programs/bpf_loader", version = "=2.0.2" } +solana-bucket-map = { path = "bucket_map", version = "=2.0.2" } +agave-cargo-registry = { path = "cargo-registry", version = "=2.0.2" } +solana-clap-utils = { path = "clap-utils", version = "=2.0.2" } +solana-clap-v3-utils = { path = "clap-v3-utils", version = "=2.0.2" } +solana-cli = { path = "cli", version = "=2.0.2" } +solana-cli-config = { path = "cli-config", version = "=2.0.2" } +solana-cli-output = { path = "cli-output", version = "=2.0.2" } +solana-client = { path = "client", version = "=2.0.2" } +solana-compute-budget = { path = "compute-budget", version = "=2.0.2" } +solana-compute-budget-program = { path = "programs/compute-budget", version = "=2.0.2" } +solana-config-program = { path = "programs/config", version = "=2.0.2" } +solana-connection-cache = { path = "connection-cache", version = "=2.0.2", default-features = false } +solana-core = { path = "core", version = "=2.0.2" } +solana-cost-model = { path = "cost-model", version = "=2.0.2" } +solana-curve25519 = { path = "curves/curve25519", version = "=2.0.2" } +solana-download-utils = { path = "download-utils", version = "=2.0.2" } +solana-entry = { path = "entry", version = "=2.0.2" } +solana-faucet = { path = "faucet", version = "=2.0.2" } +solana-frozen-abi = { path = "frozen-abi", version = "=2.0.2" } +solana-frozen-abi-macro = { path = "frozen-abi/macro", version = "=2.0.2" } +solana-tps-client = { path = "tps-client", version = "=2.0.2" } +solana-genesis = { path = "genesis", version = "=2.0.2" } +solana-genesis-utils = { path = "genesis-utils", version = "=2.0.2" } +agave-geyser-plugin-interface = { path = "geyser-plugin-interface", version = "=2.0.2" } +solana-geyser-plugin-manager = { path = "geyser-plugin-manager", version = "=2.0.2" } +solana-gossip = { path = "gossip", version = "=2.0.2" } +solana-inline-spl = { path = "inline-spl", version = "=2.0.2" } +solana-ledger = { path = "ledger", version = "=2.0.2" } +solana-loader-v4-program = { path = "programs/loader-v4", version = "=2.0.2" } +solana-local-cluster = { path = "local-cluster", version = "=2.0.2" } +solana-logger = { path = "logger", version = "=2.0.2" } +solana-measure = { path = "measure", version = "=2.0.2" } +solana-merkle-tree = { path = "merkle-tree", version = "=2.0.2" } +solana-metrics = { path = "metrics", version = "=2.0.2" } +solana-net-utils = { path = "net-utils", version = "=2.0.2" } solana-nohash-hasher = "0.2.1" -solana-notifier = { path = "notifier", version = "=2.0.0" } -solana-package-metadata-macro = { path = "sdk/package-metadata-macro", version = "=2.0.0" } -solana-perf = { path = "perf", version = "=2.0.0" } -solana-poh = { path = "poh", version = "=2.0.0" } -solana-poseidon = { path = "poseidon", version = "=2.0.0" } -solana-program = { path = "sdk/program", version = "=2.0.0" } -solana-program-runtime = { path = "program-runtime", version = "=2.0.0" } -solana-program-test = { path = "program-test", version = "=2.0.0" } -solana-pubsub-client = { path = "pubsub-client", version = "=2.0.0" } -solana-quic-client = { path = "quic-client", version = "=2.0.0" } -solana-rayon-threadlimit = { path = "rayon-threadlimit", version = "=2.0.0" } -solana-remote-wallet = { path = "remote-wallet", version = "=2.0.0", default-features = false } -solana-unified-scheduler-logic = { path = "unified-scheduler-logic", version = "=2.0.0" } -solana-unified-scheduler-pool = { path = "unified-scheduler-pool", version = "=2.0.0" } -solana-rpc = { path = "rpc", version = "=2.0.0" } -solana-rpc-client = { path = "rpc-client", version = "=2.0.0", default-features = false } -solana-rpc-client-api = { path = "rpc-client-api", version = "=2.0.0" } -solana-rpc-client-nonce-utils = { path = "rpc-client-nonce-utils", version = "=2.0.0" } -solana-runtime = { path = "runtime", version = "=2.0.0" } -solana-runtime-transaction = { path = "runtime-transaction", version = "=2.0.0" } -solana-sdk = { path = "sdk", version = "=2.0.0" } -solana-sdk-macro = { path = "sdk/macro", version = "=2.0.0" } -solana-send-transaction-service = { path = "send-transaction-service", version = "=2.0.0" } -solana-stake-program = { path = "programs/stake", version = "=2.0.0" } -solana-storage-bigtable = { path = "storage-bigtable", version = "=2.0.0" } -solana-storage-proto = { path = "storage-proto", version = "=2.0.0" } -solana-streamer = { path = "streamer", version = "=2.0.0" } -solana-svm = { path = "svm", version = "=2.0.0" } -solana-system-program = { path = "programs/system", version = "=2.0.0" } -solana-test-validator = { path = "test-validator", version = "=2.0.0" } -solana-thin-client = { path = "thin-client", version = "=2.0.0" } -solana-tpu-client = { path = "tpu-client", version = "=2.0.0", default-features = false } -solana-transaction-status = { path = "transaction-status", version = "=2.0.0" } -solana-transaction-metrics-tracker = { path = "transaction-metrics-tracker", version = "=2.0.0" } -solana-turbine = { path = "turbine", version = "=2.0.0" } -solana-udp-client = { path = "udp-client", version = "=2.0.0" } -solana-version = { path = "version", version = "=2.0.0" } -solana-vote = { path = "vote", version = "=2.0.0" } -solana-vote-program = { path = "programs/vote", version = "=2.0.0" } -solana-wen-restart = { path = "wen-restart", version = "=2.0.0" } -solana-zk-elgamal-proof-program = { path = "programs/zk-elgamal-proof", version = "=2.0.0" } -solana-zk-keygen = { path = "zk-keygen", version = "=2.0.0" } -solana-zk-sdk = { path = "zk-sdk", version = "=2.0.0" } -solana-zk-token-proof-program = { path = "programs/zk-token-proof", version = "=2.0.0" } -solana-zk-token-sdk = { path = "zk-token-sdk", version = "=2.0.0" } +solana-notifier = { path = "notifier", version = "=2.0.2" } +solana-package-metadata-macro = { path = "sdk/package-metadata-macro", version = "=2.0.2" } +solana-perf = { path = "perf", version = "=2.0.2" } +solana-poh = { path = "poh", version = "=2.0.2" } +solana-poseidon = { path = "poseidon", version = "=2.0.2" } +solana-program = { path = "sdk/program", version = "=2.0.2" } +solana-program-runtime = { path = "program-runtime", version = "=2.0.2" } +solana-program-test = { path = "program-test", version = "=2.0.2" } +solana-pubsub-client = { path = "pubsub-client", version = "=2.0.2" } +solana-quic-client = { path = "quic-client", version = "=2.0.2" } +solana-rayon-threadlimit = { path = "rayon-threadlimit", version = "=2.0.2" } +solana-remote-wallet = { path = "remote-wallet", version = "=2.0.2", default-features = false } +solana-unified-scheduler-logic = { path = "unified-scheduler-logic", version = "=2.0.2" } +solana-unified-scheduler-pool = { path = "unified-scheduler-pool", version = "=2.0.2" } +solana-rpc = { path = "rpc", version = "=2.0.2" } +solana-rpc-client = { path = "rpc-client", version = "=2.0.2", default-features = false } +solana-rpc-client-api = { path = "rpc-client-api", version = "=2.0.2" } +solana-rpc-client-nonce-utils = { path = "rpc-client-nonce-utils", version = "=2.0.2" } +solana-runtime = { path = "runtime", version = "=2.0.2" } +solana-runtime-transaction = { path = "runtime-transaction", version = "=2.0.2" } +solana-sdk = { path = "sdk", version = "=2.0.2" } +solana-sdk-macro = { path = "sdk/macro", version = "=2.0.2" } +solana-send-transaction-service = { path = "send-transaction-service", version = "=2.0.2" } +solana-stake-program = { path = "programs/stake", version = "=2.0.2" } +solana-storage-bigtable = { path = "storage-bigtable", version = "=2.0.2" } +solana-storage-proto = { path = "storage-proto", version = "=2.0.2" } +solana-streamer = { path = "streamer", version = "=2.0.2" } +solana-svm = { path = "svm", version = "=2.0.2" } +solana-system-program = { path = "programs/system", version = "=2.0.2" } +solana-test-validator = { path = "test-validator", version = "=2.0.2" } +solana-thin-client = { path = "thin-client", version = "=2.0.2" } +solana-tpu-client = { path = "tpu-client", version = "=2.0.2", default-features = false } +solana-transaction-status = { path = "transaction-status", version = "=2.0.2" } +solana-transaction-metrics-tracker = { path = "transaction-metrics-tracker", version = "=2.0.2" } +solana-turbine = { path = "turbine", version = "=2.0.2" } +solana-type-overrides = { path = "type-overrides", version = "=2.0.2" } +solana-udp-client = { path = "udp-client", version = "=2.0.2" } +solana-version = { path = "version", version = "=2.0.2" } +solana-vote = { path = "vote", version = "=2.0.2" } +solana-vote-program = { path = "programs/vote", version = "=2.0.2" } +solana-wen-restart = { path = "wen-restart", version = "=2.0.2" } +solana-zk-elgamal-proof-program = { path = "programs/zk-elgamal-proof", version = "=2.0.2" } +solana-zk-keygen = { path = "zk-keygen", version = "=2.0.2" } +solana-zk-sdk = { path = "zk-sdk", version = "=2.0.2" } +solana-zk-token-proof-program = { path = "programs/zk-token-proof", version = "=2.0.2" } +solana-zk-token-sdk = { path = "zk-token-sdk", version = "=2.0.2" } solana_rbpf = "=0.8.1" -spl-associated-token-account = "=3.0.2" -spl-instruction-padding = "0.1" -spl-memo = "=4.0.1" -spl-pod = "=0.2.2" -spl-token = "=4.0.1" -spl-token-2022 = "=3.0.2" -spl-token-group-interface = "=0.2.3" -spl-token-metadata-interface = "=0.3.3" +spl-associated-token-account = "=4.0.0" +spl-instruction-padding = "0.2" +spl-memo = "=5.0.0" +spl-pod = "=0.3.0" +spl-token = "=6.0.0" +spl-token-2022 = "=4.0.0" +spl-token-group-interface = "=0.3.0" +spl-token-metadata-interface = "=0.4.0" static_assertions = "1.1.0" stream-cancel = "0.8.2" strum = "0.24" @@ -442,7 +447,7 @@ tonic-build = "0.9.2" trees = "0.4.2" tungstenite = "0.20.1" uriparse = "0.6.4" -url = "2.5.1" +url = "2.5.2" vec_extract_if_polyfill = "0.1.0" wasm-bindgen = "0.2" winapi = "0.3.8" @@ -488,6 +493,7 @@ crossbeam-epoch = { git = "https://github.com/anza-xyz/crossbeam", rev = "fd279d # # There is a similar override in `programs/sbf/Cargo.toml`. Please keep both # comments and the overrides in sync. +solana-curve25519 = { path = "curves/curve25519" } solana-program = { path = "sdk/program" } solana-zk-sdk = { path = "zk-sdk" } solana-zk-token-sdk = { path = "zk-token-sdk" } diff --git a/account-decoder/src/parse_token.rs b/account-decoder/src/parse_token.rs index 41a7eb44f9e14f..878d738fe03367 100644 --- a/account-decoder/src/parse_token.rs +++ b/account-decoder/src/parse_token.rs @@ -26,37 +26,6 @@ pub fn is_known_spl_token_id(program_id: &Pubkey) -> bool { *program_id == spl_token::id() || *program_id == spl_token_2022::id() } -// A helper function to convert spl_token::native_mint::id() as spl_sdk::pubkey::Pubkey to -// solana_sdk::pubkey::Pubkey -#[deprecated( - since = "1.16.0", - note = "Pubkey conversions no longer needed. Please use spl_token::native_mint::id() directly" -)] -pub fn spl_token_native_mint() -> Pubkey { - Pubkey::new_from_array(spl_token::native_mint::id().to_bytes()) -} - -// The program id of the `spl_token_native_mint` account -#[deprecated( - since = "1.16.0", - note = "Pubkey conversions no longer needed. Please use spl_token::id() directly" -)] -pub fn spl_token_native_mint_program_id() -> Pubkey { - spl_token::id() -} - -// A helper function to convert a solana_sdk::pubkey::Pubkey to spl_sdk::pubkey::Pubkey -#[deprecated(since = "1.16.0", note = "Pubkey conversions no longer needed")] -pub fn spl_token_pubkey(pubkey: &Pubkey) -> SplTokenPubkey { - SplTokenPubkey::new_from_array(pubkey.to_bytes()) -} - -// A helper function to convert a spl_sdk::pubkey::Pubkey to solana_sdk::pubkey::Pubkey -#[deprecated(since = "1.16.0", note = "Pubkey conversions no longer needed")] -pub fn pubkey_from_spl_token(pubkey: &SplTokenPubkey) -> Pubkey { - Pubkey::new_from_array(pubkey.to_bytes()) -} - #[deprecated(since = "2.0.0", note = "Use `parse_token_v2` instead")] pub fn parse_token( data: &[u8], diff --git a/accounts-db/Cargo.toml b/accounts-db/Cargo.toml index 62632c24de696f..b7c1327194f2dd 100644 --- a/accounts-db/Cargo.toml +++ b/accounts-db/Cargo.toml @@ -14,6 +14,7 @@ bincode = { workspace = true } blake3 = { workspace = true } bv = { workspace = true, features = ["serde"] } bytemuck = { workspace = true } +bytemuck_derive = { workspace = true } # bzip2 = { workspace = true } crossbeam-channel = { workspace = true } dashmap = { workspace = true, features = ["rayon", "raw-api"] } @@ -65,8 +66,10 @@ rand_chacha = { workspace = true } serde_bytes = { workspace = true } # See order-crates-for-publishing.py for using this unusual `path = "."` solana-accounts-db = { path = ".", features = ["dev-context-only-utils"] } +solana-compute-budget = { workspace = true } solana-logger = { workspace = true } solana-sdk = { workspace = true, features = ["dev-context-only-utils"] } +solana-svm = { workspace = true, features = ["dev-context-only-utils"] } static_assertions = { workspace = true } strum = { workspace = true, features = ["derive"] } strum_macros = { workspace = true } diff --git a/accounts-db/accounts-hash-cache-tool/Cargo.toml b/accounts-db/accounts-hash-cache-tool/Cargo.toml index 501e0dfdb8b71d..e4803261ef6995 100644 --- a/accounts-db/accounts-hash-cache-tool/Cargo.toml +++ b/accounts-db/accounts-hash-cache-tool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "agave-accounts-hash-cache-tool" -description = "Tool to inspect accounts hash cache files" +description = "Tool for accounts hash cache files" publish = false version = { workspace = true } authors = { workspace = true } diff --git a/accounts-db/accounts-hash-cache-tool/src/main.rs b/accounts-db/accounts-hash-cache-tool/src/main.rs index 98778049504216..c56369391c8077 100644 --- a/accounts-db/accounts-hash-cache-tool/src/main.rs +++ b/accounts-db/accounts-hash-cache-tool/src/main.rs @@ -1,12 +1,17 @@ use { bytemuck::Zeroable as _, - clap::{crate_description, crate_name, value_t_or_exit, App, Arg}, + clap::{ + crate_description, crate_name, value_t_or_exit, App, AppSettings, Arg, ArgMatches, + SubCommand, + }, solana_accounts_db::{CacheHashDataFileEntry, CacheHashDataFileHeader}, std::{ + collections::HashMap, fs::File, io::{self, BufReader, Read as _}, mem::size_of, num::Saturating, + path::Path, }, }; @@ -14,62 +19,73 @@ fn main() { let matches = App::new(crate_name!()) .about(crate_description!()) .version(solana_version::version!()) - .arg( - Arg::with_name("path") - .index(1) - .takes_value(true) - .value_name("PATH") - .help("Accounts hash cache file to inspect"), + .global_setting(AppSettings::ArgRequiredElseHelp) + .global_setting(AppSettings::ColoredHelp) + .global_setting(AppSettings::InferSubcommands) + .global_setting(AppSettings::UnifiedHelpMessage) + .global_setting(AppSettings::VersionlessSubcommands) + .subcommand( + SubCommand::with_name("inspect") + .about( + "Inspect an accounts hash cache file and display \ + each account's address, hash, and balance", + ) + .arg( + Arg::with_name("force") + .long("force") + .takes_value(false) + .help("Continue even if sanity checks fail"), + ) + .arg( + Arg::with_name("path") + .index(1) + .takes_value(true) + .value_name("PATH") + .help("Accounts hash cache file to inspect"), + ), ) - .arg( - Arg::with_name("force") - .long("force") - .takes_value(false) - .help("Continue even if sanity checks fail"), + .subcommand( + SubCommand::with_name("diff") + .about("Diff two accounts hash cache files") + .arg( + Arg::with_name("path1") + .index(1) + .takes_value(true) + .value_name("PATH1") + .help("Accounts hash cache file 1 to diff"), + ) + .arg( + Arg::with_name("path2") + .index(2) + .takes_value(true) + .value_name("PATH2") + .help("Accounts hash cache file 2 to diff"), + ), ) .get_matches(); - let force = matches.is_present("force"); - let path = value_t_or_exit!(matches, "path", String); - - let file = File::open(&path).unwrap_or_else(|err| { - eprintln!("Failed to open accounts hash cache file '{path}': {err}"); - std::process::exit(1); - }); - let actual_file_size = file - .metadata() - .unwrap_or_else(|err| { - eprintln!("Failed to query file metadata: {err}"); - std::process::exit(1); - }) - .len(); - let mut reader = BufReader::new(file); - - let header = { - let mut header = CacheHashDataFileHeader::zeroed(); - reader - .read_exact(bytemuck::bytes_of_mut(&mut header)) - .unwrap_or_else(|err| { - eprintln!("Failed to read header: {err}"); - std::process::exit(1); - }); - header - }; - - // Sanity checks -- ensure the actual file size matches the expected file size - let expected_file_size = size_of::() - .saturating_add(size_of::().saturating_mul(header.count)); - if actual_file_size != expected_file_size as u64 { - eprintln!( - "Failed sanitization: actual file size does not match expected file size! \ - actual: {actual_file_size}, expected: {expected_file_size}", - ); - if !force { - std::process::exit(1); + match matches.subcommand() { + ("inspect", Some(subcommand_matches)) => do_inspect(&matches, subcommand_matches) + .map_err(|err| format!("inspection failed: {err}")), + ("diff", Some(subcommand_matches)) => { + do_diff(&matches, subcommand_matches).map_err(|err| format!("diff failed: {err}")) } - eprintln!("Forced. Continuing... Results may be incorrect."); + _ => unreachable!(), } + .unwrap_or_else(|err| { + eprintln!("Error: {err}"); + std::process::exit(1); + }); +} +fn do_inspect( + _app_matches: &ArgMatches<'_>, + subcommand_matches: &ArgMatches<'_>, +) -> Result<(), String> { + let force = subcommand_matches.is_present("force"); + let path = value_t_or_exit!(subcommand_matches, "path", String); + let (mut reader, header) = open_file(&path, force) + .map_err(|err| format!("failed to open accounts hash cache file '{path}': {err}"))?; let count_width = (header.count as f64).log10().ceil() as usize; let mut count = Saturating(0usize); loop { @@ -80,10 +96,13 @@ fn main() { Err(err) => { if err.kind() == io::ErrorKind::UnexpectedEof && count.0 == header.count { // we've hit the expected end of the file + break; } else { - eprintln!("Failed to read entry {count}: {err}"); + return Err(format!( + "failed to read entry {count}, expected {}: {err}", + header.count, + )); } - break; } }; println!( @@ -96,4 +115,173 @@ fn main() { } println!("actual entries: {count}, expected: {}", header.count); + Ok(()) +} + +fn do_diff( + _app_matches: &ArgMatches<'_>, + subcommand_matches: &ArgMatches<'_>, +) -> Result<(), String> { + let force = false; // skipping sanity checks is not supported when diffing + let path1 = value_t_or_exit!(subcommand_matches, "path1", String); + let path2 = value_t_or_exit!(subcommand_matches, "path2", String); + let (mut reader1, header1) = open_file(&path1, force) + .map_err(|err| format!("failed to open accounts hash cache file 1 '{path1}': {err}"))?; + let (mut reader2, header2) = open_file(&path2, force) + .map_err(|err| format!("failed to open accounts hash cache file 2 '{path2}': {err}"))?; + // Note: Purposely open both files before reading either one. This way, if there's an error + // opening file 2, we can bail early without having to wait for file 1 to be read completely. + + // extract the entries from both files + let do_extract = |num, reader: &mut BufReader<_>, header: &CacheHashDataFileHeader| { + let mut entries = HashMap::<_, _>::default(); + loop { + let mut entry = CacheHashDataFileEntry::zeroed(); + let result = reader.read_exact(bytemuck::bytes_of_mut(&mut entry)); + match result { + Ok(()) => {} + Err(err) => { + if err.kind() == io::ErrorKind::UnexpectedEof && entries.len() == header.count { + // we've hit the expected end of the file + break; + } else { + return Err(format!( + "failed to read entry {}, expected {}: {err}", + entries.len(), + header.count, + )); + } + } + }; + let CacheHashDataFileEntry { + hash, + lamports, + pubkey, + } = entry; + let old_value = entries.insert(pubkey, (hash, lamports)); + if let Some(old_value) = old_value { + let new_value = entries.get(&pubkey); + return Err(format!("found duplicate pubkey in file {num}: {pubkey}, old value: {old_value:?}, new value: {new_value:?}")); + } + } + Ok(entries) + }; + let entries1 = do_extract(1, &mut reader1, &header1)?; + let entries2 = do_extract(2, &mut reader2, &header2)?; + + // compute the differences between the files + let do_compute = |lhs: &HashMap<_, (_, _)>, rhs: &HashMap<_, (_, _)>| { + let mut unique_entries = Vec::new(); + let mut mismatch_entries = Vec::new(); + for (lhs_key, lhs_value) in lhs.iter() { + if let Some(rhs_value) = rhs.get(lhs_key) { + if lhs_value != rhs_value { + mismatch_entries.push(( + CacheHashDataFileEntry { + hash: lhs_value.0, + lamports: lhs_value.1, + pubkey: *lhs_key, + }, + CacheHashDataFileEntry { + hash: rhs_value.0, + lamports: rhs_value.1, + pubkey: *lhs_key, + }, + )); + } + } else { + unique_entries.push(CacheHashDataFileEntry { + hash: lhs_value.0, + lamports: lhs_value.1, + pubkey: *lhs_key, + }); + } + } + unique_entries.sort_unstable_by(|a, b| a.pubkey.cmp(&b.pubkey)); + mismatch_entries.sort_unstable_by(|a, b| a.0.pubkey.cmp(&b.0.pubkey)); + (unique_entries, mismatch_entries) + }; + let (unique_entries1, mismatch_entries) = do_compute(&entries1, &entries2); + let (unique_entries2, _) = do_compute(&entries2, &entries1); + + // display the unique entries in each file + let do_print = |entries: &[CacheHashDataFileEntry]| { + let count_width = (entries.len() as f64).log10().ceil() as usize; + if entries.is_empty() { + println!("(none)"); + } else { + for (i, entry) in entries.iter().enumerate() { + println!( + "{i:count_width$}: pubkey: {:44}, hash: {:44}, lamports: {}", + entry.pubkey.to_string(), + entry.hash.0.to_string(), + entry.lamports, + ); + } + } + }; + println!("Unique entries in file 1:"); + do_print(&unique_entries1); + println!("Unique entries in file 2:"); + do_print(&unique_entries2); + + println!("Mismatch values:"); + let count_width = (mismatch_entries.len() as f64).log10().ceil() as usize; + if mismatch_entries.is_empty() { + println!("(none)"); + } else { + for (i, (lhs, rhs)) in mismatch_entries.iter().enumerate() { + println!( + "{i:count_width$}: pubkey: {:44}, hash: {:44}, lamports: {}", + lhs.pubkey.to_string(), + lhs.hash.0.to_string(), + lhs.lamports, + ); + println!( + "{i:count_width$}: file 2: {:44}, hash: {:44}, lamports: {}", + "(same)".to_string(), + rhs.hash.0.to_string(), + rhs.lamports, + ); + } + } + + Ok(()) +} + +fn open_file( + path: impl AsRef, + force: bool, +) -> Result<(BufReader, CacheHashDataFileHeader), String> { + let file = File::open(path).map_err(|err| format!("{err}"))?; + let actual_file_size = file + .metadata() + .map_err(|err| format!("failed to query file metadata: {err}"))? + .len(); + let mut reader = BufReader::new(file); + + let header = { + let mut header = CacheHashDataFileHeader::zeroed(); + reader + .read_exact(bytemuck::bytes_of_mut(&mut header)) + .map_err(|err| format!("failed to read header: {err}"))?; + header + }; + + // Sanity checks -- ensure the actual file size matches the expected file size + let expected_file_size = size_of::() + .saturating_add(size_of::().saturating_mul(header.count)); + if actual_file_size != expected_file_size as u64 { + let err_msg = format!( + "failed sanitization: actual file size does not match expected file size! \ + actual: {actual_file_size}, expected: {expected_file_size}", + ); + if force { + eprintln!("Warning: {err_msg}\nForced. Continuing... Results may be incorrect."); + } else { + return Err(err_msg); + } + } + + Ok((reader, header)) } diff --git a/accounts-db/benches/accounts.rs b/accounts-db/benches/accounts.rs index 6c3f26ebfd3b41..202a220e10e425 100644 --- a/accounts-db/benches/accounts.rs +++ b/accounts-db/benches/accounts.rs @@ -340,6 +340,7 @@ fn bench_load_largest_accounts(b: &mut Bencher) { 20, &HashSet::new(), AccountAddressFilter::Exclude, + false, ) }); } diff --git a/accounts-db/src/accounts.rs b/accounts-db/src/accounts.rs index 67fde4ce30394b..1f87be1ae86e44 100644 --- a/accounts-db/src/accounts.rs +++ b/accounts-db/src/accounts.rs @@ -29,9 +29,8 @@ use { transaction_context::TransactionAccount, }, solana_svm::{ - account_loader::TransactionLoadResult, - nonce_info::{NonceFull, NonceInfo}, - transaction_results::TransactionExecutionResult, + account_loader::TransactionLoadResult, nonce_info::NonceInfo, + rollback_accounts::RollbackAccounts, transaction_results::TransactionExecutionResult, }, std::{ cmp::Reverse, @@ -254,6 +253,7 @@ impl Accounts { num: usize, filter_by_address: &HashSet, filter: AccountAddressFilter, + sort_results: bool, ) -> ScanResult> { if num == 0 { return Ok(vec![]); @@ -287,7 +287,7 @@ impl Accounts { account_balances.push(Reverse((account.lamports(), *pubkey))); } }, - &ScanConfig::default(), + &ScanConfig::new(!sort_results), )?; Ok(account_balances .into_sorted_vec() @@ -480,6 +480,7 @@ impl Accounts { &self, ancestors: &Ancestors, bank_id: BankId, + sort_results: bool, ) -> ScanResult> { let mut collector = Vec::new(); self.accounts_db @@ -493,7 +494,7 @@ impl Accounts { collector.push((*pubkey, account, slot)) } }, - &ScanConfig::default(), + &ScanConfig::new(!sort_results), ) .map(|_| collector) } @@ -503,12 +504,17 @@ impl Accounts { ancestors: &Ancestors, bank_id: BankId, scan_func: F, + sort_results: bool, ) -> ScanResult<()> where F: FnMut(Option<(&Pubkey, AccountSharedData, Slot)>), { - self.accounts_db - .scan_accounts(ancestors, bank_id, scan_func, &ScanConfig::default()) + self.accounts_db.scan_accounts( + ancestors, + bank_id, + scan_func, + &ScanConfig::new(!sort_results), + ) } pub fn hold_range_in_memory( @@ -534,7 +540,7 @@ impl Accounts { "", // disable logging of this. We now parallelize it and this results in multiple parallel logs ancestors, range, - &ScanConfig::new(true), + &ScanConfig::default(), |option| Self::load_with_slot(&mut collector, option), ); collector @@ -716,21 +722,6 @@ impl Accounts { TransactionExecutionResult::NotExecuted(_) => continue, }; - enum AccountCollectionMode<'a> { - Normal, - FailedWithNonce { nonce: &'a NonceFull }, - } - - let collection_mode = match (execution_status, &loaded_transaction.nonce) { - (Ok(_), _) => AccountCollectionMode::Normal, - (Err(_), Some(nonce)) => AccountCollectionMode::FailedWithNonce { nonce }, - (Err(_), None) => { - // Fees for failed transactions which don't use durable nonces are - // deducted in Bank::filter_program_errors_and_collect_fee - continue; - } - }; - // Accounts that are invoked and also not passed as an instruction // account to a program don't need to be stored because it's assumed // to be impossible for a committable transaction to modify an @@ -748,21 +739,24 @@ impl Accounts { }; let message = tx.message(); + let rollback_accounts = &loaded_transaction.rollback_accounts; + let maybe_nonce_address = rollback_accounts.nonce().map(|account| account.address()); + for (i, (address, account)) in (0..message.account_keys().len()) .zip(loaded_transaction.accounts.iter_mut()) .filter(|(i, _)| is_storable_account(message, *i)) { if message.is_writable(i) { - let should_collect_account = match collection_mode { - AccountCollectionMode::Normal => true, - AccountCollectionMode::FailedWithNonce { nonce } => { + let should_collect_account = match execution_status { + Ok(()) => true, + Err(_) => { let is_fee_payer = i == 0; - let is_nonce_account = address == nonce.address(); - post_process_failed_nonce( + let is_nonce_account = Some(&*address) == maybe_nonce_address; + post_process_failed_tx( account, is_fee_payer, is_nonce_account, - nonce, + rollback_accounts, durable_nonce, lamports_per_signature, ); @@ -783,41 +777,43 @@ impl Accounts { } } -fn post_process_failed_nonce( +fn post_process_failed_tx( account: &mut AccountSharedData, is_fee_payer: bool, is_nonce_account: bool, - nonce: &NonceFull, + rollback_accounts: &RollbackAccounts, &durable_nonce: &DurableNonce, lamports_per_signature: u64, ) { + // For the case of RollbackAccounts::SameNonceAndFeePayer, it's crucial + // for `is_nonce_account` to be checked earlier than `is_fee_payer`. if is_nonce_account { - // The transaction failed which would normally drop the account - // processing changes, since this account is now being included - // in the accounts written back to the db, roll it back to - // pre-processing state. - *account = nonce.account().clone(); - - // Advance the stored blockhash to prevent fee theft by someone - // replaying nonce transactions that have failed with an - // `InstructionError`. - // - // Since we know we are dealing with a valid nonce account, - // unwrap is safe here - let nonce_versions = StateMut::::state(account).unwrap(); - if let NonceState::Initialized(ref data) = nonce_versions.state() { - let nonce_state = - NonceState::new_initialized(&data.authority, durable_nonce, lamports_per_signature); - let nonce_versions = NonceVersions::new(nonce_state); - account.set_state(&nonce_versions).unwrap(); + if let Some(nonce) = rollback_accounts.nonce() { + // The transaction failed which would normally drop the account + // processing changes, since this account is now being included + // in the accounts written back to the db, roll it back to + // pre-processing state. + *account = nonce.account().clone(); + + // Advance the stored blockhash to prevent fee theft by someone + // replaying nonce transactions that have failed with an + // `InstructionError`. + // + // Since we know we are dealing with a valid nonce account, + // unwrap is safe here + let nonce_versions = StateMut::::state(account).unwrap(); + if let NonceState::Initialized(ref data) = nonce_versions.state() { + let nonce_state = NonceState::new_initialized( + &data.authority, + durable_nonce, + lamports_per_signature, + ); + let nonce_versions = NonceVersions::new(nonce_state); + account.set_state(&nonce_versions).unwrap(); + } } } else if is_fee_payer { - if let Some(fee_payer_account) = nonce.fee_payer_account() { - // Instruction error and fee-payer for this nonce tx is not - // the nonce account itself, rollback the fee payer to the - // fee-paid original state. - *account = fee_payer_account.clone(); - } + *account = rollback_accounts.fee_payer_account().clone(); } } @@ -826,6 +822,7 @@ mod tests { use { super::*, assert_matches::assert_matches, + solana_compute_budget::compute_budget_processor::ComputeBudgetLimits, solana_sdk::{ account::{AccountSharedData, WritableAccount}, address_lookup_table::state::LookupTableMeta, @@ -833,14 +830,17 @@ mod tests { hash::Hash, instruction::{CompiledInstruction, InstructionError}, message::{Message, MessageHeader}, - native_loader, nonce, nonce_account, + native_loader, + nonce::state::Data as NonceData, + nonce_account, rent_debits::RentDebits, signature::{keypair_from_seed, signers::Signers, Keypair, Signer}, system_instruction, system_program, transaction::{Transaction, MAX_TX_ACCOUNT_LOCKS}, }, solana_svm::{ - account_loader::LoadedTransaction, transaction_results::TransactionExecutionDetails, + account_loader::LoadedTransaction, nonce_info::NoncePartial, + transaction_results::TransactionExecutionDetails, }, std::{ borrow::Cow, @@ -862,17 +862,13 @@ mod tests { )) } - fn new_execution_result( - status: Result<()>, - nonce: Option<&NonceFull>, - ) -> TransactionExecutionResult { + fn new_execution_result(status: Result<()>) -> TransactionExecutionResult { TransactionExecutionResult::Executed { details: TransactionExecutionDetails { status, log_messages: None, inner_instructions: None, fee_details: FeeDetails::default(), - is_nonce: nonce.is_some(), return_data: None, executed_units: 0, accounts_data_len_delta: 0, @@ -1569,8 +1565,9 @@ mod tests { let loaded0 = Ok(LoadedTransaction { accounts: transaction_accounts0, program_indices: vec![], - nonce: None, fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1579,8 +1576,9 @@ mod tests { let loaded1 = Ok(LoadedTransaction { accounts: transaction_accounts1, program_indices: vec![], - nonce: None, fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1598,7 +1596,7 @@ mod tests { .insert_new_readonly(&pubkey); } let txs = vec![tx0.clone(), tx1.clone()]; - let execution_results = vec![new_execution_result(Ok(()), None); 2]; + let execution_results = vec![new_execution_result(Ok(())); 2]; let (collected_accounts, transactions) = accounts.collect_accounts_to_store( &txs, &execution_results, @@ -1655,34 +1653,26 @@ mod tests { accounts.accounts_db.clean_accounts_for_tests(); } - fn create_accounts_post_process_failed_nonce() -> ( + fn create_accounts_post_process_failed_tx() -> ( Pubkey, AccountSharedData, AccountSharedData, DurableNonce, u64, - Option, ) { - let data = NonceVersions::new(NonceState::Initialized(nonce::state::Data::default())); + let data = NonceVersions::new(NonceState::Initialized(NonceData::default())); let account = AccountSharedData::new_data(42, &data, &system_program::id()).unwrap(); let mut pre_account = account.clone(); pre_account.set_lamports(43); let durable_nonce = DurableNonce::from_blockhash(&Hash::new(&[1u8; 32])); - ( - Pubkey::default(), - pre_account, - account, - durable_nonce, - 1234, - None, - ) + (Pubkey::default(), pre_account, account, durable_nonce, 1234) } - fn run_post_process_failed_nonce_test( + fn run_post_process_failed_tx_test( account: &mut AccountSharedData, is_fee_payer: bool, is_nonce_account: bool, - nonce: &NonceFull, + rollback_accounts: &RollbackAccounts, durable_nonce: &DurableNonce, lamports_per_signature: u64, expect_account: &AccountSharedData, @@ -1690,17 +1680,17 @@ mod tests { // Verify expect_account's relationship if !is_fee_payer { if is_nonce_account { - assert_ne!(expect_account, nonce.account()); + assert_ne!(expect_account, rollback_accounts.nonce().unwrap().account()); } else { assert_eq!(expect_account, account); } } - post_process_failed_nonce( + post_process_failed_tx( account, is_fee_payer, is_nonce_account, - nonce, + rollback_accounts, durable_nonce, lamports_per_signature, ); @@ -1709,33 +1699,25 @@ mod tests { } #[test] - fn test_post_process_failed_nonce_expected() { - let ( - pre_account_address, - pre_account, - mut post_account, - blockhash, - lamports_per_signature, - maybe_fee_payer_account, - ) = create_accounts_post_process_failed_nonce(); - let nonce = NonceFull::new( - pre_account_address, - pre_account.clone(), - maybe_fee_payer_account, - ); + fn test_post_process_failed_tx_expected() { + let (pre_account_address, pre_account, mut post_account, blockhash, lamports_per_signature) = + create_accounts_post_process_failed_tx(); + let rollback_accounts = RollbackAccounts::SameNonceAndFeePayer { + nonce: NoncePartial::new(pre_account_address, pre_account.clone()), + }; let mut expect_account = pre_account; expect_account .set_state(&NonceVersions::new(NonceState::Initialized( - nonce::state::Data::new(Pubkey::default(), blockhash, lamports_per_signature), + NonceData::new(Pubkey::default(), blockhash, lamports_per_signature), ))) .unwrap(); - assert!(run_post_process_failed_nonce_test( + assert!(run_post_process_failed_tx_test( &mut post_account, false, // is_fee_payer true, // is_nonce_account - &nonce, + &rollback_accounts, &blockhash, lamports_per_signature, &expect_account, @@ -1743,24 +1725,20 @@ mod tests { } #[test] - fn test_post_process_failed_nonce_not_nonce_address() { - let ( - pre_account_address, - pre_account, - mut post_account, - blockhash, - lamports_per_signature, - maybe_fee_payer_account, - ) = create_accounts_post_process_failed_nonce(); + fn test_post_process_failed_tx_not_nonce_address() { + let (pre_account_address, pre_account, mut post_account, blockhash, lamports_per_signature) = + create_accounts_post_process_failed_tx(); - let nonce = NonceFull::new(pre_account_address, pre_account, maybe_fee_payer_account); + let rollback_accounts = RollbackAccounts::SameNonceAndFeePayer { + nonce: NoncePartial::new(pre_account_address, pre_account.clone()), + }; let expect_account = post_account.clone(); - assert!(run_post_process_failed_nonce_test( + assert!(run_post_process_failed_tx_test( &mut post_account, false, // is_fee_payer false, // is_nonce_account - &nonce, + &rollback_accounts, &blockhash, lamports_per_signature, &expect_account, @@ -1774,27 +1752,26 @@ mod tests { AccountSharedData::new_data(42, &(), &system_program::id()).unwrap(); let post_fee_payer_account = AccountSharedData::new_data(84, &[1, 2, 3, 4], &system_program::id()).unwrap(); - let nonce = NonceFull::new( - Pubkey::new_unique(), - nonce_account, - Some(pre_fee_payer_account.clone()), - ); + let rollback_accounts = RollbackAccounts::SeparateNonceAndFeePayer { + nonce: NoncePartial::new(Pubkey::new_unique(), nonce_account), + fee_payer_account: pre_fee_payer_account.clone(), + }; - assert!(run_post_process_failed_nonce_test( + assert!(run_post_process_failed_tx_test( &mut post_fee_payer_account.clone(), false, // is_fee_payer false, // is_nonce_account - &nonce, + &rollback_accounts, &DurableNonce::default(), 1, &post_fee_payer_account, )); - assert!(run_post_process_failed_nonce_test( + assert!(run_post_process_failed_tx_test( &mut post_fee_payer_account.clone(), true, // is_fee_payer false, // is_nonce_account - &nonce, + &rollback_accounts, &DurableNonce::default(), 1, &pre_fee_payer_account, @@ -1809,7 +1786,7 @@ mod tests { let from_address = from.pubkey(); let to_address = Pubkey::new_unique(); let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); - let nonce_state = NonceVersions::new(NonceState::Initialized(nonce::state::Data::new( + let nonce_state = NonceVersions::new(NonceState::Initialized(NonceData::new( nonce_authority.pubkey(), durable_nonce, 0, @@ -1837,7 +1814,7 @@ mod tests { let tx = new_sanitized_tx(&[&nonce_authority, &from], message, blockhash); let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); - let nonce_state = NonceVersions::new(NonceState::Initialized(nonce::state::Data::new( + let nonce_state = NonceVersions::new(NonceState::Initialized(NonceData::new( nonce_authority.pubkey(), durable_nonce, 0, @@ -1846,17 +1823,16 @@ mod tests { AccountSharedData::new_data(42, &nonce_state, &system_program::id()).unwrap(); let from_account_pre = AccountSharedData::new(4242, 0, &Pubkey::default()); - let nonce = Some(NonceFull::new( - nonce_address, - nonce_account_pre.clone(), - Some(from_account_pre.clone()), - )); - + let nonce = NoncePartial::new(nonce_address, nonce_account_pre.clone()); let loaded = Ok(LoadedTransaction { accounts: transaction_accounts, program_indices: vec![], - nonce: nonce.clone(), fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::SeparateNonceAndFeePayer { + nonce: nonce.clone(), + fee_payer_account: from_account_pre.clone(), + }, + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1868,13 +1844,9 @@ mod tests { let accounts_db = AccountsDb::new_single_for_tests(); let accounts = Accounts::new(Arc::new(accounts_db)); let txs = vec![tx]; - let execution_results = vec![new_execution_result( - Err(TransactionError::InstructionError( - 1, - InstructionError::InvalidArgument, - )), - nonce.as_ref(), - )]; + let execution_results = vec![new_execution_result(Err( + TransactionError::InstructionError(1, InstructionError::InvalidArgument), + ))]; let (collected_accounts, _) = accounts.collect_accounts_to_store( &txs, &execution_results, @@ -1916,7 +1888,7 @@ mod tests { let from_address = from.pubkey(); let to_address = Pubkey::new_unique(); let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); - let nonce_state = NonceVersions::new(NonceState::Initialized(nonce::state::Data::new( + let nonce_state = NonceVersions::new(NonceState::Initialized(NonceData::new( nonce_authority.pubkey(), durable_nonce, 0, @@ -1944,7 +1916,7 @@ mod tests { let tx = new_sanitized_tx(&[&nonce_authority, &from], message, blockhash); let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); - let nonce_state = NonceVersions::new(NonceState::Initialized(nonce::state::Data::new( + let nonce_state = NonceVersions::new(NonceState::Initialized(NonceData::new( nonce_authority.pubkey(), durable_nonce, 0, @@ -1952,17 +1924,15 @@ mod tests { let nonce_account_pre = AccountSharedData::new_data(42, &nonce_state, &system_program::id()).unwrap(); - let nonce = Some(NonceFull::new( - nonce_address, - nonce_account_pre.clone(), - None, - )); - + let nonce = NoncePartial::new(nonce_address, nonce_account_pre.clone()); let loaded = Ok(LoadedTransaction { accounts: transaction_accounts, program_indices: vec![], - nonce: nonce.clone(), fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::SameNonceAndFeePayer { + nonce: nonce.clone(), + }, + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1974,13 +1944,9 @@ mod tests { let accounts_db = AccountsDb::new_single_for_tests(); let accounts = Accounts::new(Arc::new(accounts_db)); let txs = vec![tx]; - let execution_results = vec![new_execution_result( - Err(TransactionError::InstructionError( - 1, - InstructionError::InvalidArgument, - )), - nonce.as_ref(), - )]; + let execution_results = vec![new_execution_result(Err( + TransactionError::InstructionError(1, InstructionError::InvalidArgument), + ))]; let (collected_accounts, _) = accounts.collect_accounts_to_store( &txs, &execution_results, @@ -2046,7 +2012,8 @@ mod tests { bank_id, 0, &HashSet::new(), - AccountAddressFilter::Exclude + AccountAddressFilter::Exclude, + false ) .unwrap(), vec![] @@ -2058,7 +2025,8 @@ mod tests { bank_id, 0, &all_pubkeys, - AccountAddressFilter::Include + AccountAddressFilter::Include, + false ) .unwrap(), vec![] @@ -2073,7 +2041,8 @@ mod tests { bank_id, 1, &HashSet::new(), - AccountAddressFilter::Exclude + AccountAddressFilter::Exclude, + false ) .unwrap(), vec![(pubkey1, 42)] @@ -2085,7 +2054,8 @@ mod tests { bank_id, 2, &HashSet::new(), - AccountAddressFilter::Exclude + AccountAddressFilter::Exclude, + false ) .unwrap(), vec![(pubkey1, 42), (pubkey0, 42)] @@ -2097,7 +2067,8 @@ mod tests { bank_id, 3, &HashSet::new(), - AccountAddressFilter::Exclude + AccountAddressFilter::Exclude, + false ) .unwrap(), vec![(pubkey1, 42), (pubkey0, 42), (pubkey2, 41)] @@ -2111,7 +2082,8 @@ mod tests { bank_id, 6, &HashSet::new(), - AccountAddressFilter::Exclude + AccountAddressFilter::Exclude, + false ) .unwrap(), vec![(pubkey1, 42), (pubkey0, 42), (pubkey2, 41)] @@ -2126,7 +2098,8 @@ mod tests { bank_id, 1, &exclude1, - AccountAddressFilter::Exclude + AccountAddressFilter::Exclude, + false ) .unwrap(), vec![(pubkey0, 42)] @@ -2138,7 +2111,8 @@ mod tests { bank_id, 2, &exclude1, - AccountAddressFilter::Exclude + AccountAddressFilter::Exclude, + false ) .unwrap(), vec![(pubkey0, 42), (pubkey2, 41)] @@ -2150,7 +2124,8 @@ mod tests { bank_id, 3, &exclude1, - AccountAddressFilter::Exclude + AccountAddressFilter::Exclude, + false ) .unwrap(), vec![(pubkey0, 42), (pubkey2, 41)] @@ -2165,7 +2140,8 @@ mod tests { bank_id, 1, &include1_2, - AccountAddressFilter::Include + AccountAddressFilter::Include, + false ) .unwrap(), vec![(pubkey1, 42)] @@ -2177,7 +2153,8 @@ mod tests { bank_id, 2, &include1_2, - AccountAddressFilter::Include + AccountAddressFilter::Include, + false ) .unwrap(), vec![(pubkey1, 42), (pubkey2, 41)] @@ -2189,7 +2166,8 @@ mod tests { bank_id, 3, &include1_2, - AccountAddressFilter::Include + AccountAddressFilter::Include, + false ) .unwrap(), vec![(pubkey1, 42), (pubkey2, 41)] @@ -2217,7 +2195,10 @@ mod tests { #[test] fn test_maybe_abort_scan() { assert!(Accounts::maybe_abort_scan(ScanResult::Ok(vec![]), &ScanConfig::default()).is_ok()); - let config = ScanConfig::default().recreate_with_abort(); + assert!( + Accounts::maybe_abort_scan(ScanResult::Ok(vec![]), &ScanConfig::new(false)).is_ok() + ); + let config = ScanConfig::new(false).recreate_with_abort(); assert!(Accounts::maybe_abort_scan(ScanResult::Ok(vec![]), &config).is_ok()); config.abort(); assert!(Accounts::maybe_abort_scan(ScanResult::Ok(vec![]), &config).is_err()); diff --git a/accounts-db/src/accounts_db.rs b/accounts-db/src/accounts_db.rs index c21f000975b235..5f312704f540b5 100644 --- a/accounts-db/src/accounts_db.rs +++ b/accounts-db/src/accounts_db.rs @@ -1701,7 +1701,7 @@ impl SplitAncientStorages { i += 1; if treat_as_ancient(storage) { // even though the slot is in range of being an ancient append vec, if it isn't actually a large append vec, - // then we are better off treating all these slots as normally cachable to reduce work in dedup. + // then we are better off treating all these slots as normally cacheable to reduce work in dedup. // Since this one is large, for the moment, this one becomes the highest slot where we want to individually cache files. len_truncate = i; } @@ -1957,6 +1957,9 @@ pub(crate) struct ShrinkAncientStats { pub(crate) slots_considered: AtomicU64, pub(crate) ancient_scanned: AtomicU64, pub(crate) bytes_ancient_created: AtomicU64, + pub(crate) bytes_from_must_shrink: AtomicU64, + pub(crate) bytes_from_smallest_storages: AtomicU64, + pub(crate) bytes_from_newest_storages: AtomicU64, pub(crate) many_ref_slots_skipped: AtomicU64, pub(crate) slots_cannot_move_count: AtomicU64, pub(crate) many_refs_old_alive: AtomicU64, @@ -2250,6 +2253,21 @@ impl ShrinkAncientStats { // i64 // ), // ( + // "bytes_from_must_shrink", + // self.bytes_from_must_shrink.swap(0, Ordering::Relaxed) as i64, + // i64 + // ), + // ( + // "bytes_from_smallest_storages", + // self.bytes_from_smallest_storages.swap(0, Ordering::Relaxed) as i64, + // i64 + // ), + // ( + // "bytes_from_newest_storages", + // self.bytes_from_newest_storages.swap(0, Ordering::Relaxed) as i64, + // i64 + // ), + // ( // "many_ref_slots_skipped", // self.many_ref_slots_skipped.swap(0, Ordering::Relaxed), // i64 @@ -5790,7 +5808,7 @@ impl AccountsDb { /// This should only be called after the `Bank::drop()` runs in bank.rs, See BANK_DROP_SAFETY /// comment below for more explanation. - /// * `is_serialized_with_abs` - indicates whehter this call runs sequentially with all other + /// * `is_serialized_with_abs` - indicates whether this call runs sequentially with all other /// accounts_db relevant calls, such as shrinking, purging etc., in account background /// service. pub fn purge_slot(&self, slot: Slot, bank_id: BankId, is_serialized_with_abs: bool) { @@ -6174,7 +6192,7 @@ impl AccountsDb { // allocate a buffer on the stack that's big enough // to hold a token account or a stake account const META_SIZE: usize = 8 /* lamports */ + 8 /* rent_epoch */ + 1 /* executable */ + 32 /* owner */ + 32 /* pubkey */; - const DATA_SIZE: usize = 200; // stake acounts are 200 B and token accounts are 165-182ish B + const DATA_SIZE: usize = 200; // stake accounts are 200 B and token accounts are 165-182ish B const BUFFER_SIZE: usize = META_SIZE + DATA_SIZE; let mut buffer = SmallVec::<[u8; BUFFER_SIZE]>::new(); @@ -8942,7 +8960,7 @@ impl AccountsDb { // these write directly to disk, so the more threads, the better num_cpus::get() } else { - // seems to be a good hueristic given varying # cpus for in-mem disk index + // seems to be a good heuristic given varying # cpus for in-mem disk index 8 }; let chunk_size = (outer_slots_len / (std::cmp::max(1, threads.saturating_sub(1)))) + 1; // approximately 400k slots in a snapshot diff --git a/accounts-db/src/accounts_db/geyser_plugin_utils.rs b/accounts-db/src/accounts_db/geyser_plugin_utils.rs index efd765e0c722b7..b4f11664ab7955 100644 --- a/accounts-db/src/accounts_db/geyser_plugin_utils.rs +++ b/accounts-db/src/accounts_db/geyser_plugin_utils.rs @@ -121,7 +121,7 @@ impl AccountsDb { // later entries in the same slot are more recent and override earlier accounts for the same pubkey // We can pass an incrementing number here for write_version in the future, if the storage does not have a write_version. - // As long as all accounts for this slot are in 1 append vec that can be itereated olest to newest. + // As long as all accounts for this slot are in 1 append vec that can be iterated oldest to newest. self.notify_filtered_accounts( slot, notified_accounts, diff --git a/accounts-db/src/accounts_hash.rs b/accounts-db/src/accounts_hash.rs index 2d673b13d2d08d..b2ffff49eea56b 100644 --- a/accounts-db/src/accounts_hash.rs +++ b/accounts-db/src/accounts_hash.rs @@ -5,7 +5,7 @@ use { ancestors::Ancestors, pubkey_bins::PubkeyBinCalculator24, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, log::*, memmap2::MmapMut, rayon::prelude::*, diff --git a/accounts-db/src/accounts_index.rs b/accounts-db/src/accounts_index.rs index db7ddf6ea2e44e..6df2051556c809 100644 --- a/accounts-db/src/accounts_index.rs +++ b/accounts-db/src/accounts_index.rs @@ -90,7 +90,7 @@ pub enum UpsertReclaim { IgnoreReclaims, } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct ScanConfig { /// checked by the scan. When true, abort scan. pub abort: Option>, @@ -100,11 +100,20 @@ pub struct ScanConfig { pub collect_all_unsorted: bool, } +impl Default for ScanConfig { + fn default() -> Self { + Self { + abort: None, + collect_all_unsorted: true, + } + } +} + impl ScanConfig { pub fn new(collect_all_unsorted: bool) -> Self { Self { collect_all_unsorted, - ..ScanConfig::default() + ..Default::default() } } @@ -4210,10 +4219,14 @@ pub mod tests { assert!(!config.is_aborted()); } - let config = ScanConfig::default(); + let config = ScanConfig::new(false); assert!(!config.collect_all_unsorted); assert!(config.abort.is_none()); + let config = ScanConfig::default(); + assert!(config.collect_all_unsorted); + assert!(config.abort.is_none()); + let config = config.recreate_with_abort(); assert!(config.abort.is_some()); assert!(!config.is_aborted()); diff --git a/accounts-db/src/ancient_append_vecs.rs b/accounts-db/src/ancient_append_vecs.rs index 5b73aab489d6ac..07660e33efaf31 100644 --- a/accounts-db/src/ancient_append_vecs.rs +++ b/accounts-db/src/ancient_append_vecs.rs @@ -8,8 +8,8 @@ use { account_storage::ShrinkInProgress, accounts_db::{ AccountFromStorage, AccountStorageEntry, AccountsDb, AliveAccounts, - GetUniqueAccountsResult, ShrinkCollect, ShrinkCollectAliveSeparatedByRefs, - ShrinkStatsSub, + GetUniqueAccountsResult, ShrinkAncientStats, ShrinkCollect, + ShrinkCollectAliveSeparatedByRefs, ShrinkStatsSub, }, accounts_file::AccountsFile, accounts_index::AccountsIndexScanResult, @@ -27,6 +27,10 @@ use { }, }; +/// this many # of highest slot values should be treated as desirable to pack. +/// This gives us high slots to move packed accounts into. +const HIGH_SLOT_OFFSET: u64 = 100; + /// ancient packing algorithm tuning per pass #[derive(Debug)] struct PackedAncientStorageTuning { @@ -57,6 +61,9 @@ struct SlotInfo { alive_bytes: u64, /// true if this should be shrunk due to ratio should_shrink: bool, + /// this slot is a high slot # + /// It is important to include some high slot #s so that we have new slots to try each time pack runs. + is_high_slot: bool, } /// info for all storages in ancient slots @@ -83,6 +90,7 @@ impl AncientSlotInfos { storage: Arc, can_randomly_shrink: bool, ideal_size: NonZeroU64, + is_high_slot: bool, ) -> bool { let mut was_randomly_shrunk = false; let alive_bytes = storage.alive_bytes() as u64; @@ -122,6 +130,7 @@ impl AncientSlotInfos { storage, alive_bytes, should_shrink, + is_high_slot, }); self.total_alive_bytes += alive_bytes; } @@ -130,12 +139,16 @@ impl AncientSlotInfos { /// modify 'self' to contain only the slot infos for the slots that should be combined /// (and in this process effectively shrunk) - fn filter_ancient_slots(&mut self, tuning: &PackedAncientStorageTuning) { + fn filter_ancient_slots( + &mut self, + tuning: &PackedAncientStorageTuning, + stats: &ShrinkAncientStats, + ) { // figure out which slots to combine // 1. should_shrink: largest bytes saved above some cutoff of ratio self.choose_storages_to_shrink(tuning); // 2. smallest files so we get the largest number of files to remove - self.filter_by_smallest_capacity(tuning); + self.filter_by_smallest_capacity(tuning, stats); } // sort 'shrink_indexes' by most bytes saved, highest to lowest @@ -185,42 +198,73 @@ impl AncientSlotInfos { /// 'all_infos' are combined, the total number of storages <= 'max_storages' /// The idea is that 'all_infos' is sorted from smallest capacity to largest, /// but that isn't required for this function to be 'correct'. - fn truncate_to_max_storages(&mut self, tuning: &PackedAncientStorageTuning) { + fn truncate_to_max_storages( + &mut self, + tuning: &PackedAncientStorageTuning, + stats: &ShrinkAncientStats, + ) { // these indexes into 'all_infos' are useless once we truncate 'all_infos', so make sure they're cleared out to avoid any issues self.shrink_indexes.clear(); let total_storages = self.all_infos.len(); let mut cumulative_bytes = Saturating(0u64); let low_threshold = tuning.max_ancient_slots * 50 / 100; + let mut bytes_from_must_shrink = 0; + let mut bytes_from_smallest_storages = 0; + let mut bytes_from_newest_storages = 0; for (i, info) in self.all_infos.iter().enumerate() { cumulative_bytes += info.alive_bytes; let ancient_storages_required = - (cumulative_bytes.0 / tuning.ideal_storage_size + 1) as usize; + div_ceil(cumulative_bytes.0, tuning.ideal_storage_size) as usize; let storages_remaining = total_storages - i - 1; // if the remaining uncombined storages and the # of resulting - // combined ancient storages is less than the threshold, then + // combined ancient storages are less than the threshold, then // we've gone too far, so get rid of this entry and all after it. - // Every storage after this one is larger. + // Every storage after this one is larger than the ones we've chosen. // if we ever get to more than `max_resulting_storages` required ancient storages, that is enough to stop for now. - // It will take a while to create that many. This should be a limit that only affects - // extreme testing environments. - if storages_remaining + ancient_storages_required < low_threshold - || ancient_storages_required as u64 > u64::from(tuning.max_resulting_storages) + // It will take a lot of time for the pack algorithm to create that many, and that is bad for system performance. + // This should be a limit that only affects extreme testing environments. + // We do not stop including entries until we have dealt with all the high slot #s. This allows the algorithm to continue + // to make progress each time it is called. There are exceptions that can cause the pack to fail, such as accounts with multiple + // refs. + if !info.is_high_slot + && (storages_remaining + ancient_storages_required < low_threshold + || ancient_storages_required as u64 > u64::from(tuning.max_resulting_storages)) { self.all_infos.truncate(i); break; } + if info.should_shrink { + bytes_from_must_shrink += info.alive_bytes; + } else if info.is_high_slot { + bytes_from_newest_storages += info.alive_bytes; + } else { + bytes_from_smallest_storages += info.alive_bytes; + } } + stats + .bytes_from_must_shrink + .fetch_add(bytes_from_must_shrink, Ordering::Relaxed); + stats + .bytes_from_smallest_storages + .fetch_add(bytes_from_smallest_storages, Ordering::Relaxed); + stats + .bytes_from_newest_storages + .fetch_add(bytes_from_newest_storages, Ordering::Relaxed); } /// remove entries from 'all_infos' such that combining /// the remaining entries into storages of 'ideal_storage_size' /// will get us below 'max_storages' - /// The entires that are removed will be reconsidered the next time around. + /// The entries that are removed will be reconsidered the next time around. /// Combining too many storages costs i/o and cpu so the goal is to find the sweet spot so /// that we make progress in cleaning/shrinking/combining but that we don't cause unnecessary /// churn. - fn filter_by_smallest_capacity(&mut self, tuning: &PackedAncientStorageTuning) { + fn filter_by_smallest_capacity( + &mut self, + tuning: &PackedAncientStorageTuning, + stats: &ShrinkAncientStats, + ) { let total_storages = self.all_infos.len(); if total_storages <= tuning.max_ancient_slots { // currently fewer storages than max, so nothing to shrink @@ -229,16 +273,21 @@ impl AncientSlotInfos { return; } - // sort by 'should_shrink' then smallest capacity to largest + // sort by: + // 1. `high_slot`: we want to include new, high slots each time so that we try new slots + // each time alg runs and have several high target slots for packed storages. + // 2. 'should_shrink' so we make progress on shrinking ancient storages + // 3. smallest capacity to largest so that we remove the most slots possible self.all_infos.sort_unstable_by(|l, r| { - r.should_shrink - .cmp(&l.should_shrink) + r.is_high_slot + .cmp(&l.is_high_slot) + .then_with(|| r.should_shrink.cmp(&l.should_shrink)) .then_with(|| l.capacity.cmp(&r.capacity)) }); // remove any storages we don't need to combine this pass to achieve // # resulting storages <= 'max_storages' - self.truncate_to_max_storages(tuning); + self.truncate_to_max_storages(tuning, stats); } } @@ -452,7 +501,7 @@ impl AccountsDb { tuning.ideal_storage_size, ); - ancient_slot_infos.filter_ancient_slots(tuning); + ancient_slot_infos.filter_ancient_slots(tuning, &self.shrink_ancient_stats); ancient_slot_infos } @@ -498,10 +547,20 @@ impl AccountsDb { ..AncientSlotInfos::default() }; let mut randoms = 0; + let max_slot = slots.iter().max().cloned().unwrap_or_default(); + // heuristic to include some # of newly eligible ancient slots so that the pack algorithm always makes progress + let high_slot_boundary = max_slot.saturating_sub(HIGH_SLOT_OFFSET); + let is_high_slot = |slot| slot >= high_slot_boundary; for slot in &slots { if let Some(storage) = self.storage.get_slot_storage_entry(*slot) { - if infos.add(*slot, storage, can_randomly_shrink, ideal_size) { + if infos.add( + *slot, + storage, + can_randomly_shrink, + ideal_size, + is_high_slot(*slot), + ) { randoms += 1; } } @@ -548,9 +607,6 @@ impl AccountsDb { self.thread_pool_clean.install(|| { packer.par_iter().for_each(|(target_slot, pack)| { let mut write_ancient_accounts_local = WriteAncientAccounts::default(); - self.shrink_ancient_stats - .bytes_ancient_created - .fetch_add(pack.bytes, Ordering::Relaxed); self.write_one_packed_storage( pack, **target_slot, @@ -1059,6 +1115,25 @@ pub fn is_ancient(storage: &AccountsFile) -> bool { storage.capacity() >= get_ancient_append_vec_capacity() } +/// Divides `x` by `y` and rounds up +/// +/// # Notes +/// +/// It is undefined behavior if `x + y` overflows a u64. +/// Debug builds check this invariant, and will panic if broken. +fn div_ceil(x: u64, y: NonZeroU64) -> u64 { + let y = y.get(); + debug_assert!( + x.checked_add(y).is_some(), + "x + y must not overflow! x: {x}, y: {y}", + ); + // SAFETY: The caller guaranteed `x + y` does not overflow + // SAFETY: Since `y` is NonZero: + // - we know the denominator is > 0, and thus safe (cannot have divide-by-zero) + // - we know `x + y` is non-zero, and thus the numerator is safe (cannot underflow) + (x + y - 1) / y +} + #[cfg(test)] pub mod tests { use { @@ -1078,17 +1153,22 @@ pub mod tests { }, accounts_hash::AccountHash, accounts_index::UpsertReclaim, - append_vec::{aligned_stored_size, AppendVec, AppendVecStoredAccountMeta}, + append_vec::{ + aligned_stored_size, AppendVec, AppendVecStoredAccountMeta, + MAXIMUM_APPEND_VEC_FILE_SIZE, + }, storable_accounts::{tests::build_accounts_from_storage, StorableAccountsBySlot}, }, + rand::seq::SliceRandom as _, solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, hash::Hash, pubkey::Pubkey, }, - std::ops::Range, + std::{collections::HashSet, ops::Range}, strum::IntoEnumIterator, strum_macros::EnumIter, + test_case::test_case, }; fn get_sample_storages( @@ -1105,6 +1185,7 @@ pub mod tests { let original_stores = (0..slots) .filter_map(|slot| db.storage.get_slot_storage_entry((slot as Slot) + slot1)) .collect::>(); + let is_high_slot = false; let slot_infos = original_stores .iter() .map(|storage| SlotInfo { @@ -1113,6 +1194,7 @@ pub mod tests { capacity: 0, alive_bytes: 0, should_shrink: false, + is_high_slot, }) .collect(); ( @@ -2379,6 +2461,7 @@ pub mod tests { let mut infos = AncientSlotInfos::default(); let storage = db.storage.get_slot_storage_entry(slot1).unwrap(); let alive_bytes_expected = storage.alive_bytes(); + let high_slot = false; match method { TestCollectInfo::Add => { // test lower level 'add' @@ -2387,6 +2470,7 @@ pub mod tests { Arc::clone(&storage), can_randomly_shrink, NonZeroU64::new(get_ancient_append_vec_capacity()).unwrap(), + high_slot, ); } TestCollectInfo::CalcAncientSlotInfo => { @@ -2445,12 +2529,14 @@ pub mod tests { let (db, slot1) = create_db_with_storages_and_index(alive, slots, None); let mut infos = AncientSlotInfos::default(); let storage = db.storage.get_slot_storage_entry(slot1).unwrap(); + let high_slot = false; if call_add { infos.add( slot1, Arc::clone(&storage), can_randomly_shrink, NonZeroU64::new(get_ancient_append_vec_capacity()).unwrap(), + high_slot, ); } else { infos = db.calc_ancient_slot_info( @@ -2628,6 +2714,7 @@ pub mod tests { capacity: 1, alive_bytes: 1, should_shrink: false, + is_high_slot: false, }) .collect(), shrink_indexes: (0..count).collect(), @@ -2659,10 +2746,10 @@ pub mod tests { match method { TestSmallestCapacity::FilterAncientSlots => { infos.shrink_indexes.clear(); - infos.filter_ancient_slots(&tuning); + infos.filter_ancient_slots(&tuning, &ShrinkAncientStats::default()); } TestSmallestCapacity::FilterBySmallestCapacity => { - infos.filter_by_smallest_capacity(&tuning); + infos.filter_by_smallest_capacity(&tuning, &ShrinkAncientStats::default()); } } assert!(infos.all_infos.is_empty()); @@ -2671,7 +2758,7 @@ pub mod tests { } #[test] - fn test_filter_by_smaller_capacity_sort() { + fn test_filter_by_smallest_capacity_sort() { // max is 6 // 7 storages // storage[last] is big enough to cause us to need another storage @@ -2705,11 +2792,11 @@ pub mod tests { }; match method { TestSmallestCapacity::FilterBySmallestCapacity => { - infos.filter_by_smallest_capacity(&tuning); + infos.filter_by_smallest_capacity(&tuning, &ShrinkAncientStats::default()); } TestSmallestCapacity::FilterAncientSlots => { infos.shrink_indexes.clear(); - infos.filter_ancient_slots(&tuning); + infos.filter_ancient_slots(&tuning, &ShrinkAncientStats::default()); } } assert_eq!( @@ -2729,11 +2816,106 @@ pub mod tests { } } + /// Test that we always include the high slots when filtering which ancient infos to pack + /// + /// If we have *more* high slots than max resulting storages set in the tuning parameters, + /// we should still have all the high slots after calling `filter_by_smallest_capacity(). + #[test] + fn test_filter_by_smallest_capacity_high_slot_more() { + let tuning = default_tuning(); + + // Ensure we have more storages with high slots than the 'max resulting storages'. + let num_high_slots = tuning.max_resulting_storages.get() * 2; + let num_ancient_storages = num_high_slots * 3; + let mut infos = create_test_infos(num_ancient_storages as usize); + infos + .all_infos + .sort_unstable_by_key(|slot_info| slot_info.slot); + infos + .all_infos + .iter_mut() + .rev() + .take(num_high_slots as usize) + .for_each(|slot_info| { + slot_info.is_high_slot = true; + }); + let slots_expected: Vec<_> = infos + .all_infos + .iter() + .filter_map(|slot_info| slot_info.is_high_slot.then_some(slot_info.slot)) + .collect(); + + // shuffle the infos so they actually need to be sorted + infos.all_infos.shuffle(&mut thread_rng()); + infos.filter_by_smallest_capacity(&tuning, &ShrinkAncientStats::default()); + + infos + .all_infos + .sort_unstable_by_key(|slot_info| slot_info.slot); + let slots_actual: Vec<_> = infos + .all_infos + .iter() + .map(|slot_info| slot_info.slot) + .collect(); + assert_eq!(infos.all_infos.len() as u64, num_high_slots); + assert_eq!(slots_actual, slots_expected); + } + + /// Test that we always include the high slots when filtering which ancient infos to pack + /// + /// If we have *less* high slots than max resulting storages set in the tuning parameters, + /// we should still have all the high slots after calling `filter_by_smallest_capacity(). + #[test] + fn test_filter_by_smallest_capacity_high_slot_less() { + let tuning = default_tuning(); + + // Ensure we have less storages with high slots than the 'max resulting storages'. + let num_high_slots = tuning.max_resulting_storages.get() / 2; + let num_ancient_storages = num_high_slots * 5; + let mut infos = create_test_infos(num_ancient_storages as usize); + infos + .all_infos + .sort_unstable_by_key(|slot_info| slot_info.slot); + infos + .all_infos + .iter_mut() + .rev() + .take(num_high_slots as usize) + .for_each(|slot_info| { + slot_info.is_high_slot = true; + }); + let high_slots: Vec<_> = infos + .all_infos + .iter() + .filter_map(|slot_info| slot_info.is_high_slot.then_some(slot_info.slot)) + .collect(); + + // shuffle the infos so they actually need to be sorted + infos.all_infos.shuffle(&mut thread_rng()); + infos.filter_by_smallest_capacity(&tuning, &ShrinkAncientStats::default()); + + infos + .all_infos + .sort_unstable_by_key(|slot_info| slot_info.slot); + let slots_actual: HashSet<_> = infos + .all_infos + .iter() + .map(|slot_info| slot_info.slot) + .collect(); + assert_eq!( + infos.all_infos.len() as u64, + tuning.max_resulting_storages.get(), + ); + assert!(high_slots + .iter() + .all(|high_slot| slots_actual.contains(high_slot))); + } + fn test(filter: bool, infos: &mut AncientSlotInfos, tuning: &PackedAncientStorageTuning) { if filter { - infos.filter_by_smallest_capacity(tuning); + infos.filter_by_smallest_capacity(tuning, &ShrinkAncientStats::default()); } else { - infos.truncate_to_max_storages(tuning); + infos.truncate_to_max_storages(tuning, &ShrinkAncientStats::default()); } } @@ -3157,7 +3339,7 @@ pub mod tests { }; match method { TestShouldShrink::FilterAncientSlots => { - infos.filter_ancient_slots(&tuning); + infos.filter_ancient_slots(&tuning, &ShrinkAncientStats::default()); } TestShouldShrink::ClearShouldShrink => { infos.clear_should_shrink_after_cutoff(&tuning); @@ -3212,6 +3394,7 @@ pub mod tests { capacity: info1_capacity, alive_bytes: 0, should_shrink: false, + is_high_slot: false, }; let info2 = SlotInfo { storage: storage.clone(), @@ -3219,6 +3402,7 @@ pub mod tests { capacity: 2, alive_bytes: 1, should_shrink: false, + is_high_slot: false, }; let mut infos = AncientSlotInfos { all_infos: vec![info1, info2], @@ -3653,4 +3837,29 @@ pub mod tests { assert!(expected_ref_counts.is_empty()); } } + + #[test_case(0, 1 => 0)] + #[test_case(1, 1 => 1)] + #[test_case(2, 1 => 2)] + #[test_case(2, 2 => 1)] + #[test_case(2, 3 => 1)] + #[test_case(2, 4 => 1)] + #[test_case(3, 4 => 1)] + #[test_case(4, 4 => 1)] + #[test_case(5, 4 => 2)] + #[test_case(0, u64::MAX => 0)] + #[test_case(MAXIMUM_APPEND_VEC_FILE_SIZE - 1, MAXIMUM_APPEND_VEC_FILE_SIZE => 1)] + #[test_case(MAXIMUM_APPEND_VEC_FILE_SIZE + 1, MAXIMUM_APPEND_VEC_FILE_SIZE => 2)] + fn test_div_ceil(x: u64, y: u64) -> u64 { + div_ceil(x, NonZeroU64::new(y).unwrap()) + } + + #[should_panic(expected = "x + y must not overflow")] + #[test_case(1, u64::MAX)] + #[test_case(u64::MAX, 1)] + #[test_case(u64::MAX/2 + 2, u64::MAX/2)] + #[test_case(u64::MAX/2, u64::MAX/2 + 2)] + fn test_div_ceil_overflow(x: u64, y: u64) { + div_ceil(x, NonZeroU64::new(y).unwrap()); + } } diff --git a/accounts-db/src/bucket_map_holder.rs b/accounts-db/src/bucket_map_holder.rs index bc7e19112e516f..fb90b68d124702 100644 --- a/accounts-db/src/bucket_map_holder.rs +++ b/accounts-db/src/bucket_map_holder.rs @@ -533,7 +533,7 @@ pub mod tests { (0..threads).into_par_iter().for_each(|_| { // This test used to be more strict with time, but in a parallel, multi test environment, // sometimes threads starve and this test intermittently fails. So, give it more time than it should require. - // This may be aggrevated by the strategy of only allowing thread 0 to advance the age. + // This may be aggravated by the strategy of only allowing thread 0 to advance the age. while now.elapsed().as_millis() < (time as u128) * 100 { if test.maybe_advance_age() { test.bucket_flushed_at_current_age(true); diff --git a/accounts-db/src/buffered_reader.rs b/accounts-db/src/buffered_reader.rs index e6efd07f7d138c..5298b386793d2a 100644 --- a/accounts-db/src/buffered_reader.rs +++ b/accounts-db/src/buffered_reader.rs @@ -5,7 +5,7 @@ //! calling read(), advance_offset() and set_required_data_len(account_data_len) once the next account //! data length is known. //! -//! Unlike BufRead/BufReader, this type guarnatees that on the next read() after calling +//! Unlike BufRead/BufReader, this type guarantees that on the next read() after calling //! set_required_data_len(len), the whole account data is buffered _linearly_ in memory and available to //! be returned. use { @@ -119,147 +119,209 @@ mod tests { #[test] fn test_buffered_reader() { // Setup a sample file with 32 bytes of data + let file_size = 32; let mut sample_file = tempfile().unwrap(); - let bytes: Vec = (0..32).collect(); + let bytes: Vec = (0..file_size as u8).collect(); sample_file.write_all(&bytes).unwrap(); // First read 16 bytes to fill buffer - let mut reader = BufferedReader::new(16, 32, &sample_file, 8); + let buffer_size = 16; + let file_len_valid = 32; + let default_min_read = 8; + let mut reader = + BufferedReader::new(buffer_size, file_len_valid, &sample_file, default_min_read); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 0); - assert_eq!(slice.len(), 16); - assert_eq!(slice.slice(), &bytes[0..16]); + let mut expected_offset = 0; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), buffer_size); + assert_eq!(slice.slice(), &bytes[0..buffer_size]); // Consume the data and attempt to read next 32 bytes, expect to hit EOF and only read 16 bytes - reader.advance_offset(16); - reader.set_required_data_len(32); + let advance = 16; + let mut required_len = 32; + reader.advance_offset(advance); + reader.set_required_data_len(required_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Eof); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 16); - assert_eq!(slice.len(), 16); - assert_eq!(slice.slice(), &bytes[16..32]); + expected_offset += advance; + let expected_slice_len = 16; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), expected_slice_len); + assert_eq!(slice.slice(), &bytes[offset..file_size]); // Continue reading should yield EOF and empty slice. - reader.advance_offset(16); - reader.set_required_data_len(32); + reader.advance_offset(advance); + reader.set_required_data_len(required_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Eof); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 32); - assert_eq!(slice.len(), 0); + expected_offset += advance; + assert_eq!(offset, expected_offset); + let expected_slice_len = 0; + assert_eq!(slice.len(), expected_slice_len); // set_required_data to zero and offset should not change, and slice should be empty. - reader.set_required_data_len(0); + required_len = 0; + reader.set_required_data_len(required_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 32); - assert_eq!(slice.len(), 0); + let expected_offset = file_len_valid; + assert_eq!(offset, expected_offset); + let expected_slice_len = 0; + assert_eq!(slice.len(), expected_slice_len); } #[test] fn test_buffered_reader_with_extra_data_in_file() { // Setup a sample file with 32 bytes of data let mut sample_file = tempfile().unwrap(); - let bytes: Vec = (0..32).collect(); + let file_size = 32; + let bytes: Vec = (0..file_size as u8).collect(); sample_file.write_all(&bytes).unwrap(); // Set file valid_len to 30 (i.e. 2 garbage bytes at the end of the file) let valid_len = 30; // First read 16 bytes to fill buffer - let mut reader = BufferedReader::new(16, valid_len, &sample_file, 8); + let buffer_size = 16; + let default_min_read_size = 8; + let mut reader = + BufferedReader::new(buffer_size, valid_len, &sample_file, default_min_read_size); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 0); - assert_eq!(slice.len(), 16); - assert_eq!(slice.slice(), &bytes[0..16]); + let mut expected_offset = 0; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), buffer_size); + assert_eq!(slice.slice(), &bytes[0..buffer_size]); // Consume the data and attempt read next 32 bytes, expect to hit `valid_len`, and only read 14 bytes - reader.advance_offset(16); - reader.set_required_data_len(32); + let mut advance = 16; + let mut required_data_len = 32; + reader.advance_offset(advance); + reader.set_required_data_len(required_data_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Eof); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 16); - assert_eq!(slice.len(), 14); - assert_eq!(slice.slice(), &bytes[16..30]); + expected_offset += advance; + assert_eq!(offset, expected_offset); + let expected_slice_len = valid_len - offset; + assert_eq!(slice.len(), expected_slice_len); + let expected_slice_range = 16..30; + assert_eq!(slice.slice(), &bytes[expected_slice_range]); // Continue reading should yield EOF and empty slice. - reader.advance_offset(14); - reader.set_required_data_len(32); + advance = 14; + required_data_len = 32; + reader.advance_offset(advance); + reader.set_required_data_len(required_data_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Eof); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 30); - assert_eq!(slice.len(), 0); + expected_offset += advance; + assert_eq!(offset, expected_offset); + let expected_slice_len = 0; + assert_eq!(slice.len(), expected_slice_len); // Move the offset passed `valid_len`, expect to hit EOF and return empty slice. - reader.advance_offset(1); - reader.set_required_data_len(8); + advance = 1; + required_data_len = 8; + reader.advance_offset(advance); + reader.set_required_data_len(required_data_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Eof); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 31); - assert_eq!(slice.len(), 0); + expected_offset += advance; + assert_eq!(offset, expected_offset); + let expected_slice_len = 0; + assert_eq!(slice.len(), expected_slice_len); // Move the offset passed file_len, expect to hit EOF and return empty slice. - reader.advance_offset(3); - reader.set_required_data_len(8); + advance = 3; + required_data_len = 8; + reader.advance_offset(advance); + reader.set_required_data_len(required_data_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Eof); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 34); - assert_eq!(slice.len(), 0); + expected_offset += advance; + assert_eq!(offset, expected_offset); + let expected_slice_len = 0; + assert_eq!(slice.len(), expected_slice_len); } #[test] fn test_buffered_reader_partial_consume() { // Setup a sample file with 32 bytes of data let mut sample_file = tempfile().unwrap(); - let bytes: Vec = (0..32).collect(); + let file_size = 32; + let bytes: Vec = (0..file_size as u8).collect(); sample_file.write_all(&bytes).unwrap(); // First read 16 bytes to fill buffer - let mut reader = BufferedReader::new(16, 32, &sample_file, 8); + let buffer_size = 16; + let file_len_valid = 32; + let default_min_read_size = 8; + let mut reader = BufferedReader::new( + buffer_size, + file_len_valid, + &sample_file, + default_min_read_size, + ); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 0); - assert_eq!(slice.len(), 16); - assert_eq!(slice.slice(), &bytes[0..16]); + let mut expected_offset = 0; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), buffer_size); + assert_eq!(slice.slice(), &bytes[0..buffer_size]); // Consume the partial data (8 byte) and attempt to read next 8 bytes - reader.advance_offset(8); - reader.set_required_data_len(8); + let mut advance = 8; + let mut required_len = 8; + reader.advance_offset(advance); + reader.set_required_data_len(required_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 8); - assert_eq!(slice.len(), 8); - assert_eq!(slice.slice(), &bytes[8..16]); // no need to read more + expected_offset += advance; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), required_len); + assert_eq!( + slice.slice(), + &bytes[expected_offset..expected_offset + required_len] + ); // no need to read more // Continue reading should succeed and read the rest 16 bytes. - reader.advance_offset(8); - reader.set_required_data_len(16); + advance = 8; + required_len = 16; + reader.advance_offset(advance); + reader.set_required_data_len(required_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 16); - assert_eq!(slice.len(), 16); - assert_eq!(slice.slice(), &bytes[16..32]); + expected_offset += advance; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), required_len); + assert_eq!( + slice.slice(), + &bytes[expected_offset..expected_offset + required_len] + ); // Continue reading should yield EOF and empty slice. - reader.advance_offset(16); - reader.set_required_data_len(32); + advance = 16; + required_len = 32; + reader.advance_offset(advance); + reader.set_required_data_len(required_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Eof); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 32); + expected_offset += advance; + assert_eq!(offset, expected_offset); assert_eq!(slice.len(), 0); } @@ -267,37 +329,55 @@ mod tests { fn test_buffered_reader_partial_consume_with_move() { // Setup a sample file with 32 bytes of data let mut sample_file = tempfile().unwrap(); - let bytes: Vec = (0..32).collect(); + let file_size = 32; + let bytes: Vec = (0..file_size as u8).collect(); sample_file.write_all(&bytes).unwrap(); // First read 16 bytes to fill buffer - let mut reader = BufferedReader::new(16, 32, &sample_file, 8); + let buffer_size = 16; + let valid_len = 32; + let default_min_read = 8; + let mut reader = + BufferedReader::new(buffer_size, valid_len, &sample_file, default_min_read); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 0); - assert_eq!(slice.len(), 16); - assert_eq!(slice.slice(), &bytes[0..16]); + let mut expected_offset = 0; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), buffer_size); + assert_eq!(slice.slice(), &bytes[0..buffer_size]); // Consume the partial data (8 bytes) and attempt to read next 16 bytes // This will move the leftover 8bytes and read next 8 bytes. - reader.advance_offset(8); - reader.set_required_data_len(16); + let mut advance = 8; + let mut required_data_len = 16; + reader.advance_offset(advance); + reader.set_required_data_len(required_data_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 8); - assert_eq!(slice.len(), 16); - assert_eq!(slice.slice(), &bytes[8..24]); + expected_offset += advance; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), required_data_len); + assert_eq!( + slice.slice(), + &bytes[expected_offset..expected_offset + required_data_len] + ); // Continue reading should succeed and read the rest 8 bytes. - reader.advance_offset(16); - reader.set_required_data_len(8); + advance = 16; + required_data_len = 8; + reader.advance_offset(advance); + reader.set_required_data_len(required_data_len); let result = reader.read().unwrap(); assert_eq!(result, BufferedReaderStatus::Success); let (offset, slice) = reader.get_offset_and_data(); - assert_eq!(offset, 24); - assert_eq!(slice.len(), 8); - assert_eq!(slice.slice(), &bytes[24..32]); + expected_offset += advance; + assert_eq!(offset, expected_offset); + assert_eq!(slice.len(), required_data_len); + assert_eq!( + slice.slice(), + &bytes[expected_offset..expected_offset + required_data_len] + ); } } diff --git a/accounts-db/src/cache_hash_data.rs b/accounts-db/src/cache_hash_data.rs index f5e7c0129563fc..b69ee39185bda5 100644 --- a/accounts-db/src/cache_hash_data.rs +++ b/accounts-db/src/cache_hash_data.rs @@ -3,7 +3,7 @@ use crate::pubkey_bins::PubkeyBinCalculator24; use { crate::{accounts_hash::CalculateHashIntermediate, cache_hash_data_stats::CacheHashDataStats}, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, memmap2::MmapMut, solana_measure::measure::Measure, solana_sdk::clock::Slot, diff --git a/accounts-db/src/file_io.rs b/accounts-db/src/file_io.rs index 517c2834f1dbe6..a217aa94002ed6 100644 --- a/accounts-db/src/file_io.rs +++ b/accounts-db/src/file_io.rs @@ -92,65 +92,105 @@ mod tests { fn test_read_into_buffer() { // Setup a sample file with 32 bytes of data let mut sample_file = tempfile().unwrap(); - let bytes: Vec = (0..32).collect(); + let file_size = 32; + let bytes: Vec = (0..file_size as u8).collect(); sample_file.write_all(&bytes).unwrap(); // Read all 32 bytes into buffer let mut buffer = [0; 32]; - let num_bytes_read = read_into_buffer(&sample_file, 32, 0, &mut buffer).unwrap(); - assert_eq!(num_bytes_read, 32); + let mut buffer_len = buffer.len(); + let mut valid_len = 32; + let mut start_offset = 0; + let num_bytes_read = + read_into_buffer(&sample_file, valid_len, start_offset, &mut buffer).unwrap(); + assert_eq!(num_bytes_read, buffer_len); assert_eq!(bytes, buffer); // Given a 64-byte buffer, it should only read 32 bytes into the buffer let mut buffer = [0; 64]; - let num_bytes_read = read_into_buffer(&sample_file, 32, 0, &mut buffer).unwrap(); - assert_eq!(num_bytes_read, 32); - assert_eq!(bytes, buffer[0..32]); - assert_eq!(buffer[32..64], [0; 32]); + buffer_len = buffer.len(); + let num_bytes_read = + read_into_buffer(&sample_file, valid_len, start_offset, &mut buffer).unwrap(); + assert_eq!(num_bytes_read, valid_len); + assert_eq!(bytes, buffer[0..valid_len]); + assert_eq!(buffer[valid_len..buffer_len], [0; 32]); // Given the `valid_file_len` is 16, it should only read 16 bytes into the buffer let mut buffer = [0; 32]; - let num_bytes_read = read_into_buffer(&sample_file, 16, 0, &mut buffer).unwrap(); - assert_eq!(num_bytes_read, 16); - assert_eq!(bytes[0..16], buffer[0..16]); + buffer_len = buffer.len(); + valid_len = 16; + let num_bytes_read = + read_into_buffer(&sample_file, valid_len, start_offset, &mut buffer).unwrap(); + assert_eq!(num_bytes_read, valid_len); + assert_eq!(bytes[0..valid_len], buffer[0..valid_len]); // As a side effect of the `read_into_buffer` the data passed `valid_file_len` was // read and put into the buffer, though these data should not be // consumed. - assert_eq!(buffer[16..32], bytes[16..32]); + assert_eq!(buffer[valid_len..buffer_len], bytes[valid_len..buffer_len]); // Given the start offset 8, it should only read 24 bytes into buffer let mut buffer = [0; 32]; - let num_bytes_read = read_into_buffer(&sample_file, 32, 8, &mut buffer).unwrap(); - assert_eq!(num_bytes_read, 24); - assert_eq!(buffer[0..24], bytes[8..32]); - assert_eq!(buffer[24..32], [0; 8]) + buffer_len = buffer.len(); + valid_len = 32; + start_offset = 8; + let num_bytes_read = + read_into_buffer(&sample_file, valid_len, start_offset, &mut buffer).unwrap(); + assert_eq!(num_bytes_read, valid_len - start_offset); + assert_eq!(buffer[0..num_bytes_read], bytes[start_offset..buffer_len]); + assert_eq!(buffer[num_bytes_read..buffer_len], [0; 8]) } #[test] fn test_read_more_buffer() { // Setup a sample file with 32 bytes of data let mut sample_file = tempfile().unwrap(); - let bytes: Vec = (0..32).collect(); + let file_size = 32; + let bytes: Vec = (0..file_size as u8).collect(); sample_file.write_all(&bytes).unwrap(); // Should move left-over 8 bytes to and read 24 bytes from file let mut buffer = [0xFFu8; 32]; + let buffer_len = buffer.len(); let mut offset = 0; let mut valid_bytes = 24..32; - read_more_buffer(&sample_file, 32, &mut offset, &mut buffer, &mut valid_bytes).unwrap(); - assert_eq!(offset, 24); - assert_eq!(valid_bytes, 0..32); - assert_eq!(buffer[0..8], [0xFFu8; 8]); - assert_eq!(buffer[8..32], bytes[0..24]); + let mut valid_bytes_len = valid_bytes.len(); + let valid_len = 32; + read_more_buffer( + &sample_file, + valid_len, + &mut offset, + &mut buffer, + &mut valid_bytes, + ) + .unwrap(); + assert_eq!(offset, buffer_len - valid_bytes_len); + assert_eq!(valid_bytes, 0..buffer_len); + assert_eq!(buffer[0..valid_bytes_len], [0xFFu8; 8]); + assert_eq!( + buffer[valid_bytes_len..buffer_len], + bytes[0..buffer_len - valid_bytes_len] + ); // Should move left-over 8 bytes to and read 16 bytes from file due to EOF let mut buffer = [0xFFu8; 32]; + let start_offset = 16; let mut offset = 16; let mut valid_bytes = 24..32; - read_more_buffer(&sample_file, 32, &mut offset, &mut buffer, &mut valid_bytes).unwrap(); - assert_eq!(offset, 32); + valid_bytes_len = valid_bytes.len(); + read_more_buffer( + &sample_file, + valid_len, + &mut offset, + &mut buffer, + &mut valid_bytes, + ) + .unwrap(); + assert_eq!(offset, file_size); assert_eq!(valid_bytes, 0..24); - assert_eq!(buffer[0..8], [0xFFu8; 8]); - assert_eq!(buffer[8..24], bytes[16..32]); + assert_eq!(buffer[0..valid_bytes_len], [0xFFu8; 8]); + assert_eq!( + buffer[valid_bytes_len..valid_bytes.end], + bytes[start_offset..file_size] + ); } } diff --git a/accounts-db/src/tiered_storage/file.rs b/accounts-db/src/tiered_storage/file.rs index e14f75673ca3f2..4728134a4f990c 100644 --- a/accounts-db/src/tiered_storage/file.rs +++ b/accounts-db/src/tiered_storage/file.rs @@ -1,6 +1,6 @@ use { super::{error::TieredStorageError, TieredStorageResult}, - bytemuck::{AnyBitPattern, NoUninit, Pod, Zeroable}, + bytemuck::{AnyBitPattern, NoUninit, Zeroable}, std::{ fs::{File, OpenOptions}, io::{BufWriter, Read, Result as IoResult, Seek, SeekFrom, Write}, @@ -13,7 +13,7 @@ use { /// The ending 8 bytes of a valid tiered account storage file. pub const FILE_MAGIC_NUMBER: u64 = u64::from_le_bytes(*b"AnzaTech"); -#[derive(Debug, PartialEq, Eq, Clone, Copy, Pod, Zeroable)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct TieredStorageMagicNumber(pub u64); diff --git a/accounts-db/src/tiered_storage/hot.rs b/accounts-db/src/tiered_storage/hot.rs index 4d4a14e9cf28c7..74656584dee99b 100644 --- a/accounts-db/src/tiered_storage/hot.rs +++ b/accounts-db/src/tiered_storage/hot.rs @@ -19,7 +19,7 @@ use { StorableAccounts, TieredStorageError, TieredStorageFormat, TieredStorageResult, }, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, memmap2::{Mmap, MmapOptions}, modular_bitfield::prelude::*, solana_sdk::{ diff --git a/accounts-db/src/tiered_storage/index.rs b/accounts-db/src/tiered_storage/index.rs index 5caf0687be5d2b..326ab3df66ea1b 100644 --- a/accounts-db/src/tiered_storage/index.rs +++ b/accounts-db/src/tiered_storage/index.rs @@ -24,7 +24,7 @@ pub trait AccountOffset: Clone + Copy + Pod + Zeroable {} /// This can be used to obtain the AccountOffset and address by looking through /// the accounts index block. #[repr(C)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, Pod, Zeroable)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] pub struct IndexOffset(pub u32); // Ensure there are no implicit padding bytes diff --git a/accounts-db/src/tiered_storage/meta.rs b/accounts-db/src/tiered_storage/meta.rs index a174bbc0e5299e..16d50f43086738 100644 --- a/accounts-db/src/tiered_storage/meta.rs +++ b/accounts-db/src/tiered_storage/meta.rs @@ -2,7 +2,7 @@ use { crate::tiered_storage::owners::OwnerOffset, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, modular_bitfield::prelude::*, solana_sdk::{pubkey::Pubkey, stake_history::Epoch}, }; diff --git a/banks-client/src/lib.rs b/banks-client/src/lib.rs index 61105575e2ca2e..aae81192d3a07b 100644 --- a/banks-client/src/lib.rs +++ b/banks-client/src/lib.rs @@ -17,8 +17,7 @@ use { BanksTransactionResultWithSimulation, }, solana_program::{ - clock::Slot, fee_calculator::FeeCalculator, hash::Hash, program_pack::Pack, pubkey::Pubkey, - rent::Rent, sysvar::Sysvar, + clock::Slot, hash::Hash, program_pack::Pack, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, }, solana_sdk::{ account::{from_account, Account}, @@ -69,21 +68,6 @@ impl BanksClient { .map_err(Into::into) } - #[deprecated( - since = "1.9.0", - note = "Please use `get_fee_for_message` or `is_blockhash_valid` instead" - )] - pub fn get_fees_with_commitment_and_context( - &mut self, - ctx: Context, - commitment: CommitmentLevel, - ) -> impl Future> + '_ { - #[allow(deprecated)] - self.inner - .get_fees_with_commitment_and_context(ctx, commitment) - .map_err(Into::into) - } - pub fn get_transaction_status_with_context( &mut self, ctx: Context, @@ -185,20 +169,6 @@ impl BanksClient { self.send_transaction_with_context(context::current(), transaction.into()) } - /// Return the fee parameters associated with a recent, rooted blockhash. The cluster - /// will use the transaction's blockhash to look up these same fee parameters and - /// use them to calculate the transaction fee. - #[deprecated( - since = "1.9.0", - note = "Please use `get_fee_for_message` or `is_blockhash_valid` instead" - )] - pub fn get_fees( - &mut self, - ) -> impl Future> + '_ { - #[allow(deprecated)] - self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::default()) - } - /// Return the cluster Sysvar pub fn get_sysvar( &mut self, @@ -216,17 +186,6 @@ impl BanksClient { self.get_sysvar::() } - /// Return a recent, rooted blockhash from the server. The cluster will only accept - /// transactions with a blockhash that has not yet expired. Use the `get_fees` - /// method to get both a blockhash and the blockhash's last valid slot. - #[deprecated(since = "1.9.0", note = "Please use `get_latest_blockhash` instead")] - pub fn get_recent_blockhash( - &mut self, - ) -> impl Future> + '_ { - #[allow(deprecated)] - self.get_fees().map(|result| Ok(result?.1)) - } - /// Send a transaction and return after the transaction has been rejected or /// reached the given level of commitment. pub fn process_transaction_with_commitment( diff --git a/banks-interface/src/lib.rs b/banks-interface/src/lib.rs index d1cd7b867b514d..8f005be62f9749 100644 --- a/banks-interface/src/lib.rs +++ b/banks-interface/src/lib.rs @@ -6,7 +6,6 @@ use { account::Account, clock::Slot, commitment_config::CommitmentLevel, - fee_calculator::FeeCalculator, hash::Hash, inner_instruction::InnerInstructions, message::Message, @@ -64,13 +63,6 @@ pub struct BanksTransactionResultWithMetadata { #[tarpc::service] pub trait Banks { async fn send_transaction_with_context(transaction: VersionedTransaction); - #[deprecated( - since = "1.9.0", - note = "Please use `get_fee_for_message_with_commitment_and_context` instead" - )] - async fn get_fees_with_commitment_and_context( - commitment: CommitmentLevel, - ) -> (FeeCalculator, Hash, Slot); async fn get_transaction_status_with_context(signature: Signature) -> Option; async fn get_slot_with_context(commitment: CommitmentLevel) -> Slot; diff --git a/banks-server/src/banks_server.rs b/banks-server/src/banks_server.rs index 92add43452dceb..c08a41c5d91a6b 100644 --- a/banks-server/src/banks_server.rs +++ b/banks-server/src/banks_server.rs @@ -18,7 +18,6 @@ use { clock::Slot, commitment_config::CommitmentLevel, feature_set::FeatureSet, - fee_calculator::FeeCalculator, hash::Hash, message::{Message, SanitizedMessage}, pubkey::Pubkey, @@ -232,24 +231,6 @@ impl Banks for BanksServer { self.transaction_sender.send(info).unwrap(); } - async fn get_fees_with_commitment_and_context( - self, - _: Context, - commitment: CommitmentLevel, - ) -> (FeeCalculator, Hash, u64) { - let bank = self.bank(commitment); - let blockhash = bank.last_blockhash(); - let lamports_per_signature = bank.get_lamports_per_signature(); - let last_valid_block_height = bank - .get_blockhash_last_valid_block_height(&blockhash) - .unwrap(); - ( - FeeCalculator::new(lamports_per_signature), - blockhash, - last_valid_block_height, - ) - } - async fn get_transaction_status_with_context( self, _: Context, diff --git a/bucket_map/Cargo.toml b/bucket_map/Cargo.toml index a37051e5d3054b..36d29140a025a1 100644 --- a/bucket_map/Cargo.toml +++ b/bucket_map/Cargo.toml @@ -12,7 +12,8 @@ edition = { workspace = true } [dependencies] bv = { workspace = true, features = ["serde"] } -bytemuck = { workspace = true, features = ["derive"] } +bytemuck = { workspace = true } +bytemuck_derive = { workspace = true } log = { workspace = true } memmap2 = { workspace = true } modular-bitfield = { workspace = true } diff --git a/bucket_map/src/restart.rs b/bucket_map/src/restart.rs index 1748a27b2f458e..bdf61ae2bfe82f 100644 --- a/bucket_map/src/restart.rs +++ b/bucket_map/src/restart.rs @@ -1,7 +1,7 @@ //! Persistent info of disk index files to allow files to be reused on restart. use { crate::bucket_map::{BucketMapConfig, MAX_SEARCH_DEFAULT}, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, memmap2::MmapMut, std::{ collections::HashMap, diff --git a/ci/bench/part2.sh b/ci/bench/part2.sh index 44a6c46f2ed67f..76f1dfb50abffc 100755 --- a/ci/bench/part2.sh +++ b/ci/bench/part2.sh @@ -19,6 +19,8 @@ _ cargo +"$rust_nightly" bench --manifest-path runtime/Cargo.toml ${V:+--verbose _ cargo build --manifest-path=keygen/Cargo.toml export PATH="$PWD/target/debug":$PATH + _ make -C programs/sbf all + # Run sbf benches _ cargo +"$rust_nightly" bench --manifest-path programs/sbf/Cargo.toml ${V:+--verbose} --features=sbf_c \ -- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE" diff --git a/ci/do-audit.sh b/ci/do-audit.sh index 039df6b63cb635..df395e8a2bbc22 100755 --- a/ci/do-audit.sh +++ b/ci/do-audit.sh @@ -30,6 +30,9 @@ cargo_audit_ignores=( --ignore RUSTSEC-2023-0001 --ignore RUSTSEC-2022-0093 + + # curve25519-dalek + --ignore RUSTSEC-2024-0344 ) scripts/cargo-for-all-lock-files.sh audit "${cargo_audit_ignores[@]}" | $dep_tree_filter # we want the `cargo audit` exit code, not `$dep_tree_filter`'s diff --git a/ci/test-bench.sh b/ci/test-bench.sh index aacc82cffbb0a6..c145512d7b1cc6 100755 --- a/ci/test-bench.sh +++ b/ci/test-bench.sh @@ -61,6 +61,7 @@ _ $cargoNightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \ -- -Z unstable-options --format=json | tee -a "$BENCH_FILE" # Run sbf benches +_ make -C programs/sbf all _ $cargoNightly bench --manifest-path programs/sbf/Cargo.toml ${V:+--verbose} --features=sbf_c \ -- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE" diff --git a/ci/test-stable.sh b/ci/test-stable.sh index 40ee0ae2c40a74..acd4578542f23c 100755 --- a/ci/test-stable.sh +++ b/ci/test-stable.sh @@ -50,6 +50,9 @@ test-stable-sbf) cargo_build_sbf="$(realpath ./cargo-build-sbf)" cargo_test_sbf="$(realpath ./cargo-test-sbf)" + # platform-tools version + "$cargo_build_sbf" --version + # SBF solana-sdk legacy compile test "$cargo_build_sbf" --manifest-path sdk/Cargo.toml @@ -67,11 +70,9 @@ test-stable-sbf) exit 1 fi - # SBF C program system tests - _ make -C programs/sbf/c tests - _ cargo test \ - --manifest-path programs/sbf/Cargo.toml \ - --no-default-features --features=sbf_c,sbf_rust -- --nocapture + # SBF program tests + export SBF_OUT_DIR=target/sbf-solana-solana/release + _ make -C programs/sbf test # SBF Rust program unit tests for sbf_test in programs/sbf/rust/*; do @@ -99,14 +100,11 @@ test-stable-sbf) exit 1 fi - # platform-tools version - "$cargo_build_sbf" -V - # SBF program instruction count assertion sbf_target_path=programs/sbf/target _ cargo test \ --manifest-path programs/sbf/Cargo.toml \ - --no-default-features --features=sbf_c,sbf_rust assert_instruction_count \ + --features=sbf_c,sbf_rust assert_instruction_count \ -- --nocapture &> "${sbf_target_path}"/deploy/instruction_counts.txt sbf_dump_archive="sbf-dumps.tar.bz2" diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 5fc886134d0507..1aee7e38ec3bc8 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -65,9 +65,6 @@ pub enum CliCommand { ClusterVersion, Feature(FeatureCliCommand), Inflation(InflationCliCommand), - Fees { - blockhash: Option, - }, FindProgramDerivedAddress { seeds: Vec>, program_id: Pubkey, @@ -640,12 +637,6 @@ pub fn parse_command( ("feature", Some(matches)) => { parse_feature_subcommand(matches, default_signer, wallet_manager) } - ("fees", Some(matches)) => { - let blockhash = value_of::(matches, "blockhash"); - Ok(CliCommandInfo::without_signers(CliCommand::Fees { - blockhash, - })) - } ("first-available-block", Some(_matches)) => Ok(CliCommandInfo::without_signers( CliCommand::FirstAvailableBlock, )), @@ -911,7 +902,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { seed, program_id, } => process_create_address_with_seed(config, from_pubkey.as_ref(), seed, program_id), - CliCommand::Fees { ref blockhash } => process_fees(&rpc_client, config, blockhash.as_ref()), CliCommand::Feature(feature_subcommand) => { process_feature_subcommand(&rpc_client, config, feature_subcommand) } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index eaa8142c3b05f3..46067f29d38eb1 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -171,19 +171,6 @@ impl ClusterQuerySubCommands for App<'_, '_> { SubCommand::with_name("cluster-version") .about("Get the version of the cluster entrypoint"), ) - // Deprecated in v1.8.0 - .subcommand( - SubCommand::with_name("fees") - .about("Display current cluster fees (Deprecated in v1.8.0)") - .arg( - Arg::with_name("blockhash") - .long("blockhash") - .takes_value(true) - .value_name("BLOCKHASH") - .validator(is_hash) - .help("Query fees for BLOCKHASH instead of the most recent blockhash"), - ), - ) .subcommand( SubCommand::with_name("first-available-block") .about("Get the first available block in the storage"), @@ -982,42 +969,6 @@ pub fn process_cluster_version(rpc_client: &RpcClient, config: &CliConfig) -> Pr } } -pub fn process_fees( - rpc_client: &RpcClient, - config: &CliConfig, - blockhash: Option<&Hash>, -) -> ProcessResult { - let fees = if let Some(recent_blockhash) = blockhash { - #[allow(deprecated)] - let result = rpc_client.get_fee_calculator_for_blockhash_with_commitment( - recent_blockhash, - config.commitment, - )?; - if let Some(fee_calculator) = result.value { - CliFees::some( - result.context.slot, - *recent_blockhash, - fee_calculator.lamports_per_signature, - None, - None, - ) - } else { - CliFees::none() - } - } else { - #[allow(deprecated)] - let result = rpc_client.get_fees_with_commitment(config.commitment)?; - CliFees::some( - result.context.slot, - result.value.blockhash, - result.value.fee_calculator.lamports_per_signature, - None, - Some(result.value.last_valid_block_height), - ) - }; - Ok(config.output_format.formatted_string(&fees)) -} - pub fn process_first_available_block(rpc_client: &RpcClient) -> ProcessResult { let first_available_block = rpc_client.get_first_available_block()?; Ok(format!("{first_available_block}")) @@ -1444,6 +1395,7 @@ pub fn process_largest_accounts( .get_largest_accounts_with_config(RpcLargestAccountsConfig { commitment: Some(config.commitment), filter, + sort_results: None, })? .value; let largest_accounts = CliAccountBalances { accounts }; @@ -2372,26 +2324,6 @@ mod tests { CliCommandInfo::without_signers(CliCommand::ClusterVersion) ); - let test_fees = test_commands.clone().get_matches_from(vec!["test", "fees"]); - assert_eq!( - parse_command(&test_fees, &default_signer, &mut None).unwrap(), - CliCommandInfo::without_signers(CliCommand::Fees { blockhash: None }) - ); - - let blockhash = Hash::new_unique(); - let test_fees = test_commands.clone().get_matches_from(vec![ - "test", - "fees", - "--blockhash", - &blockhash.to_string(), - ]); - assert_eq!( - parse_command(&test_fees, &default_signer, &mut None).unwrap(), - CliCommandInfo::without_signers(CliCommand::Fees { - blockhash: Some(blockhash) - }) - ); - let slot = 100; let test_get_block_time = test_commands diff --git a/cli/src/feature.rs b/cli/src/feature.rs index 785ed93425e5be..c6e397d2e01088 100644 --- a/cli/src/feature.rs +++ b/cli/src/feature.rs @@ -819,7 +819,7 @@ fn feature_activation_allowed( )) } -fn status_from_account(account: Account) -> Option { +pub(super) fn status_from_account(account: Account) -> Option { feature::from_account(&account).map(|feature| match feature.activated_at { None => CliFeatureStatus::Pending, Some(activation_slot) => CliFeatureStatus::Active(activation_slot), diff --git a/cli/src/program.rs b/cli/src/program.rs index df4aa8731bdb69..880bab2e435431 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -9,6 +9,7 @@ use { simulate_and_update_compute_unit_limit, ComputeUnitConfig, UpdateComputeUnitLimitResult, WithComputeUnitConfig, }, + feature::{status_from_account, CliFeatureStatus}, }, bip39::{Language, Mnemonic, MnemonicType, Seed}, clap::{App, AppSettings, Arg, ArgMatches, SubCommand}, @@ -47,6 +48,7 @@ use { client_error::ErrorKind as ClientErrorKind, config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig}, filter::{Memcmp, RpcFilterType}, + request::MAX_MULTIPLE_ACCOUNTS, }, solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery, solana_sdk::{ @@ -56,7 +58,7 @@ use { bpf_loader_upgradeable::{self, get_program_data_address, UpgradeableLoaderState}, commitment_config::CommitmentConfig, compute_budget, - feature_set::FeatureSet, + feature_set::{FeatureSet, FEATURE_NAMES}, instruction::{Instruction, InstructionError}, message::Message, packet::PACKET_DATA_SIZE, @@ -99,6 +101,7 @@ pub enum ProgramCliCommand { max_sign_attempts: usize, auto_extend: bool, use_rpc: bool, + skip_feature_verification: bool, }, Upgrade { fee_payer_signer_index: SignerIndex, @@ -108,6 +111,7 @@ pub enum ProgramCliCommand { sign_only: bool, dump_transaction_message: bool, blockhash_query: BlockhashQuery, + skip_feature_verification: bool, }, WriteBuffer { program_location: String, @@ -120,6 +124,7 @@ pub enum ProgramCliCommand { compute_unit_price: Option, max_sign_attempts: usize, use_rpc: bool, + skip_feature_verification: bool, }, SetBufferAuthority { buffer_pubkey: Pubkey, @@ -279,6 +284,14 @@ impl ProgramSubCommands for App<'_, '_> { .long("no-auto-extend") .takes_value(false) .help("Don't automatically extend the program's data account size"), + ) + .arg( + Arg::with_name("skip_feature_verify") + .long("skip-feature-verify") + .takes_value(false) + .help("Don't verify program against the activated feature set. \ + This setting means a program containing a syscall not yet active on \ + mainnet will succeed local verification, but fail during the last step of deployment.") ), ) .subcommand( @@ -309,6 +322,14 @@ impl ProgramSubCommands for App<'_, '_> { "Upgrade authority [default: the default configured keypair]", ), ) + .arg( + Arg::with_name("skip_feature_verify") + .long("skip-feature-verify") + .takes_value(false) + .help("Don't verify program against the activated feature set. \ + This setting means a program containing a syscall not yet active on \ + mainnet will succeed local verification, but fail during the last step of deployment.") + ) .offline_args(), ) .subcommand( @@ -375,7 +396,15 @@ impl ProgramSubCommands for App<'_, '_> { .arg(Arg::with_name("use_rpc").long("use-rpc").help( "Send transactions to the configured RPC instead of validator TPUs", )) - .arg(compute_unit_price_arg()), + .arg(compute_unit_price_arg()) + .arg( + Arg::with_name("skip_feature_verify") + .long("skip-feature-verify") + .takes_value(false) + .help("Don't verify program against the activated feature set. \ + This setting means a program containing a syscall not yet active on \ + mainnet will succeed local verification, but fail during the last step of deployment.") + ), ) .subcommand( SubCommand::with_name("set-buffer-authority") @@ -673,6 +702,8 @@ pub fn parse_program_subcommand( let auto_extend = !matches.is_present("no_auto_extend"); + let skip_feature_verify = matches.is_present("skip_feature_verify"); + CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { program_location, @@ -691,6 +722,7 @@ pub fn parse_program_subcommand( max_sign_attempts, use_rpc: matches.is_present("use_rpc"), auto_extend, + skip_feature_verification: skip_feature_verify, }), signers: signer_info.signers, } @@ -720,6 +752,8 @@ pub fn parse_program_subcommand( let signer_info = default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + let skip_feature_verify = matches.is_present("skip_feature_verify"); + CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Upgrade { fee_payer_signer_index: signer_info.index_of(fee_payer_pubkey).unwrap(), @@ -731,6 +765,7 @@ pub fn parse_program_subcommand( sign_only, dump_transaction_message, blockhash_query, + skip_feature_verification: skip_feature_verify, }), signers: signer_info.signers, } @@ -764,6 +799,7 @@ pub fn parse_program_subcommand( let compute_unit_price = value_of(matches, "compute_unit_price"); let max_sign_attempts = value_of(matches, "max_sign_attempts").unwrap(); + let skip_feature_verify = matches.is_present("skip_feature_verify"); CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::WriteBuffer { @@ -779,6 +815,7 @@ pub fn parse_program_subcommand( compute_unit_price, max_sign_attempts, use_rpc: matches.is_present("use_rpc"), + skip_feature_verification: skip_feature_verify, }), signers: signer_info.signers, } @@ -979,6 +1016,7 @@ pub fn process_program_subcommand( max_sign_attempts, auto_extend, use_rpc, + skip_feature_verification, } => process_program_deploy( rpc_client, config, @@ -996,6 +1034,7 @@ pub fn process_program_subcommand( *max_sign_attempts, *auto_extend, *use_rpc, + *skip_feature_verification, ), ProgramCliCommand::Upgrade { fee_payer_signer_index, @@ -1005,6 +1044,7 @@ pub fn process_program_subcommand( sign_only, dump_transaction_message, blockhash_query, + skip_feature_verification, } => process_program_upgrade( rpc_client, config, @@ -1015,6 +1055,7 @@ pub fn process_program_subcommand( *sign_only, *dump_transaction_message, blockhash_query, + *skip_feature_verification, ), ProgramCliCommand::WriteBuffer { program_location, @@ -1027,6 +1068,7 @@ pub fn process_program_subcommand( compute_unit_price, max_sign_attempts, use_rpc, + skip_feature_verification, } => process_write_buffer( rpc_client, config, @@ -1040,6 +1082,7 @@ pub fn process_program_subcommand( *compute_unit_price, *max_sign_attempts, *use_rpc, + *skip_feature_verification, ), ProgramCliCommand::SetBufferAuthority { buffer_pubkey, @@ -1174,6 +1217,7 @@ fn process_program_deploy( max_sign_attempts: usize, auto_extend: bool, use_rpc: bool, + skip_feature_verification: bool, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let upgrade_authority_signer = config.signers[upgrade_authority_signer_index]; @@ -1265,9 +1309,15 @@ fn process_program_deploy( true }; + let feature_set = if skip_feature_verification { + FeatureSet::all_enabled() + } else { + fetch_feature_set(&rpc_client)? + }; + let (program_data, program_len, buffer_program_data) = if let Some(program_location) = program_location { - let program_data = read_and_verify_elf(program_location)?; + let program_data = read_and_verify_elf(program_location, feature_set)?; let program_len = program_data.len(); // If a buffer was provided, check if it has already been created and set up properly @@ -1290,6 +1340,7 @@ fn process_program_deploy( config, buffer_pubkey, upgrade_authority_signer.pubkey(), + feature_set, )?; (vec![], buffer_program_data.len(), Some(buffer_program_data)) @@ -1382,6 +1433,7 @@ fn fetch_verified_buffer_program_data( config: &CliConfig, buffer_pubkey: Pubkey, buffer_authority: Pubkey, + feature_set: FeatureSet, ) -> Result, Box> { let Some(buffer_program_data) = fetch_buffer_program_data(rpc_client, config, None, buffer_pubkey, buffer_authority)? @@ -1389,7 +1441,7 @@ fn fetch_verified_buffer_program_data( return Err(format!("Buffer account {buffer_pubkey} not found").into()); }; - verify_elf(&buffer_program_data).map_err(|err| { + verify_elf(&buffer_program_data, feature_set).map_err(|err| { format!( "Buffer account {buffer_pubkey} has invalid program data: {:?}", err @@ -1466,6 +1518,7 @@ fn process_program_upgrade( sign_only: bool, dump_transaction_message: bool, blockhash_query: &BlockhashQuery, + skip_feature_verification: bool, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let upgrade_authority_signer = config.signers[upgrade_authority_signer_index]; @@ -1496,11 +1549,18 @@ fn process_program_upgrade( }, ) } else { + let feature_set = if skip_feature_verification { + FeatureSet::all_enabled() + } else { + fetch_feature_set(&rpc_client)? + }; + fetch_verified_buffer_program_data( &rpc_client, config, buffer_pubkey, upgrade_authority_signer.pubkey(), + feature_set, )?; let fee = rpc_client.get_fee_for_message(&message)?; @@ -1539,11 +1599,18 @@ fn process_write_buffer( compute_unit_price: Option, max_sign_attempts: usize, use_rpc: bool, + skip_feature_verification: bool, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let buffer_authority = config.signers[buffer_authority_signer_index]; - let program_data = read_and_verify_elf(program_location)?; + let feature_set = if skip_feature_verification { + FeatureSet::all_enabled() + } else { + fetch_feature_set(&rpc_client)? + }; + + let program_data = read_and_verify_elf(program_location, feature_set)?; let program_len = program_data.len(); // Create ephemeral keypair to use for Buffer account, if not provided @@ -2749,27 +2816,29 @@ fn extend_program_data_if_needed( Ok(()) } -fn read_and_verify_elf(program_location: &str) -> Result, Box> { +fn read_and_verify_elf( + program_location: &str, + feature_set: FeatureSet, +) -> Result, Box> { let mut file = File::open(program_location) .map_err(|err| format!("Unable to open program file: {err}"))?; let mut program_data = Vec::new(); file.read_to_end(&mut program_data) .map_err(|err| format!("Unable to read program file: {err}"))?; - verify_elf(&program_data)?; + verify_elf(&program_data, feature_set)?; Ok(program_data) } -fn verify_elf(program_data: &[u8]) -> Result<(), Box> { +fn verify_elf( + program_data: &[u8], + feature_set: FeatureSet, +) -> Result<(), Box> { // Verify the program - let program_runtime_environment = create_program_runtime_environment_v1( - &FeatureSet::all_enabled(), - &ComputeBudget::default(), - true, - false, - ) - .unwrap(); + let program_runtime_environment = + create_program_runtime_environment_v1(&feature_set, &ComputeBudget::default(), true, false) + .unwrap(); let executable = Executable::::from_elf(program_data, Arc::new(program_runtime_environment)) .map_err(|err| format!("ELF error: {err}"))?; @@ -2982,6 +3051,30 @@ fn report_ephemeral_mnemonic(words: usize, mnemonic: bip39::Mnemonic) { eprintln!("[BUFFER_ACCOUNT_ADDRESS] argument to `solana program close`.\n{divider}"); } +fn fetch_feature_set(rpc_client: &RpcClient) -> Result> { + let mut feature_set = FeatureSet::default(); + for feature_ids in FEATURE_NAMES + .keys() + .cloned() + .collect::>() + .chunks(MAX_MULTIPLE_ACCOUNTS) + { + rpc_client + .get_multiple_accounts(feature_ids)? + .into_iter() + .zip(feature_ids) + .for_each(|(account, feature_id)| { + let activation_slot = account.and_then(status_from_account); + + if let Some(CliFeatureStatus::Active(slot)) = activation_slot { + feature_set.activate(feature_id, slot); + } + }); + } + + Ok(feature_set) +} + #[cfg(test)] mod tests { use { @@ -3043,6 +3136,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3074,6 +3168,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3107,6 +3202,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3142,6 +3238,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3176,6 +3273,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3213,6 +3311,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3246,6 +3345,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3277,6 +3377,7 @@ mod tests { max_sign_attempts: 1, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3307,6 +3408,75 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: true, + skip_feature_verification: false, + }), + signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], + } + ); + + let test_command = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "deploy", + "/Users/test/program.so", + "--skip-feature-verify", + ]); + assert_eq!( + parse_command(&test_command, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some("/Users/test/program.so".to_string()), + fee_payer_signer_index: 0, + buffer_signer_index: None, + buffer_pubkey: None, + program_signer_index: None, + program_pubkey: None, + upgrade_authority_signer_index: 0, + is_final: false, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + auto_extend: true, + use_rpc: false, + skip_feature_verification: true, + }), + signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], + } + ); + } + + #[test] + fn test_cli_parse_upgrade() { + let test_commands = get_clap_app("test", "desc", "version"); + + let default_keypair = Keypair::new(); + let keypair_file = make_tmp_path("keypair_file"); + write_keypair_file(&default_keypair, &keypair_file).unwrap(); + let default_signer = DefaultSigner::new("", &keypair_file); + + let program_key = Pubkey::new_unique(); + let buffer_key = Pubkey::new_unique(); + let test_command = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "upgrade", + format!("{}", buffer_key).as_str(), + format!("{}", program_key).as_str(), + "--skip-feature-verify", + ]); + assert_eq!( + parse_command(&test_command, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::Upgrade { + fee_payer_signer_index: 0, + program_pubkey: program_key, + buffer_pubkey: buffer_key, + upgrade_authority_signer_index: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::default(), + skip_feature_verification: true, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3344,6 +3514,7 @@ mod tests { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3372,6 +3543,7 @@ mod tests { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3403,6 +3575,7 @@ mod tests { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3437,6 +3610,7 @@ mod tests { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3476,6 +3650,7 @@ mod tests { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3508,6 +3683,35 @@ mod tests { compute_unit_price: None, max_sign_attempts: 10, use_rpc: false, + skip_feature_verification: false + }), + signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], + } + ); + + // skip feature verification + let test_command = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + "--skip-feature-verify", + ]); + assert_eq!( + parse_command(&test_command, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: "/Users/test/program.so".to_string(), + fee_payer_signer_index: 0, + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: 0, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + use_rpc: false, + skip_feature_verification: true, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -4050,6 +4254,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }), signers: vec![&default_keypair], output_format: OutputFormat::JsonCompact, diff --git a/cli/tests/fixtures/alt_bn128.so b/cli/tests/fixtures/alt_bn128.so new file mode 100755 index 0000000000000000000000000000000000000000..6d20e91a8ac71cb7281305891ee1404c99f60cc7 GIT binary patch literal 4648 zcmbtX4OCOt9ls%z6dFOW5k$}@qdipGNcb{Z+ycmVw6xY$kCYH2C`d>M!I4ob9ATwa zU8@u6WObqw>k754E#q`GR(qUUKda7u%#S(6+Rh3^q|Maq|9|)1n7$L;X?IWZe((Rg z|Ihp0{dmi=^x1wg8R1P%h6zdnqjaoeQ3%%{L`Ss5AFdCOKvDNTFMV#uagjf@<48tQ z8Qo)73mW^tC^_XhO6}$Z(tar(V|jDrc7C890RL_GQ#&c{pfusg)QEE{?NETR?zY?Q zG*USN9PVeiBhv%z0@~f9{wA;I`Tx|v*hl?6 zz+qcQfjHUx#c>jVdn_5RkRQH0(4JuL&oh8H1)TU0q0ROmDHFR@Z2G4Ag5UD>@`UKxR`RwC% z`OLs+2LjSwZqE^#l<$tx_YQ6B%&UE-wXt>13H8kpagA+*wlc+@o}m>@9~iUxmF@An z-;az~Cb%D6(>k?9_S$n5vYA%{j5CD~bFS#VJw0Ocu>SYq=TgH1w&WJs)MK(x9xwNHMHfMiIwf6_Kqu(k6n4?!LK*J?(Ti@`@^Sp z^pD9@6kP8gRFwzsJ+iMY@@D*rzGH)LWvqFksB>rgrQTEe9Qo<)U*`5j`#tY1ed5X| zzq?qpLEc$$EnWD-@nYxD$;yIHTP{VeJ^E^AVr%NYln19Q&d(C$o97MtJ3|l3avyiS zl)N*=*t9C{<+&xH`W3r%d2be&R9i;Z#s4KS|Hjb+DU&<8BBvMKQg(Dk9{%-5IMg>@xnW1{Xv3w8XJ4o-3>_D?F=`<2S0hhG z<^L_@uxnn2Uz%m$&YewWwwUsz~H2-<_p@HprNhz;i4qJb*xu<(DY)=F^do67IxFs1Cq^!7T zx?^Ex%*o;6&(CC^{h;WxH8-vX2E2G|j-kKy-><#eRjy20_tyeVlJ%X{gR|fG{2#|- z*3S#uU;L*pR+_)g|8sT2s*iQQ{nzo!<;u(x_4>X;vNG4w?dnN2Hq#_yrQI;UN|UI) zbEh`>esFhKoHk?}llg;Y+Zwq)h<32;z;kY4+W_DGiOcP_pe8#uh-U$m`T5}i6rj=7 zg%G~AA7chA3`wDCWHiGc3s_7TtOyCLkU3&?bd5n?VDd;rP^VJ;yInCIe=;Dh@!mQkK#;*1kDnbISZADwGO~@; z!Dupx@of^GhKKis)xq_r!43N7XF?t8n8$g#O_37D*NF&mC4QDMzD>gahVcR$FXo1< zf*Xv>&tf{((ZzUvR%&2%92EN@uEftLjBk_hpD|wG3xFsS@=ritPFeuyq|m* zo~Z4mb#|h*Sr!=WMxtKes4|+%P2x?B!%Wd`1BKE`>?CTF#cYOEf~cX}Zk!LesK#!i zcNB?d)qq7>tv47+gw|;4zwbW!UUEsS{^rmKx zQmLa93R>S5+@D(GyPtYYW~n7moWjIJwpK!MVg*_XNfcOojmPMxEUbpdGDp5IgNoRs`Q!SETRFGq=LU{^ z9QpL;@p=9lutlE<@b_+U?BKx98@!Ll=QtHQcx noop_large.so +cp ../../../programs/sbf/c/out/alt_bn128.so . diff --git a/cli/tests/program.rs b/cli/tests/program.rs index 7a6bc4ee30c9a2..45d243c5babcd2 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -25,6 +25,7 @@ use { bpf_loader_upgradeable::{self, UpgradeableLoaderState}, commitment_config::CommitmentConfig, compute_budget::{self, ComputeBudgetInstruction}, + feature_set::enable_alt_bn128_syscall, fee_calculator::FeeRateGovernor, pubkey::Pubkey, rent::Rent, @@ -125,6 +126,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -174,6 +176,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); let account1 = rpc_client @@ -232,6 +235,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -258,6 +262,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -330,6 +335,7 @@ fn test_cli_program_deploy_no_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -360,6 +366,7 @@ fn test_cli_program_deploy_no_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -368,6 +375,298 @@ fn test_cli_program_deploy_no_authority() { ); } +#[test_case(true; "Feature enabled")] +#[test_case(false; "Feature disabled")] +fn test_cli_program_deploy_feature(enable_feature: bool) { + solana_logger::setup(); + + let mut program_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + program_path.push("tests"); + program_path.push("fixtures"); + program_path.push("alt_bn128"); + program_path.set_extension("so"); + + let mint_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + let faucet_addr = run_local_faucet(mint_keypair, None); + let mut genesis = TestValidatorGenesis::default(); + let mut test_validator_builder = genesis + .fee_rate_governor(FeeRateGovernor::new(0, 0)) + .rent(Rent { + lamports_per_byte_year: 1, + exemption_threshold: 1.0, + ..Rent::default() + }) + .faucet_addr(Some(faucet_addr)); + + // Deactivate the enable alt bn128 syscall and try to submit a program with that syscall + if !enable_feature { + test_validator_builder = + test_validator_builder.deactivate_features(&[enable_alt_bn128_syscall::id()]); + } + + let test_validator = test_validator_builder + .start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified) + .expect("validator start failed"); + + let rpc_client = + RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed()); + + let mut file = File::open(program_path.to_str().unwrap()).unwrap(); + let mut program_data = Vec::new(); + file.read_to_end(&mut program_data).unwrap(); + let max_len = program_data.len(); + let minimum_balance_for_programdata = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata( + max_len, + )) + .unwrap(); + let minimum_balance_for_program = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()) + .unwrap(); + let upgrade_authority = Keypair::new(); + + let mut config = CliConfig::recent_for_tests(); + let keypair = Keypair::new(); + config.json_rpc_url = test_validator.rpc_url(); + config.command = CliCommand::Airdrop { + pubkey: None, + lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program, + }; + config.signers = vec![&keypair]; + process_command(&config).unwrap(); + + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(program_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: None, + program_pubkey: None, + buffer_signer_index: None, + buffer_pubkey: None, + upgrade_authority_signer_index: 1, + is_final: true, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + auto_extend: true, + use_rpc: false, + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + + if enable_feature { + let res = process_command(&config); + assert!(res.is_ok()); + } else { + expect_command_failure( + &config, + "Program contains a syscall from a deactivated feature", + "ELF error: ELF error: Unresolved symbol (sol_alt_bn128_group_op) at instruction #49 (ELF file offset 0x188)" + ); + + // If we bypass the verification, there should be no error + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(program_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: None, + program_pubkey: None, + buffer_signer_index: None, + buffer_pubkey: None, + upgrade_authority_signer_index: 1, + is_final: true, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + auto_extend: true, + use_rpc: false, + skip_feature_verification: true, + }); + + // When we skip verification, we fail at a later stage + let response = process_command(&config); + assert!(response + .err() + .unwrap() + .to_string() + .contains("Deploying program failed: RPC response error -32002:")); + } +} + +#[test_case(true; "Feature enabled")] +#[test_case(false; "Feature disabled")] +fn test_cli_program_upgrade_with_feature(enable_feature: bool) { + solana_logger::setup(); + + let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + noop_path.push("tests"); + noop_path.push("fixtures"); + noop_path.push("noop"); + noop_path.set_extension("so"); + + let mut syscall_program_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + syscall_program_path.push("tests"); + syscall_program_path.push("fixtures"); + syscall_program_path.push("alt_bn128"); + syscall_program_path.set_extension("so"); + + let mint_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + let faucet_addr = run_local_faucet(mint_keypair, None); + + let mut genesis = TestValidatorGenesis::default(); + let mut test_validator_builder = genesis + .fee_rate_governor(FeeRateGovernor::new(0, 0)) + .rent(Rent { + lamports_per_byte_year: 1, + exemption_threshold: 1.0, + ..Rent::default() + }) + .faucet_addr(Some(faucet_addr)); + + // Deactivate the enable alt bn128 syscall and try to submit a program with that syscall + if !enable_feature { + test_validator_builder = + test_validator_builder.deactivate_features(&[enable_alt_bn128_syscall::id()]); + } + + let test_validator = test_validator_builder + .start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified) + .expect("validator start failed"); + + let rpc_client = + RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed()); + + let blockhash = rpc_client.get_latest_blockhash().unwrap(); + + let mut file = File::open(syscall_program_path.to_str().unwrap()).unwrap(); + let mut large_program_data = Vec::new(); + file.read_to_end(&mut large_program_data).unwrap(); + let max_program_data_len = large_program_data.len(); + let minimum_balance_for_large_buffer = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata( + max_program_data_len, + )) + .unwrap(); + + let mut config = CliConfig::recent_for_tests(); + config.json_rpc_url = test_validator.rpc_url(); + + let online_signer = Keypair::new(); + let offline_signer = Keypair::new(); + let buffer_signer = Keypair::new(); + // Typically, keypair for program signer should be different from online signer or + // offline signer keypairs. + let program_signer = Keypair::new(); + + config.command = CliCommand::Airdrop { + pubkey: None, + lamports: 100 * minimum_balance_for_large_buffer, // gotta be enough for this test + }; + config.signers = vec![&online_signer]; + process_command(&config).unwrap(); + config.command = CliCommand::Airdrop { + pubkey: None, + lamports: 100 * minimum_balance_for_large_buffer, // gotta be enough for this test + }; + config.signers = vec![&offline_signer]; + process_command(&config).unwrap(); + + // Deploy upgradeable program with authority set to offline signer + config.signers = vec![&online_signer, &offline_signer, &program_signer]; + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(noop_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: Some(2), + program_pubkey: Some(program_signer.pubkey()), + buffer_signer_index: None, + buffer_pubkey: None, + upgrade_authority_signer_index: 1, // must be offline signer for security reasons + is_final: false, + max_len: Some(max_program_data_len), // allows for larger program size with future upgrades + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + auto_extend: true, + use_rpc: false, + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + process_command(&config).unwrap(); + + // Prepare buffer to upgrade deployed program to a larger program + create_buffer_with_offline_authority( + &rpc_client, + &syscall_program_path, + &mut config, + &online_signer, + &offline_signer, + &buffer_signer, + ); + + config.signers = vec![&offline_signer]; + config.command = CliCommand::Program(ProgramCliCommand::Upgrade { + fee_payer_signer_index: 0, + program_pubkey: program_signer.pubkey(), + buffer_pubkey: buffer_signer.pubkey(), + upgrade_authority_signer_index: 0, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + let sig_response = process_command(&config).unwrap(); + let sign_only = parse_sign_only_reply_string(&sig_response); + let offline_pre_signer = sign_only.presigner_of(&offline_signer.pubkey()).unwrap(); + // Attempt to deploy from buffer using signature over correct message (should succeed) + config.signers = vec![&offline_pre_signer, &program_signer]; + + config.command = CliCommand::Program(ProgramCliCommand::Upgrade { + fee_payer_signer_index: 0, + program_pubkey: program_signer.pubkey(), + buffer_pubkey: buffer_signer.pubkey(), + upgrade_authority_signer_index: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + if enable_feature { + let res = process_command(&config); + assert!(res.is_ok()); + } else { + expect_command_failure( + &config, + "Program contains a syscall to a disabled feature", + format!("Buffer account {} has invalid program data: \"ELF error: ELF error: Unresolved symbol (sol_alt_bn128_group_op) at instruction #49 (ELF file offset 0x188)\"", buffer_signer.pubkey()).as_str(), + ); + + // If we skip verification, the failure should be at a later stage + config.command = CliCommand::Program(ProgramCliCommand::Upgrade { + fee_payer_signer_index: 0, + program_pubkey: program_signer.pubkey(), + buffer_pubkey: buffer_signer.pubkey(), + upgrade_authority_signer_index: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, + }); + config.output_format = OutputFormat::JsonCompact; + + let response = process_command(&config); + assert!(response + .err() + .unwrap() + .to_string() + .contains("Upgrading program failed: RPC response error -32002")); + } +} + #[test] fn test_cli_program_deploy_with_authority() { solana_logger::setup(); @@ -429,6 +728,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -481,6 +781,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -527,6 +828,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); let program_account = rpc_client.get_account(&program_pubkey).unwrap(); @@ -605,6 +907,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); let program_account = rpc_client.get_account(&program_pubkey).unwrap(); @@ -687,6 +990,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -711,6 +1015,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -830,6 +1135,7 @@ fn test_cli_program_upgrade_auto_extend() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -852,6 +1158,7 @@ fn test_cli_program_upgrade_auto_extend() { max_sign_attempts: 5, auto_extend: false, // --no-auto-extend flag is present use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -884,6 +1191,7 @@ fn test_cli_program_upgrade_auto_extend() { max_sign_attempts: 5, auto_extend: true, // --no-auto-extend flag is absent use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -974,6 +1282,7 @@ fn test_cli_program_close_program() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1092,6 +1401,7 @@ fn test_cli_program_extend_program() { max_sign_attempts: 5, auto_extend: false, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1142,6 +1452,7 @@ fn test_cli_program_extend_program() { max_sign_attempts: 5, auto_extend: false, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -1188,6 +1499,7 @@ fn test_cli_program_extend_program() { max_sign_attempts: 5, auto_extend: false, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); } @@ -1255,6 +1567,7 @@ fn test_cli_program_write_buffer() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -1294,6 +1607,7 @@ fn test_cli_program_write_buffer() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1360,6 +1674,7 @@ fn test_cli_program_write_buffer() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1402,6 +1717,7 @@ fn test_cli_program_write_buffer() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1484,6 +1800,7 @@ fn test_cli_program_write_buffer() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -1533,6 +1850,7 @@ fn test_cli_program_write_buffer() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); config.signers = vec![&keypair, &buffer_keypair]; @@ -1551,6 +1869,7 @@ fn test_cli_program_write_buffer() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let buffer_account_len = { @@ -1573,6 +1892,111 @@ fn test_cli_program_write_buffer() { ); } +#[test_case(true; "Feature enabled")] +#[test_case(false; "Feature disabled")] +fn test_cli_program_write_buffer_feature(enable_feature: bool) { + solana_logger::setup(); + + let mut program_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + program_path.push("tests"); + program_path.push("fixtures"); + program_path.push("alt_bn128"); + program_path.set_extension("so"); + + let mint_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + let faucet_addr = run_local_faucet(mint_keypair, None); + let mut genesis = TestValidatorGenesis::default(); + let mut test_validator_builder = genesis + .fee_rate_governor(FeeRateGovernor::new(0, 0)) + .rent(Rent { + lamports_per_byte_year: 1, + exemption_threshold: 1.0, + ..Rent::default() + }) + .faucet_addr(Some(faucet_addr)); + + // Deactivate the enable alt bn128 syscall and try to submit a program with that syscall + if !enable_feature { + test_validator_builder = + test_validator_builder.deactivate_features(&[enable_alt_bn128_syscall::id()]); + } + + let test_validator = test_validator_builder + .start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified) + .expect("validator start failed"); + + let rpc_client = + RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed()); + + let mut file = File::open(program_path.to_str().unwrap()).unwrap(); + let mut program_data = Vec::new(); + file.read_to_end(&mut program_data).unwrap(); + let max_len = program_data.len(); + let minimum_balance_for_buffer = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata( + max_len, + )) + .unwrap(); + + let mut config = CliConfig::recent_for_tests(); + let keypair = Keypair::new(); + config.json_rpc_url = test_validator.rpc_url(); + config.signers = vec![&keypair]; + config.command = CliCommand::Airdrop { + pubkey: None, + lamports: 100 * minimum_balance_for_buffer, + }; + process_command(&config).unwrap(); + + // Write a buffer with default params + config.signers = vec![&keypair]; + config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: program_path.to_str().unwrap().to_string(), + fee_payer_signer_index: 0, + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: 0, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + use_rpc: false, + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + + if enable_feature { + let response = process_command(&config); + assert!(response.is_ok()); + } else { + expect_command_failure( + &config, + "Program contains a syscall from a deactivated feature", + "ELF error: ELF error: Unresolved symbol (sol_alt_bn128_group_op) at instruction #49 (ELF file offset 0x188)" + ); + + // If we bypass the verification, there should be no error + config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer { + program_location: program_path.to_str().unwrap().to_string(), + fee_payer_signer_index: 0, + buffer_signer_index: None, + buffer_pubkey: None, + buffer_authority_signer_index: 0, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + use_rpc: false, + skip_feature_verification: true, + }); + + // When we skip verification, we won't fail + let response = process_command(&config); + assert!(response.is_ok()); + } +} + #[test] fn test_cli_program_set_buffer_authority() { solana_logger::setup(); @@ -1626,6 +2050,7 @@ fn test_cli_program_set_buffer_authority() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap(); @@ -1681,6 +2106,7 @@ fn test_cli_program_set_buffer_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; expect_command_failure( @@ -1737,6 +2163,7 @@ fn test_cli_program_set_buffer_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1796,6 +2223,7 @@ fn test_cli_program_mismatch_buffer_authority() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap(); @@ -1823,6 +2251,7 @@ fn test_cli_program_mismatch_buffer_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -1851,6 +2280,7 @@ fn test_cli_program_mismatch_buffer_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); } @@ -1937,6 +2367,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1967,6 +2398,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: sign_only: true, dump_transaction_message: false, blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let sig_response = process_command(&config).unwrap(); @@ -1988,6 +2420,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; expect_command_failure( @@ -2012,6 +2445,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: sign_only: true, dump_transaction_message: false, blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let sig_response = process_command(&config).unwrap(); @@ -2033,6 +2467,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -2110,6 +2545,7 @@ fn test_cli_program_show() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); @@ -2174,6 +2610,7 @@ fn test_cli_program_show() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let min_slot = rpc_client.get_slot().unwrap(); @@ -2305,6 +2742,7 @@ fn test_cli_program_dump() { compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); @@ -2351,6 +2789,7 @@ fn create_buffer_with_offline_authority<'a>( compute_unit_price: None, max_sign_attempts: 5, use_rpc: false, + skip_feature_verification: true, }); process_command(config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_signer.pubkey()).unwrap(); @@ -2451,6 +2890,7 @@ fn test_cli_program_deploy_with_args(compute_unit_price: Option, use_rpc: b max_sign_attempts: 5, auto_extend: true, use_rpc, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index 217e073f1c3ada..92a4b1ef109535 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -12,8 +12,7 @@ use { solana_faucet::faucet::run_local_faucet, solana_rpc_client::rpc_client::RpcClient, solana_rpc_client_api::{ - request::DELINQUENT_VALIDATOR_SLOT_DISTANCE, - response::{RpcStakeActivation, StakeActivationState}, + request::DELINQUENT_VALIDATOR_SLOT_DISTANCE, response::StakeActivationState, }, solana_rpc_client_nonce_utils::blockhash_query::{self, BlockhashQuery}, solana_sdk::{ @@ -29,8 +28,9 @@ use { stake::{ self, instruction::LockupArgs, - state::{Lockup, StakeAuthorize, StakeStateV2}, + state::{Delegation, Lockup, StakeActivationStatus, StakeAuthorize, StakeStateV2}, }, + sysvar::stake_history, }, solana_streamer::socket::SocketAddrSpace, solana_test_validator::{TestValidator, TestValidatorGenesis}, @@ -163,6 +163,30 @@ fn test_stake_redelegation() { // wait for new epoch plus one additional slot for rewards payout wait_for_next_epoch_plus_n_slots(&rpc_client, 1); + let check_activation_status = |delegation: &Delegation, + expected_state: StakeActivationState, + expected_active_stake: u64| { + let stake_history_account = rpc_client.get_account(&stake_history::id()).unwrap(); + let stake_history = solana_sdk::account::from_account(&stake_history_account).unwrap(); + let current_epoch = rpc_client.get_epoch_info().unwrap().epoch; + let StakeActivationStatus { + effective, + activating, + deactivating, + } = delegation.stake_activating_and_deactivating(current_epoch, &stake_history, None); + let stake_activation_state = if deactivating > 0 { + StakeActivationState::Deactivating + } else if activating > 0 { + StakeActivationState::Activating + } else if effective > 0 { + StakeActivationState::Active + } else { + StakeActivationState::Inactive + }; + assert_eq!(stake_activation_state, expected_state); + assert_eq!(effective, expected_active_stake); + }; + // `stake_keypair` should now be delegated to `vote_keypair` and fully activated let stake_account = rpc_client.get_account(&stake_keypair.pubkey()).unwrap(); let stake_state: StakeStateV2 = stake_account.state().unwrap(); @@ -170,21 +194,16 @@ fn test_stake_redelegation() { let rent_exempt_reserve = match stake_state { StakeStateV2::Stake(meta, stake, _) => { assert_eq!(stake.delegation.voter_pubkey, vote_keypair.pubkey()); + check_activation_status( + &stake.delegation, + StakeActivationState::Active, + 50_000_000_000 - meta.rent_exempt_reserve, + ); meta.rent_exempt_reserve } _ => panic!("Unexpected stake state!"), }; - assert_eq!( - rpc_client - .get_stake_activation(stake_keypair.pubkey(), None) - .unwrap(), - RpcStakeActivation { - state: StakeActivationState::Active, - active: 50_000_000_000 - rent_exempt_reserve, - inactive: 0 - } - ); check_balance!(50_000_000_000, &rpc_client, &stake_keypair.pubkey()); let stake2_keypair = Keypair::new(); @@ -226,28 +245,24 @@ fn test_stake_redelegation() { process_command(&config).unwrap(); // `stake_keypair` should now be deactivating - assert_eq!( - rpc_client - .get_stake_activation(stake_keypair.pubkey(), None) - .unwrap(), - RpcStakeActivation { - state: StakeActivationState::Deactivating, - active: 50_000_000_000 - rent_exempt_reserve, - inactive: 0, - } + let stake_account = rpc_client.get_account(&stake_keypair.pubkey()).unwrap(); + let stake_state: StakeStateV2 = stake_account.state().unwrap(); + let StakeStateV2::Stake(_, stake, _) = stake_state else { + panic!() + }; + check_activation_status( + &stake.delegation, + StakeActivationState::Deactivating, + 50_000_000_000 - rent_exempt_reserve, ); // `stake_keypair2` should now be activating - assert_eq!( - rpc_client - .get_stake_activation(stake2_keypair.pubkey(), None) - .unwrap(), - RpcStakeActivation { - state: StakeActivationState::Activating, - active: 0, - inactive: 50_000_000_000 - rent_exempt_reserve, - } - ); + let stake_account = rpc_client.get_account(&stake2_keypair.pubkey()).unwrap(); + let stake_state: StakeStateV2 = stake_account.state().unwrap(); + let StakeStateV2::Stake(_, stake, _) = stake_state else { + panic!() + }; + check_activation_status(&stake.delegation, StakeActivationState::Activating, 0); // check that all the stake, save `rent_exempt_reserve`, have been moved from `stake_keypair` // to `stake2_keypair` @@ -258,38 +273,28 @@ fn test_stake_redelegation() { wait_for_next_epoch_plus_n_slots(&rpc_client, 1); // `stake_keypair` should now be deactivated - assert_eq!( - rpc_client - .get_stake_activation(stake_keypair.pubkey(), None) - .unwrap(), - RpcStakeActivation { - state: StakeActivationState::Inactive, - active: 0, - inactive: 0, - } - ); + let stake_account = rpc_client.get_account(&stake_keypair.pubkey()).unwrap(); + let stake_state: StakeStateV2 = stake_account.state().unwrap(); + let StakeStateV2::Stake(_, stake, _) = stake_state else { + panic!() + }; + check_activation_status(&stake.delegation, StakeActivationState::Inactive, 0); // `stake2_keypair` should now be delegated to `vote2_keypair` and fully activated let stake2_account = rpc_client.get_account(&stake2_keypair.pubkey()).unwrap(); let stake2_state: StakeStateV2 = stake2_account.state().unwrap(); match stake2_state { - StakeStateV2::Stake(_meta, stake, _) => { + StakeStateV2::Stake(meta, stake, _) => { assert_eq!(stake.delegation.voter_pubkey, vote2_keypair.pubkey()); + check_activation_status( + &stake.delegation, + StakeActivationState::Active, + 50_000_000_000 - meta.rent_exempt_reserve, + ); } _ => panic!("Unexpected stake2 state!"), }; - - assert_eq!( - rpc_client - .get_stake_activation(stake2_keypair.pubkey(), None) - .unwrap(), - RpcStakeActivation { - state: StakeActivationState::Active, - active: 50_000_000_000 - rent_exempt_reserve, - inactive: 0 - } - ); } #[test] diff --git a/client/src/connection_cache.rs b/client/src/connection_cache.rs index a682b6e6db2247..107c7210082aab 100644 --- a/client/src/connection_cache.rs +++ b/client/src/connection_cache.rs @@ -16,7 +16,6 @@ use { solana_streamer::streamer::StakedNodes, solana_udp_client::{UdpConfig, UdpConnectionManager, UdpPool}, std::{ - error::Error, net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, RwLock}, }, @@ -110,29 +109,6 @@ impl ConnectionCache { } } - #[deprecated( - since = "1.15.0", - note = "This method does not do anything. Please use `new_with_client_options` instead to set the client certificate." - )] - pub fn update_client_certificate( - &mut self, - _keypair: &Keypair, - _ipaddr: IpAddr, - ) -> Result<(), Box> { - Ok(()) - } - - #[deprecated( - since = "1.15.0", - note = "This method does not do anything. Please use `new_with_client_options` instead to set staked nodes information." - )] - pub fn set_staked_nodes( - &mut self, - _staked_nodes: &Arc>, - _client_pubkey: &Pubkey, - ) { - } - pub fn with_udp(name: &'static str, connection_pool_size: usize) -> Self { // The minimum pool size is 1. let connection_pool_size = 1.max(connection_pool_size); diff --git a/client/src/lib.rs b/client/src/lib.rs index 889b0c4d279c08..f5e045ff531604 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -2,13 +2,10 @@ pub mod connection_cache; pub mod nonblocking; -pub mod quic_client; pub mod send_and_confirm_transactions_in_parallel; pub mod thin_client; pub mod tpu_client; -pub mod tpu_connection; pub mod transaction_executor; -pub mod udp_client; extern crate solana_metrics; @@ -47,9 +44,6 @@ pub mod rpc_config { pub mod rpc_custom_error { pub use solana_rpc_client_api::custom_error::*; } -pub mod rpc_deprecated_config { - pub use solana_rpc_client_api::deprecated_config::*; -} pub mod rpc_filter { pub use solana_rpc_client_api::filter::*; } diff --git a/client/src/nonblocking/mod.rs b/client/src/nonblocking/mod.rs index ab11ae5c6782b2..b62618c024b5ca 100644 --- a/client/src/nonblocking/mod.rs +++ b/client/src/nonblocking/mod.rs @@ -1,7 +1,4 @@ -pub mod quic_client; pub mod tpu_client; -pub mod tpu_connection; -pub mod udp_client; pub mod blockhash_query { pub use solana_rpc_client_nonce_utils::nonblocking::blockhash_query::*; diff --git a/client/src/nonblocking/quic_client.rs b/client/src/nonblocking/quic_client.rs deleted file mode 100644 index 28b9649289e2b4..00000000000000 --- a/client/src/nonblocking/quic_client.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[deprecated( - since = "1.15.0", - note = "Please use `solana_quic_client::nonblocking::quic_client::QuicClientConnection` instead." -)] -pub use solana_quic_client::nonblocking::quic_client::QuicClientConnection as QuicTpuConnection; -pub use solana_quic_client::nonblocking::quic_client::{ - QuicClient, QuicClientCertificate, QuicLazyInitializedEndpoint, -}; diff --git a/client/src/nonblocking/tpu_connection.rs b/client/src/nonblocking/tpu_connection.rs deleted file mode 100644 index b91a88853310b4..00000000000000 --- a/client/src/nonblocking/tpu_connection.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[deprecated( - since = "1.15.0", - note = "Please use `solana_connection_cache::nonblocking::client_connection::ClientConnection` instead." -)] -pub use solana_connection_cache::nonblocking::client_connection::ClientConnection as TpuConnection; diff --git a/client/src/nonblocking/udp_client.rs b/client/src/nonblocking/udp_client.rs deleted file mode 100644 index e880b1fb107cf8..00000000000000 --- a/client/src/nonblocking/udp_client.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[deprecated( - since = "1.15.0", - note = "Please use `solana_udp_client::nonblocking::udp_client::UdpClientConnection` instead." -)] -pub use solana_udp_client::nonblocking::udp_client::UdpClientConnection as UdpTpuConnection; diff --git a/client/src/quic_client.rs b/client/src/quic_client.rs deleted file mode 100644 index a32aa381cb10ef..00000000000000 --- a/client/src/quic_client.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[deprecated( - since = "1.15.0", - note = "Please use `solana_quic_client::quic_client::QuicClientConnection` instead." -)] -pub use solana_quic_client::quic_client::QuicClientConnection as QuicTpuConnection; diff --git a/client/src/thin_client.rs b/client/src/thin_client.rs index 61f24018e8c778..596c9a13a6dbd3 100644 --- a/client/src/thin_client.rs +++ b/client/src/thin_client.rs @@ -11,10 +11,8 @@ use { solana_sdk::{ account::Account, client::{AsyncClient, Client, SyncClient}, - clock::Slot, commitment_config::CommitmentConfig, epoch_info::EpochInfo, - fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, instruction::Instruction, message::Message, @@ -213,20 +211,6 @@ impl SyncClient for ThinClient { dispatch!(fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> TransportResult); - dispatch!(#[allow(deprecated)] fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)>); - - dispatch!(#[allow(deprecated)] fn get_recent_blockhash_with_commitment( - &self, - commitment_config: CommitmentConfig - ) -> TransportResult<(Hash, FeeCalculator, Slot)>); - - dispatch!(#[allow(deprecated)] fn get_fee_calculator_for_blockhash( - &self, - blockhash: &Hash - ) -> TransportResult>); - - dispatch!(#[allow(deprecated)] fn get_fee_rate_governor(&self) -> TransportResult); - dispatch!(fn get_signature_status( &self, signature: &Signature @@ -262,8 +246,6 @@ impl SyncClient for ThinClient { dispatch!(fn poll_for_signature(&self, signature: &Signature) -> TransportResult<()>); - dispatch!(#[allow(deprecated)] fn get_new_blockhash(&self, blockhash: &Hash) -> TransportResult<(Hash, FeeCalculator)>); - dispatch!(fn get_latest_blockhash(&self) -> TransportResult); dispatch!(fn get_latest_blockhash_with_commitment( diff --git a/client/src/tpu_connection.rs b/client/src/tpu_connection.rs deleted file mode 100644 index 9e000612a51e03..00000000000000 --- a/client/src/tpu_connection.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[deprecated( - since = "1.15.0", - note = "Please use `solana_connection_cache::client_connection::ClientConnection` instead." -)] -pub use solana_connection_cache::client_connection::ClientConnection as TpuConnection; -pub use solana_connection_cache::client_connection::ClientStats; diff --git a/client/src/udp_client.rs b/client/src/udp_client.rs deleted file mode 100644 index c05b74b3640749..00000000000000 --- a/client/src/udp_client.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[deprecated( - since = "1.15.0", - note = "Please use `solana_udp_client::udp_client::UdpClientConnection` instead." -)] -pub use solana_udp_client::udp_client::UdpClientConnection as UdpTpuConnection; diff --git a/compute-budget/src/compute_budget.rs b/compute-budget/src/compute_budget.rs index 6b91affca4dd1c..24eeb46815372d 100644 --- a/compute-budget/src/compute_budget.rs +++ b/compute-budget/src/compute_budget.rs @@ -1,9 +1,4 @@ -use { - crate::compute_budget_processor::{ - self, process_compute_budget_instructions, DEFAULT_HEAP_COST, - }, - solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey, transaction::Result}, -}; +use crate::compute_budget_processor::{self, ComputeBudgetLimits, DEFAULT_HEAP_COST}; #[cfg(all(RUSTC_WITH_SPECIALIZATION, feature = "frozen-abi"))] impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget { @@ -13,6 +8,16 @@ impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget { } } +/// Max instruction stack depth. This is the maximum nesting of instructions that can happen during +/// a transaction. +pub const MAX_INSTRUCTION_STACK_DEPTH: usize = 5; + +/// Max call depth. This is the maximum nesting of SBF to SBF call that can happen within a program. +pub const MAX_CALL_DEPTH: usize = 64; + +/// The size of one SBF stack frame. +pub const STACK_FRAME_SIZE: usize = 4096; + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ComputeBudget { /// Number of compute units that a transaction or individual instruction is @@ -26,11 +31,11 @@ pub struct ComputeBudget { /// Number of compute units consumed by an invoke call (not including the cost incurred by /// the called program) pub invoke_units: u64, - /// Maximum program instruction invocation stack height. Invocation stack - /// height starts at 1 for transaction instructions and the stack height is + /// Maximum program instruction invocation stack depth. Invocation stack + /// depth starts at 1 for transaction instructions and the stack depth is /// incremented each time a program invokes an instruction and decremented /// when a program returns. - pub max_invoke_stack_height: usize, + pub max_instruction_stack_depth: usize, /// Maximum cross-program invocation and instructions per transaction pub max_instruction_trace_length: usize, /// Base number of compute units consumed to call SHA256 @@ -126,6 +131,16 @@ impl Default for ComputeBudget { } } +impl From for ComputeBudget { + fn from(compute_budget_limits: ComputeBudgetLimits) -> Self { + ComputeBudget { + compute_unit_limit: u64::from(compute_budget_limits.compute_unit_limit), + heap_size: compute_budget_limits.updated_heap_bytes, + ..ComputeBudget::default() + } + } +} + impl ComputeBudget { pub fn new(compute_unit_limit: u64) -> Self { ComputeBudget { @@ -133,13 +148,13 @@ impl ComputeBudget { log_64_units: 100, create_program_address_units: 1500, invoke_units: 1000, - max_invoke_stack_height: 5, + max_instruction_stack_depth: MAX_INSTRUCTION_STACK_DEPTH, max_instruction_trace_length: 64, sha256_base_cost: 85, sha256_byte_cost: 1, sha256_max_slices: 20_000, - max_call_depth: 64, - stack_frame_size: 4_096, + max_call_depth: MAX_CALL_DEPTH, + stack_frame_size: STACK_FRAME_SIZE, log_pubkey_units: 100, max_cpi_instruction_size: 1280, // IPv6 Min MTU size cpi_bytes_per_unit: 250, // ~50MB at 200,000 units @@ -176,17 +191,6 @@ impl ComputeBudget { } } - pub fn try_from_instructions<'a>( - instructions: impl Iterator, - ) -> Result { - let compute_budget_limits = process_compute_budget_instructions(instructions)?; - Ok(ComputeBudget { - compute_unit_limit: u64::from(compute_budget_limits.compute_unit_limit), - heap_size: compute_budget_limits.updated_heap_bytes, - ..ComputeBudget::default() - }) - } - /// Returns cost of the Poseidon hash function for the given number of /// inputs is determined by the following quadratic function: /// diff --git a/compute-budget/src/compute_budget_processor.rs b/compute-budget/src/compute_budget_processor.rs index dc568f82f169bb..edd56e382a6bf2 100644 --- a/compute-budget/src/compute_budget_processor.rs +++ b/compute-budget/src/compute_budget_processor.rs @@ -3,7 +3,7 @@ use { solana_sdk::{ borsh1::try_from_slice_unchecked, compute_budget::{self, ComputeBudgetInstruction}, - entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES, + entrypoint::HEAP_LENGTH, fee::FeeBudgetLimits, instruction::{CompiledInstruction, InstructionError}, pubkey::Pubkey, @@ -11,18 +11,19 @@ use { }, }; -const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024; /// Roughly 0.5us/page, where page is 32K; given roughly 15CU/us, the /// default heap page cost = 0.5 * 15 ~= 8CU/page pub const DEFAULT_HEAP_COST: u64 = 8; pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000; pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; +pub const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024; +pub const MIN_HEAP_FRAME_BYTES: u32 = HEAP_LENGTH as u32; /// The total accounts data a transaction can load is limited to 64MiB to not break /// anyone in Mainnet-beta today. It can be set by set_loaded_accounts_data_size_limit instruction pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: u32 = 64 * 1024 * 1024; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ComputeBudgetLimits { pub updated_heap_bytes: u32, pub compute_unit_limit: u32, @@ -33,7 +34,7 @@ pub struct ComputeBudgetLimits { impl Default for ComputeBudgetLimits { fn default() -> Self { ComputeBudgetLimits { - updated_heap_bytes: u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap(), + updated_heap_bytes: MIN_HEAP_FRAME_BYTES, compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT, compute_unit_price: 0, loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, @@ -122,7 +123,7 @@ pub fn process_compute_budget_instructions<'a>( // sanitize limits let updated_heap_bytes = requested_heap_size - .unwrap_or(u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap()) // loader's default heap_size + .unwrap_or(MIN_HEAP_FRAME_BYTES) // loader's default heap_size .min(MAX_HEAP_FRAME_BYTES); let compute_unit_limit = updated_compute_unit_limit @@ -147,8 +148,7 @@ pub fn process_compute_budget_instructions<'a>( } fn sanitize_requested_heap_size(bytes: u32) -> bool { - (u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap()..=MAX_HEAP_FRAME_BYTES).contains(&bytes) - && bytes % 1024 == 0 + (MIN_HEAP_FRAME_BYTES..=MAX_HEAP_FRAME_BYTES).contains(&bytes) && bytes % 1024 == 0 } #[cfg(test)] @@ -377,7 +377,7 @@ mod tests { test!( &[ Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32), + ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES), ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), ], Err(TransactionError::DuplicateInstruction(2)) diff --git a/core/Cargo.toml b/core/Cargo.toml index 09aaa981310e68..d396697613fb54 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -46,6 +46,7 @@ solana-accounts-db = { workspace = true } solana-bloom = { workspace = true } solana-client = { workspace = true } solana-compute-budget = { workspace = true } +solana-connection-cache = { workspace = true } solana-cost-model = { workspace = true } solana-entry = { workspace = true } solana-frozen-abi = { workspace = true, optional = true } diff --git a/core/benches/consumer.rs b/core/benches/consumer.rs index 14010e8d91a875..6dd9eb5b8bf0fa 100644 --- a/core/benches/consumer.rs +++ b/core/benches/consumer.rs @@ -22,7 +22,6 @@ use { solana_runtime::bank::Bank, solana_sdk::{ account::{Account, ReadableAccount}, - feature_set::apply_cost_tracker_during_replay, signature::Keypair, signer::Signer, stake_history::Epoch, @@ -97,7 +96,7 @@ struct BenchFrame { signal_receiver: Receiver<(Arc, (Entry, u64))>, } -fn setup(apply_cost_tracker_during_replay: bool) -> BenchFrame { +fn setup() -> BenchFrame { let mint_total = u64::MAX; let GenesisConfigInfo { mut genesis_config, .. @@ -109,10 +108,6 @@ fn setup(apply_cost_tracker_during_replay: bool) -> BenchFrame { let mut bank = Bank::new_for_benches(&genesis_config); - if !apply_cost_tracker_during_replay { - bank.deactivate_feature(&apply_cost_tracker_during_replay::id()); - } - // Allow arbitrary transaction processing time for the purposes of this bench bank.ns_per_slot = u128::MAX; @@ -139,11 +134,7 @@ fn setup(apply_cost_tracker_during_replay: bool) -> BenchFrame { } } -fn bench_process_and_record_transactions( - bencher: &mut Bencher, - batch_size: usize, - apply_cost_tracker_during_replay: bool, -) { +fn bench_process_and_record_transactions(bencher: &mut Bencher, batch_size: usize) { const TRANSACTIONS_PER_ITERATION: usize = 64; assert_eq!( TRANSACTIONS_PER_ITERATION % batch_size, @@ -161,7 +152,7 @@ fn bench_process_and_record_transactions( poh_recorder, poh_service, signal_receiver: _signal_receiver, - } = setup(apply_cost_tracker_during_replay); + } = setup(); let consumer = create_consumer(&poh_recorder); let transactions = create_transactions(&bank, 2_usize.pow(20)); let mut transaction_iter = transactions.chunks(batch_size); @@ -186,30 +177,15 @@ fn bench_process_and_record_transactions( #[bench] fn bench_process_and_record_transactions_unbatched(bencher: &mut Bencher) { - bench_process_and_record_transactions(bencher, 1, true); + bench_process_and_record_transactions(bencher, 1); } #[bench] fn bench_process_and_record_transactions_half_batch(bencher: &mut Bencher) { - bench_process_and_record_transactions(bencher, 32, true); + bench_process_and_record_transactions(bencher, 32); } #[bench] fn bench_process_and_record_transactions_full_batch(bencher: &mut Bencher) { - bench_process_and_record_transactions(bencher, 64, true); -} - -#[bench] -fn bench_process_and_record_transactions_unbatched_disable_tx_cost_update(bencher: &mut Bencher) { - bench_process_and_record_transactions(bencher, 1, false); -} - -#[bench] -fn bench_process_and_record_transactions_half_batch_disable_tx_cost_update(bencher: &mut Bencher) { - bench_process_and_record_transactions(bencher, 32, false); -} - -#[bench] -fn bench_process_and_record_transactions_full_batch_disable_tx_cost_update(bencher: &mut Bencher) { - bench_process_and_record_transactions(bencher, 64, false); + bench_process_and_record_transactions(bencher, 64); } diff --git a/core/src/banking_stage/committer.rs b/core/src/banking_stage/committer.rs index 2637d38c883f14..0ca1304a4560f5 100644 --- a/core/src/banking_stage/committer.rs +++ b/core/src/banking_stage/committer.rs @@ -6,7 +6,7 @@ use { }, solana_measure::measure_us, solana_runtime::{ - bank::{Bank, CommitTransactionCounts, TransactionBalancesSet}, + bank::{Bank, ExecutedTransactionCounts, TransactionBalancesSet}, bank_utils, prioritization_fee_cache::PrioritizationFeeCache, transaction_batch::TransactionBatch, @@ -92,10 +92,10 @@ impl Committer { execution_results, last_blockhash, lamports_per_signature, - CommitTransactionCounts { - committed_transactions_count: executed_transactions_count as u64, - committed_non_vote_transactions_count: executed_non_vote_transactions_count as u64, - committed_with_failure_result_count: executed_transactions_count + ExecutedTransactionCounts { + executed_transactions_count: executed_transactions_count as u64, + executed_non_vote_transactions_count: executed_non_vote_transactions_count as u64, + executed_with_failure_result_count: executed_transactions_count .saturating_sub(executed_with_successful_result_count) as u64, signature_count, diff --git a/core/src/banking_stage/consume_worker.rs b/core/src/banking_stage/consume_worker.rs index 4a4b4e729bc114..f83ca6724d415e 100644 --- a/core/src/banking_stage/consume_worker.rs +++ b/core/src/banking_stage/consume_worker.rs @@ -318,6 +318,7 @@ impl ConsumeWorkerMetrics { invalid_account_for_fee, invalid_account_index, invalid_program_for_execution, + invalid_compute_budget, not_allowed_during_cluster_maintenance, invalid_writable_account, invalid_rent_paying_account, @@ -371,6 +372,9 @@ impl ConsumeWorkerMetrics { self.error_metrics .invalid_program_for_execution .fetch_add(*invalid_program_for_execution, Ordering::Relaxed); + self.error_metrics + .invalid_compute_budget + .fetch_add(*invalid_compute_budget, Ordering::Relaxed); self.error_metrics .not_allowed_during_cluster_maintenance .fetch_add(*not_allowed_during_cluster_maintenance, Ordering::Relaxed); @@ -561,6 +565,7 @@ struct ConsumeWorkerTransactionErrorMetrics { invalid_account_for_fee: AtomicUsize, invalid_account_index: AtomicUsize, invalid_program_for_execution: AtomicUsize, + invalid_compute_budget: AtomicUsize, not_allowed_during_cluster_maintenance: AtomicUsize, invalid_writable_account: AtomicUsize, invalid_rent_paying_account: AtomicUsize, @@ -644,6 +649,12 @@ impl ConsumeWorkerTransactionErrorMetrics { .swap(0, Ordering::Relaxed), i64 ), + ( + "invalid_compute_budget", + self.invalid_compute_budget + .swap(0, Ordering::Relaxed), + i64 + ), ( "not_allowed_during_cluster_maintenance", self.not_allowed_during_cluster_maintenance diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index 2fd8524d7454b8..6ae0881da45d8a 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -518,27 +518,14 @@ impl Consumer { // Costs of all transactions are added to the cost_tracker before processing. // To ensure accurate tracking of compute units, transactions that ultimately - // were not included in the block should have their cost removed. - QosService::remove_costs( + // were not included in the block should have their cost removed, the rest + // should update with their actually consumed units. + QosService::remove_or_update_costs( transaction_qos_cost_results.iter(), commit_transactions_result.as_ref().ok(), bank, ); - // once feature `apply_cost_tracker_during_replay` is activated, leader shall no longer - // adjust block with executed cost (a behavior more inline with bankless leader), it - // should use requested, or default `compute_unit_limit` as transaction's execution cost. - if !bank - .feature_set - .is_active(&feature_set::apply_cost_tracker_during_replay::id()) - { - QosService::update_costs( - transaction_qos_cost_results.iter(), - commit_transactions_result.as_ref().ok(), - bank, - ); - } - retryable_transaction_indexes .iter_mut() .for_each(|x| *x += chunk_offset); @@ -606,6 +593,7 @@ impl Consumer { &mut execute_and_commit_timings.execute_timings, TransactionProcessingConfig { account_overrides: None, + check_program_modification_slot: bank.check_program_modification_slot(), compute_budget: bank.compute_budget(), log_messages_bytes_limit: self.log_messages_bytes_limit, limit_to_load_programs: true, @@ -1431,16 +1419,6 @@ mod tests { #[test] fn test_bank_process_and_record_transactions_cost_tracker() { - for apply_cost_tracker_during_replay_enabled in [true, false] { - bank_process_and_record_transactions_cost_tracker( - apply_cost_tracker_during_replay_enabled, - ); - } - } - - fn bank_process_and_record_transactions_cost_tracker( - apply_cost_tracker_during_replay_enabled: bool, - ) { solana_logger::setup(); let GenesisConfigInfo { genesis_config, @@ -1449,9 +1427,6 @@ mod tests { } = create_slow_genesis_config(10_000); let mut bank = Bank::new_for_tests(&genesis_config); bank.ns_per_slot = u128::MAX; - if !apply_cost_tracker_during_replay_enabled { - bank.deactivate_feature(&feature_set::apply_cost_tracker_during_replay::id()); - } let bank = bank.wrap_with_bank_forks_for_tests().0; let pubkey = solana_sdk::pubkey::new_rand(); @@ -1520,8 +1495,7 @@ mod tests { // TEST: it's expected that the allocation will execute but the transfer will not // because of a shared write-lock between mint_keypair. Ensure only the first transaction - // takes compute units in the block AND the apply_cost_tracker_during_replay_enabled feature - // is applied correctly + // takes compute units in the block let allocate_keypair = Keypair::new(); let transactions = sanitize_transactions(vec![ system_transaction::allocate( @@ -1560,7 +1534,7 @@ mod tests { ); assert_eq!(retryable_transaction_indexes, vec![1]); - let expected_block_cost = if !apply_cost_tracker_during_replay_enabled { + let expected_block_cost = { let (actual_programs_execution_cost, actual_loaded_accounts_data_size_cost) = match commit_transactions_result.first().unwrap() { CommitTransactionDetails::Committed { @@ -1586,8 +1560,6 @@ mod tests { } block_cost + cost.sum() - } else { - block_cost + CostModel::calculate_cost(&transactions[0], &bank.feature_set).sum() }; assert_eq!(get_block_cost(), expected_block_cost); diff --git a/core/src/banking_stage/forwarder.rs b/core/src/banking_stage/forwarder.rs index 492ba94504558b..acb34b8b4dc1e9 100644 --- a/core/src/banking_stage/forwarder.rs +++ b/core/src/banking_stage/forwarder.rs @@ -10,7 +10,8 @@ use { next_leader::{next_leader, next_leader_tpu_vote}, tracer_packet_stats::TracerPacketStats, }, - solana_client::{connection_cache::ConnectionCache, tpu_connection::TpuConnection}, + solana_client::connection_cache::ConnectionCache, + solana_connection_cache::client_connection::ClientConnection as TpuConnection, solana_gossip::cluster_info::ClusterInfo, solana_measure::measure_us, solana_perf::{data_budget::DataBudget, packet::Packet}, diff --git a/core/src/banking_stage/immutable_deserialized_packet.rs b/core/src/banking_stage/immutable_deserialized_packet.rs index bd64b81f77ec3a..cb4561d50d8f08 100644 --- a/core/src/banking_stage/immutable_deserialized_packet.rs +++ b/core/src/banking_stage/immutable_deserialized_packet.rs @@ -3,7 +3,6 @@ use { solana_perf::packet::Packet, solana_runtime::compute_budget_details::{ComputeBudgetDetails, GetComputeBudgetDetails}, solana_sdk::{ - feature_set, hash::Hash, message::Message, pubkey::Pubkey, @@ -15,7 +14,7 @@ use { VersionedTransaction, }, }, - std::{cmp::Ordering, collections::HashSet, mem::size_of, sync::Arc}, + std::{cmp::Ordering, collections::HashSet, mem::size_of}, thiserror::Error, }; @@ -106,7 +105,6 @@ impl ImmutableDeserializedPacket { // messages. pub fn build_sanitized_transaction( &self, - _feature_set: &Arc, votes_only: bool, address_loader: impl AddressLoader, reserved_account_keys: &HashSet, diff --git a/core/src/banking_stage/latest_unprocessed_votes.rs b/core/src/banking_stage/latest_unprocessed_votes.rs index 44ed870e3fb86b..084e4125b842ae 100644 --- a/core/src/banking_stage/latest_unprocessed_votes.rs +++ b/core/src/banking_stage/latest_unprocessed_votes.rs @@ -203,7 +203,10 @@ impl LatestUnprocessedVotes { let pubkey = vote.pubkey(); let slot = vote.slot(); let timestamp = vote.timestamp(); - if let Some(latest_vote) = self.get_entry(pubkey) { + + let with_latest_vote = |latest_vote: &RwLock, + vote: LatestValidatorVotePacket| + -> Option { let (latest_slot, latest_timestamp) = latest_vote .read() .map(|vote| (vote.slot(), vote.timestamp())) @@ -225,15 +228,24 @@ impl LatestUnprocessedVotes { } } } - return Some(vote); - } + Some(vote) + }; - // Should have low lock contention because this is only hit on the first few blocks of startup - // and when a new vote account starts voting. - let mut latest_votes_per_pubkey = self.latest_votes_per_pubkey.write().unwrap(); - latest_votes_per_pubkey.insert(pubkey, Arc::new(RwLock::new(vote))); - self.num_unprocessed_votes.fetch_add(1, Ordering::Relaxed); - None + if let Some(latest_vote) = self.get_entry(pubkey) { + with_latest_vote(&latest_vote, vote) + } else { + // Grab write-lock to insert new vote. + match self.latest_votes_per_pubkey.write().unwrap().entry(pubkey) { + std::collections::hash_map::Entry::Occupied(entry) => { + with_latest_vote(entry.get(), vote) + } + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(Arc::new(RwLock::new(vote))); + self.num_unprocessed_votes.fetch_add(1, Ordering::Relaxed); + None + } + } + } } #[cfg(test)] @@ -280,7 +292,6 @@ impl LatestUnprocessedVotes { let deserialized_vote_packet = vote.vote.as_ref().unwrap().clone(); if let Some(sanitized_vote_transaction) = deserialized_vote_packet .build_sanitized_transaction( - &bank.feature_set, bank.vote_only_bank(), bank.as_ref(), bank.get_reserved_account_keys(), @@ -682,6 +693,47 @@ mod tests { ); } + #[test] + fn test_update_latest_vote_race() { + // There was a race condition in updating the same pubkey in the hashmap + // when the entry does not initially exist. + let latest_unprocessed_votes = Arc::new(LatestUnprocessedVotes::new()); + + const NUM_VOTES: usize = 100; + let keypairs = Arc::new( + (0..NUM_VOTES) + .map(|_| ValidatorVoteKeypairs::new_rand()) + .collect_vec(), + ); + + // Insert votes in parallel + let insert_vote = |latest_unprocessed_votes: &LatestUnprocessedVotes, + keypairs: &Arc>, + i: usize| { + let vote = from_slots(vec![(i as u64, 1)], VoteSource::Gossip, &keypairs[i], None); + latest_unprocessed_votes.update_latest_vote(vote); + }; + + let hdl = Builder::new() + .spawn({ + let latest_unprocessed_votes = latest_unprocessed_votes.clone(); + let keypairs = keypairs.clone(); + move || { + for i in 0..NUM_VOTES { + insert_vote(&latest_unprocessed_votes, &keypairs, i); + } + } + }) + .unwrap(); + + for i in 0..NUM_VOTES { + insert_vote(&latest_unprocessed_votes, &keypairs, i); + } + + hdl.join().unwrap(); + assert_eq!(NUM_VOTES, latest_unprocessed_votes.len()); + } + #[test] fn test_simulate_threads() { let latest_unprocessed_votes = Arc::new(LatestUnprocessedVotes::new()); diff --git a/core/src/banking_stage/qos_service.rs b/core/src/banking_stage/qos_service.rs index 23d4ebd97619bd..bf8b7df963e392 100644 --- a/core/src/banking_stage/qos_service.rs +++ b/core/src/banking_stage/qos_service.rs @@ -132,39 +132,28 @@ impl QosService { (select_results, num_included) } - /// Updates the transaction costs for committed transactions. Does not handle removing costs - /// for transactions that didn't get recorded or committed - pub fn update_costs<'a>( - transaction_cost_results: impl Iterator>, - transaction_committed_status: Option<&Vec>, - bank: &Bank, - ) { - if let Some(transaction_committed_status) = transaction_committed_status { - Self::update_committed_transaction_costs( - transaction_cost_results, - transaction_committed_status, - bank, - ) - } - } - - /// Removes transaction costs from the cost tracker if not committed or recorded - pub fn remove_costs<'a>( + /// Removes transaction costs from the cost tracker if not committed or recorded, or + /// updates the transaction costs for committed transactions. + pub fn remove_or_update_costs<'a>( transaction_cost_results: impl Iterator>, transaction_committed_status: Option<&Vec>, bank: &Bank, ) { match transaction_committed_status { - Some(transaction_committed_status) => Self::remove_uncommitted_transaction_costs( - transaction_cost_results, - transaction_committed_status, - bank, - ), - None => Self::remove_transaction_costs(transaction_cost_results, bank), + Some(transaction_committed_status) => { + Self::remove_or_update_recorded_transaction_costs( + transaction_cost_results, + transaction_committed_status, + bank, + ) + } + None => Self::remove_unrecorded_transaction_costs(transaction_cost_results, bank), } } - fn remove_uncommitted_transaction_costs<'a>( + /// For recorded transactions, remove units reserved by uncommitted transaction, or update + /// units for committed transactions. + fn remove_or_update_recorded_transaction_costs<'a>( transaction_cost_results: impl Iterator>, transaction_committed_status: &Vec, bank: &Bank, @@ -178,45 +167,31 @@ impl QosService { // checked for update if let Ok(tx_cost) = tx_cost { num_included += 1; - if *transaction_committed_details == CommitTransactionDetails::NotCommitted { - cost_tracker.remove(tx_cost) + match transaction_committed_details { + CommitTransactionDetails::Committed { + compute_units, + loaded_accounts_data_size, + } => { + cost_tracker.update_execution_cost( + tx_cost, + *compute_units, + CostModel::calculate_loaded_accounts_data_size_cost( + *loaded_accounts_data_size, + &bank.feature_set, + ), + ); + } + CommitTransactionDetails::NotCommitted => { + cost_tracker.remove(tx_cost); + } } } }); cost_tracker.sub_transactions_in_flight(num_included); } - fn update_committed_transaction_costs<'a>( - transaction_cost_results: impl Iterator>, - transaction_committed_status: &Vec, - bank: &Bank, - ) { - let mut cost_tracker = bank.write_cost_tracker().unwrap(); - transaction_cost_results - .zip(transaction_committed_status) - .for_each(|(estimated_tx_cost, transaction_committed_details)| { - // Only transactions that the qos service included have to be - // checked for update - if let Ok(estimated_tx_cost) = estimated_tx_cost { - if let CommitTransactionDetails::Committed { - compute_units, - loaded_accounts_data_size, - } = transaction_committed_details - { - cost_tracker.update_execution_cost( - estimated_tx_cost, - *compute_units, - CostModel::calculate_loaded_accounts_data_size_cost( - *loaded_accounts_data_size, - &bank.feature_set, - ), - ) - } - } - }); - } - - fn remove_transaction_costs<'a>( + /// Remove reserved units for transaction batch that unsuccessfully recorded. + fn remove_unrecorded_transaction_costs<'a>( transaction_cost_results: impl Iterator>, bank: &Bank, ) { @@ -784,18 +759,11 @@ mod tests { + (execute_units_adjustment + loaded_accounts_data_size_cost_adjustment) * transaction_count; - // All transactions are committed, no costs should be removed - QosService::remove_costs(qos_cost_results.iter(), Some(&committed_status), &bank); - assert_eq!( - total_txs_cost, - bank.read_cost_tracker().unwrap().block_cost() - ); - assert_eq!( - transaction_count, - bank.read_cost_tracker().unwrap().transaction_count() + QosService::remove_or_update_costs( + qos_cost_results.iter(), + Some(&committed_status), + &bank, ); - - QosService::update_costs(qos_cost_results.iter(), Some(&committed_status), &bank); assert_eq!( final_txs_cost, bank.read_cost_tracker().unwrap().block_cost() @@ -843,18 +811,7 @@ mod tests { bank.read_cost_tracker().unwrap().block_cost() ); - // update costs doesn't impact non-committed - QosService::update_costs(qos_cost_results.iter(), None, &bank); - assert_eq!( - total_txs_cost, - bank.read_cost_tracker().unwrap().block_cost() - ); - assert_eq!( - transaction_count, - bank.read_cost_tracker().unwrap().transaction_count() - ); - - QosService::remove_costs(qos_cost_results.iter(), None, &bank); + QosService::remove_or_update_costs(qos_cost_results.iter(), None, &bank); assert_eq!(0, bank.read_cost_tracker().unwrap().block_cost()); assert_eq!(0, bank.read_cost_tracker().unwrap().transaction_count()); } @@ -926,8 +883,11 @@ mod tests { }) .collect(); - QosService::remove_costs(qos_cost_results.iter(), Some(&committed_status), &bank); - QosService::update_costs(qos_cost_results.iter(), Some(&committed_status), &bank); + QosService::remove_or_update_costs( + qos_cost_results.iter(), + Some(&committed_status), + &bank, + ); // assert the final block cost let mut expected_final_txs_count = 0u64; diff --git a/core/src/banking_stage/read_write_account_set.rs b/core/src/banking_stage/read_write_account_set.rs index 6d6b908249f168..4b1efc015e2bbf 100644 --- a/core/src/banking_stage/read_write_account_set.rs +++ b/core/src/banking_stage/read_write_account_set.rs @@ -1,15 +1,15 @@ use { + ahash::AHashSet, solana_sdk::{message::SanitizedMessage, pubkey::Pubkey}, - std::collections::HashSet, }; /// Wrapper struct to accumulate locks for a batch of transactions. #[derive(Debug, Default)] pub struct ReadWriteAccountSet { /// Set of accounts that are locked for read - read_set: HashSet, + read_set: AHashSet, /// Set of accounts that are locked for write - write_set: HashSet, + write_set: AHashSet, } impl ReadWriteAccountSet { diff --git a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs index 6dde96420c1d19..045d2cca1d8dba 100644 --- a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs @@ -358,12 +358,18 @@ impl PrioGraphScheduler { ) { let thread_id = self.in_flight_tracker.complete_batch(batch_id); for transaction in transactions { - let account_locks = transaction.get_account_locks_unchecked(); - self.account_locks.unlock_accounts( - account_locks.writable.into_iter(), - account_locks.readonly.into_iter(), - thread_id, - ); + let message = transaction.message(); + let account_keys = message.account_keys(); + let write_account_locks = account_keys + .iter() + .enumerate() + .filter_map(|(index, key)| message.is_writable(index).then_some(key)); + let read_account_locks = account_keys + .iter() + .enumerate() + .filter_map(|(index, key)| (!message.is_writable(index)).then_some(key)); + self.account_locks + .unlock_accounts(write_account_locks, read_account_locks, thread_id); } } diff --git a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs index 57a52f58a5c1cc..20462a2a1b42b2 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs @@ -475,7 +475,6 @@ impl SchedulerController { let bank = self.bank_forks.read().unwrap().working_bank(); let last_slot_in_epoch = bank.epoch_schedule().get_last_slot_in_epoch(bank.epoch()); let transaction_account_lock_limit = bank.get_transaction_account_lock_limit(); - let feature_set = &bank.feature_set; let vote_only = bank.vote_only_bank(); const CHUNK_SIZE: usize = 128; @@ -493,7 +492,6 @@ impl SchedulerController { .filter_map(|packet| { packet .build_sanitized_transaction( - feature_set, vote_only, bank.as_ref(), bank.get_reserved_account_keys(), diff --git a/core/src/banking_stage/unprocessed_packet_batches.rs b/core/src/banking_stage/unprocessed_packet_batches.rs index ebb91773c49923..2bec44dbd0ea5e 100644 --- a/core/src/banking_stage/unprocessed_packet_batches.rs +++ b/core/src/banking_stage/unprocessed_packet_batches.rs @@ -316,7 +316,6 @@ mod tests { transaction::{SimpleAddressLoader, Transaction}, }, solana_vote_program::vote_transaction, - std::sync::Arc, }; fn simple_deserialized_packet() -> DeserializedPacket { @@ -465,7 +464,6 @@ mod tests { #[test] fn test_transaction_from_deserialized_packet() { - use solana_sdk::feature_set::FeatureSet; let keypair = Keypair::new(); let transfer_tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default()); @@ -488,7 +486,6 @@ mod tests { let mut votes_only = false; let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( - &Arc::new(FeatureSet::default()), votes_only, SimpleAddressLoader::Disabled, &ReservedAccountKeys::empty_key_set(), @@ -499,7 +496,6 @@ mod tests { votes_only = true; let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( - &Arc::new(FeatureSet::default()), votes_only, SimpleAddressLoader::Disabled, &ReservedAccountKeys::empty_key_set(), @@ -519,7 +515,6 @@ mod tests { let mut votes_only = false; let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( - &Arc::new(FeatureSet::default()), votes_only, SimpleAddressLoader::Disabled, &ReservedAccountKeys::empty_key_set(), @@ -530,7 +525,6 @@ mod tests { votes_only = true; let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( - &Arc::new(FeatureSet::default()), votes_only, SimpleAddressLoader::Disabled, &ReservedAccountKeys::empty_key_set(), @@ -550,7 +544,6 @@ mod tests { let mut votes_only = false; let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( - &Arc::new(FeatureSet::default()), votes_only, SimpleAddressLoader::Disabled, &ReservedAccountKeys::empty_key_set(), @@ -561,7 +554,6 @@ mod tests { votes_only = true; let txs = packet_vector.iter().filter_map(|tx| { tx.immutable_section().build_sanitized_transaction( - &Arc::new(FeatureSet::default()), votes_only, SimpleAddressLoader::Disabled, &ReservedAccountKeys::empty_key_set(), diff --git a/core/src/banking_stage/unprocessed_transaction_storage.rs b/core/src/banking_stage/unprocessed_transaction_storage.rs index dd429cdc03e9f0..bc2dec2a82c7c1 100644 --- a/core/src/banking_stage/unprocessed_transaction_storage.rs +++ b/core/src/banking_stage/unprocessed_transaction_storage.rs @@ -155,7 +155,6 @@ fn consume_scan_should_process_packet( // Try to sanitize the packet let (maybe_sanitized_transaction, sanitization_time_us) = measure_us!(packet .build_sanitized_transaction( - &bank.feature_set, bank.vote_only_bank(), bank, bank.get_reserved_account_keys(), @@ -775,7 +774,6 @@ impl ThreadLocalUnprocessedPackets { .filter_map(|(packet_index, deserialized_packet)| { deserialized_packet .build_sanitized_transaction( - &bank.feature_set, bank.vote_only_bank(), bank, bank.get_reserved_account_keys(), diff --git a/core/src/commitment_service.rs b/core/src/commitment_service.rs index e7e349d05c57a4..cae40c587cb572 100644 --- a/core/src/commitment_service.rs +++ b/core/src/commitment_service.rs @@ -8,7 +8,7 @@ use { bank::Bank, commitment::{BlockCommitment, BlockCommitmentCache, CommitmentSlots, VOTE_THRESHOLD_SIZE}, }, - solana_sdk::clock::Slot, + solana_sdk::{clock::Slot, pubkey::Pubkey}, solana_vote_program::vote_state::VoteState, std::{ cmp::max, @@ -26,14 +26,23 @@ pub struct CommitmentAggregationData { bank: Arc, root: Slot, total_stake: Stake, + // The latest local vote state of the node running this service. + // Used for commitment aggregation if the node's vote account is staked. + node_vote_state: (Pubkey, VoteState), } impl CommitmentAggregationData { - pub fn new(bank: Arc, root: Slot, total_stake: Stake) -> Self { + pub fn new( + bank: Arc, + root: Slot, + total_stake: Stake, + node_vote_state: (Pubkey, VoteState), + ) -> Self { Self { bank, root, total_stake, + node_vote_state, } } } @@ -139,8 +148,11 @@ impl AggregateCommitmentService { aggregation_data: CommitmentAggregationData, ancestors: Vec, ) -> CommitmentSlots { - let (block_commitment, rooted_stake) = - Self::aggregate_commitment(&ancestors, &aggregation_data.bank); + let (block_commitment, rooted_stake) = Self::aggregate_commitment( + &ancestors, + &aggregation_data.bank, + &aggregation_data.node_vote_state, + ); let highest_super_majority_root = get_highest_super_majority_root(rooted_stake, aggregation_data.total_stake); @@ -173,6 +185,7 @@ impl AggregateCommitmentService { pub fn aggregate_commitment( ancestors: &[Slot], bank: &Bank, + (node_vote_pubkey, node_vote_state): &(Pubkey, VoteState), ) -> (HashMap, Vec<(Slot, u64)>) { assert!(!ancestors.is_empty()); @@ -183,11 +196,17 @@ impl AggregateCommitmentService { let mut commitment = HashMap::new(); let mut rooted_stake: Vec<(Slot, u64)> = Vec::new(); - for (lamports, account) in bank.vote_accounts().values() { + for (pubkey, (lamports, account)) in bank.vote_accounts().iter() { if *lamports == 0 { continue; } - if let Ok(vote_state) = account.vote_state().as_ref() { + let vote_state = if pubkey == node_vote_pubkey { + // Override old vote_state in bank with latest one for my own vote pubkey + Ok(node_vote_state) + } else { + account.vote_state() + }; + if let Ok(vote_state) = vote_state { Self::aggregate_commitment_for_vote_account( &mut commitment, &mut rooted_stake, @@ -382,8 +401,7 @@ mod tests { assert_eq!(rooted_stake[0], (root, lamports)); } - #[test] - fn test_aggregate_commitment_validity() { + fn do_test_aggregate_commitment_validity(with_node_vote_state: bool) { let ancestors = vec![3, 4, 5, 7, 9, 10, 11]; let GenesisConfigInfo { mut genesis_config, .. @@ -447,9 +465,11 @@ mod tests { let mut vote_state1 = vote_state::from(&vote_account1).unwrap(); process_slot_vote_unchecked(&mut vote_state1, 3); process_slot_vote_unchecked(&mut vote_state1, 5); - let versioned = VoteStateVersions::new_current(vote_state1); - vote_state::to(&versioned, &mut vote_account1).unwrap(); - bank.store_account(&pk1, &vote_account1); + if !with_node_vote_state { + let versioned = VoteStateVersions::new_current(vote_state1.clone()); + vote_state::to(&versioned, &mut vote_account1).unwrap(); + bank.store_account(&pk1, &vote_account1); + } let mut vote_state2 = vote_state::from(&vote_account2).unwrap(); process_slot_vote_unchecked(&mut vote_state2, 9); @@ -470,8 +490,18 @@ mod tests { vote_state::to(&versioned, &mut vote_account4).unwrap(); bank.store_account(&pk4, &vote_account4); - let (commitment, rooted_stake) = - AggregateCommitmentService::aggregate_commitment(&ancestors, &bank); + let node_vote_pubkey = if with_node_vote_state { + pk1 + } else { + // Use some random pubkey as dummy to suppress the override. + solana_sdk::pubkey::new_rand() + }; + + let (commitment, rooted_stake) = AggregateCommitmentService::aggregate_commitment( + &ancestors, + &bank, + &(node_vote_pubkey, vote_state1), + ); for a in ancestors { if a <= 3 { @@ -499,17 +529,21 @@ mod tests { assert_eq!(get_highest_super_majority_root(rooted_stake, 100), 1) } + #[test] + fn test_aggregate_commitment_validity_with_node_vote_state() { + do_test_aggregate_commitment_validity(true) + } + + #[test] + fn test_aggregate_commitment_validity_without_node_vote_state() { + do_test_aggregate_commitment_validity(false); + } + #[test] fn test_highest_super_majority_root_advance() { - fn get_vote_account_root_slot(vote_pubkey: Pubkey, bank: &Bank) -> Slot { + fn get_vote_state(vote_pubkey: Pubkey, bank: &Bank) -> VoteState { let vote_account = bank.get_vote_account(&vote_pubkey).unwrap(); - let slot = vote_account - .vote_state() - .as_ref() - .unwrap() - .root_slot - .unwrap(); - slot + vote_account.vote_state().cloned().unwrap() } let block_commitment_cache = RwLock::new(BlockCommitmentCache::new_for_tests()); @@ -547,10 +581,10 @@ mod tests { } let working_bank = bank_forks.read().unwrap().working_bank(); - let root = get_vote_account_root_slot( - validator_vote_keypairs.vote_keypair.pubkey(), - &working_bank, - ); + let vote_pubkey = validator_vote_keypairs.vote_keypair.pubkey(); + let root = get_vote_state(vote_pubkey, &working_bank) + .root_slot + .unwrap(); for x in 0..root { bank_forks .write() @@ -579,10 +613,8 @@ mod tests { bank34.process_transaction(&vote33).unwrap(); let working_bank = bank_forks.read().unwrap().working_bank(); - let root = get_vote_account_root_slot( - validator_vote_keypairs.vote_keypair.pubkey(), - &working_bank, - ); + let vote_state = get_vote_state(vote_pubkey, &working_bank); + let root = vote_state.root_slot.unwrap(); let ancestors = working_bank.status_cache_ancestors(); let _ = AggregateCommitmentService::update_commitment_cache( &block_commitment_cache, @@ -590,6 +622,7 @@ mod tests { bank: working_bank, root: 0, total_stake: 100, + node_vote_state: (vote_pubkey, vote_state.clone()), }, ancestors, ); @@ -628,6 +661,7 @@ mod tests { bank: working_bank, root: 1, total_stake: 100, + node_vote_state: (vote_pubkey, vote_state), }, ancestors, ); @@ -662,10 +696,9 @@ mod tests { } let working_bank = bank_forks.read().unwrap().working_bank(); - let root = get_vote_account_root_slot( - validator_vote_keypairs.vote_keypair.pubkey(), - &working_bank, - ); + let vote_state = + get_vote_state(validator_vote_keypairs.vote_keypair.pubkey(), &working_bank); + let root = vote_state.root_slot.unwrap(); let ancestors = working_bank.status_cache_ancestors(); let _ = AggregateCommitmentService::update_commitment_cache( &block_commitment_cache, @@ -673,6 +706,7 @@ mod tests { bank: working_bank, root: 0, total_stake: 100, + node_vote_state: (vote_pubkey, vote_state), }, ancestors, ); diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index 4861b7893e5554..627e0175c89e71 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -76,7 +76,7 @@ use { timing::timestamp, transaction::Transaction, }, - solana_vote_program::vote_state::VoteTransaction, + solana_vote_program::vote_state::{VoteState, VoteTransaction}, std::{ collections::{HashMap, HashSet}, num::NonZeroUsize, @@ -2406,10 +2406,28 @@ impl ReplayStage { } let mut update_commitment_cache_time = Measure::start("update_commitment_cache"); + // Send (voted) bank along with the updated vote account state for this node, the vote + // state is always newer than the one in the bank by definition, because banks can't + // contain vote transactions which are voting on its own slot. + // + // It should be acceptable to aggressively use the vote for our own _local view_ of + // commitment aggregation, although it's not guaranteed that the new vote transaction is + // observed by other nodes at this point. + // + // The justification stems from the assumption of the sensible voting behavior from the + // consensus subsystem. That's because it means there would be a slashing possibility + // otherwise. + // + // This behavior isn't significant normally for mainnet-beta, because staked nodes aren't + // servicing RPC requests. However, this eliminates artificial 1-slot delay of the + // `finalized` confirmation if a node is materially staked and servicing RPC requests at + // the same time for development purposes. + let node_vote_state = (*vote_account_pubkey, tower.vote_state.clone()); Self::update_commitment_cache( bank.clone(), bank_forks.read().unwrap().root(), progress.get_fork_stats(bank.slot()).unwrap().total_stake, + node_vote_state, lockouts_sender, ); update_commitment_cache_time.stop(); @@ -2699,11 +2717,15 @@ impl ReplayStage { bank: Arc, root: Slot, total_stake: Stake, + node_vote_state: (Pubkey, VoteState), lockouts_sender: &Sender, ) { - if let Err(e) = - lockouts_sender.send(CommitmentAggregationData::new(bank, root, total_stake)) - { + if let Err(e) = lockouts_sender.send(CommitmentAggregationData::new( + bank, + root, + total_stake, + node_vote_state, + )) { trace!("lockouts_sender failed: {:?}", e); } } @@ -3009,10 +3031,14 @@ impl ReplayStage { .expect("Bank fork progress entry missing for completed bank"); let replay_stats = bank_progress.replay_stats.clone(); + let mut is_unified_scheduler_enabled = false; if let Some((result, completed_execute_timings)) = bank.wait_for_completed_scheduler() { + // It's guaranteed that wait_for_completed_scheduler() returns Some(_), iff the + // unified scheduler is enabled for the bank. + is_unified_scheduler_enabled = true; let metrics = ExecuteBatchesInternalMetrics::new_with_timings_from_all_threads( completed_execute_timings, ); @@ -3020,7 +3046,7 @@ impl ReplayStage { .write() .unwrap() .batch_execute - .accumulate(metrics); + .accumulate(metrics, is_unified_scheduler_enabled); if let Err(err) = result { let root = bank_forks.read().unwrap().root(); @@ -3219,6 +3245,7 @@ impl ReplayStage { r_replay_progress.num_entries, r_replay_progress.num_shreds, bank_complete_time.as_us(), + is_unified_scheduler_enabled, ); execute_timings.accumulate(&r_replay_stats.batch_execute.totals); } else { @@ -4393,10 +4420,10 @@ impl ReplayStage { fn record_rewards(bank: &Bank, rewards_recorder_sender: &Option) { if let Some(rewards_recorder_sender) = rewards_recorder_sender { - let rewards = bank.rewards.read().unwrap(); - if !rewards.is_empty() { + let rewards = bank.get_rewards_and_num_partitions(); + if rewards.should_record() { rewards_recorder_sender - .send(RewardsMessage::Batch((bank.slot(), rewards.clone()))) + .send(RewardsMessage::Batch((bank.slot(), rewards))) .unwrap_or_else(|err| warn!("rewards_recorder_sender failed: {:?}", err)); } rewards_recorder_sender @@ -5276,13 +5303,14 @@ pub(crate) mod tests { #[test] fn test_replay_commitment_cache() { - fn leader_vote(vote_slot: Slot, bank: &Bank, pubkey: &Pubkey) { + fn leader_vote(vote_slot: Slot, bank: &Bank, pubkey: &Pubkey) -> (Pubkey, VoteState) { let mut leader_vote_account = bank.get_account(pubkey).unwrap(); let mut vote_state = vote_state::from(&leader_vote_account).unwrap(); vote_state::process_slot_vote_unchecked(&mut vote_state, vote_slot); - let versioned = VoteStateVersions::new_current(vote_state); + let versioned = VoteStateVersions::new_current(vote_state.clone()); vote_state::to(&versioned, &mut leader_vote_account).unwrap(); bank.store_account(pubkey, &leader_vote_account); + (*pubkey, vote_state) } let leader_pubkey = solana_sdk::pubkey::new_rand(); @@ -5348,11 +5376,12 @@ pub(crate) mod tests { } let arc_bank = bank_forks.read().unwrap().get(i).unwrap(); - leader_vote(i - 1, &arc_bank, &leader_voting_pubkey); + let node_vote_state = leader_vote(i - 1, &arc_bank, &leader_voting_pubkey); ReplayStage::update_commitment_cache( arc_bank.clone(), 0, leader_lamports, + node_vote_state, &lockouts_sender, ); arc_bank.freeze(); diff --git a/core/src/rewards_recorder_service.rs b/core/src/rewards_recorder_service.rs index 3fc2c8dc5b5149..044fd2de53adc7 100644 --- a/core/src/rewards_recorder_service.rs +++ b/core/src/rewards_recorder_service.rs @@ -1,8 +1,9 @@ use { crossbeam_channel::{Receiver, RecvTimeoutError, Sender}, solana_ledger::blockstore::Blockstore, - solana_sdk::{clock::Slot, pubkey::Pubkey, reward_info::RewardInfo}, - solana_transaction_status::Reward, + solana_runtime::bank::KeyedRewardsAndNumPartitions, + solana_sdk::clock::Slot, + solana_transaction_status::{Reward, RewardsAndNumPartitions}, std::{ sync::{ atomic::{AtomicBool, AtomicU64, Ordering}, @@ -13,7 +14,7 @@ use { }, }; -pub type RewardsBatch = (Slot, Vec<(Pubkey, RewardInfo)>); +pub type RewardsBatch = (Slot, KeyedRewardsAndNumPartitions); pub type RewardsRecorderReceiver = Receiver; pub type RewardsRecorderSender = Sender; @@ -55,7 +56,13 @@ impl RewardsRecorderService { blockstore: &Blockstore, ) -> Result<(), RecvTimeoutError> { match rewards_receiver.recv_timeout(Duration::from_secs(1))? { - RewardsMessage::Batch((slot, rewards)) => { + RewardsMessage::Batch(( + slot, + KeyedRewardsAndNumPartitions { + keyed_rewards: rewards, + num_partitions, + }, + )) => { let rpc_rewards = rewards .into_iter() .map(|(pubkey, reward_info)| Reward { @@ -68,7 +75,13 @@ impl RewardsRecorderService { .collect(); blockstore - .write_rewards(slot, rpc_rewards) + .write_rewards( + slot, + RewardsAndNumPartitions { + rewards: rpc_rewards, + num_partitions, + }, + ) .expect("Expect database write to succeed"); } RewardsMessage::Complete(slot) => { diff --git a/core/src/tvu.rs b/core/src/tvu.rs index 6ba61f578b5671..4dcd7bbfa3e589 100644 --- a/core/src/tvu.rs +++ b/core/src/tvu.rs @@ -352,6 +352,7 @@ impl Tvu { leader_schedule_cache.clone(), bank_forks.clone(), duplicate_slots_sender, + tvu_config.shred_version, ), ); diff --git a/core/src/warm_quic_cache_service.rs b/core/src/warm_quic_cache_service.rs index b7f196661ba150..e4a67cbe993169 100644 --- a/core/src/warm_quic_cache_service.rs +++ b/core/src/warm_quic_cache_service.rs @@ -3,10 +3,8 @@ use { rand::{thread_rng, Rng}, - solana_client::{ - connection_cache::{ConnectionCache, Protocol}, - tpu_connection::TpuConnection, - }, + solana_client::connection_cache::{ConnectionCache, Protocol}, + solana_connection_cache::client_connection::ClientConnection as TpuConnection, solana_gossip::cluster_info::ClusterInfo, solana_poh::poh_recorder::PohRecorder, std::{ diff --git a/cost-model/src/cost_model.rs b/cost-model/src/cost_model.rs index ba40811d033a31..c444ad0885566f 100644 --- a/cost-model/src/cost_model.rs +++ b/cost-model/src/cost_model.rs @@ -15,7 +15,7 @@ use { solana_sdk::{ borsh1::try_from_slice_unchecked, compute_budget::{self, ComputeBudgetInstruction}, - feature_set::{self, include_loaded_accounts_data_size_in_fee_calculation, FeatureSet}, + feature_set::{self, FeatureSet}, fee::FeeStructure, instruction::CompiledInstruction, program_utils::limited_deserialize, @@ -43,13 +43,45 @@ impl CostModel { Self::get_signature_cost(&mut tx_cost, transaction); Self::get_write_lock_cost(&mut tx_cost, transaction, feature_set); Self::get_transaction_cost(&mut tx_cost, transaction, feature_set); - tx_cost.account_data_size = Self::calculate_account_data_size(transaction); + tx_cost.allocated_accounts_data_size = + Self::calculate_allocated_accounts_data_size(transaction); debug!("transaction {:?} has cost {:?}", transaction, tx_cost); TransactionCost::Transaction(tx_cost) } } + // Calculate executed transaction CU cost, with actual execution and loaded accounts size + // costs. + pub fn calculate_cost_for_executed_transaction( + transaction: &SanitizedTransaction, + actual_programs_execution_cost: u64, + actual_loaded_accounts_data_size_bytes: usize, + feature_set: &FeatureSet, + ) -> TransactionCost { + if transaction.is_simple_vote_transaction() { + TransactionCost::SimpleVote { + writable_accounts: Self::get_writable_accounts(transaction), + } + } else { + let mut tx_cost = UsageCostDetails::new_with_default_capacity(); + + Self::get_signature_cost(&mut tx_cost, transaction); + Self::get_write_lock_cost(&mut tx_cost, transaction, feature_set); + Self::get_instructions_data_cost(&mut tx_cost, transaction); + tx_cost.allocated_accounts_data_size = + Self::calculate_allocated_accounts_data_size(transaction); + + tx_cost.programs_execution_cost = actual_programs_execution_cost; + tx_cost.loaded_accounts_data_size_cost = Self::calculate_loaded_accounts_data_size_cost( + actual_loaded_accounts_data_size_bytes, + feature_set, + ); + + TransactionCost::Transaction(tx_cost) + } + } + fn get_signature_cost(tx_cost: &mut UsageCostDetails, transaction: &SanitizedTransaction) { let signatures_count_detail = transaction.message().get_signature_details(); tx_cost.num_transaction_signatures = signatures_count_detail.num_transaction_signatures(); @@ -168,15 +200,25 @@ impl CostModel { tx_cost.data_bytes_cost = data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST; } + fn get_instructions_data_cost( + tx_cost: &mut UsageCostDetails, + transaction: &SanitizedTransaction, + ) { + let ix_data_bytes_len_total: u64 = transaction + .message() + .instructions() + .iter() + .map(|instruction| instruction.data.len() as u64) + .sum(); + + tx_cost.data_bytes_cost = ix_data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST; + } + pub fn calculate_loaded_accounts_data_size_cost( loaded_accounts_data_size: usize, - feature_set: &FeatureSet, + _feature_set: &FeatureSet, ) -> u64 { - if feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()) { - FeeStructure::calculate_memory_usage_cost(loaded_accounts_data_size, DEFAULT_HEAP_COST) - } else { - 0 - } + FeeStructure::calculate_memory_usage_cost(loaded_accounts_data_size, DEFAULT_HEAP_COST) } fn calculate_account_data_size_on_deserialized_system_instruction( @@ -222,7 +264,7 @@ impl CostModel { /// eventually, potentially determine account data size of all writable accounts /// at the moment, calculate account data size of account creation - fn calculate_account_data_size(transaction: &SanitizedTransaction) -> u64 { + fn calculate_allocated_accounts_data_size(transaction: &SanitizedTransaction) -> u64 { transaction .message() .program_instructions_iter() @@ -571,8 +613,6 @@ mod tests { let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS .get(&system_program::id()) .unwrap(); - // feature `include_loaded_accounts_data_size_in_fee_calculation` enabled, using - // default loaded_accounts_data_size_limit const DEFAULT_PAGE_COST: u64 = 8; let expected_loaded_accounts_data_size_cost = solana_compute_budget::compute_budget_processor::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES @@ -590,35 +630,6 @@ mod tests { ); } - #[test] - fn test_cost_model_calculate_cost_disabled_feature() { - let (mint_keypair, start_hash) = test_setup(); - let tx = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( - &mint_keypair, - &Keypair::new().pubkey(), - 2, - start_hash, - )); - - let feature_set = FeatureSet::default(); - assert!(!feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id())); - let expected_account_cost = WRITE_LOCK_UNITS * 2; - let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS - .get(&system_program::id()) - .unwrap(); - // feature `include_loaded_accounts_data_size_in_fee_calculation` not enabled - let expected_loaded_accounts_data_size_cost = 0; - - let tx_cost = CostModel::calculate_cost(&tx, &feature_set); - assert_eq!(expected_account_cost, tx_cost.write_lock_cost()); - assert_eq!(*expected_execution_cost, tx_cost.programs_execution_cost()); - assert_eq!(2, tx_cost.writable_accounts().len()); - assert_eq!( - expected_loaded_accounts_data_size_cost, - tx_cost.loaded_accounts_data_size_cost() - ); - } - #[test] fn test_cost_model_calculate_cost_with_limit() { let (mint_keypair, start_hash) = test_setup(); @@ -636,7 +647,6 @@ mod tests { )); let feature_set = FeatureSet::all_enabled(); - assert!(feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id())); let expected_account_cost = WRITE_LOCK_UNITS * 2; let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS .get(&system_program::id()) @@ -644,8 +654,6 @@ mod tests { + BUILT_IN_INSTRUCTION_COSTS .get(&compute_budget::id()) .unwrap(); - // feature `include_loaded_accounts_data_size_in_fee_calculation` is enabled, accounts data - // size limit is set. let expected_loaded_accounts_data_size_cost = (data_limit as u64) / (32 * 1024) * 8; let tx_cost = CostModel::calculate_cost(&tx, &feature_set); diff --git a/cost-model/src/cost_tracker.rs b/cost-model/src/cost_tracker.rs index f891cb22a5697b..49ccbbb035f9bb 100644 --- a/cost-model/src/cost_tracker.rs +++ b/cost-model/src/cost_tracker.rs @@ -67,7 +67,7 @@ pub struct CostTracker { block_cost: u64, vote_cost: u64, transaction_count: u64, - account_data_size: u64, + allocated_accounts_data_size: u64, transaction_signature_count: u64, secp256k1_instruction_signature_count: u64, ed25519_instruction_signature_count: u64, @@ -96,7 +96,7 @@ impl Default for CostTracker { block_cost: 0, vote_cost: 0, transaction_count: 0, - account_data_size: 0, + allocated_accounts_data_size: 0, transaction_signature_count: 0, secp256k1_instruction_signature_count: 0, ed25519_instruction_signature_count: 0, @@ -111,7 +111,7 @@ impl CostTracker { self.block_cost = 0; self.vote_cost = 0; self.transaction_count = 0; - self.account_data_size = 0; + self.allocated_accounts_data_size = 0; self.transaction_signature_count = 0; self.secp256k1_instruction_signature_count = 0; self.ed25519_instruction_signature_count = 0; @@ -213,7 +213,11 @@ impl CostTracker { // ("number_of_accounts", self.number_of_accounts() as i64, i64), // ("costliest_account", costliest_account.to_string(), String), // ("costliest_account_cost", costliest_account_cost as i64, i64), - // ("account_data_size", self.account_data_size, i64), + // ( + // "allocated_accounts_data_size", + // self.allocated_accounts_data_size, + // i64 + // ), // ( // "transaction_signature_count", // self.transaction_signature_count, @@ -265,11 +269,11 @@ impl CostTracker { return Err(CostTrackerError::WouldExceedAccountMaxLimit); } - let account_data_size = self - .account_data_size - .saturating_add(tx_cost.account_data_size()); + let allocated_accounts_data_size = self + .allocated_accounts_data_size + .saturating_add(tx_cost.allocated_accounts_data_size()); - if account_data_size > MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA { + if allocated_accounts_data_size > MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA { return Err(CostTrackerError::WouldExceedAccountDataBlockLimit); } @@ -292,7 +296,10 @@ impl CostTracker { // Returns the highest account cost for all write-lock accounts `TransactionCost` updated fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) -> u64 { - saturating_add_assign!(self.account_data_size, tx_cost.account_data_size()); + saturating_add_assign!( + self.allocated_accounts_data_size, + tx_cost.allocated_accounts_data_size() + ); saturating_add_assign!(self.transaction_count, 1); saturating_add_assign!( self.transaction_signature_count, @@ -312,9 +319,9 @@ impl CostTracker { fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost) { let cost = tx_cost.sum(); self.sub_transaction_execution_cost(tx_cost, cost); - self.account_data_size = self - .account_data_size - .saturating_sub(tx_cost.account_data_size()); + self.allocated_accounts_data_size = self + .allocated_accounts_data_size + .saturating_sub(tx_cost.allocated_accounts_data_size()); self.transaction_count = self.transaction_count.saturating_sub(1); self.transaction_signature_count = self .transaction_signature_count @@ -504,7 +511,7 @@ mod tests { let (mint_keypair, start_hash) = test_setup(); let (_tx, mut tx_cost) = build_simple_transaction(&mint_keypair, &start_hash); if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost { - usage_cost.account_data_size = 1; + usage_cost.allocated_accounts_data_size = 1; } else { unreachable!(); } @@ -513,9 +520,9 @@ mod tests { // build testee to have capacity for one simple transaction let mut testee = CostTracker::new(cost, cost, cost); assert!(testee.would_fit(&tx_cost).is_ok()); - let old = testee.account_data_size; + let old = testee.allocated_accounts_data_size; testee.add_transaction_cost(&tx_cost); - assert_eq!(old + 1, testee.account_data_size); + assert_eq!(old + 1, testee.allocated_accounts_data_size); } #[test] @@ -652,12 +659,12 @@ mod tests { let (_tx1, mut tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); let (_tx2, mut tx_cost2) = build_simple_transaction(&second_account, &start_hash); if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost1 { - usage_cost.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA; + usage_cost.allocated_accounts_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA; } else { unreachable!(); } if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost2 { - usage_cost.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA + 1; + usage_cost.allocated_accounts_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA + 1; } else { unreachable!(); } @@ -945,7 +952,7 @@ mod tests { assert_eq!(1, cost_tracker.number_of_accounts()); assert_eq!(cost, cost_tracker.block_cost); assert_eq!(0, cost_tracker.vote_cost); - assert_eq!(0, cost_tracker.account_data_size); + assert_eq!(0, cost_tracker.allocated_accounts_data_size); cost_tracker.remove_transaction_cost(&tx_cost); // assert cost_tracker is reverted to default @@ -953,6 +960,6 @@ mod tests { assert_eq!(0, cost_tracker.number_of_accounts()); assert_eq!(0, cost_tracker.block_cost); assert_eq!(0, cost_tracker.vote_cost); - assert_eq!(0, cost_tracker.account_data_size); + assert_eq!(0, cost_tracker.allocated_accounts_data_size); } } diff --git a/cost-model/src/transaction_cost.rs b/cost-model/src/transaction_cost.rs index ee280c873312f9..4951e50036ca8b 100644 --- a/cost-model/src/transaction_cost.rs +++ b/cost-model/src/transaction_cost.rs @@ -56,10 +56,10 @@ impl TransactionCost { } } - pub fn account_data_size(&self) -> u64 { + pub fn allocated_accounts_data_size(&self) -> u64 { match self { Self::SimpleVote { .. } => 0, - Self::Transaction(usage_cost) => usage_cost.account_data_size, + Self::Transaction(usage_cost) => usage_cost.allocated_accounts_data_size, } } @@ -125,7 +125,7 @@ pub struct UsageCostDetails { pub data_bytes_cost: u64, pub programs_execution_cost: u64, pub loaded_accounts_data_size_cost: u64, - pub account_data_size: u64, + pub allocated_accounts_data_size: u64, pub num_transaction_signatures: u64, pub num_secp256k1_instruction_signatures: u64, pub num_ed25519_instruction_signatures: u64, @@ -140,7 +140,7 @@ impl Default for UsageCostDetails { data_bytes_cost: 0u64, programs_execution_cost: 0u64, loaded_accounts_data_size_cost: 0u64, - account_data_size: 0u64, + allocated_accounts_data_size: 0u64, num_transaction_signatures: 0u64, num_secp256k1_instruction_signatures: 0u64, num_ed25519_instruction_signatures: 0u64, @@ -160,7 +160,7 @@ impl PartialEq for UsageCostDetails { && self.data_bytes_cost == other.data_bytes_cost && self.programs_execution_cost == other.programs_execution_cost && self.loaded_accounts_data_size_cost == other.loaded_accounts_data_size_cost - && self.account_data_size == other.account_data_size + && self.allocated_accounts_data_size == other.allocated_accounts_data_size && self.num_transaction_signatures == other.num_transaction_signatures && self.num_secp256k1_instruction_signatures == other.num_secp256k1_instruction_signatures diff --git a/curves/curve25519/.gitignore b/curves/curve25519/.gitignore new file mode 100644 index 00000000000000..b645148aa9118c --- /dev/null +++ b/curves/curve25519/.gitignore @@ -0,0 +1 @@ +/farf/ diff --git a/curves/curve25519/Cargo.toml b/curves/curve25519/Cargo.toml new file mode 100644 index 00000000000000..fb04c29b60171b --- /dev/null +++ b/curves/curve25519/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solana-curve25519" +description = "Solana Curve25519 Syscalls" +documentation = "https://docs.rs/solana-curve25519" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +bytemuck = { workspace = true } +bytemuck_derive = { workspace = true } +solana-program = { workspace = true } +thiserror = { workspace = true } + +[target.'cfg(not(target_os = "solana"))'.dependencies] +curve25519-dalek = { workspace = true, features = ["serde"] } diff --git a/zk-token-sdk/src/curve25519/curve_syscall_traits.rs b/curves/curve25519/src/curve_syscall_traits.rs similarity index 100% rename from zk-token-sdk/src/curve25519/curve_syscall_traits.rs rename to curves/curve25519/src/curve_syscall_traits.rs diff --git a/zk-token-sdk/src/curve25519/edwards.rs b/curves/curve25519/src/edwards.rs similarity index 98% rename from zk-token-sdk/src/curve25519/edwards.rs rename to curves/curve25519/src/edwards.rs index 0dd019b1910d0a..4de6bf81456601 100644 --- a/zk-token-sdk/src/curve25519/edwards.rs +++ b/curves/curve25519/src/edwards.rs @@ -1,4 +1,4 @@ -use bytemuck::{Pod, Zeroable}; +use bytemuck_derive::{Pod, Zeroable}; pub use target_arch::*; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] @@ -9,7 +9,7 @@ pub struct PodEdwardsPoint(pub [u8; 32]); mod target_arch { use { super::*, - crate::curve25519::{ + crate::{ curve_syscall_traits::{GroupOperations, MultiScalarMultiplication, PointValidation}, errors::Curve25519Error, scalar::PodScalar, @@ -134,10 +134,11 @@ mod target_arch { mod target_arch { use { super::*, - crate::curve25519::{ + crate::{ curve_syscall_traits::{ADD, CURVE25519_EDWARDS, MUL, SUB}, scalar::PodScalar, }, + bytemuck::Zeroable, }; pub fn validate_edwards(point: &PodEdwardsPoint) -> bool { @@ -245,7 +246,7 @@ mod target_arch { mod tests { use { super::*, - crate::curve25519::scalar::PodScalar, + crate::scalar::PodScalar, curve25519_dalek::{ constants::ED25519_BASEPOINT_POINT as G, edwards::EdwardsPoint, traits::Identity, }, diff --git a/zk-token-sdk/src/curve25519/errors.rs b/curves/curve25519/src/errors.rs similarity index 100% rename from zk-token-sdk/src/curve25519/errors.rs rename to curves/curve25519/src/errors.rs diff --git a/curves/curve25519/src/lib.rs b/curves/curve25519/src/lib.rs new file mode 100644 index 00000000000000..d0ab9d4709da11 --- /dev/null +++ b/curves/curve25519/src/lib.rs @@ -0,0 +1,8 @@ +#![allow(clippy::arithmetic_side_effects, clippy::op_ref)] +//! Syscall operations for curve25519 + +pub mod curve_syscall_traits; +pub mod edwards; +pub mod errors; +pub mod ristretto; +pub mod scalar; diff --git a/zk-token-sdk/src/curve25519/ristretto.rs b/curves/curve25519/src/ristretto.rs similarity index 98% rename from zk-token-sdk/src/curve25519/ristretto.rs rename to curves/curve25519/src/ristretto.rs index 772441a32aa65f..e0b47c15f1dfbe 100644 --- a/zk-token-sdk/src/curve25519/ristretto.rs +++ b/curves/curve25519/src/ristretto.rs @@ -1,4 +1,4 @@ -use bytemuck::{Pod, Zeroable}; +use bytemuck_derive::{Pod, Zeroable}; pub use target_arch::*; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] @@ -9,7 +9,7 @@ pub struct PodRistrettoPoint(pub [u8; 32]); mod target_arch { use { super::*, - crate::curve25519::{ + crate::{ curve_syscall_traits::{GroupOperations, MultiScalarMultiplication, PointValidation}, errors::Curve25519Error, scalar::PodScalar, @@ -135,10 +135,11 @@ mod target_arch { mod target_arch { use { super::*, - crate::curve25519::{ + crate::{ curve_syscall_traits::{ADD, CURVE25519_RISTRETTO, MUL, SUB}, scalar::PodScalar, }, + bytemuck::Zeroable, }; pub fn validate_ristretto(point: &PodRistrettoPoint) -> bool { @@ -247,7 +248,7 @@ mod target_arch { mod tests { use { super::*, - crate::curve25519::scalar::PodScalar, + crate::scalar::PodScalar, curve25519_dalek::{ constants::RISTRETTO_BASEPOINT_POINT as G, ristretto::RistrettoPoint, traits::Identity, }, diff --git a/zk-token-sdk/src/curve25519/scalar.rs b/curves/curve25519/src/scalar.rs similarity index 52% rename from zk-token-sdk/src/curve25519/scalar.rs rename to curves/curve25519/src/scalar.rs index e154851902a043..f840a27c1b4980 100644 --- a/zk-token-sdk/src/curve25519/scalar.rs +++ b/curves/curve25519/src/scalar.rs @@ -1,4 +1,4 @@ -pub use bytemuck::{Pod, Zeroable}; +pub use bytemuck_derive::{Pod, Zeroable}; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] #[repr(transparent)] @@ -6,7 +6,7 @@ pub struct PodScalar(pub [u8; 32]); #[cfg(not(target_os = "solana"))] mod target_arch { - use {super::*, crate::curve25519::errors::Curve25519Error, curve25519_dalek::scalar::Scalar}; + use {super::*, crate::errors::Curve25519Error, curve25519_dalek::scalar::Scalar}; impl From<&Scalar> for PodScalar { fn from(scalar: &Scalar) -> Self { @@ -21,4 +21,18 @@ mod target_arch { Scalar::from_canonical_bytes(pod.0).ok_or(Curve25519Error::PodConversion) } } + + impl From for PodScalar { + fn from(scalar: Scalar) -> Self { + Self(scalar.to_bytes()) + } + } + + impl TryFrom for Scalar { + type Error = Curve25519Error; + + fn try_from(pod: PodScalar) -> Result { + Scalar::from_canonical_bytes(pod.0).ok_or(Curve25519Error::PodConversion) + } + } } diff --git a/docs/src/cli/install.md b/docs/src/cli/install.md index fbcd894660ccd8..c9a5c682d40592 100644 --- a/docs/src/cli/install.md +++ b/docs/src/cli/install.md @@ -19,26 +19,26 @@ on your preferred workflow: - Open your favorite Terminal application -- Install the Solana release - [LATEST_SOLANA_RELEASE_VERSION](https://github.com/anza-xyz/agave/releases/tag/LATEST_SOLANA_RELEASE_VERSION) +- Install the Agave release + [LATEST_AGAVE_RELEASE_VERSION](https://github.com/anza-xyz/agave/releases/tag/LATEST_AGAVE_RELEASE_VERSION) on your machine by running: ```bash -sh -c "$(curl -sSfL https://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/install)" +sh -c "$(curl -sSfL https://release.anza.xyz/LATEST_AGAVE_RELEASE_VERSION/install)" ``` -- You can replace `LATEST_SOLANA_RELEASE_VERSION` with the release tag matching +- You can replace `LATEST_AGAVE_RELEASE_VERSION` with the release tag matching the software version of your desired release, or use one of the three symbolic channel names: `stable`, `beta`, or `edge`. - The following output indicates a successful update: ```text -downloading LATEST_SOLANA_RELEASE_VERSION installer +downloading LATEST_AGAVE_RELEASE_VERSION installer Configuration: /home/solana/.config/solana/install/config.yml Active release directory: /home/solana/.local/share/solana/install/active_release -* Release version: LATEST_SOLANA_RELEASE_VERSION -* Release URL: https://github.com/anza-xyz/agave/releases/download/LATEST_SOLANA_RELEASE_VERSION/solana-release-x86_64-unknown-linux-gnu.tar.bz2 +* Release version: LATEST_AGAVE_RELEASE_VERSION +* Release URL: https://github.com/anza-xyz/agave/releases/download/LATEST_AGAVE_RELEASE_VERSION/solana-release-x86_64-unknown-linux-gnu.tar.bz2 Update successful ``` @@ -74,7 +74,7 @@ solana --version installer into a temporary directory: ```bash -cmd /c "curl https://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/agave-install-init-x86_64-pc-windows-msvc.exe --output C:\agave-install-tmp\agave-install-init.exe --create-dirs" +cmd /c "curl https://release.anza.xyz/LATEST_AGAVE_RELEASE_VERSION/agave-install-init-x86_64-pc-windows-msvc.exe --output C:\agave-install-tmp\agave-install-init.exe --create-dirs" ``` - Copy and paste the following command, then press Enter to install the latest @@ -82,7 +82,7 @@ cmd /c "curl https://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/agave-inst to allow the program to run. ```bash -C:\agave-install-tmp\agave-install-init.exe LATEST_SOLANA_RELEASE_VERSION +C:\agave-install-tmp\agave-install-init.exe LATEST_AGAVE_RELEASE_VERSION ``` - When the installer is finished, press Enter. diff --git a/docs/src/runtime/zk-docs/ciphertext_validity.pdf b/docs/src/runtime/zk-docs/ciphertext_validity.pdf new file mode 100644 index 0000000000000000000000000000000000000000..41d9c3181124997d36b169346155550be2529ebd GIT binary patch literal 277600 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�m*=WG`uBfXi+YXq zz@-kTe++`TCZ%x|VXS12{MFoSX<>*K?s;E#`f|B;^67q_>JJZWh1Nq9|01Ydtv!n1 z>!q^4SkkJH)5U7JiuX$lAg0>c3)CmF1Xu{$rgEq!_akS_IoL3nCrgdy5mX3aKB7lG z2{%cGoPDR8SmVWus@e$*&-bVf;5H$8r$JK`B)ks)A-b2u6Py)vX$vnID+}~(I)z<>%rP6vrMatLuC9P$;;n^N~&M7ewN$D&l2ElovPa)@c3^-M?X5ax>{burjciVYDe4!FC#QjAha z?eJ2N1`iF9Q4PN|Ph6GWx|r_XIC>AfNN$K;#xvtOi0tBgW)*!FETrBZ|4MzF`)Au^ zdV}9byZt6i2M_1DOHnI?i?Q-nkLtxTI~Qg4l?hl-Rn8X2HihP|=qy)=P?ja(Qk5&& zyB#bF#`2!#MBvMrJ$=8ao7sbMt*YsfZ%`{|jXU(;O9>&&xL=!o;c4~gt&}8v# zC}L$ky7QaVBv8T@_l>7!f?i5z z2NZ9vL)+I$>G3UH$oYqbDw`(eXj0pA{TLw}{v73I9DVQpdP3j`3jjg}C9Fm8y1PjW&09RPH6n!66kEvJH!zg7-9+l+hsvH1Q1 zo+T%Ge@pyW@#PGqZK^oVh9744$3vAi8GZ^I%oMb+4ccfR;C)C^psO$70_!;{Jwn_a z^g5amUkbf}N~<|4Ele-6n25E4g6gLLUS$d*9rg5%P9Jc?r14L$ zM;ML0!QlWa3J*IMEx9rl_Hk=B@WbF$Pv0t(nj=NMjke`8vxDSsDO5S% zR()Z8RJ?voNXCw+6;C)lJUR!(A#u3h7r8agt1uh1R~k=%&vW-h`zF-0D)rpq?JEQ* z*9$p1&rC772zI_|<+eMyq49LUg!7H1Bzamzl7?HUM=PP|EyG^+wO7BO&hh2n*Lgbm z*kj*hP{#E=Ya;!w9&N}Z)N0h@GwTPKzHQL@S-J-qKFOeU=&gu^W!;&mj0jt1`4K^U z3sSS?I#c$Vw6rpVfjmMloQVHW*V>easGC&wvh>qLm0Gj9r%HdtE|}6@YX+k~Cl-f7 zN1aO^XXPCLkOgT7doGzMD!Ep4#^UcZ27_8lWI6!p@*g3OJY9&uHHGS z1rwSzeX~JUX{QYg!q+FMt_{YnjXAW}rzkjVjv<8Q=>k4ltVU1XEVG8p1P2+sg=a24 zt@&h~;k}V~Gi8L(InUN;@kL%!fV558<#~V&IaM|Pcw81OndOb;6;_YMT6f+{*J!uD zWI3xuMsC->KqI82&GDK9=B#I`A|VnTIJ1iv7>IwcACFF@=)WZ~ED_efx zL^8~S$g+0Jy)%}R&*yfJ?9Wqqraea00+f_h`-pCA_zRG(jjIIy-2vt z=54H`X7G$Ib$4Q0PU7?gNNthuJsm7Bo%6jZ4c*Tb2}tON ze#5u-N9Z$i;IkL%HKSf{hW^M^@HC|4p_E>2BY@CHE8rgiZ@-|4nVVX^*P^+p1{)1L z1Tn6r9apI@Ujwn)2D?w)y0<6xm+g7~F&Yd&~N9UZxk~ z(>aEva^Iz0!^s$N*Ie%N!fpdq0pLjs|4247&9ol#mPi?kc|z++)6@NkLrP_mQP1jp zZ5tg+VxDe%K*lCD1H8UJk@B?GQHb&5?gHK2h2jz}Pvuavz9N_YTrQqd92Bii82^>h z?(Duc*&m)NK!!ot*sQ~JgB?mj<*-uC3=f_lX~t6@iV@sD{X;@+?7^~D{{}7mWv8E& zL(b(M7^r)YW7uUq{nwj)ob(pM2`iAXHgeYQY1iU?$8Kkc`Nf9odK;=5gx!*BoSn7u zZ;mA!rPhoGVfMThx7-B09iOvSjP5V1yvEwCasFXJrP%uxamcvOtbnEU=SD?z2=tbA zWbxEDmOw6Y#rDg{=C%=*&RM=I*xs6C441=W=nh({3ZL5B-QrBxB9>E;5bQvl!#hBa z9b4NTMmDy#6rTe^lfplOXte(5gW`uxrS_-Mbfc>!K6QE$4Lgs3BHPu^+DWuv^sU z3h#&`4j@I@U?;b}lZO;u680O!f1vRa^f&v4UJ=HELRv42smi(O)&0QY(~Wm`Z!jH% z4qqsuCXZ1&{}6t$@7DV3E#^-2Ch+&NeF2FtHE*R5kmTq2zFR3G`VWq(61`CPuO!Q% zCEX@{XR1;zz;rQ65U9_d>x)$Aa4Ap9{luXD?=vjyo(m6_*(G%~wq9@(1ngXk&&*0Y)h z0t^{yhaFFB$I)yqy_ld-@i}}XstN$MHVW}QjSO>P$!DZR(i^2tt1}s5FR;9$#;ggj z1)mjpovG!DW};d3)5JBE$fM~nq(_gec%A-&rIhR@V&^U+{6YCxM3Pa< zppUm^zua|Thm{aRTR@;C_(7i@ViRGLr}`{OlFRaGEqy&FS1#J zP7k-$#Ux|%6)}QmQIWHqU{beU9Giaiil4R-3wnS~?uP(;^{U*ru;SQ-n4YJUj3Jpw zI~n$SlOZ%0wlFG%FV^RVEPc-(hSxZ^^`4O5y=rYC-Csa1Bsv2R$NHg#Nbzy8t3&5M zz9lt{t;E6nGj|v%BR~CYF|3($4=}PBQ!Hx?IXRcByv8RZ+ox!zq!sG3MQKuGfeU<;pt? zFX77d|2$lUkJhsP^nKX|-SP-y*`2AGX2c*+*=Fn-caFL22$hhd*F`a&7?<7hezfQb z#J8;7fMy6bg8Ak4*}kBc6N}Q=9(+ZbWfLV;2eE0dQ8*S+?kU8#gi9-eE}VwanX%hQ zh{E{xM%wD-UvonwxkdgAy@JE|k@{r&PvCKEDX@w>NzDT@}E<|4X> z3{U_F$2;k$Jc67rwh**9-QQg2^V84}RS2+^J+-919o<-e#`0E?L1#az8GW)5At}@1 zb}U1(7w>Grq_qKq+}9$q@qAU5(QAUDljtTdqA!P)O5tlSd&6&GmZ9Cc>%)2*so5RX zkaJ)u5rkN8it0bJ+q>5hj+sDDbKXxa=Rfzc8BDE~c|q{us`o5kBtYIppB2@{T%OUD z1B~0yjMqe^V=G`jA#`bn;iyq<%Yx2*TT%sO%h+>I-kq<&^Y$MCB*qSL)1!hKH4|UR zo9l){2~#adFZ>O@^{0 zGq!BK!3;z9uKczW_QkPd*E6mNe53?Xi;15RJ$B1YC`^!4R-xbuM1lPWisMsBMU<|j z`mo{Y-3!;qs*TJ6nTg`ivDfVRTvp>TLX^W8iE77m2(QEcuwk}2BZ?gpHyODH-VBZc zq=64Hbot*xhXx8reABff#c0(b=aKqYRKxc&~IavAhof!!#^|6 z#Lr8=%=4TwR-ay2whH#`LhXd+GXhi3D}@~^okrK;HFA~?7{68gQQtRk+%=@(*NLhL z^C~Vjf@OnmhLCs1*|b8=2`SSR644B!n4(28T9NQ=QeA7YB1`MA&B-CQQheTOBmgi z!W}>yiU{2f2Oe_nb5eKDp;83%V;XkRGLP?oL8N3lwRx)@UL?1~rL45nQ~s(&o9q^h zGN0P}^kZO!rc6g-$bXFkBF|R?$qb==(Ee46=O2qSE=g@K=$WdF7iE$m+&k1%4!f*S zg}*!oGQ8Uu6?-$Tb_G+IDOT!?Z{7+63+r<5P$N6D+~-Y7-0c)D(B3LxK zUDKo%laQ&EpqDSy)&u;-9R0t19y27hw-QQ$n6Ks1LxS`eS*^T>R0EnO&FfnmOTiU` zQh-l2eUN&Rym1)hc}v<+cdR&i{&VHzhHszV$4Y1P{JoT}Sx$Pbt(?mFopGDGZNw=B zIB zJJ|Wt@`<|!CDU+XHQk!dh=#-C?mc%5APgQN&D&Uk%GSGt&2 zBB?^o_{Hs)B0F2230bmTWf~|c*NU0}|6Fr=y#Y!jENMd)6o}Y_s(+^W_{HM5mmuNP zyTq}7u7`VGF}K{CPX-fV(GF!XA_HG|z|wN-W58j4@B!HsEo99)h9dRsN$72_h#HCg0%aFz% z6}AhodF=g8vKw79iCmM=<<@+e-p|#vZpZIASf7pZ+L@6n_&Wu*^zTp$P%*7^ug6*p z%7gpH?e3~Ao|uJvsSMl|BppwrN@o7s?;RxBYRewRxWn!%RWFPmWX(YzdKNLJCkq!@ zu?bBY?v9g{Ym3i8nak;IGARR~>V$5LcyrVRu5J$Bh#?g7(p0M>DzS>&A*hX8S>mdW zB}o)HQ|F){%r4nisNyf(=b>8dDx8~KJR5rWuk0$PRjk#3*l7b(jt6teV|SZ3)}7OY zgjzQ@wT+y2MD-OrtT6ty>)TNvBMj}v%1b+&%|MO5A~F6VV`{x%mE!x3_S?PF#C7~< z!6o&IS*rSeAyG6h@NUe?QEWfgOK#@ag2Gi>%#T9>_E7L2bIA= z+)r&z?qIsf%gPm9E%piQCHUPwnX^UKU*KOm7b5vT|5~Y`hkWdI3ceya(_pf-wZj*@ zcxqD(e6nM`d5PV6ad(jHl(&z&IV#gVMJ7JI0|9tM({fuqq+pofOC*Q+^2ZC1S?YmI zlH2KRbTyheJ>d_^9J5t*>S3UVbm9a_ul&82S^C6QV$9_a^czAT)C};x!nVgBmc~rx zqC9gg6t}Mqj3!)y6*@6j@>|25#%*rf+4-!w5vt(V$LfU|)Ilohjyajmt)m%_oN#sR~dH#jUmqb<3s)w8duX^ zE}prImlYbIx_+cB(dsdz8XKsL@J-W5kasLR%!WrGHrFd5WUH4Ip>8c@mUVF<3YKcY<|L zYtN>iG09r+yI~$mU{{?eTv=2}+I;LIB0#hPFtl?S@dr-qym3$oz+p||34znWR|>J$ zyZE;BYpKcWoj8hSGcooNC-QM?4M@UbY z;V{V5Q-2wZlCjUrG0wI5C7>~|I3SisYZ8Y*qwlwS1AFcx;-?!MCwa%4<}Fcr^gJbR z5%=8jrjNw$iL=$%*Kt{?k#e|m*;ZSSX6~GOmq(@41Bo9NnWNHZH>A(x)Ykm|`3OR- zYJ{NanJ)<+{MEDqI~$j3)HKCiL6n&<^W{Aok!+-$Cy$$tV9v-wTNa{(3uS0oAtkh2 z^>T!%TxTDkYrMt&GGJ)gMbxmV zya@Fu3h5=CpLVxu8BO=Wh`Z>y4<#$?1Ed*!o**JxLWWX^C z$P5dxN_Csgl42h&+X-TnQF?Go#tt6+dXgj$A|EtsdbVQ&Dvo&_??UV1!*OLX7O-zL zm17EuP8iK*nKJC_JMG*oOQf7S-vLF3Vqc@oIhP?_RzHcrH`wsGF~zd~g3kSz1YH?y zvv*uk$nko^R?8^&4~SFYTd+xq{DJ z%Vj7|oqYR;E`&k9lUTivPY2J6-Wq!VS3aV*W5{0YvX|L}`yftJNE?Z;)%PPNkROtwC)nwS zG?I^Bu}mt}l}Vy1o&AYM*H1rNbi&v*erymMF23BpPUd<{pkHDp>=AedxQgdZk(xD? zXe-P?0H9okHP72w2%O5tUc+IEJu`t1hcd_kT&E9o!?CunihB(7<7)&lphtlFjKd$y zJvu4J+)6z~Vx`jlg}EG3w{S#$&OaToh89zfNmfdda70+#f~6R!S72*+?}>^{wJ70| z`oJ2R`UD(YO!_+3AtvXQRB%3!AWchErfHqwKD3aGY&_hbvbbY8Z3GUF?!dpyie&`` zK&LMwYXtXG$oL3+m5JXv ziaPScwNtH?2J+PDT`(l#9GtKM>~UAWWGU@RonMr+p_eD`q2)Mm(CWWB#TjMx1@g+%zn+%7G z5pNmX#=S=J*Nj(MB5p8fi4=ukRbI&^n9f}%>E~kUDSr(*29@5#XmB_939ah@iq04W z{{+XoCY;QX?lb8LXwkZ0Ox<2XRt$!^Ob010S>^5!xqgXEt6SDbUPAh|v$8a>#bHEK z8%8v>hZGTwsykSE&4|R4PF*(E0UZnx%8_*Y*}_~w8vbB%X;CF=nx6KMRhWVki-N7x zVbi+GSay~sqN*JtH<1cVF34meNbu?mAZ{*aoeA(lb+lfpw-PN=4`4Gekj_8ni3ak~ z)Fc-$QqHsng3M>>Z!Us9__>G_Mvk&Wb{lD_KuPV4Z1tsx31p;uTSaHd-N!gI=YxLD zprr`$tz~{{hLz<94LOUiA=H}mJrUT5F&=E8g9P}Tg{r%oUx!s}aQ`I$r7;8GZx}6t z28BpJYOtuWr{z*b%2NJ#8J41)zTCCGd1Ch+y!B?D`sEy3Dz4`%VBvJ zTQm|JO$m*54D-4Z8uW?LzH1b`!XOR95V}7_aghFz0bQn_m}d-Smi4}6f_Cy7L1OMU zLtW3s(Q%(|Nf?D(gAn~Iq4xSFyBgs~Q+L67_0VBN-M=84q`ZaK^H*BV_{$%NMjU$f z|HV79{$Emt|9D4EcBcPDHU7stGI4PJ??#*dlXqlf_@DNM{}0}=4NM_t3z-)7Kd5nN ze|Dd4P&bGh%OGG7YkP;60~G3pqyr2J^wj_g9$3i1-2=Ci{B-xX-mAU>+sw88W@l&b z;u{VxwTmn}B(xYyV|8ILH8wd{7?QA*xN>X)Y=3{>>~w!$sI+(~sADtm*I2xC30PQ{ zFb-bm*GNKekZ{dBn`Qhtv*Pd!NWrBU#K8%K<2{7qJ(Pn3NCy`;=$D8~C=_Us&^-c7 zAQZ3Q>3OEoSAODKb8^cY!A{7&bdI03m3T_^#JD~8!0A_%I z7DPqxWxrqhzpH{+XbS86I9YIMb$JC87z{KI!W+T6Ie5Z5IyV3b1J9L%u%M*?Tge%O z_m5BdBWVEpBR~vH%)ZNa^%wt2fI7Z0W186ASlog--34)I0Mih;83bh2Fm+d#Q&R&0 z4DKP2OimBqa1op!1g`|l=0f=E1?Tm`?2`79w(ZGL#%>G$^#)>a1>h6hji$B_>W4By3Z(Cy>|Y#}0=aWn)! z>}Q*yp4(@jub}sj|9g*96wrWNU;}w)>wN+U3s26#Ul*qzL%*BBxv_}_D1-NH@O1k^ose%c?O;>h6M@@72M;qeYcNP!9n zoxGOXT{RkM4)t49#P{FGG6K?q<0~wFzpXmzBR}%3?G5zJI+ra;jh3he?c+MlfH``(ENq|U8l&UKjEBn z@v5HO>Kw*_{l8|o|6oNP^8cvEQ>ANv;n`EAU$oDjZ-KGBjr`8J+i?HR{A%ZqYri;G zuf8I@tAVt-{@NUW?cdE>|LM757q4B4F@LzQ$If(r*U5LCj;a2A=aJjD(__#sE#blb zYEN{$`IX^&ec-vdk?a4C_lozQdC$Cgr+>?tyOe*^k79KX=1af%IL&>aIsM(|*GD#J zAYaFQ-pVQNbf}QkwHSMbfMP;ut94wcchT;`lCk02E6CrrV@y=awAur!7osOs3g-ms--)~;q3_x8-`|A3hef>}TVr*57=W3YL;9q1RU-Sb52F;nhT5Y=rVXmJ` zsNJEqPAA&sW6lmN^ZcjBI-ZYn?t>mJy7<6ZyT0A}J!Srex@_aH#W*%d%KU6M`*<2z z`EOT~A|dk;{(sps9UZ^XnQV_D6&WMJ=dJi7x)(-`qp1GOXT7?`L#K1H|0+5O%L2Lw z@N$b@6TdRcBv}6QvL+Q^;wu|T8ZxcXygdgP3@zr21H&mrWNgjCYka=8&gb{SWgZwrVHzuW2{E_9GGd+o3F!t@xT z{<{6dyk)Y@wF@ZC|BBB0XB8e?D-TqcfYCL-)`ju%5z5B|&;B>_6{yHi*Ph-C4s*{_{@Cbi) zq>Vn$&>th)T}|sLT9?71Aw(XueAB-r>qkKRMJ^b!!=;*cgAAs((s3nNb#E4_&^8Um z7I^-)Sdc0y=&Az(o5+T)dS0b+L@c`us(g5nZPtp^miMdfQiVnj3^KbTf0e+h%bfnm z{H66B$yA=G&DL4R8|CBjTrC}!8)w&N7A8t66fI(Ek>8;}hbA>6sZ@f)Cmfd*-h(Wh zK(}UVIPYws!?ZG)nD=|C@a5VN>9sGA{aWDeG;FD>o}^Qqu3)xxDe3B!&HFufhGB;% z8;S{etIVZrla}w4>hOA01=BL`NiQu^-}ul;-@u;LxWy4c-z=N7d*eWzuod$HaC)y`i{ufraNUA^+ojfM?*+5=Hc}VnyXn50n%Cfy-U7!1N$Fl>@DagA}nkZ z<(2PDB(CsZJ~D7Pu>U%-!>flvKDkZGzBai8`lrbc7>GR^RK(`OhUljU%iTaLpLyT^ z`s?F!N6gjT{0j<234=4mHGAum#Y)*VerE(d!q>2=%f=_pWxNwmA_Kx<@~K{!vj|Z| z-(-tZ2ikkJ?V`TSTRq35&$LR)GCG%O;8O!3oV0hiQ2()`T*xq;!@(7+2{y^#j1h{m z*c)G+=;_+%{ihrx|4AzGB^Oje zRh3QX z6dLkx1wD3;ZuJfBqrNxG+*q!lPxUH-W?RKqlNPL@AvXwpD^-XyO~tLD&d-IH;~jjOuRxzY-@zHSL;J0tolfd?>vH3R${%xILB> zQzNoJjJcOxe;4>*ntYk_hM_>dGqn2re?+GX9voameTe;J41{-pR)5TiiDP*+w7z|) zs%etyV`(Ul#TrI*m$k~aRN@==s2O#hxj-Tn6Ixut$AjGEN(@Bf%l{PSV#HG&VCu?K z;oJvLOOH;yP0pIx6o+CN_rAW3*jm!#ST)`DZ!oq`$0R|WC$l$2CUV2w>zB1rwC}LO z(qIDMCEnK%)^)daR78*3v`w7QW1)b9?SR!(YTiI9^3%Q!ca8h-%{j%80ju}GE@gdj z&DmSY)s{A!1N-5H$Ynmjm&emheY&1OjJ4|t*o2MY0TE5tex@KHa*Giz@^J*=899h62<^x%91viT9BYfH-m8KlG!+n>Ew|KVdk;M;O@K77?m8 z{kOiURe}z(()Z8!vkGMSV-WN;a}(Mi72eI!6Jd+^D&S120_%1%ZS8V(K-zgsE-j!p zm|W)U>BP;a(~^!IiPji8idX(c_3K(DsTo4|2E}FvmF2pW=-GC(lZd62!2L=_tG)|F zD5{sx_FN-nZTH zFbtxGaqVOhw=6(8%ZNJ^af5qs-0&wF)Ilj$Kxp`n#l0AOpgeil+~^=(OFMe+w)Ny}MJ`aNjy9`z4h%A#2V`PUciEzM{b0JzAIiKK|2e3`oNv9yxnu zX#lcsf5Og>&E3u&oSbf6_u7 z{EGr34Z;=9!_69@JcIj%t<74YD|hj#fqaShA&d8dUKo8j(Pl=_Dxz^(_E{Iu=2ygZ zqI}dwmmahx!q2nMFl938T*5;&y8lwXt+@x=l-pO9Bq*>ktWrO>Ps*D?^{J+_$U!dq z=t?2gpo?K($Sb=6~wUy5?jdxzaG(Q1HW4$ zw-C^~s>JCMGp$yyNvp*x)W8|m%KaKte?l9qG-yLD%dX>YkFVIVqj__KL&xCvz;@T} znVLz}B~F5>Z$(9d3q`Ej9+SKsZZjTrEmo;FR)NgeV&JPF7KnpSrJbWOH4i8~v!5pz<>wEfzkKUi#NVwr za9x9^mn>oI&LR_(cj|E4b#gwaxShGxoj2^JAcBXMlX+icESNLqI7{_mWRMWC+1hT2 z`aC*hl{}j+_k_uZd5rOwb7WI}#Pbk7-}sa*Oc#52F;xFz%9b|r5=ghjGXh)ep)UA! z#Ob<_$Qc4n^o4#V7fc!ZDn0@YPo)0vm|xdgXJ0yzeT-{c3^-OyPbF0M)wDmcha*m- zOi#XALN>dQT5xur4j^4!5ScQ?2Ir%owN~C|?ukL+P300+IJ_Zlyciu2XcXr|^qHGs z-#MDc-WT~QpVMh0G#e@no5+fiD8uygnxmbL-R!*YOLA`(<6U_ID&`ACiP=&Qi2U(+uGFdn<0W;r5!iX%5aA^DFydjkEC97_i`bo}VHdPi{AO18rgMkes zeD$qH8ha0w2V3scU6p?u67Cz>#YZ)x z@z;X8N@1-sqPlkC8|v<2lDx*xYMIWi4&6gFSK-gAn`*nhj=-9*K(t@$O$6Vb$>l!>Kw|8-LiPPd?SYr1|E1sZ^Zz_bfh3kJhh(ICq-C8W(g;DwbKUgLi(QB_XXg6g9VyI~ zS>_($Fs~wBK$41U!oxI+AX;wmO)OdLMv=znOjx8|Ym4RQ?lKmVssN_{jiDK{H5fex zVOd^8c+S^w4371DDqa>+W1w%}2L+sM`7Md1Uv2x%a24g`qt+0vG0{U_jU`tqo*7f? z4p-8!PxPX>IqKa@!TJV9?F|`i5W45JTo*iNa}@JbWQVR_MBbs5s0K~9JTgQ9tthU# zP%Ul02(fyTon$Q@$PU(i@(j|TsH|x7Wc0B6m9$P2MtAY8!WavS3lCIO*60_raX)AIEyY7b zA?;cy(;X}{%kmeko!UewOsZXc_co{c=8(o1u$0wWW*UX`sIsD^ruUB9$~b*0f$u7@ z43WJg_nTy`WJsE&$rK0g2Qy7ZFBx1dyNh+!O5GkP{sE|bP zZ5n2z8}zqazaS|ReC5#fiNdf@ua_{dSwKSTyPp*VnZs}J%y`j9#M2|nc9ujPn^mPg zQb#vK$)mpRFE?{R;jzBCb?f%LGeGf$6kPQ*pjdb9RqC<- zCnmlXGXUO{JduAt8ukp=ZL~ctcC8}rSt1=+Vzmz^lR3N&6Ga-0g78^2Y=sPAEkdKK zbRu0_JS!VkM>VN>V3dNUfPosuDEQtPJPg*G$8BrZPB5Nj2auD!8C6DrDt()^Lsvk zGel+VQ<)AIR+HgNy6r39B;mh=?F66ntF-@NnR<_~yK5LNZ9|mci&Td?9#@ukM zi6`HVtVK3t>^ikVyaGj7{XBUb4+p0olPk}VU)USDU#Z#CNs_(;+!P4FUrChpcM-JC z@W|K>^+(z-Cf){peB2Sy+u&A5ZP~vw&COrCaN}=*kq$M`v7gk`O862}yvZJSfo>c< zg;V1kj#-N$$2jhtBxLI4*?MJMYZL-&DS)W0(yvIOb(*e>n}h5fsjTvTuU@aTq)VM5 zYRwr559Yha`WnFkUQ$0_w0rx%18J+oh_oFA9?4pCe~$8`H(x7mU%^YYRne~Q{>AhD z69FSukp*3qDGt!vvym3|41(@1b+{QVFdSZiy!D%=7XUIkSB&@(w)Donl#i~2EN-Ub z8|#P(-pmZW$*(P8$_Fz5n87uc-!stSNcHN0JzQd)6dR8X5MOKUUohRZQcI=mOz2Yl ziJ!OlE~PlE#Af??U}OV1P))g5AjyIYp?j3%@@7a%!u`Gz{~wIqQpcgdPw1Okz~@gsx8aH#JD@( z*W2&0MLLjWA!wzf-`73*`{V2Nnf4*ZWM^QCN>+ygbz*kZC^}Jj-X8h zQx=YiBIF&Jq$5eYALRVD>0g_i@|3`M!6E;hQ_^*PNTW%gI41R0dVZ?=yNaWVtj^Si zi{lBgTlsk<$@K0oR`YDqK}FfazA#h8pxFF@pnN=ab-@|;Vb1zqwn+f8p%KNMJhy(N zR3TNi*8tfcX`K>MMAMfLnON2IAUq96ST@QuRJuoo2cC;n64<q-VViClcMfcxj!IQOC7m8zKIm6xnamro~MGSZ|Ki6P?E zhSjM{)KdVnEyF|$F%P)zud_hucb|fPjw+oBk{L?Ed?y$8aBM1c_t&;E_##Soz^n1W z!IF$zDZ-J zow3M3-w47)Fh7VMob%kmD6{~Zf;4DDhH$)*!5`k@*j}2OHOZ@Veu?fD*OaSh0O|9e zdR(fw35%~HB37h5thykrV?A-2P~5D=sqO&-jP&iAR$P9s~QYvuU*F&d8F zC=|Fdw8r;1r7pdeCqet<^B2ru7=Jn*BE;!~cEu%cKElvuy6w(YEWraa%9-AUZP`^{ zLy=!X>#VBJh7TLKF>g`dg#_?p9ven}p#|Q;{#y?9IOcJn=Mh}0oDId$sb6dCL}|F} zbcCM(r2Q*(SLe7#6X68!z#M4-7(jxKg^whCHnnjlP=-*oimsj|TKVB*(dz9G*Q;4r z6iM(by+10~M)|#l$kD0Lh;XkyMZ%9DM#-Zdy9b8V$d`XQE`NJwl9yl@Ksq6kJ$Xwr zS#4!2s;^~(tPPyCw$0cgKNkF4EVPOY*3OpWsL(od$$TK?Ru^?ClL6uUfw?JXg=(Th z&FV|tKr+V5jQIgqWS}B+QLGC8g%C49T3pjQ5Q^+2g%GolO*tg644z~$hM(=?)kgjH zOdGrQ$DK(v99`@&@parH>&_A<3a<*ze8Er;%|^4M=!|+!U$(^HH&8Z~oV5>?TtJxY zb(fx5sAtQ7KU~50x9L6_Fb0h>dS`7L%nG8aSghjG(_LyzgvNj zCMRtLWRTpV><{h2@JzW+OfVxIc#$jOw;$6bn64mP6Y`zzxP@m-ba8@j=kbZM&^(Y& zoI={>Va|qdM{oKrS@vZ2aGNZmxx2B7&yi+AT3T{JIVG_EF@gwE zSA^?;^|S$3r^!{OywNe=JNzmFajW|v57hPne74NoZ!~L46rsxXm~6=Nsq`p(=`yBF zIX}Bg_^__5&!je;QQM1XUV6nn?zev<4z#3`#N0{Wnqw#Ot@Z};8%Kk$_mI%arx}mW z(67B{9Dp)fBu^SmKhLdAYyY6b2H?&*4|@0P zy+OIiLOCo+ZB@A>n@i=58_%0kYxd(8V7*Ib97w)3ENeC}^d&``YAPv{p;E6Bb1&NP zJOQiiAA8@*bTw8otKyM{;2QS3W5r{>BxWRldq~*5 z{dxeVH-vN+)Z+*bUFrIbcCQ5Op-2=Pa5_$c^S6H@XKKrU#d87XAj60=R7$6wjvzvn zyTdJ8pv>^0dq4#k1r7$3-~-v58nY=N0+)Jz?a0ItLzw#))yD}0Uj#i>YVjD4(&6FL z4hik)>dZzqDs3(5RaVm}?c-zs$#tCm?Tiie$N1b$n?kcN?5ZwTE7UWb8X^Am6iThV`2hw=PrN9qQlUmBhRx( zGdNo%@x}vxrr|iG=eI!vJM0Oxsat2`n}g#gF+NV7ir1~0kJKyg9H`42M zUn1b^<=&M5?X#UFvp+yAWT+|rns*YuR!HRC{Lo;HtgVT$+Yu2ySLz1sy1A?}x1rK@ zyq?C}Ez6=vvRj1V03S`*$)Xpi#YvOj``hx%@{Zg48!a6F>k}tP`*?A`?$J>DS3A6{ z`#2~Y@Q%z!HsCuL@b|+p*NceAS^UkBN#E3RB&T|wMQSlAmNhaAJkwI;F)5wBLzUx5 zsJPhTAr}{2GN-d1h>Qn6sguD2EH-L&N$t|6U5xwjS5#(?Vcxk-6GPrPW=HsvFV|>6}iFnXDLor_g>l?H_Z+Y@W0ix)j$gg=(C?dAyhXFf9YSKdHZ6wQ0mDTXG5C z&o}lF;6Kl{E+ODJZ@OvpBR;M^ebIaK`UG0KtNjxghadBmnpmUPI)sr3T~Yqmh(F5R1}z2tpgrYJYHrZU5Rsi z?sDFNZ8+kv6trnb>rd+6f(WNRVJtM&5a!*}$g&s*o6`$(x?3)4pAtMRCWAN1&13Ji zV(BSfzls-&FYk|~c`Q}4$(n{sLYwbAKwClgA67M8PM(<_wDxw)7>_LPvTpxT;@fI} zKBp)S7YcYpV%cYK*pm;2yu|cm)14BnCC2mjdF)Ja6w|Lu4Al(KVS>>7rpkLV=e;ps zGgJl6+x!RI7M(dS9X&{H@bBMHsZ*J>5Gt6qQOcpzk6hO$4SjFX@$TiJx57Ih&o8H* zp?l@k&EV?suPvOV86~}gpD|&F2H=Wb&nW*^KF~3T-HFPqtZQXUVEu<22U?U_(Z$#< zgu%Q8&hDRH9B+@ciJ?3c9h))JFaTI-TfjSzszf+)#B%h03Zweybt zZdI?U8}^@=w}ZSsxl+thUUd!SUovR~dy<2OYDiyH@l1O<2%e0dZHRaC78|he<~k|v z!1+{&=&VE#%D!EsclsVV&r+#c?PFR z;tE8!?vD^ls_)P*kRdE$uS^14mrBAD6jQWBi_-`bcxfK>Dlaoum@ewi&AIKfe~ot+ zgH+(Ir-tmO?Z&q{br8u>jj*E7zyrL}D$Xwz`&%%)31?2Ol}M?~wWv%Aw{KC?AJBus z`5nsO(mu2UQ!qL}g(+clc5&JyY*b9A&jYp05f(9K>aT!d4Kc*3OuDXXC;|>{8Wyw^ z=w5J4rBS_HC2X4E^cO9xD=P{nqs<$e>}IURKl*sZdGEc2 z4CeDi61I-T8vG*0=#e+Yj3VQHOzGB(01n1LbK(=RS<6eZ5oy;FtJUhV&WuQdA=@>O zL}eLK&H_6MDZ18gcpMWav(&$;YHQi-r{0PSBfx39U)OB;26c+_BiC-XJiuv;iG-2* zDGaN0^cZxm34k{OuR971LN~BQXRa{WwX5cEFfwvM>{fR!qBzlidn(?{4}0!X2$P$BJPTb% z0h0S`zlt`M(1=?w97!G&S_4y)3`g8mQmwG*#eX?~u%m8GR5>zYuyoBd2@+5e8pI8-MC=<3X<;a^h7G&A^sTq!U>VjFw}^&4bT0a58shOq6C`kexBl zSl1E)-p`)Nx@V*-p$9t9+5-OuHn?V?f)f-uep8@j_~ z+2f^_FS+fTHXe-}{e#trw5m4`q=T}uCb=oBvkMqRC$P)M{1o{^eRQ}DVJ&Ru4N_5j z+<*4?$3dc~nec-e+Efp_RD2F=7=&uXZ{b8FCGg&cZ#?%KjK-JAtr(FiI`5Tb4sh@X zS(dAI2RxNxp7w3uI*c1qKC@N$R3z5!E{*?!Dk=*+UDRwbP7)bZqS8tbyJs}b} z*4VJ^<#a`2d2}~Y;9S{M1Wez>4M%=B>Hz$o7g6FQv+6$;8fwOfsqvV~=>jm9p`Lrr zqQIqCQF59l{~+cqVOkQh_s24*a@BZ<>$_9kPci0=@Zi1fs)5u4+}LtHj;)Ezglw<_ z!t=8A&2zUKu@qB=*eTfj#SNYG;0Q$}S&4g56o1w$V#j`xWqIml14=ZW?cMORdP3pq zK}$hsYKr44*yc;B>{4wBD=CSAP>S7}12jr!*`t+%`KiQ`yBJiu; z*C;d_qmdoaN)8=%3L7FC>|BC_Xx^ts*%nKK5a=x^DEfUk1xAdqZAWc$yFJ(;Bnx}R z#`7h0arxlXH8F1L;f9?@) zQgrdrH*LaM!F9YiHhZVIFzlH`uPMQ;-osGvH)J%h9c?Z~(Vw@Ae%|jL5Cj)6{}Acs zDi&%g?EFz4&wc0U2(g5xp`qeElIPhK1v5Fb@4A>$SXWBLXlvNGqcSY@Hx+#7&T8y( z4Xe3X${*OB`7O)xnh>rtY63Zn+s#inpoJ9<@8Gleqn;9p4Kh+xVytegs^E}j+4Pe1sfaWMry&-E^4qxAWB%Wo$fG<4Q5f#`-fjwZ};XDV`!K*zZ^M_V8UKC!U7SYyvlh0S{OA zz3m)^U`Alt_wf5r#NhRl+Yxp=g!Nrx zZOobg*(YOQ&F-K5^f=r7vO{8j6&|snTj!INz(DevDJnhAgFc$m@x0(Xbe6BXYtjt2 zVT7GX$W)nK1s_Lt(sg>KPEZ-%5E}k9SnCSpUOv<1REY!ZS#ak(iebu!ULBAz2n{xv z^O5iGB%J$I6AVyONNp2(B>B+WZ<&=qc#y*Npye*!aV;Kmh)0HZ-im<}b%PP}1M<9a zhqKV4kgGe;F*@&}L!DH_n30F|^v%99bG&;G<_zzH2eZ)fw1D+nflXfok28=x(OgHW zKx6(pMs3$Q4BO60n*rB5-`~b_N+1+XVTFLePYTsO|E_lly5nAjh_6~OsNQF3^V*rY z*2&@mPqvXlnpl=LTYr)`TYs2e`sm2aL>cRWVYax}Me7&QXq! z5s9tt6o}IHqi}A_R_aHxWlRTrsJ_~QGzAH+L$(Y23E3i1=B0De<>{Uy$!EAMWo8hF z3aPj%9=44{WAY$QS&?#iA0~rZrv1$ZORUoUAxQ-BT-41Db4Zhs_jUqT_A2i#aYWEQ zHuv+wHqcm#jR&JU-XJPR*322~ukh^)NmMp8@^uG+MuH8Br2~JLs;RBY4V4^@{<564 zYrpV*WzKipH!X_ydy4vzDX^t}^6Cq7jO;i54aYd1jz7_9FZWqqU}~|!zwIvQE+tGh zF<0k_k{k4I{@?ju!SDx(;}8>lIpvBLyJ~?uk~j6J(Rkpv2>YwO<0L^8n~9byt9T{t zsbDtmZ*(t7Hjh8XBv25@4N3m=3AorA|Bz5CZ0%y4r<%&wX;9p+`Z9WtZC+8Obd(}p zk|tvV+UK%+!wDE1+zvr=tS$R4X(c+a7PU!EOdnIiM}HQksFRK~ZYxSiex;1)*ioD8 ztz;-X)9k7W&uVGSexuen;KkJfu3CCaIEFGRLBPMXXOpOi<~m-`l14-DYzsIsE-Zy# zb+)bu51iuY!to;;bNC(tYI(d;^^JQz?GUByC*DB;pJo7eL;_7^Wo6n@9dltOeWZ+1 zLPwzLKP?k8k+&_l)atbX+gh?)VfM)tYp61O7YfzBcI#FK+nkHNYLc_QBI8k7`EMP% z!$&*SP|zHPau;*vg%wJ1=rg3mJST1dY!Z_)nguR5(fRzn)#1cxi$kyTb7GlCh%TS) zD7v{ZVMAnQPi55FujVGV8TX3#&G8`VVPXr6sA!j~;C`mCHC-MomOS>J`i#Z(rqu0> zMqAAN#l2qnzkSm(guc|#Iyv<8g zGv4K?NBcKXagiPbrmzl41C=70dpNPQ8L+iHk+&MfE$D3O4 zNa2I5boix*uF^+O>~Bg$Nby>GsKtU-^ED$xH_JCDYO;|*otq1}=;;x`stH)}sG!MW zvavq2l80L|{ynHY5J>GyZm+fKA@Ho{be$Epj5$Y-8{FdLvkF_eO=ij?RTbaAkGG^N zCm&yg{LO3e;wGW)Csa5m(nFiT>2;_(*`il4BUH#Hlp4;ZejZ4svm3rGd`BKD;}yDn z&G$$RzdiWy0hCbcr*f5k{5X@e7a9MYG76URJe@D-vod~k$ zl8{j|S9eDR4cJ+y@ZAm4$m~O*C1S|%zxVASqG_44NNFx>@p17$cu-f{oWz^RdEV-M z3#-yzT>L{j4nwcZDWuvDXl~$zI>=#mR;BR!Se?*|vkP6lFE(~JH3c22yCotBNx2rN zxes>{)?FeBJ+i`wC>NXxKm>_6`|9uHknc#3(lGb4X3%^q7)%?Z^qnh;$KH}Ef+9qd zMHXCnpPuiWWI(J|-at+sN1&`(FInqTi(`VjWdce{D80nQmw9!NRwLQ=6sRHB`OVRu zD%4j);u~ngyL&tOgKr~_TN|QRT30|**feQd9`~B%SmR!V#l~>h&FtYu-WUf(MQs{K zW`?3n@$f{GATJ#`#W-q|BDhs_d3%bk(*5pnQiAglZQ`>8Y$gidLGA1pk!XGjs@=u) zCcyk8>INcf;TWfpjvJ#yt;vY!+;ch2W}E#Rma$&9X#N&x3e$@5E(~iqr`I5nogP@1 z;-Nh~=AS?#&ynFUmy(-&Yh`5Y6#s72tH5PGFi-PDTz6}d`D3~}7{Dj>M%O*tSqU9W z=(N{kIQBEKe0xcjY?}0DM6WPgxqAItJ}Nu-bBH}yMq{Jy$DiRmo=(}_A6$=~I$x~LQ2l``Q)lAFH)q5=PkH%WpV=c7xN}t)V z+w6H_JOBMD#scy^ocU42m(ygw?eW3vPrs2mj8?#acxSC{6J%ICMi$61js7g1*Jbe) zytg=0Qk!5ZrBW;iSz+1mk_h_;+TWXbN6buYELAe7fK8Z0T&YJhWZM=Lb2W$F6U?Xm z#4W8OS3WWNW(r(s2m|x+c`s^Ti}zQ?UdUZ5QkKK%GyFLSbkk((xWW{mqykTt6!Zo^*^I06D@Ma`tQsO1bG!DA@E!lw3_6~(V~ z)=O`utkx2Hs5Yc6)jP^nluU<@i**1!=Y8l41RNpKHp@9vG&#*}EWN@h!6{BFWR8#b z!Bu)9D=iEaAZwCJ1v|K%1UV!8NkoaD%9WcNY-$JN(CSfSLGvg4Q80R=x_o)*Lvq&I z=PY$BEE2z#leIyPcMc(R*CiS*yDVx2of?CWqT+C|E`^iHlD*ewuRkQ~0-^43UBJb< zoSw+PppTb82W02#qLoAc71WBma%X%k89~^`>%LfTXih?n1p(>%)O}!^XYn zFp}aaE|e_TxojVP0E}lR+tSH0elPK*F!x9^^GuA$qZmByF0{7W*#gPo&W+`R-yHbw z-v6tQ_kYu<|M3Ue{;QA2z{>J}>Usaw$75zms4R1-<1N=!_Aprl5Kgwp7zMHY>_2lNqk5yNTJMH_1O2@hKQp*KPV&S-DByq9J zFrfsumYJw3Il8WIs()%aCN+$2&=D^R3=9r2SrOOj8unugNSq?d;VEcy!_gOp$RrY| zvv*S%NS0n2v`h0Z4GgPa7aqT;Dd?#&=wV=dqk@7?_=UN}bbUEy(X656^?^s%*ZeaP zsfI2ud2#!W(Wh%5zD|$?jD%qHkM{NqUrBHY4M7}1Gk)0sK?|6{#7JmKirS|e@-&|kx2%T1s>-4|90^37)PZ20C>>jKz_;jCLo_{s0hzjuOGtXXNfIO<}qK!i9zkR)Z zjP0rz7rQhsznND1KW4;y?z4l zO$c9WxW3VT8(En`yk=bhx@rR6<)*5A#(a-!$nwBG0ZbwtYES;ZSbjova)JVe4xbP% zei{ycAm4tJ-fw7r0Chih67h7)%RY$a--e$)kODZhO(S}VYH#fvJ=OmHTNgsB-}EXl zk0-#?zS4bkY(E!C4&dF@fuU7xPdg2u+!FpNkKQwkOpFZoLH*+5 z0_H{3+xev4cSY;JNKW_P{E21rO&eRDeq#l_U9U#yot#Fxd(k>R{KXLQ$@Ue-0W_Ka zLumV};dLi80eF@x^zD zyUz1ZyTONZkngBfy%~=#fc1CXXEs^G8Gr0mk{vWSr+=nyO|Fl44X^JvnBIc=KZ0-? z_>=y1J@}|+*Xn5br*}ogp!o~KbISTf&Z{QxxyHZcLrtVcYwYrs{?&c8=eLnpl>Ik` zC+*IW-tCk1icjClTNQQ@r?fMgr!@^iO+Grp#?S32BCyP2lLVBr@4Ct@CAoJbAdl zg)kx$>y1)@@KbU1wT85PLH|4X7#YrD`RcaD8?6j}NUYd{tJG54n5(>3$N_c?m(v6} zxjwfdKaIv6bsDbEhuolR3v^?Y-`>B1rfz;UB)^8*PnJ`-zL_&rx1f;2Gdo&3HK$z; z6O2wwx1sC5bu4Itv}ZJC{cG0%^Iy*C7wG{po$p;jje^K}viH@37gPiPvNgRH`$pLA;pp5|D%@F8b!Tcv)mwsJ?;m;@k zii&`kMjejZY=M=NnFLn1oo%m{MC%>vSm%x*#@#P6dV|nsH}e!bLJvSCWi#DYU=BF9 z(t=Y^0js?E>mE!`N_zX*+A8mxP$C^-w{!hy`8yj^fky+%71bL9u=G~erOJzRp~BP za)4R-%FK%qQrd@|g(_`dqe_wZPJ+@$?14DL0Ch>8@~&&e3*@JE=*3A9KGW=xUV5ef z5rq5(%2){!<6W);L7csLWo3n~NNIQ$d8?nUSB^L9sw6md6b1I>yf;1>(g!ypKlOzg zr<)t?(wV-A(u{eE#q<&lu&Kw)bEkI6i^@k(QuxV5JGx+sDE)@Q;e;`%9C}Jc9VD890qO(%+!um+YZU~EZGCqg4GEKLb&SC_C z@jrs|Fx$IabKGCNHoQ$w9YHjLJj!>ePBvuSz+q3iDW}IbltlI$hOQ1qB6ly=+stwr zpN<_1*n6Ajs?|jDmugiAnG2*KzE?yCC;K%t!+k)MZEk$t1pYj|9{kA7tIB`4|Fs{_ z{e1S&pUKoSQez26DpNVGOsPF+Xl21(#&(6#Y&v^aM$8TJWzZ89J%wwIRfl88^Qk_P z;m+ilcGatB+ZJNXR*v(?w>{EH-c1dV@%Dcm+J;0hTp?T}C2t-*RxNtYz5)A!&5L!# zcuU?fB(;hlW96zRxD7(w;T@BMB-z-O0rK%HdG;96KF#CjP&X8ECR_``LyJfZ1lX~I zH?uREJh99&q=D=S7Vy4`-(BM^C;M7+)jM(`lWYXJA$j!Tf0r-B|6C`SInVCloHulo zWyU~XqBS^FBX25=e|enWp9j?g#~$Ga&bv3^O+hCo8x7y@0lcGQAb~D~h)5;Ou@i~B zUK875L>-VkH)!I!eq}tKyyjHs?a;%GpiSipi(_q~V{wTt;n?ONj$|*MZ?)zV4$1F) z{>B0Fu+B4gMFGkekq{ZFVHCERvo|Vk!|aTEOrS#cFJ<5Pk#QN7nT?lriOmlv3fQEZ4+olJfsWg_6^E1NrKw$jLZe=Upu zxmIV|64ZILO00sLx`_PwN_t(`SROhN$xT|IhAIDcl%XAWM9Mr^YoewWBj7Z~^s~B& z+s=Q+s2QW)g!3wv0vyg}(b<|Jlf%kGCHT#?^45+BL6eMcDy_R|&SEPQF zLb!LRExE;z*NHyTilB3*23i^D9ik(ZDRd{3Ywy&_R^OZX#K}wM}%-_a&t;#e-y}15TicoTO)>uqH?k`zOzmbKYiak|=X+jGr?ZsLNrk=hF9K zkUhAq%vUWzOtg726F$0B^gpqe3-E(rXC;ctDig;q#j5Bn0@2LSrm}WEOwaG+Sb4!g z-0L8hW#~<|4WXBnWkom@;q6NGczSSkjN;)%#CVoa>o|oW+IZmGpI)}r8j+V; zdy$>Xf_w5ZG=TC(9I*{{0vCvWVDaQ(8~uDSs_5<*sGUsO9rO6NbcsDChwi{387*Ss zOth2;q7jWJy;xp>{AMms**yp{ZS{MtW1w^V4OuikS`A|iScs+Mj-3)*)x_9yq{Wq6 zsqv;{!yZE`Ejruc1nbV98(7PBrw%=Re_!M7N2%L*#gkMAi-dvyux^{S;M?i|aTw{# z1&g@C(N2(OMt88}8V$}~hP@G=cL6U?qY~qVdAfx`nMvAdwZ&X%vj$-+i(i_f?r7lX?#No6WC;06j?q)PTE-~=q;zJ-` zGx}8Qf;L)(ZG?!=IRv-Ve~>T#9$hP{!1fyoBcGBrd?PbKuBz>nZ`4P!D1=#Jzj`)6 z4NeO*jC57h!zFCFs1+fQ)2HywADJ}Xu`nu(ZHZ)DCP`!PA%bENUok&x0+Ow~9VX zRdV%NaNgFlQyXij?wA}WFBXoOS!r%r(Le?e)_0mGzYfV`j&RJ1dCg+tTV=Q3y6sa5 zGYa3P3Ld%l+=I_e*}Gag?{+f@yA7?#e8u(LpA{S9;+YE>(&G8@O z>zs8`Swn1>sw-P1t0caU4^}Hp+Zs(*Ic;rzxAPf;m=BBxJHk2x1TE%b$L?D=HdoZx{)A~kz z8sbD+;V%m=m1+^|g0}KUmI>VxzKVn5D-oZ4kw2Njxq;}c8-NY4#{DH>UssEc3_Tyq zrMT(0Fr~qs;JNngDiZ_Zk|Xx`T(dRvZuo$BX%B7ob6uK1U^6BQr@O{3M7bln}j-IAa)$iqS{sFM$usG&?-T zK*T4!9Y0T3)TvWRV?v!^UoQn2SI*mq@AR3*+t)@XZUD)vI`PbjQoa1ZPYW(wT@ z(nZQ|^&pR#%&Ba|-E?A`v_^Yo*=KBFk;aH_W6E%LSdZjx|I4x?&yi3eo#(>A^%D*| zJzgI*)IYnqWTsYydI81hcb>>Yn87B>l(Y4yu`j8}pS!MdTiMF=4u+2v2XVCfWkHIX z=8mYF*RZ4(*a!uG`K@ARM*#{lxl~ z>Fky$8m^P2BZlYd1k-6kd{Jo_!ilgEz~LELw&EnN=+59KvsZ8$qQRL|@zE}($Zv3` z1MF>lkyJ>e;EPUmX$7h_V*Gi68Dlydglkq!us zF=4o8sslQrs_=YO>7%So`oDR=;eYl;5;zZdj>}x^SdcNsCj+c~y}Vs?PVJ9Ch1JFB z4V!QBTV8%oG79|d`cqh>IuCT{k0&vd zr16!A@~{uudQ}**w4MVX`kLI8>#vKR^d`Ey5Ez?2$?N6ooYb}s z+WC_yw{xaByPxZ^rj->KYJ;h6{a#PPs}`mkGj>PjpecF5W?XsDLEzvl>P#XJkmXQ+}UFI#{#vEDJ_OX|^3!Zdz2)qGP9FwJXrmE10# zr%Zuv?NHYbV@Sl#?@?<_c)v_|QP%5-fo_7}b3m|n_qCq)Jz!R)uZR{h?sdL{yd;E`HE*_pXRA!=8Pa{!j??19(P&)D7^qxvE@;lHEB zUo|O&LPhQ=9E8@{R_kGTK?@Lw$|i<$0Z&if3C6c~v|9=o4yl(q*ly@;nE_Sm6$)=a zLrCO97KVi3ZLSy}Lk+oNjeG;CCGmj=#B+DmvOQc{v2S%6-LBGajA-5OC8x|LUyYh$ zslyQ>?SRWZi1af!r8Qhr-P0T4e*V6LDSkAwCyt`jH?FnZ7L4OnQI?{$m`RKQHJNrW z3+-fODuFaiD54>L*PiANzU{;=1j6vA3ZIknf0Vp9PM@dM%?_7A5NFbfI=(mla_}@k z+!788SF4mMncn&VOMB4^q7aK1O+rMt%8w35Yd?rH>a%jj2*!@GD(p$5GS*BWuRkN0 zetGtS zh{Bs8XKVIbN56@Z{k}!5R=RV{J+b!ZlSQrcgJa-CQOewY@UAz{33A_ZpE?z$U>o$> z^NAv#e{3%-rnj1d@lNLDaMdzkk0#$TG#;Qb7+hMgzxyMvPNJpT!lOW=Jpo508Ha7G zOt#NET4Nb24EFV{HL^d%qVbSyT#66b)Z~C@m6xY}BjQCSIfd*1>bDHCV?E(~kOB{7 zw_)ABgvWO9itn#jr)69JlkIX8m+}^7<)YZ?@WFZ_hyGL@P36ZyFkp-gbla>CYQrXkd+&={^B(G#t`>Xfg|^?xt&Mg^ zRAO&XJ3{2_^USSO(+-^ISYHsV*iNB5g|0sxrnaJEHKz8Y``a+<3w&7@4hei|ayMs& zBV_S>o!q-!z1jlO!xzoF3cs|!TaQ&>vbKF8Kn8q9*V)!_cg0;jC`VGsQV58fBPfM+ z_}Hr|;+R*#0PllWPEaT(gh<7Z8c~B^2}672geI(~uWw2!BrdD&_cE0ny7P>i=YZd7 z5(p5fTwgZ2NrY;A&(H7CF_Rjkg(1=DkrDzI%G#w+bO?SZm6D}VYwg_)$)(AdBc*+q zAm_2ha9P^yLjMQ9p{MHs*$*gMn0G^=CfMipr)9?_kI|{@QE7#G+Dg-xuQqsC|96Ii zJV5*J0~JJ+3a&ZrcbOQy+u#X*R5F4FDXwmEDg=DDw!lV+CfBr)ymq1zW;la2EU*mT z)spvXNTD_gnxcUtQbqOcY4dn+a`v)clzZb6(IXxHw^)(Opq4${(s{>xB?k!bw}9E) zuS+vBg1;{V z5f)udncCtcAXEgEF)Me#pLd#0;rs|0=Bl}MZmIK zL>Ag+ShHA~ajvdMayb1Y-4m0GKU%=g%#JYt8gr)6F^$nxd3VRFU9XAZ!F9KD#(ZbuW8ni$Jwl+182%?V+$f z_>tcuNY5U9k;;aj0m^v$a>zzZUN$MGO3qBs zG1`HqaBLMJa+Oa(={CQ{pdV)=*7L$L}LseZCOb)(zZV(7L~Gfbd?$ zUKQjzWH*1&prhl(ok|rOP(dPPOH#jU;8l8#DRcJ)A7A0Q2Jl!7v-JCC#)!K)1#4{% zc8s;M8hXni0@Q(SZB`Zza793j9l5dhsRM#uKt~$d#uWCek1}gD(?j8gw#Fd;C5EwR z*;DbE`2$?>XQB8-w`thQ*1;pBVZI#+-Vj%I`f>aX3&=9v#n{q-MNqf_$Loz8gPIan z-*tZ4@O~5H`+oCPQ}w5r{egm!BDqz$n)c1Av20mU7XaTb1}U!u4Q^Tg?r+*gFES58z`=NDD)mn&a>A86H=t5+eSq?VBR=8Yw4e*^xTW~ zBwo^Y(P`>!-}mXTz1=3%ntWtZR?C_E%+!@#UlzRwFv9gRKLN*ZeK35kYN5Y%XrJGo z%vz=lp9Z#bdo-!$(Aivw7(oaX+hx#E=$#0}NPXjsP1|CZ|D%$vzqs~0{RTm!&6t_+ zN9hXXJe>TdvIxhW_b;(fM(%r`O=m{git;d;VCp-ZW(3=;CDx(&cH}06Uoc6s`0d+FsbKQHu zNFrAq$+yS5|VY&COGx*8;2XiLH4H zHZ>ZO;B%NBpzP09kYUsB%1d^ICi)->V!vU|81P5u?9R_sD&oUD@rJP_mdGRxk>p#M zn_vS{TczOBC50NHHtp2N&Qs14w(d`%=ICcA^#@8~X4NO$P5Tefi=vf5)Szb7A&oO# z?T#v?$fwL5w=y2OVAORDDGTjF&rRNAv2+Wy1+<_OUY2k_Ksn9SI@Q$HXt0$iqip4A zE1S^`Q#MBhw@DJrsd_NY+MNLhym6%SPT*Vw$ zC)=>*Tk_1GJy?Z8oUY1fuz&N5Xx9q|(x)ybcItzXcuF_BUjv>W&jzD#q+Ksr_)!VA#b4Kfu-#QJYJK)@ zo=fNeyUTpGNAQ7SCSodRf$}h3ud$Qm7ML(kGJ|t`>*UG4(BA=wzNDDh1;^71q1++# z!DVBLXjjNlNTPMJ9VGnV+^YwHQq`i=SYVS=>T?)*o=Uv)n-{twh_1!nm|qV3!ml_H z0<-MXHxgYcU2!~xPhW@&D8a4DuyD-EZ>GSgTN8JKHpmCbbYGHeeI;ym>eL!omq#S#@mZ%V3+qjG6E$e~&;+qr;iUP;!do2FqX@$sfIaTBh1!kSy&EbVV51fa=i|%80p1d)`8`T!h!LGydo$_Z9^k36QP(RTEqAHWX;FhhUzl@E%h)A zNSNtAV_QMHx4PE`S+=1}LREQhagPj&mJCoLXXpG|Lzmw@eAKFq@OhcBEy}SDrh{k6 zjlk;BA|5)B4M7n0w_svZDZ!)HUJB6Nwu@b2RMe$L23l&6u7M)@0`3B^Udn zfoe1pJ1~3mrg1evNg3|*x3|oT}q51J8WdgDd~!dJ$mEyHJ%k%as*u2OcOImxXf1Z|c* zwr$(CZQHhO+cxj9ZQHi*v2D-oiTEcvV)~);tupF4qw34G*T(L%xF%ba^mpiW8eCSF zLVRM_Kh-!JQqn`WsViA2-^`#Ay;0CFN#Rnf#8F`n)YM$r@v&-{d=WWLXy8WqwL@Xi zvN91p%j_rlA`TV!Ny^D!E7yj|rAS>25l^D&u!qx1RR(gU(~OtqLHqQ^!2Jf%!+BbY z|AhP8y&IAQwTi@1kmRtC2W}&AGgQSvJU}^G0uTju^W<3W?e=hDJ6pSLN=!%2tUC1@d9e+1n@&n;2`r^w{m5cUPP$WX!bW=R(PUtR z*;l9E$6e&KLcfq(mrA1pDXEO)aUMPM3`mmkf)AGVtg4(o8oAGwy&KxKZi1)QmQIW_ zNs@6W+L}$Aq;CaK&K`CsUae_B*081{ASybRv!-ocHQ#RZfQr3CKgO=R&M^6`fTFha zFfXYVg66u)eUk+4^Midkmh(L}3_GnIOKUcxC#J-_w`441>luex`iIx)&qZ;?TJ@my zoBX?pw!yWu-$9ul`+=3Y%wTWdFD4I9bzJ36^bqkHfAsGH1?&eE-f8uzX~?JSGwIak zl*|s7T*MkEbFH0PyDpD!H{c{MMyXv|CwK^*m^V+I|19zOPw{pjvKJF<)s2=ef(#mqnF-?tn zyz}3+tkvBc8%KeLs`*)*$Yr9;#!h@PMVOsRc9j%$B-w&X@V+udt?uVE(0wLSnItnD z=`Tl$XXnCDoTNmcwpSd}@1fqEC8! zlbUd~y8ENBD(;lWFP)y8acmEt{OY^?@;w?i6Qif>4ND^tm3cQl&CVt6Y5gJ&oYeH4 zVszYy`t@*be+AI!9JBr-Lr{|gj$=9>O_MjE0&CG+rwYB5(PCxqs)7=L%zDR>WCCyt zj0L?8SGq<=0CFBiRt9`a?jMmk5^I6E$(9Fxp)EHr|MjHWN~Uc>tgSd`Syx>eNv0_Y z8AK-IXqDoINdZVkk(5;!y?}1FyhmJg*}rL>7SrbGnm7YWr1Gnn+r^7445HmMO%&|y zCTQ71Z555og>LTR=ktm?0dx&L8@RLzA(OuzQvJGT>v0hD{tTEUaHyGAzY>lm7!0zw zTKfC`$`zUprXIpG)(~sI`)SoTMDzyfzPWwy&{Y8I0Ty;(bUzOvJupWn(Xcwh zf^5xmhRq5mnf$k7#01c%n5HM8?q=Xx#B1jM7!p{rhVbkUelbtsM$4U%BpZc}RY-SF zpy+rWo^tW{mbvb40ylCpy%P?eE&{ktzUWO@g$KCsS=|r+MYP@)S%{$$mzPis653MU zg}K_s%#yVZ^BO{epI9_Zqy?SRpH4-KJ~Lu^C#%#Jk-+eQrx{V8ge5JfZ*D}F4(&*; zp+N#~a&PEr&WH*tLwCTl3#Fv8YEH9NU)jYZN##2E8p!7}xQl`Ur+Kb*5j64n&O2k| ziJjkdB(=sR@PTKhz37sZ*0xK(%V}pU@n(I)On;(G3(|*CagK_;rNg}OleX<1QTHC| zLJkS{v;u79LBjlCeCfHUN<(=GoMssydnv;-oS^&jtz?yO*gKXZIk&(>46vpCqI@12 z#Rw+y4JS)HWNV3}qhKa-<`G_Z+UodhkU2~oT4le>rVd+vp|JE;A?_Z_GKh$+>dtxg zM@}Js?$V7DhE*w6O*596UtLke^Gb5%09;Uzs)Af5c&pCC)Bq^H39#o8K>??MYq_M; z5-$$6rcW%@zg9ovmREQl-gMS}&L=6?;lSK;E#G{m+tKvf*^{l138C)vk9z*-)AK!cs+1aBe)_~+{uCtm$@KvISA?b>Kz2wV;A-_Yb5Gu0>L&kJC!;L zsfTJ%@#YU(`*#}$rpv)z9x4VRgz``T3C9iA$0duTV-H1Rl6~}vw5`LFfB&Cqb``rr z_>d~?8!`QPZ`RaD#0pGu(?W4ras_!K%CmGTr7^$w#}wa%9GjlOkMhA4Rx!}v3Ng&i zHXo@Kj70d8`^MY>Jo&=>dc^ph$9Z=ds$WS(Vp61w_eQ7bOn>}FVZjt`RE{j9sXAAN0CZ* z7|Ta=v-q4)B`$Ek`=~bDzw_{}*XX<*tygXfGHjy=#WJz`A24V*uxYVm^*UVw_uRF| z3R9iHCHUvX+Y=6dGDn`thSmf$dJw=!W43<>T+okhvP6>lv+E$#|MdsU1j!6_aH z7kRBW^ayuE4G;BjdPc6tZEGEKkR3VnG=c8V4U*wGm=cfZulHEeEYq;#MiVd-C8bGX z!+PGbe?^lPUs8L*Bl0fLiLf#Q3O&*nkwLUZaH&OmirArj5&g~fwqDs$axV%f-p7QNHbFU{PL+HX5?BRSqi$3P9 z*$}NHX7bYVy~#B%*u`8isM{E4xN@s)B;C59-G-jnDt<4|s(7vu>7WX+4Y4QNYH2%! zVX65G&)0M5deQJlG)#{tw7S^#+&^+k{-v;|B6-w+hE>R>nvfe(KGKb(XLZxzi#=P? zLui&-cAn;>dfj$_1=QdK-%{>~}g~g%TGP z8!H&~>Xl<$1px$3UO<#ak?4%L>o^mp{4HsLp)t$bXq(93{tPaGM`(tKGnNEt65Oq1 ztU}EvokGtVTE~yg#8A+J1VYk06!hAV*-5%R$z}33+Z{duaA`xrL!D01&d2S`(cQeA z!~J7&lVF7ir;?=_PS$WD10Ve`A(ydY{e!vK_}~;KBq_0>#yiqE zOq{%O*`RQor4laa{erpEZf;dj#Wlc_VJ!Sa-1TV;^}A)5S&bP6SG^3WYMt;OU*^ z(*Z>W?NFiFPWNWj70Ax>j25f;;#{LpH;X)c2P=qa6kmLfI9x!RsKW`gJ1+1U6qIeO z|2O1Y)y(UdwrZ}c2y8Rp5slA@r8(ok@wLdL-MQOzj+!s&fYM#)DzSFv3DKnJfhqwpSpMMp18NA~|oHTa0%}v1yx^RRasgNr&9zoJnS>Fe&1!pK4 zs!PO+wS8wKRSzLK1v?j5S{}-8)0vi7g=1pEr+5I$TWj`PpIO%IEF378&8GBi=rhR{ zk_UME3A>0Ry1prKQM)3?{3|}bOT0FKQJG#D4+9{36M=5#aP{}1W!{e12TzlF<<<$(b$oX@}jb6oIJFH6orNlhS3qI z)#O>lylfZiv+x9YEW6?tTGu5-wYJE=D&n#*J=hd0p;J#B{KHU&qbA1XFbD+t)mQTo zirmiB@_HpqrNfvhz>n&tTfKV%=@k3SD8PFca}tm`2GYXFfAR!_$~VZ{C+~c8g%siu z4!=s2ctsrTpkaI4oA`PeW>?WHmB%!Sa~=MI%twd|cd;T=)WufzDLC*K`KI(N8{DCR zG-yx^<0AxOlf|?-`suV?89wJx`TyYf8M5-4XR;0Za8qj3O%RdCbQ94YU^-##mst_A z$d7~QiYEogSDw)ptWY* zU4leKpEnEK9`+bsK$O0*lFX5#OKIMK0sJoGtbH>6gS!InJ0>aH$uB_0Me~y?!=F29 z-FDqg3r~eXW47gP7kVuimZwuJo?5J`SX^(R>OU!shSe(GQY+?E50>U{+B-)H=GtO} z4hH;PrH1T!uLRtA`(Rr?a zAai+?StXEDzF$xLV8{o1u|GFKMLSuV#k!b<^LF=b8KoUZ<-raFWK)QC_f$hv&Nm}N znM5xB5#*fW5~fHtn`i z+y_p)EI#(sdE^{~LDdP3^37i)og`{WL%v*+l8E9<{gG^=jk&E(JoB4we)Y?Ki&dUx ztk|N3)L5NsoSt8D9b8i+v?h+AxA^RD(tbPeh7l!h^(@+YS+s3IthB}c*Go}jzVGB5 zFHM~dk3W_qj!W!WjH>SDv~3YMXL#aq%-Nn|p=DcLsHlZEe!#G)AS z_nj=H*IwiRtvc^WYSLP;6VkvZ{u(J7`?m_-fY8%RUEue$D^Pc}Ga;35j$8Oki z;8^OCbt=iIfQV}ek@)Dgh({Z7%hGVO=uUB@kWwY`cWZyMHO;W`@8Z$Zdj^U-Xmm0K zwy80(o7DLoR)q|(&|m}B3vO(p2t>p1fJ?{_Js!+w zue}M9@^#_t{?7hF$?5K{Iwiu^1vh64dU!DQKb zzNo6sd=mkhWr1eRVcw$E(n%_9I-ADXoRd8b9qj?ix`n9?-{H2up8?@c#tGcQ zxC(b?uT-P8ridYvBa`2j{Ho8kW$U60aD=KJ4#DwL>x!IrLA7;wZexG8jli#t?L?R? z6k$FD@$dln+$Pu%hve835s7d4vI!Ib2$rMdO2SeLg49Xm50qQV$||`y1LFNpq#5_^ zs+Fa`_PD;OrbOT{n>y|ZW_K+M*?FL67coWsbTiq|vK`LN8l`8@$Vtl>)zo*wsycx~ ztbtvIlxVW)*X}tBlgSnF0EF$dV{^aFTmY%lV;oO9kVQ@7$`aHQG z##Q*YI>{@u$`qa)>JLVuOwhp2ts=P@3}*9zyb(8K>o54F_hife6b$10pNhITSUCTa z1Y#m!=ip@f&(nX;1#xh4a{S*|5W)W<7vu`6f}(}astdG8$Qt0{F6AB^lEMH348sUK zG#~2{93qu2SSG;^At5C#B_SQY9M8A+`o8;G_xx+UYIKt4`_l2b-8B~)sdvhlUd=iU zRSgnM)c)4)IRf~Q6(#@X0odIQwB6nP#UUf}fe=FccFO^mOo(v<6(~6JJDw0892n7+ z0fgX~UJe-yw7iW2=mP}6XXwa>=%}X$z)xSdz8A^R&LIGJ2G;;);RL*FpioZ@m&`N0 z0S(vCBD{V5_$m)T9Rb@EDA07_QK0lF)4c3-wbO_=hfZzteTThK~1J?@nWrJA&>H_p@7mFPZSzr(D z_zmp=(+m8qYB&>T&VW(itfaB1{{kNeKOkfKW=9^mcunt77|8@xQ&hhcP zY}@{2P6*+Qo(TaYh?5hzP!FM;wjb;O0?dNi(t(%t8N zm-FkvyKe`aLI(Kq>1`@3h?VVQ6Rb76IUX>@@Ca;ms!U^O8DNI#Sfbl|~P+lMZO&Ewj~&CmY7hAzt6-0wGlZhrpXcT>Mp25XGvh0DJ4 zzS@j25n08p{)|KSsb9e3BZE8ud^$ft0Q9~L2KoSb1qlQKqCxn(Pjfxk^7K1EfK zJ3Abc!q&K7PKk&rq+w*68eqYIs~@LneaGPZrW1(3AXkIhg#0?_fav)2SN-*!fIyrZ zJ%lxG?Vq52_s7RMTGT>U0_8QRI1o+%d%PiR5Y!RZb18iaz5EOM>F?yz^e}(lBLn>( ze*{GQi2a>^$|tDb&|kdyJNTi0fZxZ|x_`rdq9MPd!`|p0^h2Am)2L zQ+lbsq5F8Cv~l{U+|kka-c?<|P_Gt@(VWkAEY+!+hJ*rx0YQgF<6h`J{k~6LniJU= zYIFHF3Fq+RZU749Bl|buL`U9zB>niRwM~Jil%=pxkdNStW%?YqEv`(IfA6N%8OJY+ zZ*uHaO=II-ffwRvH?4T!eczS8+k&?&tQ&+HtkqU%j( zXVyDwpG=$~1y}JR!G$UjZw%g)jNDso>SnF%LgvcINBWRCdyo~%3?MO`*y^wu)-st% zZ7BV{$?BVdXB|keT{wrve>EYUIqZH6c&u}UIsYNun@?`#(d$U}xyFU>BRsC!={?_6 z6BQbCRq{T#1K0wjjWaQ7(vFB%A*5zA3j8zX(hqTR)&7v;`q~9@o*{nMSiI2klFSKo z)n+mqI9dllh@n5u{p`B({Ed1aR z#ZivSa2?OVn5gYN;?)bNN!L8_*9xkVb!9+}&cGU;$3Mg@@R8STLfgLCG`A#wR!Q-<_OH*{^NxjWp!WgDv!(=Ja+{!>sf&;@8ZncjO!X|B5jubO=ioqGvu z#^E%J*JV!MsB3X;PTk-i1h8^9<##Kjd;ffmPCZ9i(WH#U?$dV?!gL~gUwADZcY@r^ zO)qYE;Ofe{uMHYCS0qiSzk8K6I>jf8+sii+ea0$#*wI&7&Q5WEFJRBCMueGPD4x;s zsoTPp+RXD^kP=dZt33r$%T)iv-HM^|io4^JZBh^O{^6%VM$MReMB^l4!<9-ej*-0= zA~z?%0m=W^C1t&-f=)iaLUb5K)vLSm89U2bqFjGLsy=*Z#gbKxxsAYecF`u^%NI(} z34u-jsv)@BI2C8>?0)TV9{g^B{AYjMk5+7@hY8Juxq&mjcWPE4mr3RH?O&fC*i?;~ z#5o++CDm^L8W(9}o2=okp=XSZvj3k77o&dB-x__besjbcdYc`c2;|nFvXQ9DYWdS7 z7TH^RpwTRTo6D_kR@)7`60Ezty{C!HE^zXM{3Cj6Rz4u#)LXhUQTi*oLR!&|dd)7f zJ_r1Ow2Rw~i>ZxSo)$}p1u{wost$Cid7=V7`_CGzgVsLAZyXv?SFb`<{49o4Cpnde ztefd$+Px>^o4^U4xHq!h_(Z^@DRcYjJ%6V;s%)1*7S1i|>GL63`m!#cWg8O|o8%!$cyf0-7*sa2dMo;L{-%tOpGbwz+$qQU*W#FS;nziS#{QUIh4Ao0 zY8)g^n36ueT@HNiQ+6y9Yki1t`$n8<73|w@I8WL~RjYqGP zA2b*=(wb^NmRc)-7A44}$IDS`#rVkzJ>^Yb9Z28z?<1Yi)_SOgns;}*e%im<;v88r zNXi5^PAQ%8P#T|T;(3O(V1t(9^mc*Yk2#L`Ba4c7=^h~ZA!29i*XYjUShTC6Awo&w zt3xko{CWEdrlbsAG#|A70gXaJIz|oVklSF%Ki`%;eVzU+^_Z7jQ*c;qp}{bI+PF~95||H zREZzMzQ=IZc{zP7eVGZ^I5W9MI^(99ODD38GEH03 z@09najqGIXplj{daK5#TS~P)@n_QBmL|;VY=%g}cH$Wbj!u?|j&da?Trt zv%Ej|0*z1yC1UUym8;h;K zNNuw8sIU9Tq4sY+XeR3)l+ z+O*u&6=x-$x>JKO!cHNAmLHt=^Yt-DrE z7UpegfQV_E0bH@VhMa=`YR6^~ zzZ@P=zk5jL-sKbhZuQoo-11s}^`yS`yN&tf`lUozIS zBI>ytZEQBJ-c0)g6qB15yX2G{fmN5b&+GAzc{k+wF{F-*=d$Z0SKWPzR}9khT{EEs z>J06D%o5lM2>PFPmEHxfp0YMsn}8FYq*sGQv?Y6A*u%jdNe@*Jtod?}uB1iJ(6pBu zn}KZSNQr;Hpg8xMt7?{S4}AylWE<`5@JoE2x=^P@vFzGDUux3bee(2EjA9jQF$g8# z^DViM!;b~bf;A$Y=QyT0Chl`&r$sNdxF0X1c?IIoqTt<1>o)mdK&grtlyqhOo%c4k z9`enq9~X%o4G@UlV%sENnK!TjyAtj<&Jf+7#b?WCo)cQiF|F6kPk`PE=;Oe@q3oJue)4_10RTGM zpYKhRDK0{9m0$wxs0j*c0k~3i z88+E1f5sCR(ZZoNK+OmD!gtD4rsp6DTl}Q9uN9XOLz#dpYMg zw6#={dYAzf45BXPiL!0saJ{?74WR${k&Tk12}}5;*x^2Ca2Vt~NLDxNYru?2rHSR= zYq#u|=`s1Be`CM71lX!^;|EuQs}ujFk}5mUl_SNwTtR11JY<2PB7!gGzH(%S`3{m- zes^5Ob!@iAsnJM{5d}|$^rum~fl_mDKMce_G$vHd>`Se#mLh6Mx_@6*Cy)-G{^kIx zn-ik~TXm~U+)Jj;8D;*hVeM$j*3d42q3O~=h+FmM8I~Zh>uU>BCU&Z;mu-LHYq_`D z8dv6Z$MTpdW$}XTSbZ`5A2+WUpDuQmVpv+HH++*Cxm}qV zpUr{fH3A`V9S&o~Qq4(ZmA}Upm% z&L{MFXA{x5@Yin=P>asP-TULB9#4`*CQLU;x6{ov_lJZp(0hz}OJ|HOPRH7**drzW zEm_*&^wqVUvEf=zHPlQeR_|6l&%)_k+zU_QORm#|cqDdn7H8Zqk&${l`Q=&Fp@VJZK*0I?}{Y8#MJ) zURKwtx@l)WQn7`~ndt4er5)$6JmPTQx53X>_?SFcR4NXdbt?Wn8N)fpWUZc&Y-dS) z#RQbS5aqP-WIC($Rwrl{T^v=(B(y?3Q|*B-3t$HlG*D3-S_(DAEBm3 zKaH5E`4G3xGceYiK3L9xxQ=B5Hq3M$HI_fKP$fzlB_n$tD#IML ztY7qh$ZuXtiyq`Wxk|2qkrY0_ZWV{k%s=aoS*$4H!?J0eCg zzwj;?Sh3C$%}AdqM>NyX9kb~6w^LD4L#t0N?M+fbMAhSZ072nZ*w~`c7lcGUhm~c# zUxlXJ?6GYyc0}ja6O1#Q!de_lKv_e%b}47%e=Bsl-r&)HG0jq1q4c!ozWlgoSNLgA za-WSnveE4Ti58V2f{sw#DiW^Ih}rqVfbs$H?k2pKCrRB;g+Kb?%>BK}E)kmm)I2Xc z3xqZWQ>(!=JBhO2(Pi}IE@8znPz80|GzC3mPtYq_AE%g05mZ}{u) zXy{qhJt9Res2g*M_L8PH291W&SB4AsP?QhUHlX)P;?F~pKc&jw7ZiGk*0w-3*+ z;G=f9kmuefyJ&eT<~hnBgY%ocH6n^L>r;C7LN+XxT+k`hBUvEU+I`ew1733t-#DIx zIeBO$oSnzhMehfpM0S9X67quJ<>V?fAUyX$DHq z9m;wP#I;+%VIRN&dH4b+focL_>t@ldR5` zmuW%n*7SQ;(Z#l-CMb4^AmsFy$~c8hXLu9tl341}q@iOUy*DfJJ|P$8hi4EGbd`{J zNe&MwgeeY@AhQL{evF%3`x~dmhsmudw_c(@gfC0%f+nl4k9tU}Icb_O9?uqB_%6(8in9;xpxj(^P?@~oE z#`W$cyUG?Yj6}>v74i);3Dzdp2-#LS;ytqvO5IL(B&s79U+z7tLWGtF=9AYn{=u!5 zPq%tD9wiiue#288vb#rnKIZOO>ilpZ1UobtuoYn8q0Bmx=0mR$@+FlS*2B%73i|=h z;7gvPck7kNz%(V^fwf@MF?p_!CW}U|WqpT86G2TOo4+;(7^2w>FT$d2j-WVES$WGV z<%81EhFJ>cbN(Dz2x6#iWpZJFX~kQbQyj&Ge%P9bxi|q!pm`kdYeHUtUB^rDmS75; z$lDaxAbmEF2<^FDIOss9gp4LWzs^yumRvl`iyO9-Gym!i=eoZl%jO;5i6`0_fh0L85)<*(SycEf^Pz#IY*z5R#jBT$f=tj+{F`}2Jh^V_l<}p>_X8jdY zGzUhM)c$~|lQ_rYa(eTz>J6Kl!fg<+&N!03^%W z%%N8D%=SS~U6jvfH82ygnSvMB*;~VNx#RP5QoblI<<6iv3g`Cnk|!h*M_cf!tIJ0@ z!X6f-v?_-sKy=Pq+vZLP^gMan>|XDQ74J4OfsMJ(L=GgDBH}99S#6$u!nIEwfyGj_ zKMOsyo^iX_HOQ@kOl=h_CwLUf^p(Or-vK5b8Mlh&+B51f6XM8jk3!#5Sl+|(Ms@AI zq|c$2xJ+&^;oioVBQnO$9dGX?qI=!$A-M7%#%`%!XO(UVeW#HHHpykU(3)P zmq{!>d#V_NtBUbr4lm41G&)I>|19L3ppij9>ky z(Ig4=nlT8%u6;bkd=%;J#Cln~;sir(tDYL|v0Nt{b_}a^ZEc24dEj2ZXt?1_K|}i9 zhOMQRe_0eK2b~q5nh(faU$Gt^lHZ4xlJ;xn%Li&_?A>iXET?{AS|)I^&JVou0AW`?~D$96KxmYU#5=H(pH8O5{j$4DhrD+TLQ$D`+N9+xC#R3>3kH z?sDfK_>9rw`*B!gWG7U4R!UlZnyp0q`%fXf`sbxR(kvKs;jTew%SpEx$&ypPW>aOY z1!;gq`5ib5$UM}lp~j?FjHN+g=Y6S_yb>$B2K>qZnoE6*!t+EVkub8Jva_vd7>HEM zO5NF-V!I2IP%gn0Be$VO)l>hu$kJksqCONX=gf{s*(2XkBt@kLJmLcP;YOX#Ydtv! zWO{CR+7N72Z)u@XBMnloc!XV?&hmDavT#Ejb+P#ISNvBruOpp$eYJuTVP1BLv z&Es2nRpYT88DZbo;2S3XrHl9x0N%=1dQ03Ox=9;gyIj52Ryw4-%tA-eI>Y-t7}#q^r2E)QIYqD32UBxz|!J z`%Hp)lx*Lb$byR{=pVg&9B6W&b>5}UW#kA!_3<=e;0XPrvQu4iV=KSRHX{PCWDXN7 z>)75B(T5(c%K057$7I>(ULujxNd{Lalj%0rNrUWh8!J)@cfZWh5(Fh&i)inbHyB&k zbjZ3-ocgT!#{A8`>|B%2*g2tY@u{9_&g}4b53Kv-%n}Am^O<#Xt1$1!8gaPZ3SZ6+ z>oqab`6&189rEzC?*0x_`VJC?RMJ{|Dn%TzcHF`!b{Jl$Y!SpR(tlbt6HZxB*BLNR zo!RpZrH{HJp<`5O1T^aI9TKvg_prO|Kx8~pW3K^nHB%~)4)vZF7jfGhA&ZGjbamUICEAO2)GN_^Z_GFyjcMo2|^7>Bibd<96MDxJm zOyA{JO3%F=EgkF5j=~s_e@6iTEFU^Iw``70-6P=)l)?CM;oQ2e7J@4tcy@`YGDey- zh%~j$+O=9-=_4s5fgE&2%w`g}b45svRCnDF!TV5-|0(+i@(j%bx*wQQ+T8|M(u~&C zF+z^t%kmD>KSG1IylZ$e#e%8sn$%>9NLttP2&LL;NKN|hzRa11p+_morC&F$Yl}Oo zf^vMk>hH)?1b}@#4RIlchKKE?$qP>-^Uxf=sUli+#l?wJ&pilpH_R}Cjp4;vx=koe zx)xee!|`rFD%jzgbtX`+2;$|Q|4`}~3HEi`*kjgBB0bY-VKpRb+^FHA_uNy#vNn2a zi);laOxB{>!lQEOe2o>XxT*PvqD{`Bx@{BFR#5ilc{TkIKk~po|BH`#g}JT%W21hj zEWB%#E8=Dkv0cI6Bm25*b%GMy6j^J=bAISwiga_jY9!-Q*nvqeaRWplyO77`BB`y! z!zn?9;cbVTLuygoaYEizLlmQ=lXt3mvlk}Uyj9*Rl2kUaJez~ir>W;^*RF&XP3)UD z_ekzJDYTk7QebB~>Q{6@Mo1>A_guRnl!g)CUXqP{xi$GzpZvI81VsBQECw6uNEZwB zz)FX-@NbKg7JR<}wbV*v*Ds?gkmfUE`<5v+BHVb*KWt`A3Ba0k||Hp z?2|f5cxOw9AF|S?9SJu)zEhAZ>b1@52ausVOh0ssm>sgM3CfkP$n0S zC$_vF5)~6ES}J!^nKo+bHHdh(mS3>jEPh^0Gf*#a-CypPE~@3fWv;DwIkb(rxY&2Rf*-e{^Gs1KD1601ye7GZP2U;0jERVjTgEJU(@}7xycA7ZU?O%q%P( z060E8jrD{7;?6w&M*rpa`(joLKpXaYZw$ilb^p94&vlDzW@2o6{v!XDuP#$ZMMPLy zEb;vh(Q$=>+-L{lvB=B>XqKsl0T2U2g9G^d1h4x&Rs_iUt~u22NLn3b3*aCBS3T#a z@VjvJ^@^bI)kByP@JpK9c4Q#s!wvcVz}=WL8T+49YAnywo>(Lf>_P4{dH~2CG!>$nbd>={aX)@W$W! zO3U2r;V*`>YyL=&KELmKjPu()&d+e>Tk)eFUfxm|yMVtnHarOiu(<`i^LLN_7qU)CKtLyxJCLPZ{8r*Bda#_Gxpyv+1v=0G1K<}t8A{SAB{s-O& zXkPj&c$fJ2Cz=BT;0fxt;8mi;AAvbQ?;U>_s6hQ4yr;?HE0_cDDeG4>=QcD2ooB4sx-1Tk&(Za;|79M<| z>wk#vdb3w;bsy4)U+J^hjn@7K-b;`8(P!P;1MtfBA8`N25uW+o?!Ndk+r<2!-q&vb z;H%!EUd#Rx8hG)8f-@)oGpWDm^$#GO8GO)3y%zg}F!i82f^dCLeb3(8zt!#?iK?ST^{P>&<{InItIaQAm}6&&{} zA*_YExyOKi(46Z9N4n9-qpvooyGYqHi*fSWN_L9aG=wY4taa$L)<5t7oEYBzJdu z6Rw8B6iLMBQq_yr))X~T$buoqD__5+B^f6V1WeLhvZ8sOq^-dJUOQYTh;*P3S?0BZ zzhR_9rNsUe2BI$Gea$lHQ=0efI42Q^t0>+8@*tvRK((ae6rWBDogSjFzymwF1R59Z zF1k*Jd|Gu#FXc`d=hCA%;$D@0IF9be^zGo^DcY(tgd|-xIlT(SrZERW5@LV68{Oh8 zcP7t}bguf@?Q2Vbf=hML&bY|i@o-C$OIgMKJHF?~NAG&muD@O?iEU#Y*x-UJ$m(PS zb`VQOWr}znm|o`2i5q0}J4zsO6wMb8B$#X!_o+4ZVJu!21We7uT-c_){c{(oZCkk$Qo~<{4j5#gcyb+7$Emn09yKDB zV0a`N+2L0k31!@06w3wP&?8-bpZRxJVW+$!(u(2GC=9@``v;Z&c*#FCLBJL6W zchptc4CB}Z^=5ivER9G{UxRD*;Jf+I+6-AIr;0_7M5u<1caS}p9lJ;VRLH~1^D^Go zZ;0VJ{X!&!d>w)N`Mg3GyMIbJyUqyiVIc+3(X-fGOeS5HZr`cTPfq%7C&FVVXNay$ z{MBldxFu&x#y5pKgWJ68wtc+~>+aI^K18bGvlHKsF2vTx&Qhd+E)M&HM z1uehZ@) zx~$jFL`P-bxsHw}=OdO#(W2vPtcu+h)JQlfrc*ZCc>mQKbLfKUdiL2#;iSoSprWx5 zdM>yyS2c+vo3Qny@Ia|ciRk26l4jc8yta;Z8>rR*JlgCUg!WzFxN(j5qAz~4M_H8Skv$zvt-05u2Vyrr=?e`Z9k z_|o0t0I^5o^>8h`lO0o11#uK6rIxVn?eZ5q7ICaHrw@-^wb{P26(zX&eiUTw;kNJo zW=9sxQB_f(wKi(*nPL#rK2Emk{@66%8b(bFqDP@Y6R=uEJ*dh&s^YS7->F@c3JCRQ~i5WF7|X zffIk>NY4zbtR-L0T#2qF$#44WY)XcRU0;HR3R&!#aexXsp*DR)dOhb*S*8}r`9h6j z<{8qPP;4ih@l-tlY#?76ePpn- zLW`W8Ktz3NG(JYDZpRBNi^8)wMcezPI#)emUg1N-@H$nHkk8V;%WN1s zM#gERaHaGmQrIFyTJi6jw}VfUrS6{91q&a``mKyGfK8w0={#i}Sn=w@%gYrUIapHy zstLlF6#HS307Ute_~vr2o$al9JUMabU@&5)f)R2NbZ*W`(EdeEG7v8&>eq3D-1t^T zJ1Zr`uQ`p?(6(-4yf@I+r`F-F@+KiZyCR>!ON%wAHK@vMrP{XED!Z7OnoHV#tV-2F zDqz{Mq@d|{Y&R{>b(>f*LhAm?FT?I{1qeMk-|wu4!5~|4FCdl0mQtj-#5jB$5-&1f zoKr$<(?(7!7STjSQIlHgZ@W_R^-hAFiGQG8fu$RWIHP;VS^=w<@Wi^J1vRX0s4K~{+&PL*ni|TUfaTfjr<;9)}8cn@)C|BX0Weh6h#l#c&P zTy7_Ox{RHt-rL`(SkJ}ZGm$*sANgj=9R@Tektjc|+VG?icgNuNo(To8x8lH%!wAF7 zcL3+>ryg9%(U-pM!X^dTV}o&9;xMA85gcz;pMhdK$)JsFhwL2 z|9V7F27hD8cVhw>oZz=xirR=tZKP^{=j!LqpGJ4d!LtoLn>qV+Y z8}K_Ak(!_Ml6b0h#wAW7n6S@okh9X$g4Slunxw$kyPka$u+FQFq~Gr%VcHj_8S(F~ zq;h2%YW-#L-V4lpfL}t5msS5Y+=rw2VPOo#U+;WG5Df75*)pGMQG!XV-pzIH^f-3g z;YIX9B&?v=pl&+vNtaGDNhfL%6jG^UPlBPnHu>L0ONGcpO-Xb&NYv-lMXBCA$dRf+ zC6JY;N8q8?1CCy1Dc7vNg?JLby($x-W;`DeC!uu@Om1p!7{2$cdIXC1UM-@tUgw%z z5Tj+{0qMHLcj%J=V@cVRYu6F$of1*=Z|(1|T-BzqsfqDSI~f~4=5Z2Cv5@_jZHb?9|g6g<6*F_QDnl3G2pxMhxh2eCT%bKHj!fK4WM(!BOAPrM_v&kE~Y9 zJ%*amL&qg2KnP(VZYS}?EZXkp`q-;k+3W;1#z$rY=Sm_yXRuh^jqPy8OO+nUS9htS zMO!zoD>A81MS#E58QGfbNFm0%RlM4|C+u!*E+im~3vaL+)~xLw*HV67UZp)mGVZEw zS4-hkhMZ*1XIq*DnAA}596v|?fHZ(BkfXX7yVag3B<#u4+o}~f`}oODkB_KnRL9D- z-tFt4owYYahJF}!BzC^TVC7UXKx>hQt$&-NaEH;;G`jJFTYpu<(Xe2CSRyRXGZWHU zatgWj{*3X#$47N5#8%?vcGL7?Jd=!xYVnhzmi;X&JPg0ZTI=cx#15-?kWJ7ChGsBs-%f5r&#j0k?_mWL@I*SG^Mgr25>>J=ZKAkAeLbuJ>Z+bwgf*hM)Mo}7UBkY^ z{?S#y*o&guf{g(}_pF1Zd4!L^*f8AL0FFO067G;hqLCX|mTnq)g6Xvj0KxX4fFVY< zN(FAOm^?*srD$SeGuu$>pA{~)o4zuLVLPrV(?|G}NROi$2z*_D@v3qbfz`I*{A%4q zls*dhxAd8E)wC3fZq8Ms5BcV{qQ_s`HX+s+8=V04$o}{>_lH~Hu8xWhRqn9^_W}== z?7&)aq<$9IH~Lw?_GkJ8o=gO$4#x_E``RkLYd0g-uhI96kU{yGW7y#IO7JQSL0qjNW=5k`ag$j)PQ+lgVQWt6OA?s9gN98*#=)l7b{TiGndlmJ= z4&TUM*jfa<>wrn{WeG6To}r^v!dPj30G}4^J_w$UYz$^nH%o>v8J9zntfO z3eAsQP#)u@kN)pWk|&)`!dwg2o!XzdqA3RU+D;b0;q3QMaJ7bU`6k4UxH+O2lwt=y zg=6a%nft?DolWQElg0&A?6k`&S-m%=Q`Gz73p~3v9$Q=G4E0a>Ku*REU*z$ltf@R} zZ}SHB?Nf$z`>P~ol%uN`y3d>%q(UP&`G~VyL2KURM=wqZ6U`x7GSNx6P)T2&^QFK_ z>EPTL24_zPD@B$2rd9uF&!1oSH#ta)^I{7Rd2fS=kijpB+LXFK>tl02IzG_GQm-|g zKZez~SG=`PsD;zYpv9=ahv+8t@8sp>O>s*2bmc1y?n^eRj zejv{Qg<@zt@hWY!9s~gi!x{0P4SC?>TFW?m(3RCOU8ih;T4b0*L@n|NA@?TBaWan( zOx#7(-wrrwSoBwprwh5jCQ^a@x>1enE{)1F&TD z&dO(n#CM2Pbc+;MK8iGF)s4g>{?@a3Fb8Os*Pb`1QfmyPYg%|`qQA5W^t}1!RNhLy zM2E?`J_l$%tBJ!hG9;Dx-H>BVE2IX2L~|K%rEb{`Cbqy_+w;m(v<(ko)rEnxGxcD| zI}n7Q(JDu$7dK521@$hk;V=H=8Gcq43g-Cw|HOS*`u`#0*k&Uj^&hh0DhFWH{z`Oj zFZkc>1vF)ba$A#t&6v1}wpEiVr%cFsj1ZL;;b90^8#<~o)$Wh!i{vZxe!S^rgA~V} ztZ9zvA?rs_VqN~)jx~m(H){w3eULjP7y$IwUTc{ll_-CYT;&Zj?Re92kxzC5DzTj{ zt*bHE;!d?pM)aQ8ZRMmE!(7-im7T6oK0aU3f&TUc0(t(3(R{Tv$#JEa6s26RKvVSE z!G6>cLiRgS&#ymKrh0uA45CRcE4keXi)Eq?R;I5o0F7(Jga~G`~B@8a%hvv{=Q?4@^wu4^ULYy3Q^HyE4N3|AUycz0j%X@@?St4 zFE`?Sg~0{D<3O-Jw(wz-IlyZp?AO?qiP4v@Ud#{=KV74ERM(+r;~e^Ae5&+XrO=pa zAjL3a<026gR({4%FWRiRU2P_nxW7ZN>L9q+SS)1q2=PS48M&)ivSznqkW|*9r6+ zsiy#|^G$SDSajPo%Z*I%FPTkoJ~X6quggD3WXt37j}|~>o*cjW~BKgYr>PZv94O?eUxr{E`pNaIN_?yz#f4Jo z>Ws85Q?ofKhvvP_5)!)E(Fl6(;`G7H(v$_gNDo!d<@MNTVy?8LZ4iqFeVkR4KbS|; z(SHHY4j2B)xChD21L~1u?YqkTu#FS5zoEa*@B6}_Eh~5y?8(SQ9TGyqvqJDJXDC}_ zvI`!FQVaaZnEk;Gx{QITE59llC$N|~qGkW5hzhF5yR=HQsXTWBNV41Z3XCyUVnkYx^risF;xEeCYC&C z90kwT?xSlaCf!UFXU!Fzx=c_-px3vq{(+tlGk%{@AXnrSJJ)EuyM=kRYZ=0MF3}D; zTGsKwLgAfrx#_d7RRY?u`jWo1kyC-1t&rYMzlF zWFpbrnM1SOHNLbMLYV6&F31fJ`5_Wq2~G`-LLHxk2=oUyyeXUV0`r4$R<4LF@pUwA z#UxntX%vNf#D2JF;9ByoBYg-pUpt^M1TqeGJROB^z+lz?gtT5%Oq3yfFZ#S&_np`` zr(M7wPFSqNSc&oLU{?jb0~-s2lAvQ|#1Z_c%1-eEs~Pc$4DrM^6C~mDkR&nBWmS!B zC#GUH4tVcqVoXq!uv8X8Cy9ut1Hq2NgXhlP`!xvs&ok`0q>jjAh1~MMt^ZGNXL> z_KU$$5#ri!i=`Di|7xbsDR=4WJQN!d>COrNG?WX@8kTo0)>rg&z6sUUuN!6-HuVdh zjQ(0|N=RI+u7JtmYCrBAo4yrUMBv^O^MaL z2{6Y-wMix|6Q47}&M*ev@**8IdK;b6k;8?m9Aj8zYMEQp?L%NEByS>JP@K(;4V;;N z*-z{dBUmC!yth!JVomkh-XTt73uGbS*McV{)pqu`<+{e!>Jm1%x>#khSZ6gsIIH3xJK(_rR3xvsP8-!EH|GDBZ+w(u+4S-#1q)KGM3Dtb>NV&o`TK-$<0)`Cq`XX;t2LpE-Xo@zW;*?WSzzf)|Y! zbC4`hB3*(Y1v8kxzmh>Txt)3cQVjRCk|$R${Y*CuK{pfq{I4uAGM0}5X9(%Fx`@@@ zYy~`i2rat3u!g7CM^rkPLDx++B4Awpy#p8HY-i6S_adMdUQjMQRgv>eUFQ8BtTk%; z*}c}A{^o#G0lSbohIDG>94rOZxr_2tl-1HXW5$FW73WrB80Im`xeirW3`(hJ_2(0$ zPn7i|VOA%3f612~hxzptkTM!-yuKZ$RqP~dnhx5W1L$v3ui;-@GUTQS^h-3(<J^HDlSYD1KhJgM~jNr?TielQi4n9gtQ}uPqJOB`1Z61>rCEGL)Jwmox+^E5{mX z6diP~rD;R8*)Hf%-BRNjM^cN7-9SZDy z)ouAQC)Waa8eI8Yod<3oCK>O&X?OxP<*ZECADV^EAFH_K#vNBhu8AjDha_%Ml&`5mz^6 zN2< z9x*2<@PH=K$sq$Z1Pr;1xuGA@g(l{-5Z3U+BV>F>a2aphkY8F6H4gZQUd6ARrIaJd z03aa~iSZ)w;vc9&Rxw3^HlF>6-+mpDCOxccaE0AM_WD-wW`8LyO}B~wQKcg9WhR zTPAJVwt&+>Xu~(1zf8azH|)c98N`P5MMFCP3eL8f&hO(L2R=Wk;}Qmq1W>c}dQ-lb z7!>%5cRr8RrAszXzkT#glat8fGnX^;nnKJ@DDKO6{)2*kQX7N(!29@yEP~u~)?gIy z2I@H-@%B>NV#?BZ>olTu#f8yle=YI{Vl0o#oTz@ynXI`Q(t3WB*R8iK5n)GOeYC~# z3;`?a3nyn_*Az7LAg5_n~tRS zd159kpC@61U7lN!V!0QJS_X(-I0A7``qcBnRVhYk4LBzgD9-xVESaV)@l0v>= z85|366~dr!^BAkGwBAJr)&`TD*uFAUaTi5zhe9^hDQ~T zC~K_cOF*;3bG96i`5GZ)MwKqUO~}pv{=IIHndfMbS;|KHYc3`hU5+?_2y}%n`!s3X z2r`6LHv`3mN-GoV=2rFVXt6Bb8>0p}%|1)D?&|277eYmV3M80P2E}vKrCyh>P{KP_ z&ezbN0;KoKM88+LVpCft$EfYUKXF7pkDxU>-S5asHLtrA+o>5&dcJoqvx=rvuJ0js zfJ5Hqs&LqY>IAO%m!ojQa}3L?nDrN%p_Hb@OI}W@A*XOIg!U2(ZFTjMEI79+6&*Cw z1!riJx#M-HjY*!d{h21?lDn@~KieH_F(mh#Z-jIpgrd zhY&?K4G>vPXsgy4)j7}fzA!T>DalK?Ro?QXJ4R1`# z$>$C3UTZv_S2oY^PXnB556U0(L+BQ9vSkA+d2G#1aaxgee`^;_CyddYqV#oN^iuA^ zo3`f*z$Eb9SG?qde2+k)plABH38OdxU7IW*8)ifyC-cdlejA=xN5&ZJ$?aI6>N1Mg zvZcId#rHC!0zPW#yc|sf{~msOR*lJM*RPla8>6Q!Uc?MND6spjiq7YZ$eC2$SDo+2 z@RD5u#=8f_4Fv08beCStYC{bO{C_Mo|D{D^dgP0OTI)Nx2O8?v*;aA|k2b;LVg=y| zct@`^r1*X%Q5Q5IdT-rwV3$&qaUfG;+55>%k06{6+4Gyhkgpv zdSVshv*y0J^2rfE%AHso5Xebq{bLN3;si zoT7GL6c?JJsYAo)pw1)eLuntZ2>0l`GyR4S$nRU`X=l*m!a*?#lXU=M!kOr1ERu3PaT;sb!OD%>OwuGAOycwLdjXtSv zkLF&b+pnUglSz3x1c4VZ1))YaFz z)4nH#cI{1_LPv*<O4eE?+LSMd$$*vvx>H2R*(7KwVkomQB)MN_8Tu}K z4q08y1Nj+}=6O%oohiU=yh?Z-$nEjC-Prw`m1u|Gxm>gn9DHjxTKHmWktwYh_q=jg$p&Mwnt8 zMv!I&#x;_E=7&d&?%GhnW@f$9-%6eNh6xhnbFFk$TfaZcUY4@~mt|J<%)K2sx_|vC z2E#s%UWy`z!*TrssF@82t%NElKZtjaU_yrB_d_)@vuK%2s0rI5%4u&YvdFZiB_ciq z>#`_Cto%l?VNLIbQ3{a09<__TkbLO^>+4yFr((=jsePM?C?R1Y>{)tf>S0$?dKbNO z!#wir{#<=~X=LOX)1A7PC7=VkiKVTK@g;aiYY!owFNkJU)BWdNc_6r!gMEk{&6%T5 zm(W_ubZpX9^gKo|ge}qaJzT%AnvHQioO`Pv*Z%L!iBonl_1)E2Xy{19uR(+l;bdv0 z<{+Zv#&YhF(cALh4P)!fK|Hgk;V+@P)i#Vk#PzTbgVxRNJpSd~x+R}yQGzrfQ!>${ zL);@%$E1bedlV2dY?g&K zNSJ0=JX}Ir?hH7gq3`W~IMZR!H36Za_O5Cva&UD|C$79jktPlt1mrutn{mhvS*Ye1 zyU;0JVvL75R4s~dz|rtVjhL3O66VDih}f-d_UJ>IY>}P(aVI>VlD83d-@w30M@pUQ z_sJ0-7T}|fT2_r`;j9_8_H+nSh+`la7DrMv2O~d#HkXjhc^Cw<31B@wo{FMXkQjB1 zx~L^!kMcS*+}>$VgWs?ntw@A>Duoo=VNo6jrg{DfO27)TnHEr?-T37WT!3Hg?&UEy zyW+>}8Ss+#aris=r(6JIy*(rj7@~5@#KcfQ-Pp01<5k>WYJ(l!E>_0ph0~uiY!pdK zTb065XC$+_yZso(Mcur^L^Ip>akD({wJ&!k4CQ`M5_J~kA<>a!?l}c}I711<`HR;O zX3-76D&S%nmQ6O^eO3c=Lb{-~X(VXK>uGv&X~^uHoblwdxS zi&3ul#2hI``NGs)nyX#&rgr>td&FGIBB=V)g3{Q2l7`Y>9Rn~1p!@vZ`2@(hOgP8cQG5xU-GXu<90u^@28lO!`$o5fnUPfNcvX+>$n^WIMD9|z6uRBSm_q3J2RD-{ zaM=i%Opot{dV`|Gf2}A4Q*x-DO(+@9(}D$dIdBhiA<6F!$p+ zetXNfy=UAQZ>cDxef^WM|J?%QO%88u=1 zi8hjpz6%4sZP1O#^CFLB`4%P8!Sti2Ktr6=nI<~xtL_bVDLZu^kw4O358>NH++Da} zLd}OzZ8#doi1A5fn0+#8!Bw)@_(2~1#h0rsO#FNVQfz{DfZg4qvRQk3`4TjdE(62c z^bRP56R)0!Wr{z+cIN=R52fI}&wJ=h1 zfl@h*TS3P?*x5{wJQSJX!FEwNL{3>OUx~|{BQw;}!lAe$8P@k}YE2y4&DIH4Q z^cIQql_m2?*5Ef!@udL;jzW_NzT_vYyuVue8i}(us5B&SvWTc3@AV!U8ill0ZtWU% zy5mJcm>B!q!fJaZP_iLZ%0MVgEIg0_R`R8P+mR~a$@)!f)SO&|dQ_*%dnu3- zW0!D3O-%S|w5tv%+loNR)5yk|YMBo^lA=628JU671Rlj58fSY^q+}@-@vVVMa1~Bl z1x9Z9i|~9F4bboj`w`C++I2^$e`M_7+*|Fbpj$$~Rfa_gg!JY)c`7(!TWvvR>WnIl zr&Mk(9zv4K`z~GQXOT3XP?l>1!Hg*}Of9`}CVLni|3r({<-L4twU&RdWixt|GCSU* zNoF@J0L|2ji)uE@tMt%oEw}*I7#rP@K@_y7qoha~F?K+8k&IG29MbS~ z3&>tEuDpT66=GlnwnKR!A+TA!8w~(i7y5%zwLQ!i+&N|~wTiv3gr7*Fl4>0Oy^gs@ zYY~oEZ=IZa|_+#)`4cgabF8$Ikj`UF1#)3yW zF2xSZhwN<Je&Xij?QVWC}7}gNu zQ=^|$(Q)5I-2q1ELEZ5G7Ks>jhA?V^_%CXsd&_H($1!EVl?LLJ<4zO(Btjs0H<;Ix>LP6BLisCt>(+GADBVB`~ z);ID4ysGCSJJ9N1FYTyDZwlV@d$^}KDwj_be?hu*9DYNM|B!*$L*uxv7&!plEhl(> zu2#ZEg>J0U^xb0R7fD0V`ps*5LER7k~Jrhpa`jpkVSRepp?ruBF7%1a8 zM3_A-#UC%!i5exSw}pCz5A(h3K%dYcQSo(Pi|JnV@a(ZdM-@{Mn&YJIVN0Nl5*?e}pglXvRvx`bf%$D6t3=B&6#1#tGXAix+O^G& zAJ&^7Qa(>}kY<5$EMXMFca^>q6wpq%?l<%buVObNFFn2?83qzaTV&s(RwI%E(tn;f z6k#Qwb9ze@V3zm<{x1Yc57EKOJKgGxmjiv>vZZ%9rrz{c5Zu=(B z5eO+ipxpt@RnMT2w9YUpYxabh|}wC-hwq)g;+%YKM7 z(wX8_zWti|om+$)9h59)%gHY;u5#;1BlUX5&u#2q#3qLiUkwPBtXdoXE|Xo50yyla z=n9|fwmKm9>@-zh=VOP9BVwD)VuXLZY)eR~8)m*h_L~huL)9K^$EhT1H-#_uZU@dv z0Uv5+b70KJ8f{uXgq3OpdZ>cu#$AZ_m!*RF11nQ-WVOpKb04>A>mt!)MZ7)-m3KfQD$GpKE!>quday(K&? z#}L+E3C$7y9C`3_wqYpB0`g6`z!cuMpfND1J%c7LtKdss(?lia_!G@~B#7O!d{U@8 z7$2=0jpAiD(sx|l=68&ov?km46r!k3(1nn*`ev@DpD^y}W4J^4!BsYA)yHwwd&bPx z;Dddqx5>@HNcXrMpHO^k*7PDBov$YGe%i(9UVopu-UrWd&hHy88QG{tI23#o&?EA4 z5bMOFx4}EbKIgbo<<!Oi)<+&a9b9|N$W1ao{@6FpsR#e(e5a z`A3(pur@h?W7?|uksz4HX0dg;9LK}QSqwD?iq@l4PzFy!FkVmLVVW7{N##D2bJyTN zO?Zc~-e76Ms-KM9C16K*Fiw)SxRHvX4FmR-BC=yCq028gp zrPNLia6okE9Piul9()m9ZULz`*23rkGzy+cBk4+URVAVE&*{#&tB*Nx@@l6QBZ31t zaC)DACsph!gSmwWze+;3Pw}a$8ZRXY9+XKlFCs-ZYmn6NNzX$^2Gp=55xgqa`g_jE zg<5RmF47vr@_VX_L9G}cT zLCc94^q$pFIJ>%KOJz6+N8dx`VqYjKzx7mpIe^Qw%E}KWf5Lun{2tu=bTo2yJ_ zm8nlu@#?sKPbg_qh)smBfcR8iT8spUz!mBikp#r)Q9f1Vto5CzfRa&UZx93Cms#5% zyo{yMKlyO5J9Ww`5#UC7*5SJ)pzTl!%#B(ZX42)XZ3=8aFvGuFg+&KFI_RFq^`M+q;U%V6F&d?H)oBKch zlL4QBnT7tp)32+Yo`LPZ)BlD++3D$7|KF~5XHezEtz}*cn0ay7Eqb9;W^uO{dT9DV zd7&Wi`K=i6`D{c)#lRv$n8?PYBJqd(?w9VDuAd*xofgx7J6T_s>|VJK++pduQmIT%W@~Y`&X7Y6VsS@f8#ds&~!+FwDxhpn*bx7yH1y${xOA;cJojS3rS+2)%z4 z0%iJf1ME=_j-H;Mj{&*7Zd+S7B9C8y=hvW3fS;J?d4R5=-|5j{nHYIy za1XCR7wxNroqgBxy*#@hLHv8TFBm!YSY=$hzE1wt^_ct%D3Cu$RnKJpP`4{<0Q}r} zeyJZ+A8G{XpKgrkKn@PBpaVVz_FVonzl!NuW#t2bSNv80{nn`({@TQ_2_N27C}<}j ztsSt>Yiwxwq`5!-H(;Mu+;HJslPG(A2Y^oREQB{Tj4!2C04)hl4z8j72)L^~?}=e; zLIf`(*ApKnrZ{`F+x9;_&|2u5!_O=5tU1-4@Ahe#WXc-fHSu2HA9j%uVQ&D^0WX`tfc`zcKf38V3lqbj*M}c& zuQwk@DogQ8YzZfyOm{s#_4Ty80DiXc{+V*{-~a)^0swm-BD;K*<^cwNE`Xn0Dj2pG zKmdS0&R%}fKj*6#bU?LVB(Sc%zZZDxW&z;WzL*pVF7x_)pwkPsOJl z@y|^tGEa7vZ^xSN#}6LcD#r2QhgvU>1rNdtki1)4D&S|25KO+Ga3yS0fCuMKiy8)C zH(DU{=5@~GCO#l;UN0W@aX$D%z`A)KJp2b=(p%8Q&)*CrXxJskJO3|Ft?LH=-k{(Fp%5#Pu8C~^udL#ukME(@AjK_z2}=1K**;6!P*luZT$UwSeSgA6BkD1 z?MvhRx+XPIsw zvrXs7oy*fIB(v-3_)^7BKSicV_(nn$^pc7B$mDi7e2ck3Zto}(6C<{~32FG=^;x#m z{V74k67nj<<@~v)=4xuSm}t#KeLEGfIa8jZ76Nz4zKovptXprIWok+xqD%pmf(sz7 zwT|b#mA<`vl78QeoHTuun)n!@5_^#Rz*qRDP5{w=eToF%F23a}wyX&~Pe_IuB(4h* z&q?)zFjo!Ntuixe`&~2Gu2j`dCcI&+bKnTA$C?&#-5aml>VAlwMUIkg zY|;UkpLs<2p+EBPpjWzcb&6o|?aSpoh4+|$zr8OBi)kE9p zHia0%wA)0;nw;)4h!pV#k{*5Dfs z8{!;^;%gPI#nS~{p4y%X6Tng&h;2Fx3N%kWIBA^OWyP9n&fDZ{CWUV6Dr{3KQ69m{ zY~N}4{_-__NvF0S*NW<3`^GZB(sURzS*Tpbw8zL zP*IJe*?itR5ej9pB)7GU5~7+FbaF&CWtammU4`IMEGMnQtq~vBaz=}PQ=-kyZvcgtj z!AKgumPVbV#l`|{TUoaSygkQF(tAR8Jq5>l?yB8<_iZGUS@B^_Gn8v0#D>r}Z@w?t z7#)LikP6QHpg(oo8EL&0eJVP+YzqZiIjY@YMa6DjTyzD@xf=OZ^Rb#tyB&DBA7+?W z=}&U?KJii=zw9U7-y2+snskJr4Gn`aOFUf~sOE3DI+;dl zZ)JOSQKOj5=xqe5+L@TkW|M>=&ob!QcDou{2kSX+`lE5!Ep7m2hIrAqq$X#r8qdBE z2Xs_`0Xu|D`-)sgTsK|blH8nJ0eKo}C`CIFjARv}gvHARaEPYgA|mFp%bL+5V>Yf* zYO^fbdgZU7gq%VJFOcP(X@w6sHQz#0(?33-&P|G_BtD0mu~QNnR&cJ`N{lGf$}Edd z%4CL(Zg%F~kjn0y_o%A)X`qgDq1%LHlQ2-xL3OhG({H(!y$I1^YCc8@UF~Gez$_tQ z*uyEDXj)~GE~k*_Yzdb>tVz|+J8g7wYS=QFHQ$Z9mZ`13oxcZ%@J&K6SgH2So&tpp z={t$C)L*FHcJui%2SDN~VjV8yuB{YBXKrsSY`Uzus5e-X1-dD6up(m z=bg4UF+eMB#9_W}aD(jeH^a%tgz0t+Crqjc8l_eJ*E#t)=j$)&K%TdUBR5w^7!AMs zEU}~vvzvdZqA;$HJAt_G@QP6aB2?MRK^^a0|1s^zb)%!^2#>*v0#8eBCO)K#o{6A{ z?d^fh!Ag~MgyJr#NZ3n;MG+ClD9_5_xvx6p{pc)H(7tXa-D{~nTx{3bTi(niV$d4Si3#b!RjV19Q3WnS7`1A~GJlUvO;*PRabuSF_(Vu*w+3Cjm5VS^)VJ0N%sIAZ` zJqY3K92Se5-HKEY8F=tDsmzFF*Awa;aSDq_YvcFhQc=8(;wRLuU(5*P5H`SpqKAp6 z^49nP;x{D-a07$pam+46ZbX{Z-S!}M7oL;pn2L=g)&<^u8}q@jC5~kRl@2eGCXnE4 zmG|DnByX5%Du$E?xus7Nq5?LIEnhKvYh_Vl)g6E2Mlj{oVq|{hIzkbC^MEm=mTSE& zq!?Q?qZch-4(-Wp;uaQPHhS3lQ=yYqOm7*%`~S|kL{3A>v$+&V-0lE952pd z8m1Y6qO~Yt2125CT*dyBZymmTu=kg&Pzi{!mo8Z0bDUJ;eXwldMA1tw#d@?$9&L47 zQA@5(jN7W!x;~sJ7L~^?B(&_NPKG6VcyKOS^1MuO-IH(zqsO;#NlI zP4Lqb%mZA;ZsOp}*Nij9W6Kh{PF2V`qMM@59Ec%x!gv-q;S}R(GF6RUbq((`TQHmr zlp(jOyIGsdQC#Cxlh0N}f|N>|=37);<@0zsAxftxKKy!N_m=otHL;~w8|s}X2BdCG z=Acwzk`;hhb&V;oA#4u{t8FlE_(TwljOC=Dkkci%$nmA5Ym?hS9LQMHbmMa!jD_{+ zLL5Y*oLYRmdU`l%Lr^}ab8=hLzq_>e((3(vld{W|)hUqh2IrENQ@ASaxY21&4xB}^ zmdk~y8QZC9Unpu2B9ch=y9%~%Hh=XyGs#XLC2WMo;K0MlF?brU+}cqzxskq9mrE!o z75%ewPL~T3A&fw+_p)`hdv_z`>GKES!aRZQr$-hiPYQO*3Yj=s{Deo}Fxa`AIklWG2U%64q)wZB)W@$NzUc zWjo8$RyE}I=#Z=}OwRV4n_|7Cv}tUg%;yM^(-NBJcx?Ih{ajU}05dT8sSF%tB&Yn+ zO+ZW*ynCq5o{c{pT342xF_BIGi7w%))Qf{5rw$V5Xm^k~GOKiOmx9J^EO-*)q?55G zXi60EUMPkE&0AD-g^kd|2X8F{&+tY(hp0otTAc(BOpo=E5W$w$#AXsoxQTPGN91CS zH^*>88%$-IdYp64YA^wi{r3x^Lo{%~8ViOW}mTx~b6q$iX`1|j37O{jH(@PMk z#a^tlqKljK16GBLo*<^*Z1+C@5xJ=6j?=R_l*rPqSpX&X30{#!DCj463L3voxz+ur zGt4ceez%L6=pD}L%tYAQf7yS~+j^gC+y`aoUnY32liXd=1K>01_Z zgxu*!v`WbF;&Iqg$=0Y(2=aD7JyZsbIZXp&Z^EEZ852aWFgfiHlOV zRXLl=KLLwtI6FAg2K83i28>P*oCtsLUFWoz>xy-zT2;FNW%+^*%&KE+1Z0DyPhohqLV&sG4kysY7Elj3eVWA(vjloOi3Sl~ zuNVnz91CjU!-6^{IWMXqh{oo(N<;;*!d12MK(A!{t!kc|BsEgs*|LqhbepfCWR>xo ztpA6WV3S>!4R@hOX<{U1Sv6%i8~ZVC{iTp$*c-#NCRiyVemu}lrX%9FtJWw_Q%7cP zehNE-mc*_M)CaA~38j?sHU<9q0(>PGFI(;;Mep{tlqzz1$bsqHI=zzKMrCIO?qb?K z38Ep3Z21Ob9>-}*UD(Exrf9~-9}dt?-8|~*yU&uwQBHa|lCKUid+LoHj*>LGX7}mYYax$YAqmD-cq~8nIo@7T zMoMZi{f4>fxZkYKxFOl+YYq$Xft`HMlk{OLnktCXYUE`We)dOevC-UYA-`IZ`4J;8 z&MnX^p>sMvQ>|L>3(<)g7TnG>yr*L41f|fFD5V_2IFMC48aj(MX+d`&{zi)ObbVCO z5hP5=k2OFVz9+@-dVa_GAI8ohN)%YZwq@J4ZR?h8+qP}nwr#s^*|u%legB}>e}gyZ z%reU;$;#O|8*&1x3G*RcEX%Y`JrRAurDtQ-HlsqBx!J8yO#?8iF7SfUmI74xFrBOH zP&|dmU+li^nql&C`ObVdhARIlWJQ26J#GHTvcd zO!>P3uY23C>B*rLSpoMMDIqCQePQ4(9vyL_3NKrcSBT}{+jM^vlq(j(WDeK2!*&vJ0(X9|d>($gCG}H!XL)9N6MN+rM?GH7KCJY+T z>oIt?RN2{5+lCX7l#lJF_gMP&wgdsp_(U^Ywy=jfhe*6}o9<;!^e4q_<@R7q?}9$- z?M`#6N_00VV)1`XoLj;qx>iik*dHa?##h`tHMhKTZOpEh5Zi0JlHse@1+sRUd@MM` z3H3QUfflaOH&STW6(d!+cZ@LNkt<9h$MfMneAQ@r(4O(2^^(p*st7|$)t7%tOXFSa zCkSEU;d6VE%kQi&6m{~s+?z-5-mo-;gOkGYzD{JihH(gIYx~3Uj#NA1*p!aH67zhc zHGKpyd0li{#&)AFW}kA^`@YXrG|;S#GR6EIXukCAzgQ=YBu|B_A$vmeqO{Mn!W!`L z*_e;DSGYIdFDcGTo8i$jFua z`!Zx3$_nE0Y@lMG9TNphrWkZ7Q+_WP-{d_hyT?zq>w#SKWwTY2*}~w!7q3u?FFMT( z@Z###I1S;_xEuIS*MA~bMis!EL4@LltbH>jv+OojxbdK$rLUm>@fC}d=fU*Z43~wX zl5Omat+BljEt$Ml@I{`1K>Fz>2}tMj2xdGsM4+~+8qfm-%oUnbY5r20?BurxUw7ON zjYe{|bSDk)?`Ouc$}k&rR|DEIW_IUQm63gYf)hGJL_<9a6S4Rf-1BH&&p)8BkT>jm zQ?F?6^!dvwDh+@zGo!24Wljm)1)}`;2P$mKk9E@ySpy)L*^(C@15LvX466LkJ2(|S zQ_!P;%=uwl3IMQM+@`+9l7A#Gx;2LQ3BwMZ)b*MXb%k^xB}6aCNGXD?G)i9Ec@!TN z%PKBNYD?OSpwtE5{#>PFs0FH z)L(`zK46#A(=L1f5rGgls`_no?I&h&_hvP_x1$@J2l~4;#?O0rg-ewoliilM=CKX`bq^F?slLLsQ?qMe6p3y78x7gZRvIG?Vngd1 zKLc*a%oM5&R9}}Up=E>_8{Id{uT`=8GRmvagZFL9AH(E}W5}}+shK~{k`C+bf(q!H z0)jmIHrl$9NkJ~O{X@lJ^z9Cm&V~Cicxr6(KxSy+ZhYA4|ZceuUfYpn`<+Kn#U5p zabfeBg4xDbhNm_DHvp^YPm!w!7w|>>1o5v;@K_THe3+aPw=>Qc#qSbeVV1?9`ik?u zp=IyvoKrh?`WDVsS}J72*->^NMAPfEw0n{y>7TG9T8hzxM2=V_(;1z#s~PFp2!{1dthqs6W= zCgE>bU#^!s4Tn6~zSsh!A=c!g-)?^9uBk9`bs}uJiy9{#52>9&N0mvHNt*V-?$J9L zF5&C033s2uN48vpRKLtI5qK|I+N55ThTZ+@?`j3`+}|?MViG6X8=ukE7aq#L=pFRV zeYfCp?Myt-mj`N|{sBc*0js4o*BN?l$ObV*hoMJ^uK{f_u=ce-z1fJ@aK_U52;xQc ztQ z?25vG8$a_Cci-7I9;s_2s6Ac=UtHYb{;z+OVs_b1HO+%L;!D{T?Db?0Z<5?k`n#wWn%D1t-_{vrj9Qa)z-5nzCajrC-|~ zY0pv&s%w~WUbs6bwf7GpIa0ByCV2Ywbp|&Eum$(;Sh{U>a39r1TnLkh%{_upwI4$$ zw0lp7UqknlgXUJ=1yRM7GD3YNic*Uy?Sh|8^Oqf}0^-VoH8~ccl|cXj>LF5J(nnm(AQXL>Pm7DMcf0sYTyjbjk zfhR&qdlV9?szEszRAZxr$15U!q<$EUTZ9A$u*^*9^)kLXeGnI%TOyN}WfZ63?|Chk zmTuw=0b?7`ZipLs6oC9#woz>1_5QIWeO?7VgD0M);v)xoZnLo%)ytIpGgQHPm3``o zcNZD&r(983@jJx#$|6}u#Su#3x&rx%D?GVfY)^iqWP>zeGfHx=tiv66*A1pnxlP5u zg`}8Z(&7FY&xI_@7jh`@ zWT7@sI!qp77g8WqUKHpnA5p`kLO<|hezyZ579f$0GV8QshKYyZBkJFi65+c|vuHqnzHJD6WEgt}S9Q!Vj(&2% zO9#0bkqSnOD;pk8H_x<2DHU=H!#g-(p9`7CZdTMFaYOiJYB=qnk4d&RLCc>NnrDgp z0;4@%J$xz+jWfS3+D@5j@7{CK6K!n*WHiISW#7oy^;!LSo(|2f=WeITl2T;WP68X& zMB2%&@?5n!>{NO*O5|TJxu1!jyhIr~Kg(|cHrtze%jl01;CD_)>5EswpgS4Z3vN9fyDDJ!(A7^iw+kBHdZf+&CW^J^R zbJR@7ldS5)=NxPMwgDt@C$;{wI;?CjA};_JVvFni(O;7xM`{~6^}$3bmXV{xHNVBp z`dN*W9P;E%x?-Sm$Uy`*Ny@&YS)^v~8Q&Zb^<|#0CcGss+5-8=m={V+Y$BmZBxw*@@rX<0f44!z*D%jZ>~GbYZ8 z0Sd>>;$see%8~#k=HVBxK-*X0zoqxA|1G^|W&4jVXAb87?_K}T^q!NMlj;AQ-Zz6P zCtIPjNrxvlFEBDV!;_M3ZaCv=`+6t$xOifAe6y`0HA2&0ua~&gv%j3X~!oa_YaR#-sK6D zM-d22OiiyKFCzVCjw7`Dtr~i5bVbG=n5C8_8R`)Fe9>nUSYe6$Z z2&}`{0=s&1%>SJN%!h2L2OZ@7RS8sB1A}uzI@+^$b2DRi3T_b6ss88J0laCo$My%q z0CRN!)&lZ{g^_1;0{P8|#tnkYH!*_#G+GVT9N3=L?)L`|3aSCc0P-5M4^a=)0e}w! zW;sp)sFc+o%hNC62dwt*a|H%y2>PLWRePxuY>nR?H$_8}7b9m5FQXm8(l@yT04txM zXc*+(s|H{WKduvC2FG|J+?|@9S{a!7BHVL3H3h(QaPeOs^!JMaZ?bQ22^?HAXoc$& zJ$h3)F_>IHkdi1bBE|p;Gx)lYn^*z0zOQ$ci{)pxf)D5v>iz{%ZAFl((i2ke_< zvd+;Ncu@2U;YnxsY0wDT2hf%MIol=o zBqTt9K(_xWJbv#lU6HBjry6$l@6m)%b-sVt+wL{r)j2KU-j z0KkZ!y6xEbq{aOm@Q2^Y$8GX2f8=lFgP+m+FB{3>#opcK^xNj|uR|8+2ABH}>RrF4 z+FKA%asfP-|Ce1k-CLaHzpAy?+SuRAl&~ybia$7p-~Fa+2=LtGv-n-K(jzOt))l7E zZT0Vi8T_u-dYVr4A^ywx44Zm!lR{|Zr@wD_Af^VtcmDl&;qRj+U|#arU$3k`fA_$7 zzq{Zq!a3x{^BbFz%zdWRp--T z^wqv|Li7`UPCkJ<0CF7thynotz(w^T-qJr22sQQ563FTTgkA5(Ult?>WfXNB^CJRz z!`Q&#-;eEf zUh|Zf&~!}KAsSXQhACQHMWRdr#kO}y+`g_~=i=rvWAHNHJj%Od53T(ti47%|oOVfw zZa7G@fH^J9CrRh3H7oYkhE9q=qP!wos^(AhJaI2;N{mif6ccvWW z^VnkYLQeOQ0}UK@MpgGWSZ=$Pt5&mBv+2?U`{tZBUkj2W3vQP9gISdUxyf5HVp0`J z;M0NPGPRWgsM5y&xA?b#|m9ZkJq6~e>uzdpS zH_sNju}Q0zvYiWmLZ`I+iXvK?M?^={j6c}TI<(S|Sw;1dW&lUc>SSWAou$i;{&+!| zS+(t&PDG}MaA!C6YzxokVI;DTwH9OPSlgLJiUZC`?CmnNMESdcf9-|`s`F3j24y|8 z8m|LLClQQ$;q9gNPYCA(>se|nq$(G8b8c*(8zD>ct)!z1Iwi=j7^En997}g`Bw@4T zQvBu8fzOq(b%^VvCE9ZWtINF;R9oi`6P|G;%?#GngTjXSLlwIteI^k0?rsK|)5j(}7YM+IT_bL?6?pp(}|H5~k#$j;`< z)^P3(1ti-jYPl<&Jil#ZQhuuxDIw-86=_m9+;5XKz)%_D1MQ2A%dNoMqL_dW=?-05$q`?7iBrcT(x3#OV`8R?UklF-Qku`9vHmGHFFL3L}##>~#|C(MKdc(ssoL8FW469wnPVpfbHM z?d3CJ&QT17`qF0XGlvV($3^$1d9=6YVm8YF%Rkf?9}lW2b~a%(8FiojtxWRIIOhj= z04qQ!F=3d5EkQ_QUz(VA0Fx~(?gBRN?v^_`?u`h^O_9bM+1n zb}A^iiyP_nM(%NMndno88`|wVY)iW;rarako8pb$37r9Nc5@IUWjl@p)5jNAZgi^i z$NcY!6r}70h#LsuTb?6B>F{$7;Y;#9QO!HPi)RkSc0s&%GX828>Z3Fg;W#sQ)YXT!*_X$95(oKP*lr6f+Ybm{Cle|ocsLQmHaLWfE1TtHhg%xC}NT{QS@kN($T zFYO0X%&kwW^;hC{Dt2w|;*B~zQV5WV(HJRfF!{R&TtKzDU0MLxfBI7k48d7bYVUi& zjq2T=t*xg)-xlrddZMtYqpmVeG?tDwcG;8h?9~zw1lz?c+#AAI118ur=Xjl!tlle~ zDlYEIano0u!#gros_-mlf*<)XSr)};F(L7)8!}$5(naCkrII|_mA=*3=5whTL2`Re z*Q%IN58;KDcKbT(fFGBVBEoh^V{ff{V<@|=#%%4pwJ<`8d!};jw-aG}`Yd@YwH5*O z?3fn?m4BUM-pbgum4pW4CQZTJM@20HzmoW`6hFt7*U=3t*@r-|4RMB7Nb9|%aZp8X z4}rR%j?c$dpjD~PI|-nPOZbMEHY8`|&QW#qlX39&7XGnyWsF5;r^Aw(3ZJBn66U48 z;$-O{rbrs6Q(cl^E|+9y>timxzGXwbZGvSc`v+ohmmZWEbajjZ+P|HpaR!QZ8;4VsN<)i`50l1V-wTIIarALb@I*=S2TYW5b39Ih*YuZ} zx1-m*N1}O7S;9B6V$w@kPA6C(BNlM;(`8esec~-%R|8dLRrDztZ6T7lz8@0l_sgX2 z3n$ci&cePmGCs1~$->($7#GZm_1Um=%Q?C+X3YB)TdMwMz)K5qyv2~n0AG- zM))`L5DHFANzVp6qW+VrdPXd}$ehY|)Mtl7?<7--2HupXDk$MFu_YYo{dwFxj#I+R z_|(^U{yd;#PuWrR5lqYTPbmDg=yMVmzS4Xn+9=nyVXU%qc0Tj`S<;C}pwr5lc14S? zLQL{EeKx=lhNz~?Lq1sU3v(^&|1ea{509N>>}M9MsszoLPLDA{tf6z-MX+6ixlPKk zNV_#<;fr`x(cA-k9o?`z*_SZp9RlMjC#7}lWc|Xj5PIXQqX%UZ_{~fR1UQ8SuamIB z51gUVPsNUmLACfHo-2*vcyAuf)e{cKKr_*i55>}SW%7_=p$&<ei8(?mr0&neMAfg&2!Rt7RM`Pp8z*m~@ue(aXZ zX;=UhFxRN2OMi@-X-){;@!n|d(@+WImTj33{Mc=E2UbG`wSzUmv?SN-%7!8ub; zV%YW~Ko<&k-W-{m(U$vefKZc{+4$nDjFBp)eW7{tyKI)P8KMuJMA7eeo@)cQG1r2IR9kS3n?2*`2yr!Cm zAM^93vz*s35;N(E;mjroNff)z#1dAuXUonzcIMqJAm7$}lrQQworzdF7t+5ugtMuu zn(q>L6aCia3GU0rZPML)=(AS05LK_Lj|1Z4M}v-bIZ9mdApyV07K6pYeCJxKgFD^p z<=CN78m6pzsQT#N6<985cS8Dw9B)vklWh{LRnPD%LF$W;zXGSQOer-30Zg(ag%ZM^ z6g?gm&$xxiENif#bu6O(tf_`Lt{~|{S#LO#dWy4YU?z7u%Q(G&}lUQE25wobzgr&hGTp_4 zPgMYcfqa&DmWc&Q>9O1}Z2dE|enYefWErn8HN2i`2Hz^r1X7=Se_bC1LYwm(4lrj| z-**#t4y&JDeUGMX0Nd3WG7kn9!8Sypc(ihP9mnMoU0{%zpBBBnr(E`sb@CsT*>WGo z-cXqH`sLwM5}Zq}-jpHTwUosw_(Y9FeN0%sTbJB1<4x&Rs`O?UKhXlLDW`jn1*owt zvYW<(XT=ZId%Hq?d7m?;%5>)QbZ=yj;gKlkjU9y9{(`JF{k76*F+woA>)I5XQHftJ z834(es*l)6G&B&dOC%DYiC69VZn!c7y?CcFIa+x{( zc(NK>Z`1L5Op(~8fGLdhMbU(vtJ0o*2K~2fxf2mY*vv*q>fm3z9ruwI1gUbMUzOb< z41A#NL|2q)*T6@0u)2vYa4E#~kuSg;h3UqSEiT(fWYTWLG|12qze8@2^hor9_z;($ zDqPK$rENIFzVXR61ZGAH7rtxyLApVXj(AZ~8;92(j}h-sjM3(5a6nemp3|&giuCnR zjr&dm%+o>;@h%`Tl60Q1029TV7=*NSldZq9KD0wRFVc?K*(V`70UwxcT6pR4iq=k6MXIE zop9@8TnC>^j-suJLBNRFJEFKd3cil+G?(=IdI<*I2ezW+Gn-%!a1)_a>~v#NVJR?3 zeBB+gK4sI5Eh~&7ynVm#lO*DO^zRxrNonXKt6!)s9sbMma(3ssC0fn~8{^?40=ngC za_5utyt!ZV@th;7GV@um-YKS|jtIEVfe&DJuB!lM^`d^|K*v6Ia%@vS8*2mM(m!Htt0slvcwQ-6F;R-xXu~fsRD~C^8SZuj4NMrt9O>+^8(NpcZabAYDPR zg-i}ij1_ZUy4os=?^0{ zjPZch*Llv1S&>^mVO^e0okoO7N-t$WK}XR}^hqf-mRu#w=vG<=J{dD@%aCnF*`K6O z_bOS{mhW9Y_$~FG(Q9}!Jft+*eGBl8A;30%Z^g6LK*u+Y)ZY zmI_lH(Lk3BjEr?|ur-Q~Y^@^j&Zm*p5e=i#BYN3@I8md~SZ2RehI!o*RX0Th8|?Pu z32|?Qzo|0EZiiHMfSLCf2G(#ck5gb~IS|_Fl~0ilW*AUsf13;!smWTBXYRlJ79ekG z5Mxad5jJhq&GQoBgoDTk-^RXZ-$`t&Bun5UWLSeJEp<{#db0aScmt*5YOf8-w zO{YKy#@M?AeZY(lY6I z4&oSkt&BvYqNGsg205h!kl>abPFrd|=0Q67tf zc*jMk=TP;JFc$|!HP|Btkw^C|2`L&@>GSUd{2>+cU_XcVufbdp^nBB!8*6GI-7?Y3 zf-3DCi%KJAvM2Zqx%V3_0DTc7by$%9D%KXoeHR3CCSJ=Idu|xmjgR9aX^gVDLF*y{ z0^-0{Hm}-og$eCG0v7{kJl77X#xBSec+iVj&Ea3+OoVcB&oB{g>9#4gSu1Za)@Af) z#A1(pw|I7bJ&Ckmj^6>V#XAd-?DTXe76+=50TS262h7DeKsasM&FC-F2Z36F_M zeZ=mWi&fu9cBlGNQQ+}*Vb^@0!&pc$DVsM@N-l|@#I|EJO?nZ|d~{=#i?+)Vw@=F7 z;H1@O4s94oSUqyOUdJ!TDILG*+8WQ9-gyA(AZ$xgAb_ncB3)Y55}zK; zsvx>XAw+P~+g6>v4@Q+&oChmKl&hR=bYUcFmG5uWvoSjB>&UL#e?gHA6k5k+k$1K0 z7~;Jck4$O^e#8}mVgkIWu57;6X8Zbj7OL(xHdrz&F@@o z{@|T$QHT9ni=Q1$xAgm-n0{7u(JNzL;CxcI%Do&i~9_n6*z*Oe%6t4+w4*!C!3SS=jgo1ybBpji$ zCa{nQngJihZmAnp5mibKzFaARECp(ghpj5Cn{oqiQs%VM2u&SCO;Qz67b_H-Vzac2 z+$6@E9HzJ{-XVi+BgT3poulJ}K})5lN@4U<-I55NUBrpk@-BD>0 z7@0TynfXp(7GBc4Y;}|SG)gnAX!Ie{@ue~L^?OnfUO=Mzm3Js3lxQ|`WX75*06QNs zBfxx+M(O_brF=WH(DfhErC188!ICGw42{(=hHB~ZfW&JP*X)UASB^|=zGn9QliYN= zCv*-)AmQ5#brW^p0!H<`jCTs*O8=K>M@oF1kkx0B-)*?-EbFQsrv<%yoXR!cY;4E1 zG_8qYw~4r&mn0ZJSN9dxX#hR^CWXqF^25+-Q$!AkoVZSEniV&x$|z##A+oZG#mwhe zNX$1^l~%Ls-{)fyk!6Q*0vCrrRJ6I!e|oSs!RbrA`v&UK@5|jV#wexqUNEO&INd>8 z>|bQm+EpK5^i`*AR?X@>m;+?6vG+S5C6)1gj+LZ^lbRW_O;s~F`*%1E_anMv;N#om;?e-9IiZY-iCRVRVIYk5 zRdD>wFS(Wb{}>B*P^s(7Jqd+QI@aLrT8DEjuXl>LqzUnub=-Kq5*?4Q$1(1)9_%C5 z5`KX#oZODXX`X95h^cS9f%jehdmmnG#fK~Zns-}pVL-{4Stc%zUFSfpXUVO-w5bse ziNjcKX~+-e%T~Ghha>SG2d<`x-kaAg;@o!3sb?Kj5gE<)eD}l^C3Ll5G>vkWrV3KQ zFr`0?;@DUt{>Wh%_Ku~4nXp)(bZARM1>Vhi|mhd02Hfb4M<||z`51>iUTXem}L`CbfTbj zIGwDST~gscRYLz|aE~m{Wg%c}ZVXTRMzq`+VXzfxdZ-zXY2~%^i{)UI(DPVG)Fr|~ zmEr9p_Ls0&^))tZ!V9CZ_d1!EK^f8&%Ip8y_O_O>(0-5^V&9Dh8flSJ<{i((eC01z zhpK0RR;K%QIQY_)HdU*;@UgScp@+5n@egt(Ri~mj=baxW{L8O(KuMf@ZBBJu6n~}2 zbYEI!LUTD0Q{EoTrv8;6&OIo;4M1I z;3f2FGAOO_0E%bAnzbn(L8*u^Df3U6_ipxtLRDigWjD{=EMx~J%{?lvvD;$FH$JRbn)Vza=BkTr3`Z{)tosxT@gg6;F*?t z14AH1w^-l2gLWM-+sFMcIF|b{_en^X~Xl< zBS2+0w303nx5hqHY-pu@(vnU=)90s5Ya;#CYhvlw-_y*&XmMSK-Eo@9aZGvS#0 zAA`pp;N31d>vL<(#6`S!vtn!*szD{Rp2o2Wk`?4Y@5vXv% zU_f1R#OK90^AXh$w$Yruhz)T$UZ=huv) zO^Q~|{UjZ9LMR&-HZaWnQKJMnRfG(Wm>nu91%kcFvbG*5hA&_NKQRZ@SpUpIX~ysm z$gEmb9wNm`sP7((px0&}-w4ypdE_V-8PnlmjR_7R>KdL?6*?MM|9o6)#LOBvD zA0wk|Wv7@6`+&(C|vtfR!9 zx_C5=MJrJEXanId#)W;n@6t^pakwIv>Ge|y_c(rljAdRoeLxTKt=3qJ5Tn|R=uE~n zB}B`O%;f5|Z-DC=j_ilMV?#npZZvP2nfirNx z(~`ZOI*`KAR*rWV_F@lmr|v?U<oRN|xA)Nt`Sd#-xn_0|n$i&1MUpgR&2g;a&teur!D?n#^Qj zf_Z_pC5PSSEYUY%amY0Gdz2J|n_NEi>O>7xJ5lN#-qz2g8}m*#j>Fr;+p?OPVelhZC^7hCjn{O0hyPu3@vEUX}=&D%Z6XNk$RMwQE@aG8n3hC2c`EgP`9?s0f zVBr4#_={R~F2F_@!&916%B@qgPIxyCF_;t1GsLZJ@qn|dM7#^XUBpIsjo8k=b(mVJ zN@Z?LOV*UKFO5osR$4W8c-c;7Z`N|Lq#uS=lnXfY9rXx19bJem#h}_c6RXBn*&@DX z_Ks{(J*U4K%>Xsd)SXs~HV-;Q-#>U!^yu>|#=sS*2`4#~Lb_0@pgiK5aD7^<-wgAl zUMRwCa5Gmt)Y@%OXQDzuLf)BHWV9be%NA*l%uLRKIS8FMtoqWZ!r?o@UE*g{ zO109UM-jm;@H#fV8xDFOyLb+GQL>2#8fzH=^?vYY@P{Kjo zM(`**EQ$kOpOqMiw{b7KhF`{Tj^#;=B%9_AveBEvVRD=yv9d*++XnHK$gSw8Nq!Ks z9H~7cFyT@z^{nNq*2{YthV0R4;^1cQ!U+-YGMq;t2a;Dm?|y@-VDGRID6zZ`vf%Z5bA&c_kwA2t#xB}Jm){5_jxb2v2$!shy1 zlFR$%Nbpm|mB#AF-PZJzyeAX(m^e3z^jWm%2@=u5maO*@vb}Wg)K;=gCD2CvWH9EJ`$bP{!-Es2sEImffe}ExhuW3 zy0)NPRJ=XT)&&a@)RyG08&f)-h(R%PxOM!h+Tu>N z2N9_>FRoZ>Sr5hce#}GAKzVl};=Y9Qq!zyEeme`JA!no9^d(ZtS*v6j$kAqCezmEP z!pdIl^1*|tP-Y>}#(<{<;^*;T03-{n#NogEJ@A?I1|=_ux|M{D_YigVn;PaEBTv(j zT9FrQncZ`Ovg5|pV(8{>Z=|C3plg0MR`wShPnWOtzj0FR|AmucVr69g-(C|Y0wzW_ z2Dbm5{wFBK#LU3@e|6qRw}Q$qdu`H5yNR>YU=?@$8%BtdO=qdHe>4RTMT}EAAZ|PY(D#4P{ONuzIo2=;+kfCEetO$q9A5{rNZcBbL_M*#H?Ps|SY z%}hY+n;IX!;|hwP`2`lHw*L`QLoo4f&H$Z63Q`1W>FYmIulTpS!mV(AoLL&`92?vj zobKyf>cG|3*Zu&g79ytc5 z6HYDvASs{@rml^RZ@PqIcG&hwMYvR?*q0Xp@c4f}?Y~HDbo|lwTD=;+c-LRyV)5Yk zegRcm;ZjxmqUxQU4Cfu|U0i`DBz{IgXaqlqo4^7Aw5zMDdAxA|@|gk7EDQ!8vUC?G zA>YyxuiG%R1M=dMije!Ev_a-(SD+w%z{f|Ir*I(vI@$rhy?-j+*aZ!ZfYmoN{Q*t{ zthB(3`LFaD=PvAE@Oq2qbD0Kr zQGeCQ>2A&-?+gx%K^YpH8i3Y(=`rEo!gl*Cl&7tKlScTxk8!GT0KNUpplug_jjw+4 zK=VIm9p-?4rc16Mb~Fk6Z)Ov*8yy=o{eKNV{dAxEw10ltzSt*!ZN`7y1Q%>wUHvM~ ze-?iImP}t-U3GsM0qAS6A-n-3WA~Wt-*!v6`upNkA$nMO4(%`%`(QXIi!l>Oze-!}- z(jIT)72(igQ~TtibE5uvX$}leLO}4CFdP8A*ZSMcA)mgiA^>To;9?2tp#ijW@%ty{ zLC;@>J30Vp4Esd*k{1WNf^DO@oF+@~eqEPa@&WH# zKlI)g4~(<3K8^Tw@#PiWC-xBM`;$9{4@Xy@L_N3jvO78Wya{&p6IbgPAfY|5o5;_y z2l5L7$Uja)fQ`P#4+dF_cC`Nn4{rau^#eE%5_OqaadOe!rDkGTYesV zm?Y%$HmpQ6vxjisbnQu0H{}Awr-~^%Z$GcDs&Jq1Ix!oSa=Av=W6)(-;V*ZJai9tbOHd`ZBcu-z%&$DOIUQ7|PvKxmJ0s;PQarhJ*v}&>pvteO^vn!5$}H)tKHv zz)1QI)nf(mzhz$9!#HnSpk!QE>42z!e7up)u%{X*vGDH)Q9Qaq^Ai6q2Z+p?4hS~V zmq%ArN!z8vuQONLEEnNod0B0ZrgE*hIwb-UHVXr*cSW$4#H^9gp-^!w*O1Mt=VQgb z3M3A~QQx<;YF*n_xm65JdM~!r`D-ZiW)=09+UDuG%vcQ)153LZRM5T-#t?IF`*IBv zeG?6>;91+1Si=;ms=YCbD;i7+9Lno(32EJ>LY-=e;X;DOR!O zQtQQdzj9<+eAV-ri}P?a$K9?x*nhTmYj0tkh84XGdX$I-zu9l4lF5nN8i#rArO~OS zIAb=Lmlt;Bbm$5@O(4rSxH*1~#GP?v3dV6C>WIkUMVmM#E;WZpNq$4|gADqlMWc~I zdqMVsMIMOmuz)rCS*dlD1qqCMH=&8;bo2qqSS;{umHXTaQpgq;c;tzl_dw2fG4S>Z z@o}{X9UG}xs86l0f*|ZgN`$paf}$iZnATFGG3ocskel{xzRV!`kbJI@C?8wC+2DSPGBj(C)l0!A zS_&zW2wsvO9Q`1>J)~L4WoWa84LBCgDu8sgE-yVwtRF!uiAP=`xU+(-=P}m0K>?qQ z8&wOs@Dr%x7zz^$TtUpt8^Zg1raKnImP`<)Na6h*(hK?%)$qj_NB{Z{;dIK={GlLm;6&R8 z>zIO!{eeCaDyGN8Qr{njsN|TIC3M1XmHOwfZw;NpLBo@e`ssI1*)h@MIDz1gk2*bO z;ZR6vky>}fSo;K-5l4O4MDAL5UKLQz^%L>hV1eCR6T;Ma1<>}ztgJ6>l&v*i`>&5` zQb0hfPs}!1H05_2%aPj^=sf42;oZRG4g0}&nz{NClRTx6#hfjI!6VsP(%VZ7n`h*i z{$3Fl!b>oT_mae~papc^mB@y;8@?^6BKEhs;wD6blN3@R}$_c7c#Zx?Ai@NN1lgWlSR`AOeA!qnhswcLbLCs znzYNaZvmGF1`l=&@olwrPS)tdhL+x8+avXrF2Hgo5440POcA$#wQp_wDWQXhb!I|B z>e3Cu6u1uuy`OU}-Eag6qO_0$YR-EL?N9lXCR~~CnmM$GK6-y&k@d{OUl!@M*PODp zl42f=w!3F*p~wJeV_-Gsw+(0Tz*ky(%cD%g1<&8geDchTX0+8wegBkvS8}pmDq>x2 ziY?BH-T8DxU`NoXviE4XUN2QDUUhsGZx}vd9YoT62AZ^&X{(zctsA~$yJ@%rsqhu) zj5#OMMs?Q1E!gK#AQTcV5D2>aETQA_8#upO9L!&5o@EPVMD>7Bym3(xX6aAQ7GLOF zcO#HD7kOS&;r2R$r=BihwC!dDG>#((tE|O3YBUlQ(~l)$adeAQF1pdor6i+~nS4Cw zt-|DAQ|oGV_l=5!_Y)d!r0^yyJ&i%D0UDsS4D(-vBY)zWFOCjYTf)?=Au*ifg4Uqt zRTv$x?W47}Nsht^rnOX=GUB&V|CI*y+!;jK&%%&CvlP?cgVoikI#w>StnA%s2S4Q@ z02IUE=j@?D2I~S@BjIBs?cCQL3L8piT6;$z+5%j@IXRROuDNUHgfL-Op57|!hHM^j zknZ)x9L1ZXHzW4ZYURd=()d=0Fqqq-WRUJRtFeY*ELV&HewEkK(r0Y5;V}e*MQxG{ z(IE9&Ol^osYxB-!d245MEcVKQ(tk%&7l$;9KW>=@Zf&k|u6wIZwq=engEzyA=9 zU!rnB4~mNL8ml}Ct$Z*vbSJhohy=V+cKu)ucX9jtf_7n^Mn>SAO8y#X(fObrgaSYh z!jY$8jcAirx{Wa11nET*?+*UiJ!}kp+G{UBz-wR~N80v_&=9D4#vl%ZzP* zVnx%K^-l?;4j@nhS~ejd=#6bS-eC7X09Qb$ziY_-6zSxo{=4M@3_MPT1s)r_WXW2dziWbuzs;mh^_b3_PVKG#+b^Y zFrxJK$4@PGa=+|G%_0d)NWWd|FJX2O=ZmDmacI($sFYxP>;siqic;Q{@Ip+!R}Pa4SE=c;34i0%SM)@kL2vT5ktI+bLqUYBMSqr6M=k&lV zwev>arS6Pf&4PwM_lA&61 z50UiSvQ=A6+>6*lV`adtrAfY$X>dW92DN6ba*G1b$7%!y_4N5;1DC>#ry;$YeykHc zXP{qO%_1>MVf5XiUbyPxHPVDMq?T0P@zqPr`HVtv*BrWR4DGjiVMu*7(Ft_9rA-iZ z{_bl^W|u(;Jx?D^$5W2YghLie4Z#mR+SwJeo(9d{3tC;5fZ2_776%iDWN3NCTwG)X zx)f2QZwEP}lb?|)>y=4-6r}4nR?dyzXPB?|IQ;a-Juy|R&No6>F)Pr@g>3Hkd5XYv z-#$~^SZ&$Q<4n9}OQQwi?W&q)NyRi}cw0R&n?a)}=+p^=*@SL~ zh2q=npHatq_{comm(svsH8JSzRpK5@wq^27+`DQd{7cw1yNLU28v-2E&#ShZh45Zo zNV9x$UV^y-d!&?=aC~czC31KqgnOPnX+%9yFAXFAi_kAn>6H(5j^gt_DiRUNQnK91 zSsxA?qok8QpsZ~?o|Z~&m>EQTu)0_ES_OeOV*HLrrZGTo-Jh}{ltPx`JS)LNtm5SzE%ABs@|0a`ZTJ+n(n&P6Gx2^4J2$( zwaq)8DC64S6vR)aPQ_=$jZG8rXzx`;Z(C0g={=LO8rcZD@%J=7Oovcw9&YY@<)7&qVW?}A|DYu<_e#P%$&4VI!~l^GG3 zXu(-gbB2m59ZY(!N!xs${YD^LFy5KSVR6?j2xO-y!Ir4o2jChfOhs}|Sm<+}E({Xc zjh^&olh9!3BF-8rE4nt|Mk0W!G%cm$UkAx)K(Ot~cQz4MH0EgA~SnQpYxfFOH zb)JDyzVy8-i|b}lvMH6D`pq-doNlpNex3b&2ff!NaM)UaDe}=2z2xJc5D(Y|zFG)WQ^P%Ji7_KKN_6 zym*;$0hCnrWJ>2I1xWc|Vu_7lN~iGjAaJ8YMY#e$t!+%v5hC4ZCI};E^#(T%OUk|r z&wI1uisORw!E-eQX`i`AczJTZx;pp!`_!ssyFLM$lxj-BgXPa(FM;v&R#Usu-WjHi zgN-72J8~8fq8hR^DT1%CZJ$nIfbI?uyjVe5(jSNoceHEDIQp;5D#ggXyxMm7FJ>69 zvn|N;J!p^zy}!Kgf-wNi4Of}bVR)zNzPd%CS} zY|`tik2OXPoL!)PH$F%%+i@1HBKwLkJRV18^3V`1a2Wn=;xZs%TfWIX7wub+(JCbp z)e?Le`U`Va3(>?%{4B)JW)*dTOxWpS0TxkVKkOWJAnTOq zEZAdy_r$%89DYz%7c@t zPCFBta56hIMh;rEIZ}BKZJO11nz7?_|A*lpe<#L&_c9ragWaVe!+{?h#?d4DY zyk<0)^+A&K`b$|$S<3*W83FQo1Bp~Y?TfX zCch+w{hZ(!_N~gS`E0029!vTmjYQ9s9!wXFr<-LIZUwl?;R-wkk6K}Nbbo6K_Yo`%l|QU*^(4qy%2g0kSGSiKsjP6i)X~9 z-)=E0D;RFE+5XOFFk9^QilA|T&!_N-AOxdNQ=!yMmulC;dN{E`CD>pa$}^hCc`!KN z3r3DE<+bhI8!pA-GPy2`dOu$q(k`k5Z^>;et?xbY9<6$@4DHVq`ur1T2YufQ`ZL)& zFYE0BmVjMEgOH|f_o7gQzAzQXWH|Gy5r=i32aK1wiB4Ivy{3Mdnw{rj77d|B8B|Y1 zy)m8c7?aA*@9`k)hCMbTB|^#d-nYF}Gz-DQ^m{k*^?t11x;{ zoWZOhP@rZG#%JzJ28s(PcO4iRUbl{XCwRv2gj>xO>uB z5$O0WGfnH{${*#f@YAPB9^qs9fr6`aS%>&+sFspSF&>H^K9XVTf-LNyXQlqZ>j&4p zqQGD8`Q&u3KUw9MW==tGzkYxI#elU`q$_n?obqOf`R5jS*JLlnTlp0Y9<;7yJ<@6f z;zQW8cPpqQG}B9Bhw>;4<@*tx&8-NS%a}qpDayHfRNU_0Gzc~IFd`{e@T(M4UpXg= zPY*;gAimSy9^dso1W4mqYRf3jGcp|Dy5dyGycBF2vow`D*Y&~--$L5N^rKfiJm`~F zc!x-qf<#d`?tqw|Grk-uEs6@iqZib%G2K;aix7TY$Qyq3-vVx)XiLh^`KGqC=%V{g z6-&584dD%F4v7sNB3t1cR`fQ_P^*QX?6xbNX$3vDR1kWjx=}Jh zFBKobi~iwBt1$D?gTCtA8Y^lP$9a~2)AAi?j6qUYeli^VNWsiFR{Y!6G-_Q?!`F)CWbsAVvh5Uh5Wpnmz#XVviXCLR!HIUi+ zga{Jm4QeeL7Zac4GYwa3aY-wQFL5o7m+=Fi9?*T^MnN6#*ro%OSeXoMa35~p>LXt5Lj1Oev*= z1*6YH;*r~5s4yXnF4(CB)tdLW{r915jJHU4evBP|4ags)TpskXEOPRg3L$~8eYE{a z=7_-Ok~psAC8TPybAlTbS+5CbQ`2-(D^ylDD2}>QaV&V&QQH0gAs8Z=?$0#J`o-cQJiQ<~4P zB9ZyaJRnO6#Fdc_rNbzvPD{7pbY1USI^I9n^h+*!K}s~ra;Q0M?;(u^?lO*{*%;tT z(4ZnV^nZb{{o?!bas~WR=PJY{LnzT#;VRI~V2Mb=7Cv;Nmj|ndDZo-R4J%7*JZsT7 zWN(uc1cOcumx%gGQAcu&t8(0>xj!7B^IW=UPXR0ylN?9O1THtM*0SO0C`y!+Oggs& zYPNgIvc!u)zFmNH+mfj?b4a68{;-WK?tXa0mYZg1S%*d9G1EiCc*1q}}g{+=B2a#TBLXTK1)eqZZsxs#ZwSGEFa z-y^^}o5egQ|1|NF3#gbqh+nS2^`!Af#Ctm_Q|+K?F{w~Xtfu!0juw;rG5HLA?^$#B zuOc;s6q$j8<67od&j%|N>8n^Rj-0n;TbhIo^=A~AjRR6??*+IPUN764EhfybL{Ri- zL~@mhKO@l+=^EX%)hR>=EwNb05>NcPrcqvuN5xyRoH@eEjg@u~?SFZ(wmHBNfFbc| z`|ycIyu6$11N=T5M+tW5oq+N&joQFmEwO;t7-lYgq=hBSR292(uruJYKx0{%$yTx` z$gM~_S0mq3AVz9jYN{A&k>yl?`9V6LF0yiPt%?Wc#}{M%h3tB(TbWXMG zI!aIN*L=0%9JI9$bTemt-Z+JeWclN5t`#^boh`q&ufQ~3zJtSn%zFM>P>Jo+Wl@_o z)y5zL&t$|L05F(Mc@T!wHjZfcVTdA7>`*PL!EiCECU)l`+OM9?!71WfrG6}?=Xg9i zE+9yodvOp?iLI1gOu8~y5U!55+mvvj9g;ln*PJ(V#OGfUGjMm26n@xKO&HGzq8 zE~4!}mNHMTcS_mX0qBmkVhl%b3oPyS>J!OhPWq}Yr~BfxN-Tfz9Nray+Q$&P=;$b4sKOIG@iW0X=C8-!NOj9EQwB@%KYzi+GPU80LxfJ7K@Vb4D6xxu z1?C%BytM|V^S=ACt_`~X;<8R9TJ8?WW?FkKLe$vDf7P^lgEhv>u!;wuNh8giG+A5= zRuy^^d29Xb9-Z9%zN_LJR>C4u1lNei*9ccLyLQ>EB4Z0=3P|-~XxZ<2=%^Tq@3y{g z=}{6w*|&@V$Pgxs6Yuc|RWY(Z`zV?4-s_1oCziLB;^OB%-$j|CBZOctrmIGX&F4fr z2N%_-8#kTJvI3m<($)^=X(xUP7g;M0^uALrplXF-O3j^NLpqWg%6UOljXchd)KGD# zOeoaMBAvPGXu=oHBTwuVR25h#PJ9w}EtKzU(W6P24u$id4uea{dUc}fHhQ0|=4Ye- zv120zZzqHlp}9>{w88l1?uH%jk<-eQidLzb8Em27Sak z%U`msc?%ng#g;q;2V}y#Gp6g5#CX*kStvx9S=D2BFmf$E zPw;+^;@j-NR(Kx?68)iGSFZ2r9+C`N>iyWmU=rRT_~={{(R^WB5Ft~!&*jx>3WE3q z8%2Fh%bkO)@tLTBz>2mxs-u=;K;^wrkz+4Bm}N946f{T!*^S zyS^4IVMkM1qNaYbr#uy(LDh&-e%=QG`las|J-?VjDaKRX=nTi?8O~aem`~}5?LosS z|f>ulcp#RN+JI{&kQsp$CTMv$EeFu4o*MW>aprE*us-!CU@4fu+g)6tRB( zq&_te)cL-zdUxZNACZ;Zw-zfS_R|5?kBfw)LrbI;y0K6 zA@~v?nLlVnpx=CQm);$t%KiCG2$_WN5%ukg%wq=DuFG!VYO-hWJu zy7zFuwN=|+qaE{0r62L~$Kwb$e%iqdGXM-OiFTGf=|IsBRSBKpm?wyb>BwL`8RKa( zv@&Bu>YgFKiL}Nkbp$p&k-zkzcfHjbQmP2IKSGZ)gw`Wmfsu(1mnrbTK@`j)0g zA7r%-nuQBx2~@zZ2lY=o`c9gJvU`w5F_h|#!wfy7o%{f zVSHOJ=r#2o!6+o~f#Z(;6n>X=v7^;YIY-nE(z{D=ziz zz2NIlE#`f(pX25SIUQT}-cDLn5l$_G*EqJG1J~OW!%h@b_HnqM3tbuK#L)_%Tl|V{ zIA*F+&anOK%Q|TT#Cng!th^YxPJQN(|>7LJ~4=<4tdiov`gaDS20 z0b()Bkbj7qZV**B73~xX$)Y`4VPFJO?lm0byQB};=L;S!h|3wu32>70nC^KD(@f^# zs5D}q{^(?o&hG8UR8gkS6q75gQdVIk4oZRfb>Mm7LOIrtjDK4p=SjXCyTG?I4E+Iz zL#)jd$ZlzkPlCqEHKx&|5&oPOpv$N)j8HaP+BzC)E#n{^*-oM@Qvy+2n7eH@czXRa z$cc5}%g1oS& za;nhUClXkVzpBeIZ+cg&JDl8cJcc3|ZPs_yv*loC*tV~JFPd`_do-T>;fF1I=kG?i z{t~5A66`JsP53@2BJr)bH%wF*_1Mbug}e8J6<0^p+NCJX2?<h*p{ zoH6G0!s-n%TJmg8F@vo&0tbUL;7HnX=(B)g!pQpZj~D~IZ?HZpD)E6hc&9Te;lw!0 z0TmJ7KSgeMo?>1x+cHhAoT&-%iql@iVg$FkfT9wq9S5g+;Nh1Atv24Ovy9oHkXeu+ zsNO8uLi>I>wa}6>N4kYbm)J}TdzZcvGl0Rzh6+t;kS926yRDF#)mZxA&Mg*SRcrH; zlmulGXiGq|^mU3@r7O36Vy9{Xuwo|hNjK{>YJ3bz_Lp! zfU+jKpt5$>dX3m&IB!~2a!WWivtWMl{PoC%^}StXzF##-)h^K}SwNV9%o_PPPlPaE zE%_}OR%vaB%kVbu5ACms;zuFsCYooG5M#x}84yg9gOejg@&mp3fO;$v#6<@GF4vQ( zkiO?cQ;KzgwOq)vX`U{+r$^VVuPus{Mh#x81ZIV+4t+z<@aO1D$4~u8KFdxE8psm%4OKg#&AMxAF9dSTGU-Fe|a@L2(cF zkE^U5^f!HbGUL8Ej)r~MR6_gjez-|Wt=x4nf6v8`tjf`y5?4IWF^#oMUrE&Ga4y8A z#H4_se=O8d*HHo82^vndZe|}J7EKFYXcBe`WCaO!K}UQbhp>1PD4OJ2G&frMIc)-p z^F!Te5l;+aKwu&wgrd_2gpYbAP0ku(M`RM81|aQ|>wXsHGw!{uj-WhlAnAy`f+&MO z{qa0kyY)uBHE_Porgft?5~Uv6W@Q~&0Q&nm6*O@R`?&azL*%<=4M&Z*&@>-Rg z%n)OoV%!RmpG51!AwKhwi0ngGwBvjOh+BI&eo$E%WQ!Xiw#4D8Un;%!Fy&YS+EB)% z^)_+g)YJ+yaYzb{l9W7;t-r3hT>Epq5-@KIIub8nu8BIKeD)#aTNdGSnFyn;)!IPW zE^38HwG)rLGEc7l?Cy;=n0N7E163}XnfyWzQ)(CEBiW0nTh=A(`Jm(s2CKMyN`FJg z2vf3P-Y5SHMoypyk?D|E!VY{J-im^&!K<>qLf=}gqc+=ZtnWgC7kWYv<_AK3Ux#Jf z4X7_T&uEtfxB z1|W-b@sjkU5?SlMx4u$&U#j)2DR^HN6Yd93JY~vHsQx*XtjL7=p0;d~Ec{mqkHRGB zZ@G}Lxj79z+Hfi5?`Y4;m!ACY4F}K-ICFmr+LAOJWoTA3629`+ULWd0lZ?VXj9Z^| zym0+Q4T{S(fA+6lu#WcPzlE9@h@gyqPv8QYgpB$|*7sG*2*PW35i!gDdYoc&U9qFu~q z3Un(2r=9mx9n(Ep?`TX1zODFi9jcw&07EW07;;XSf}49REBA0Tz)y$DKcdg-| z+MN?r5@jyDe1a*}atz~;kJ*R5W7Sys0|h-ram*l$;M$J{pYCFAEKKmy6vNLuF14T{KP)wB2nS` z%nX^8UVG?N5o)jy-E4n7aVxsBqsGUJXS#5nUI%e=O6b$sbN~}Z=ubQsJmtHYcdpC* zsi@-)#3L-hzt#2C<9czBI$=Ix zo}{d!i!x0@BJ_FlYV&M+1!Z+EB9sW~d5K@@%(%)fzmT>Q#(;cx08tY3vP8e|#qY5| z-JhVJVX{UdeMIHuyu&v?g)Va96MV_UYF(}2r@SebwmUPcDMB);R?cS9kg{auA7tYb zYxBiS{8;pfezY~hOdT~{*@9aHRkl>-L5_)yo)!9Oxg2NI!cgY;h zZwwQ^+9nt7um2PHsF~Tj)#IoZSzKgK-%4ng*)zjeULaO_8>_KK%$Sg&N zygGiY=BeTOpq43b)iSgxvd`N;skJMx($eNVMye~ggP)eyh&9$wgcm-F?qV9WAi@VvVBEGR# z^FRP(jF01o6G?JM`}+$vQo$)uWd@Rle8pfuUtETBw=Pt_gE{*7fD4HJ%+@N_h)XEq`GIzV)2hpMyj62P6 z3#);ck@juLjes;!lZfv6r;PdbqFl#P>yN6p*Sb})dag zPvUNuYA}%pzC_A>2f=e%8S1ieBonjjNP;~3&{ai?Kba>0E+VtFbukFiG907piK0PD z4fT{Xs&|&ZX3)k7{XZuoNHEhVKBqQAb*od$W#fj`_7(8KKf}2QjO5}f5}Y2_e1Lng zT8NF0=F7K=h#L(Wpf35&S)q3mFOJdlk*Rct36|+hIR@(Sd7Ix!RrYf(Y?s)y@+1Mw z6#VK8v)p)!cvYwsE#IkQ5fHL&F}wQ&DXu_t*WuQp`=X%0RVSbvJqWVds^#;oU7=2! z%*yV^yhh$K+N<`_&uVu$V+6yYeazLg49kcpna%sQIY?>*Wrn?nLjjS-D2?H$-hTBt zN9|8)h$oQ#m6%dz9Rr(TgRfAT9uxkYp%pq_S&bB1yJ5DtqXy#A7aC&35HAUjrGeAm z*&5)f-00HBJ?>*P@W)-o6u#=5wOL@{uZbh*W?_$`5g8O7Zo9nMI2#Bp@tbw@mnCAT z!R_^SDfrau1?y>*^3#eV&Dx0;aa)EqpDlHljv=lerZBGhR;vz@DW70Mc z9QeNP%wpAKL^^ZR#-94+JlsS=UgsS;g1?`$fK$`*2v29ep#r{$uufd1T)XaPY$+y@ zzMyGxfXxn#Bi&C_e}|VDwGS36V)}i!=0l%8qt{9j|N0rISQ;b@0rG(CC@zf2bCS2R zTDRs!c|(<4i+Rw0UX^cc#B0tJTf=a^mi8_bM34&8dD>p+l#uR7p}L+VqI?@!!@IV0 zwM>D!3QAAARB+V$7Hd3+jJ$2m|Gi+%&oO@D*-~x3v1mN3A_r#&D=Okd>3w^PJ0$3u zmgLttrmy~|Lgg&h;EfiXXAAgRlSviD#YUOor@PoNtEEhAz*2ZLHbJn9vPdKxUPYG{ zWyX=}@l1|QFiUHIc?D6!} z#q&k)K@Q%QJ(H$R=DNyH&X;*+;;-ooy6G8b7wJpr$E+ee3LZ;W6yf#vA99C<#x1y} zqN2FPtMhcBPxQkBrIvgA^;tbJ>zbv=O_6BjH^!HYe>i(+TrBSgb9C~j8#4=F0vo25 zj41|@vr5{p9|~uv`n<-!p9akm%VfRdUS@1bRg!+r$|dIPQWw%=m3*+P70p(|;*Qd? zyMp6!5&yuk?rar%R6%2&(!0lbMe?ouYsDZ<$I;8)n7Z~X=unme)OWIqyM~6rtH_XE zZTf&MP+dkaTukwGG(d>Ac3VPOBAyILIOb?1)%GQ(?oC6h+B+F*=0xS6i|yib4c4ZS z3U-H4x#tgw0~*p-ka}#%v@@JY zm8^J6MX@k&M-6H4eaug@9tE9afv8U3aWZ-i(y_<&>K@@I90nbgJ1f+=R14m&uDlq} zPPb575HppzeP=z;l%Qe>jhLVaiBlqxQOb58W7K1?tEzLl~?So;x#L_fM65ewT;zw0*DrYAtqj6C_(~qm^h? zvGffd?~yaFldMTkwC;^PLRoJ7_gBuhv8k@9Ct)?Eiwdg<6{QsbZ6 zM9HSC6y4l8L%y?)r2$8m+;#KUp~^o1)M|naCC~Xf7wslkTh)0`Jo$GF{%`1EZBoG=I|KQ?b-rBK0&LE~ePP3Q2O=ym^@^!A%>t3}s&vA&`P)%SH z_*I=RgwhYfHGCiy5}&hNTW(d^Oo7QJ+eJ!9akB$K2sbIta$ft}FtPsBWG+y296Y$tfY&XgTu2iZM!Fp*()VA_k+i#6tG530MnM!KC6W$Cs; zHC|zyO8P9C6Da4wK3QNl!^(jsk^M29il1iRdip(T(r#;rXjQoM`{{(7=>SA@nk)Zk z*zE6_PpCcF8n;tH!I4^B-Aen-sm6JuUMPVrO32LTSw zf=b3dFB@Pb5!wv4^i#oxPUZJ4L&!VZU z%nK%Hh45|YTik-jbQc`~?^ssDF9s(bzT))GvZ0;lI~t_!x^a8b(-?X%<~>nosc_3}pOH8!(k;H#w>c2;U*tPb`$hRn zawkX#!^k4rWG>ur)*9fmY4>~dB?Av`Do8-U?M!Rw(kZ8a;k0=Fx6tTl`~lcQtVSV` z?5P4Gu~WLpQb{<-^GVhKY;@2$I;VS+fwFLhf3k8 zW3f=O;{cx{O@UN8c^|wmqYOWE2c)FbPB(4lFC*vi_i54sRKx^q4$LHR2-CYfs*|hw zx`#RJxl}?SZ&ONkfb}Zu?pm@{%mGkLT*3&H_gNX`7-B#4NAVOa)pBjRJRl5xQs#m4 zz>#oVmDVs(@fIWUT3h|CloL8@x0EaY9@>#U^@ODuTou-?kUkP0r>oQxFSRBTS_T_V z1B>Z(b&LSi+hdEC55dQ;)x2IqF-q?%#FP_*cM=eMZq&DpA6-BOCO?sDnOeO8fyd^!Q}We9L-yQ1jx7^=*OSDJA0Y^ca)*xh75EQ%c^X3rqWwyU-a8LQi$zqw7Xb zIPvW{U6u>69Je$(pxTSmZ#~CWz;vFZila3ou$Q+VKeoynEr@fHZmrw6+ZBtpi^fPd zspn1JgMQnXnfda^rmQh~y@Ctxh$4h! z^Qv)Bv>OmW!2G-14T*+Td6F7eG7$x_ShZHAP&;D1`AUw3QJRby8W;+j*{Y7jcr?cH zucv{Ubq^vb&{j{j#!f`QTDPHuULh|qGqwh+4<+{07-=mxqt zCHz^$<{bB9wx-x47JaardOe^KsoqH%6Gnvv&U5xc34`dDRhpU?x_^J?brgU=@6+t= zS(uu5Oq_Wi&li(tx%yXn^O*d!RfO*1=W`Qv(}3r@02CM{%-(s~pHSV-L;-rMKjm>f z+iG7Mp)_zAcW(|Jc;To63!!RKC-_)A80rw6y0yO*nu*xm>h$m&r-i(cbEXt_nTuH7 zJzuGI++KR%3po}{Hjqbz%_oc9e3?IuBpaQmZ>!r&={|nHR_$ZMBh>*f#j=x0 z>`~=dkE!&u!Lr4~?CbzaF7L4iBo7lnL$XM+7LNF3IX<-g$O$Il%7Mw#Qu@wThA1K7 zXZ_W|C_LSbMJAsFqymZhrfn87Cw_2f9C-~&)ft_ec>K$eg;`s`@LDc{_qTKDR-a7jy& zt&2m<L1@|&OVhjZf>&V54r)#uF>E{&z9Eq;PLPc9Lm$+w;Q8C*5rE1AK99HcN z`d(y6UYmtmSUE=A7~(_W6T;4A_M^P23*}0g47bi8$u0|x3>5Go=mkDu`zVAxw!v5b zqSq`ai!9r)IkO2VeiN%BmQ$5WF`Of+xalN-c-Tp z{$$+Htr@t;L~7BkxQ@(@kk3$Ij4MyQ9nG-C-lD^jJtb0h+Y@pfXE`+7dFn2!K3_Aw z<;8t2CYvuxVyJH)fq`>pA6^MO1EnBd1Mj|_RAz>7%!#wWWrx0nCyjma$fA)?c6oYQ zE=P$QVtF_$D>gXSd+Ti%GSC|e4a*xUru(UVS^inBFfi_CaQRuX%Whqa?3c#OsUE=x z)hNwZT5SB{hNz2=RgH8R1r#}R^^rbZcw5Lfn90K@&34b&0$n2Co02BRStYYQ1u~If zp%}Q7SMwj3H(zI@lSNb2a&+wPVmF7ER<=dYYUwtkHX>d16F_3lrQS4ZNdd{wruwNV zREIJtBUs#g2J={5FieLXZ;P%)@v8C_lOady83t;%{63wuy?C+Fz|kb;7I=Iyl$C^e zOo*I1XE1SpVNVXIq?z%TiGv(nZOVo@fyT8Xk8Z^*&~_hhQ3!=DGlJG8%`SJ=VLo7+ zJeFtu+7OPPVF&T8_5;@@36%g+X~K~6vCp?hUHNJ196?8%&jE|H!>;`WLcl>)Nz~ck8N{Iv<6f>hU5yw-e%+gHWKv<=JOD%ai$YNY-#W0_n1EC*-Y-H&NCC{Ccm8BC_LqMacvqmU={5X^8YhSy+kkK&7ppj4wd0KP$d2z zmpr%`e&(_d+X9JUL?#zeREYGc)@58ZqJr(~bVa_#p85RjHj|NU$Ull?FeF6;VvfA2(?s4l{-k&hP&A zqSXHfpW!t1JbT&foc81Ow$<<1>1mj?l zD+PJoD$vQW`WgLiEp_$!)DYgU?bo9VY*y1d<_iU2YtnD{CHyf|IjL`Em;SWi80CJ!+ z66GSR7I*~dBjlG+*OkRxZI0+n;lGcA^_{+-+Cn@J+w0P)Td=%$&PW)^elV+n62Ym!9M>|#Qf}6K|CiOx!6NJ;T#=(i0}k{L?2382H6eR^C?@ag|{n=y#mH?P789A z)*bqM>xRTC$!EY96cReQmNgH#b)hD0AWmc+!n$kufUcEBUvbC!zFbCZ`vG|Km9yeJFJQG@MrEID7lG zA#hGFSbT*jMQ*I^G~`1@*5M6u^;u4|jvb4KO#>N?4NW&pSs|`ugq7M~CLd194rb+( z>@GDn+xd_&iCWm<5}lk|LnY)}=g*ck62)uC3DAjKq=)nH-Tt|J;Zv)5%M`pnYupZ* zn&ZYR@5kNnzEFh2<3RLx1wGRGQ;slSCcwB3hp@;qwJ>9;l&n9+90s|lX+E>~B>jfM z1TY(4T({KEst~Cwoj6ADzkZxXh}RXt^=O5&qGgxEQEI3gb+^zK4;6Dly4&jo29>T( zgV5hE2_*lk&6ceBN8$DGt(L>%WBJ3J(YBhCA3$jdWr>ZA#Qk{w#Y{4jskk@beU?t-Nr=| zgXRt+y>_=5s{FwEBwS}P9Gt86l^V+SqP`kciG^D~ZgCX@M+7!=oTQux#o_9#x(FbU zwq&(Cx`q-y^tZtoEEXxSd*a?p7n$ z;VQ(y2H6xA6z5ayWffCNrK^CUHXC#0Q*_{LeLz`N-u4 z?y&}!p)blpDbOlBZuSMn@j@52M!I4z>h zQ>+1|5@tTiF%Z@QtAfZ*>y%0*O#)BGX-#RLGw3(@Bb;qE{{I1vvP3RCtDnHjCy(!X zoB0I1Gh*5&W@Xx26(>YO?u(6t+=!i)TTf-@i)rNIFSNQFc*bZM2EA82dPkd^GglZn z%PFo9YMQ-b^?g;3;TBW#x)O1v4+ExAvTM0R#ov3iow3E$vg7ZRFqE9Anl_KWRy=8s zj?veMV*=*3(_Ylb)@|%o_d7y$~>edY7QHx|2fzp=?0=CmB;eTBN@s zrPq;Yx9$r^WJ@(8l`+~uK%!U+{XZ8{`oAs%|9YzOzV-2(09l5Kd6Y87RBcwT)PjcB zJ-HdtXgacbixYY#D$cHjJ8Y#v5bS#Ua!{&~nqVK#a^h2i^pDw+Te3rEu4qCOXbKPv zdt6RB!=a5k{&dN^C>KL%pAh}Uhuwx$6s5>Z8CTMTFmRAGo&H)2PzshKInS5(1{fd0 zJlig4TjN$xe(`Z7Bx+m#RDgzXq7r7fe1nCO@6~tiu|$r`BQ`QUnSsdGozwHpY@ZCh zt_7U>Qc^gc&j4HfQ!f)$%8m(F<`uZ$FN9)VB7Vz;O15dj^ZgWe!Dh^fB&=`~VfM1a zRlX!!;$S921+lw76z1q=Rud1ynwjYQrq~#FxQQe0wLRbh5tqzQ#0>qUHP3(dP#0@A zGJI;D&Ph{4j3aR#ePu<#=km5{&~BG8P7^taD6F$8gK)(1=4M7S1l_=Rv{3R8H~1Z7 z%O9&1W~h@rnflBEI%O&6QqzLPbz}QR+y_4U_43UhR&)R8(8QF8> zq=Oab2Q?kmXkhHiho>%DO@4i49b!2Mms33T4(Tc!4E-UL4Ok}uekdtLj%^ZxNE+n9fbnJxB z>I)R_%=A$~`Y4zh91JYJ6#9geC+1kSOzq~rk3};Iqj1p+b0r(XUjyMV@dLeQ^qZ0W z?W1*$+enHyS67Y^NXcN?xLLzMueTi*TNnYG4$yR8aC?tvJ=0-9)E4YZNl=4N1s2o{ z4vo&M5d}`bCG2{TLsXH;iGoG}7496R6~0gdZrD9JyB_Ac1#E}pE1|f zkfBKPoXHE#BWQFKw2SLj%keYtjr$#d9%(+C7CWZN>4gm@lAY#=hW;DApDSujT?Ssg zodT#VIE~m3U;iE!*6@22<7ii<=9!bStgDrXRdz@C#h6{;OW0n8y-%$&-;p*vtIF!O z2wl5|w03N0>OFVP`G5mEjJ-pUFiW^-+qP}nHvhJ5+qP}nwr$(CZQI>%zqoNC?%)h= z&2mq(5#DASyn*gM$D4kVceiT_R~+ zvBifdg&j5>6*wAQ0QPGK@D$VBW%~$)b|8BsZ4c;R)8pmH?@5#Ieni7qDv3ihUec6C z%ku1hwey*QQhd7&TCmoGz^uqad)cOd(5U})$TJR$4%c=04|dQoPT_}4VfQmo zk4$6Kb3pYF%y8|m>)50q=^9?RDfx&;K`kN;praG5a?)TXV2YRjL=jJthu4?P3Ya~} ze}0Mh<~AyEPv>s;M z{gR~zd7ZIH^0_SU?KQ9V3tc-jCj*aKUH>mNgyml?oG$qX3~)1{$<8hts;P@Y3aEFK zwe3D4NDjkcYVuG8_9Ne#IX=!r8bkUDALu@?EPWAhkR;@Lb@CvpwgN z@xBDvb0F<`6Or=*FchYLs+?2IhP+AR`nEB297(ct8jev;`Q-0cB?k1Ur@}Ww5UC2+ zyXJ(mJ7`d>ZrTh#f?npnc!xWAy;ZJ7X?Mk=M|ij2**=L3beuc(KV~`FS*9sT7c~d! zbArJhmis0({*#S9eq0ayAyD%+CCUYwCa@XyDAJUuh?bSEL<)okwC{!F5Pl=~%uHX} z3A}RIPaLwiR7mZJIulbOyj?YFs0M)dp{hg*Fj($Uda|Cyk{IX?bt<8})#IL!~J_h!45{#?5fO*U36`IZOvxiI~i1Q{7wL z7j~^h3RQOqSFY1w|1h1n3DCRxViPA&Y-`ba3e+~#e1L4?t>1w_d+4Lb2uW;Xfe&>b0h?*;cK8K@JMUPM zdtW&O_%kLWCB|7X{A4ylZ4v~2FZD%K`*<|+#HXP%)y^|!Pf@?qVRVJM=qaUagq0Wo zJt((PJ`#gG91<}<=`I-^0w%T?wAGAhrZ|S!I^RBjv~lF5a%``U9aDn(P`@VS{371( zQ}=mvNvVF^mF2(=OsqBHQ?QW|jS#)9b>%rz=@zVC7GkyQ-ZXg$C_Mv0DKK4J= zGcWPrXS3GQ3D$#<`f9OIA+dPSkfO;jV6^kE$eicftm;@m4uh2GYZjBG=hLX>@1(_! zSjW(#6Fn{OZmC?49p^OqeAa|n`Ija^BO42c9ue2OEs+RN68u2Ce;dO90~?nejh=Ya z*cvwv_R`VJ37Hzngc$9XsQu$f`kP^$m>8%69|pR7_D`Y#bmT9LZNNrbZGL{ECi)AG zrwXA@^%?B~XqT)#Fe2Q}stN9S!5NXGocaaHPZSAo<#-vAdXe>CniXUJmh{4NCJe(5 z6(67$gZsAmn*)#-?&c3KQAg#c{(MDiKU-pbE3TiE|AsVdZI=4hAEDNm+JE}bk>*R< zf{n5Wz_4($60kq9d~9Rc(!Xo(BWC32Ya;4w_=2$1E95-xtsB-AI$Om^a9t#c>t zl}I=$InDm@g8YCmE}2cRk3RAUY0)m?l;bN}=$E@`KCTEBb`kR!dHoN? zK$$er=$4i_k|2orAxqD`DrN7ViK*SLc*G5`mt({^zdBZoc>FCufGO3_0JkIhI18&` z;sCU@*A&(``(+FWHE56YV?a`JJBWFh&*jO@e+it<&;u0-NaTYUuUXY?0gyV zj!=Y7@*FqYe6%LOeU*2*(zOX5Q0Q6^8Z(*W?8__II7NO$0lIr(M2XA`8Ploj1l$By zx!xht3oSb;EkciKi2QoIpZoa?4_-!UG02OFHF7&S}IOOvn`)R#&7QeW^E zD-!tdF_cCz?HjYM$-p>{-_13wxP7AW<_ZkITpnyRWj_CoHH7`^0U+=q^6!BWb|d_G zrh|go@;hY&&=?;?Jqxr<^a%f**eG`Y{el!|nx5B3EC_ica_InVdF+x%ScJNJKU5~# zLl8^!s#}i>AIAj>)qho5T`;&$aNE$3+g1fz*NdcrN-PInFc$?8aN!vp1VAftnf|h+ z-pU8GE3N(~5w485VJO7xBf++y z99~%w54~7)!k~>D;7su_73-E<2#4f56t}bR5RCSX*^xLGQQ6lXU!94C9wwYec)k)J znReM53aMEvb)O#5qEGqeH`D=Rd*3y0QjVkzAYsnBpM=)@Bpr2)RaRo4=D=>81ZR7J z&H)0bC5Fn5saWt!&v)Z{dG=s07v)d+rrLCdBwg;A`U_cT-vT@fNTHU(=^kV!&9A_# znxCY(6!dtH9UK&|skehZWF7ho$<{er#8vMWy;JQFnOo(dy;dt5_9>A&IGFZ!j+_f_ zf)Ivy*~geon|l~csGgp7H?A!u$+LGp9SFUwngB8=SccY1S`U(w_gE;KkUCD(Q#-v5 zZLPU|5!SBK|12ji+T>NH{P$&PuDl}XUTQAVSK&*Kv1rq{pqBV@+y@j9T<2UF&XuDo zO%BmoA2I0wOv`G&^((4i@R}!Ud7z#|=ApK4IlJ*So1r$!n%Fn_ z8E8P1qr3-c`{-l)#H-e4Ozb>Kp;!H40F{ckDZ9KhgkTr^V2clLfa) z8##2jH0I##$fO1aQEX%t$i^J7_HCn_7p12MppUl9kU9)e1PSy83K?ZqFm3S0NKPW~ zMe_dcl+>eLcliuDC29nPa24$uxeXTN>55_E0KKIysuWc}B{UzAWR%e319T6=p-=7IMp^g}zBDp{{4a8%3%=%r{fo+-{&5Az?lyCJBliqD{m} z&by;V)b}>rI`>sdR!3_qa7e3W)sHc~4~`_W?{V545`n_$Y{;+{foSNIXjIs`OB7=Y zFbA%nujHY<{IJ{R_ zTBo;Q9AD92nGc)opUK^ZK|Y{$(NVxC@~2RxX6k6%C=jKb077as&76|9v}8U9SR!eW zhcdCp1F37|)QF#VVh-XK`qcNEj<*F3O=Bv}s*+swy?nay0x3aIghojNgH)6&eKqN2 z_YdRQxBLwB{mfH&eh2H6(TpO4wb3{$@-&L`qF;5Y?n7H_`c64`l^HN@(`MJ1laP5D zPp;M8lekt)L8A1;xB1X0Ql6 zfwTbNC8wYq&T`vSRX0XnhECGhC=4rt=~auQ%;~MC16x(R^-~!(oQV&3&9FIPXX}aW z2|VIl$Kze)dzxgw|7;+vw0BhZQkl7^97eLV;}G|)p-k^wjd$X;+@_W`ppqu3g1e1a z@IS?ISF!>8#Vhz7V7T4ctyDt#j0fty@Y-9pmVnUy-yGDO|BHi~k&T1nf27k)1e~0# zjQ_LxzZKMsj0{X11pog7MK5Y$?QG&mKrd=-;A|pnVq|A*0>#G%<>c&WVqgR1z8UkM zf?9h6jV`)NC|)0TJ7@n^FtCdqmSr)ft1EQ>R@NWH%?+MzfRuz|fb<|Cql4M$?oaK% z14HH9M`~S9Z%dVhUWH}7q`JV2V559EG&MPP7=eJKMotoZ|G>b^=)gdTs8kiCYXk5P zsc6v(7$;{?0m6VErZ{K7w05q^qAA^+>!duyeoPgve-d{8hh}_|Tzn94|HuI0J-$$R z5`n>UF{qYjZOb4wwwMdoHYsNR10Hj0Nm=MTlsU1 zV;&!aH3EFwV&of|!hbd~v$1IUm$I&Z$aR1%FOKX?LxQ-Ytp@}Ugg-TfQ4a$hgSgAW z&CAOJb-Mrv{<5fkF8PDruWbNuY;gR+y`{a~3FwdG&zcGlEbk7$972Yw1JVq*67*|n zkg}n}qoes_4j;A^WaKbEZ|qIaz*w7^{#xIQotOe>K(hL8I0gB&oWVE)akq0caR%M^ zwvGRCPd`yk3D6YhzrGTLM=@{SeHIVM66`-Ucs2fUYG^?$;~xH?*MkVr(D<<%99;I- zf&y}P2BH-I{U>lN_#tir>Hu)(&2OML5;{e3z%j@fl&b=@J3|aU1HToU%-dHhSMqw$*_`80$%PA&V2Hu~UoPpIp zIXnS?xW5N_cLxFJ{lgVmf%#TL#rbQe5}+vn2;h(M;Vbz=xqe;y#{Auc747#MQw-os zLIcr%4?7CQ>Bi|l!=L>#EB!lo{1dz1Q~0&R`P&OL*15UoLxgK!M^~2)rHeC-BDLZI&Ii$lDofqn(hC=b-}U-e5o zk?UB!BW(f}5~!3nyT8){?Vp?&c;N3mn*rM7+rycii~be@_0-M&lcfX({736+hk<@) z<)4w1ltGR)#+=aZ?g6-)Jy&H8`1l?)1mMaBv3#-tdcU1Z-;Z<|{OVO$=K*+)^q+;A zh!3D^c`rN;xu5Qh{m>6qf9)3=l1D4^C&pK8*7kw^{TlKU{TQrX>j(M~V7=xKG*1fP z5Bd>!{pJ@G6yW;xPhj>;INopIOnBZ;kPe`0XfOV3WA)&}F=#!2M&~!d@t5=`DdX>* zPwnKH{(bwluVwD1^B1@ySIFxdns4m+8~At4-oI9M&O`fGJ@0Jd2mH?gJx~CTfMD|x z2!#U0Pzig{_e&yhImg9L(Kyvrjyxt|#cQKt=mig&GvmQXbv|^dMfg#ie)cjJW+>$y z>O}5tDAn`$epR@o6+BNY$^GS`hpo9}E;vX@Bn-&~__*A-I`dG^$CvBV9DH-It>3k$ zV8+S$WK5B^zt80^?Wr8Q{GAD}wZ822<;Y6pc`OohRlNpdsDF`j)BEY#!;p#uUDXkV zteYABx})k!qkFr8e3JcP+Pqh+VS8lvGfVsK_Oxe}inSxVOTt|IIb!0V?tZlFqMNj@ zq?=IlnT|SQ)WQ_|i0b`=Z0no?$>w5)8tkaYpU*B!Vg%3lqUdUdf(u?=eQbR#3qX$~ znVEjWwZb#`ypF5D(-DuCsOHtuc)l1>n~eY`_SYqC?xy?dA?soh=3l)Bz$u5=nWxlp z7rJZ(h&fE)<|ilWosWh#FH35qC)Zpxlrb1l&gVNe$&39vDr*dz;K#zQXdpUu@PFw#Z|o+5s*3I(h7uj^QX2R+k)AG&n+p211Q4D$ zkCG1wH|m$swaHZl0xi6_bBH8{-^s2^@~&XF;hJ-sbkFk#TtP|to!Hs$0aUsDHY>6? zo2!gh*xY+CN6r6d#5NPv1w?5;pzZ=O9{vZ@V{-!8&V}HGtTgOlqdTrk5qXdK$Jw;) z0kbHS$Mh04OzmJQ-UCxLCbZPDga72iv%?!ti&qry97@%@xuX-7H6Rk)ug2@ifVX!i z^~T@qj3C^-CPg3JDA#PdJ`SW;CWZUC?48D#1i$w#%>8|r391~dvS&rsVG8MkWffC^ zi(PB&OAAS}w{@k7#}N;^Opp|Dv+@Hg$6XSKW7;b4))7pasigwZ-n6AhM5Jn$x`BQ! z0*)O?X!j>J!HI_2+7l$o|5&{Bu0uc5&=IiF2U-t2p2{*8WK}XLe3>*(Qw11>Xp+$; znKl_?Z{i4$T4>$NRL+PUHIm_3Bd|4{ULLu8Lb{g)16y*4)35drS$v;&lcRCcs)WT~ z6gnfgpR~8fMEFo1PGC07(20Sz@Ms^harmTc!G%mNkWs2BF(re@w+OByu~dH~zJw?} zWE~VY+6vFl(KDY#S!KwXaVU+bsbPUGq2DpK$f9@`9-bVLaM#Gy*B?Sgh;1fJZcDg4 zGkdOr2Dkyq>FRvnr)4zZnTeSjr2n*$`d3?CkAL>zk4tGsTV^@Pbk3R#JgykAcb!`@ zOE0h%x)&?f%SuzPrThx2@>O7v&-TTXLows)pS zv-uYx;oHpJNUFt*bD}D=2{%VwUtlmNtDo_uaA5TdAYv$$-i3W18`M@+A&x|IvfT_s zdLOe2=Dv(525}!}09E$n`|*?p^y_8O!Vc4&(@j6YpF2|!I_8Yft1_Pk$||~A+r+)2 ze4MI3Nc%bu{8+nNrjw1U1J*KA8`&YRnrEeOF4nJ$kN-NO4TE@R&!0~K>>0pKkl7v>6NvzpmOD0Onn$-W()d~tPZ&%>nC$=h z(kf10gxPXvy;=(6y+8tsWjbWVbI?ifx3JxhYb|5_-E8>eMbWC<)uP@A>1HDU?Cnwm zw|Y`)B;@rbRQe{1W@C)@Tw{9^+D+m6aZ?LcZ_(Kf2&yL+F+Ay{fo6Gnc#g#jz*;w5 z8x}W{=@GLE8IA{-$NOkgE+`6WYCuk5}^nc6WbH^)n^W@%8H681S#29g@gt|~^ z=DQAFgo+W>sU`_#ata@3P-S4OETzLn(TrGMVWu^&9?;%~?PxQ*AVnZsueg}@VEO@J z1Y$C1?}yLMki7RTJ(IMO5j8nep_fqs+oShO6ex!o zHsL#jiKiI;j5r`?JV%2W;$tYu-h>e+XJax2CzijEN6X#NPA(KoYF14RraTMi5)*-S z>paHIz8-AD#f$M;@03%-?3kG(Kj@U?R^BN*v9S8Mzi!-rI1n{9annWaxgZ!TEQXup zCP+5#py{NAXVSJR)L+;i|f=L1kv^B~5)`pm<5jBf(V`DLXuC zOi@)td~i74EZH=d6KS4Z4n)CI=MnW%e*gaJH81n<9@Djw^~CQ);J9us4b(9LZV`zO zH@CJCFUtlDkCBGlt8jY9s(vt;Jx%)X+h($^a05?yCD&_rlCwIsv9NKNI3S+>&x}HS zt<>w%y$CI-M@!I7M zyt7Wbk|H5A>eV(KEm`i}{>ZX}H`b877j11` zKk{pt8Zxp~>JR7GPrCPH+HMVE9r{hiKADt%Yc>RsAYx7T;D!%UdO9@;q046hN)2@o znoE~dvga%0cA|9+IE>lsL$S;LmsIhI%#0GtTVH&WW z{elgr%bIrvAWj+_y1cniyl@6>JEMMDWM9L87q#dU6>{X>eFOHuLe|NeW~>fjry1;j zSWjEH>aQA$%?@a)HuHiPz3TG}Z-}l#zdIGoMYUlS`pnpk3DG47!nC+AX)S%YLDQQk6&|7B>#DcK~0 zN{NU-R@|6$>T=VX6~jF1C_e2^t_ytO2<2LORj@A}E2}nL*Qb)mwZ3^U9)+ka(4INN zU8o1R^BPL#ePhOaj5&xF;{>8&E=ZsGCs>57NF+d8YhKBQs>Mpw6+5UR5zIkAlD%e! zCUWeakF&XzrK7oejdULKOmvxgEfTqV+c+SF$P;Aoz%qstF29g7w}+enIM zz}&}zNJCvbrZ2~fZM?#3x&lJNAt9-lHz(BCk&-x*&E~j&*A2*rFYRm8BW!o^;ynq$ zKNl6^MN*zf;U4opZ>r4CsK9-gK7)YRb_U8Awv*e;)heHWc+_gU@Nm0hNJYrdvJkz= zo|VIfhH}fgrhWVbvMpxi9iTTBpZ#4lX%!o3T-Av)shEnDw9idff~=?1;K&Wxc==hj0MM7gc`_vK zym5{0;_W1cfO#R|-qYvWQ{+F2%8gVQXX-*^>psNd$IwJEGfSPTki21Mb^baQ!@b^J z>Ll1p-o%doXmsFbM4KO4vAf3bK-S596z*9MD1ss(hCr&lQrV?p&aO)|B6KC{tEk!+ z9ex=$*?tTB|m2 zL9uME*sbCOieb=hS)~mv`|IHsX8=u~(YJUpu+kBQanh(XOqRqI4Pj}ssC+R3V3>{< zV9^=}2ZJUbwDxMfHy1uwbi`<9TqX-?{JBfyfCCgt#`Fqf8C0@15=GB!`D!O5PF|2V zQOO=ETI$ywD6GQx3q>kp6%Y;%8EDwPrMt-O`8--i;i4LLaH~t58Dq^GD0YDUdsP-? z5p9|KcObF+5Txk!e%KnnGMJzQs3f0p0FJkX)O^McmJ!5)GSh1!E zGc79)*bY4bxAUp!r}g#M_k2XFyI5WeC+$|DvpJ?Xqn&vGnM?E+>q_>;KF}fRgGTE^ zmQ*8sxpgopinqd58c;X_p305GP{VgEfPN0pV6C0(0hs<9d;ojrn`kl z*`X*O`?OIX0u{1WqDTQd=qXt^)BF2Y0^Sw7Ev{55PyT{dsV+^%b$wkasq@NzFRbL3 zvQh>p5nI2f=E!nLOeST(DOE(kOv9Vp2*FM%0d}?W`ktBeBCIr6^7T$x)R{SfVx7zd z4n7J4QhK5s9;txmHD%MY^RxT9A;mMBzL^|i4NN`F4!`zH7ClmuRnMw3C%jM*op_%v zo9N;~yg(YR3#HT-S2jsVSVt$@p52JRo{x|U;z}hTDjwpK(K7hOsjoxj69Q ze~Zi(SL|l;;W6W(yKeS|@}7cgp?8St?r)8#t_y+i1@oDs%x%%*tgsAbK7h-Jfk~eH z=7>1yK&KVuEEkMn-3i9ZFCfQIItS;YNo>nljK$3#H!J^f(9{B@<`)7F?-|NMzOckUHUW=#**yFvINiI?3h4Zs} z2atdzvw@>WEPQxOrkR?acjHPM8jDPUCYmGhOl7H}2vTwSxVHb=h`2B}Vn3iD%;`Js z)6;CU_)54~?YoJ}sd3_zS7SSsB` z5)rAndbKegk$s)9;PXfj_YF?;F`GscuF^BC5G@h|1Kt5<$rS))qJ#AMa_wRYukEx5 z30Epdz*xFu+ty7V{og;1XWieLW><;m!3M0J*+4$)9Zzqzzw3N_ulTE0ceiELv~(kD z)U}|twJ(5zPUo45?aqiE6(@m=1|>j*4`DI87u*X-H{w{YTrPS_z|rm%g??NU?X-6y z@_WqjHUEvH%wDWqw=5337i_Fi#qS9GkT23a=ZG;KZaK7PvrjFw%q5t*2T`1_)1DR{ zQZ}QM(8YcUR$6V>oSv)Lt#XmU@sUFTX)eT=1+7oWAm25uV(Ezq+X2R8J#2Zm%{6{`WukWSi=NAJ zNS+69D2;*SPgTFHj@FPv8WxA$7L#S*B1MENR;2UCJ!@3UH_16Rn2sOX#>dL?VBhy| zYijh{mhk2*Ss8kOV>T08$?l~=L$OKeC{u=`_$1DhG*dZVo)y38f8^;^fFht!g6p??1dB#J z{RQgGnbn6BrZJ&^{VylFUhjid$m23PHNdbhMtAiSh*mLde2EN0Um2y-wO27N#t%`D>Eoy;^(oqHnM-?9=ZG0HBQNEQoo1n22A>d`X?YqnZ2}OsL3Gn~U^i!D`esz9*~QKSZ|{m{ z%#{w|H<>h9ij;n@n`O|jf{8u#*7;jGab9ia?y{}zKif1B=NzQg%ON11+etzjRUgRq zF??RHu>S%X&#Xr5zhmd8nA}c;{9ECZ@T^G(<)jz|Ng#!f8W& znHXC$d{7a-y}Ob4<%|I*uL*_MV7E+Pg2||-B1`X19xHWSNJVbc6pfg0PBP=Fq%hQt zE7W01Fta)s)O!w_8kNbG&~mudVq+d@k75-{$XMA8a*!;Di@mBP-pQVn+<=jS-vM__ zP41kAmDC&IHH|(3g#8?ep!!BDOZYZ>Jgl^Bv(^C)9(4nK#1+yTRsAU*2kgsN@5S*n zE_Oshq3xVn4s$$^KIJu)Y0Od+hgBvbefP9<44gJW2GrhIW<_n?0s_3SaRW3?P!4vZQ@Xo@lepBiX_YO`Afq)So13Sg*j$3XWLRWhbra{OL`Bq(PD{%gB;=aorGrg#6(495hj{ZH zMYPQnEGUoS+bSv|4@6$x*gD=lu<5(mr+uY z9#p2cV#ZUReH*@)6?!5}rAv?^AN@q~b$q=_&nBI&C&$*%>P)F!TJUOvRg}yI9M8Yy zAQEiEbWI@2-3Nuo3kfC8BXuS_nLxRQcMb=2)mJeMGyf8xEb0$_HO5QAnrxe1!e~Dn zyR#P~FbLAy`i_d-G3q6@zMkg&9p0WyklZh4<*$7{BRpC%Q&I_hr8|4?KiG-fOuwRC zPN|ASL2}-ze*mABjCP}VM#IU~DmzDm%W=H^b@PJYO0)`1Vcmd6cmA`Y!}wX4n-NfV zSs>;)GuQmyGJho<+h5DpsKWCcRl`9CpyaY8bE4s+^|l^ow{?gw54}*f+>v$6d@5+O z!WPR`xP~sieQKP05>Ww1t|X>3_pSz zEBln*8Ix4ETIw>Rm?%RU#;!B$CrBLnX7imx<8ywo7=wqB6{V${LY`abfkfcP&SWJ; zSOw+4+1j*aCd`Hrh4KSrSGRdO>xK7ol;X@Gc8#=il?mAZ8C%*BM?q9NJGwlh^Dal3 z?t@^as7w9p1N8J2Ry-zSw`GT#M-IeCdp|t0REf>_vDZ>JVY1$)!a*k1;6jw%7sakc zPpoKiB_|oq5bBTKfaQ6hjePgDM@8}$!8o{y=p5?q5;Y4MXN1JEIb4V zWz{Ff#KL;@OS0}MY?m0}m7v(O`m)X+`1qil2A0V9Ny@_0&5E-ab-Qj=4|@rE6$jHa z{mg<_e6Bpg3F1R<8>2{~E-Y<_y>734CncSBW`K=VDavoJ&YH61&BICdIfLD_&gc3`m@DUQ1e0=-pg+b$)PXw3na>&Ri5`9*@nzkwPZnkD-~a@elt!d;}HUv?ibIwd$EX+373M zkOx;mLOA`k$~rB;g6DH9%$BxREs4_pafjiQf8zOis0l$_#Tb=lfrJ$(39p4^&~s?o zw;LoSLv4Fi-@Qu4DmBUHOB?sBq>-H@Um6e=Nsg?p9S2G3aI%^8&>LIIen~mUeut#7M&#Nn+^Y9xzWR7$FM&~pkQt5Kppy->-2-t8 znr(pMjg2Ok7s6~v#>&&l@gs@5$=my6yT0!;i=;m^99D(=zCYo<&8)kMn_DclEcw1 z?shiUz>LETuJ{)KP>H+ujud?u!+j|>^A!^81}=Z{D1K;^;8w3Ma7R=$2NA588;JH0 zO27_-=Rrvl#ZgtqMV(;JcGbSn)5%>+)(Q5FZe(7VK5UfgwkUmjW1Q)z9?#ZU5*IDw zZ;7nc!}~3MH^+7L1ZPK4Wsfl%1C;yXYvB8i1PcTsrczbMWvk0k&|1E>?d0)-{nc=@ zOTLP>`!zk6=4<-bL<-Ml*>*`y>-whfk*-(?8M8+;sKYW7;ZVZ!D|6b=1A58gSY(J= zx4fgRg1*LQg?oKvh0I?VZucBYX6fk5J-dWq+$^|E9vr&W^r4K>E&$A~iPP7Y^(L@{2OgZgiV1(!a%aM0 zzx%S4?02eWJ2#R2VIdJp)PGWp@XWy-p1~F<*=t5(2(meMy~KJbs7Uo=WiI0*Rs%G? z3$;lRS~tC(&t$niD$C_XhDqhS>8N-@1Kd8fs#wt(DmMVsKKx!=TD-KOBefG~xqS!4 zv*z0@hx)q+Yc!vhHIjMp>nAMo3yjp7W5$fbVqE^|(mJu23c)Glcp^)!IgN{aqHdk= z_f7L$+Uh`1I$QSv_f$dyyzOIhPyw}l3lDTjn2kj~4V zm1@!I#z)cLg?SS)2$QCpCzllDb>gvTLt3*2*vBFEJSZ~KD4*y*Nu@7zY>p%J zM~^@Yc5X<86ifSTA=N>}OIw?(X3!AA8ecLf)6u99YP)y?>pP)shSOEV_~DS}Q5r=) z$GvZB6QS(B05HIq`O1K5FWpW^I~j=B)e$&f2L|iLj^bq)E&RKYM23<$$+~aJ?2AN{ zWYmC`cu9;FeCfv%wa%y)4^>r1Mc;9I+*R&BG*q@`G9Bb->F(j~ z50Bf%5dnTPP>>1u%CLAg%CiqU{WwZn;S4Yoy#X%x!inRL$D+YaBgOY?e-$Heba_CI z-Y(@fGv|@9^7M`|?KDuAi1~$up~qa(I-RHI^%fl2i3*9}x0> zT;Ht6TPK)z*OA2Y802tYfd@{DY$L28#{-U)j?&0o%g;E#er_s&->bgzEw2D%8+MS9 z>G7f`)D}W4lXNey!353wPnmNuAlu+KmFYE6EZlpAa@yPYshS5HV8!I2pr&QV^EP0z zKON)I0j*l*vz%}s30^hI-(ZPcI$x>m0q`@DbuUF(IZ&de9E%?#D7f3YGmLQSA&Gu!24x`@%7rOlP zDP0FW^WH6s%}|Yx779nRIq)ycVd{+5Z|T+%x73PjlU57)EPp|9HZRtdDVv;CIj#r?EYvIdlq+qn0Axle=07#hiI3M%29X2I zGmX@pueLt3L(t*AzN63-5olr=9%~WjzJhYQtMOrwWm1`UY7eE8v4TqkHF|(Dx`q7- z)r&s0n6YZveFk1RspC56hNMq?`Vny{x`#u(8(RgOo@)|kV_9MeN<9ewEaOko_Wcq8SM3_i&bX}WgyUf0#v22#%sKAK=(atQ%hB=neU$64ivGN0O)q-A@Ob-}gMYnCRu$g!kE1@;t+a2i~`jgi}_UrlF z*^ZkuaOTfac)#ndlNM}6s$qE821`%QK)cHWF8b3Lm*gcQ@^j$MpRumVb`;6zkLpK( z@CWNIq-~A`RlNEeBRG$>*x;W*IbZtYlfGltdJUG+{HX#|^H`QO*w6)n3t3z z6e1Sf3@(i2!ea{eAb zRA?`5|3%_j-$1rn_3qz+Dtok(UGnD3DF#kO`ds{N)vVqG@pe6%H^912EmdtY4C%f1 zx$}WE)m?~~m2gUt=VR2o-<&ig6=|m_C z&@V_8lXHXN7QOCtG$9~^69phxos-F^AOKN|CDnuxh2!CXh-4yBE*6U`{hfc$Ip22w zw%<&5v!+SB{Hm^7UO#(z5xXr-DQjFJ^Yc-W zu7LlbK_NE#83DyvBp>`XSQ+gpQvEFi8$j5NSfI=i&Vhr)0SOx;5jaH{Feo6wsK2KW zV-!Fv1%u*m1&ko`0kJ^g0U#_3fBrFw!^_beB>T|@{9`a65Ec}ay_VyU-$9QR9L%T4 zXGFPzd?U?<2>Jw|U0@)=!TyXx`6rpHUY$n-fr5iW1NZ_PFJQ!@96aJbAYM&A4}=FW z1~RY{(6*q0-rp4;85ORfdHpu##p|J`SEJO)R!{Z&CeztM|mL6Aaw-zo&jB<@2O=$in4JS)@%KE&GE z=T;B^;BSyee}e!f0|reQ0NCJ9z&%1b`MH~8l|27DKJhWTbU8oJS=cKu5d>{NaXbXa z0k}W3KiEKkx-otLOwTAC}`&ZZ#+f6Un8!6Sw-#64NM01pJh z_t(>13UYwft$DKCx!?9540^FaneDYc@n_kAUlt3?JfT2#dohl@wjvlzumA~ZfMc-8 zFn?3JV1l0-;ICXYA5;Js)wc@GLF%uH{ruh%`llAgV87oflOQq%G&s~x*nX@&CPVZ+ z=*M5_M_&9Nc9);ohabXs-_-)-^TUS`>Zi>wf3>_`vE2EdE07*=1IR6Q5hRqrPg@o3 zH+=`z&`v@?@1M=e*gr@tc`=Xwcn+ilK(29w_AX2$=%?=}D!+r(KG4%3;enZX!0|nJ zD0v(pu)nmJe5!Qzg9QkJ`-eD4S%ew>xMk6S`P;q~Qc7azU_*v^c>sgq;2anPaKiC< zT)fAy0fBfBs2I{<0LU8=07P?`{WqQxRDXiOrf2N$rnD5GKnN;`ccq0s0SgZT3Yb{I zKfqrH9vXxo!tgtIUWk?-t3R;|dv^#Nuyh=;A=~XoPnWA5gBC8c`u9iw$MF5e8zBRS zZIV*T`}9;C`fnm~S2(Bq*gKoDzs6$P<~p_!uh1>`uS}i-!kz91lJ8~G8AJCDL_+#&8B~)L;JEj@tL)e7jX|qQ>WDQ z#;_H#)O-Ll(?ug_Fhb*qhg_`XQc=R+pq=LJNy_1)7ahntm4R^!q&SfRHK~J zQoGz(&{-a)q?q%Qbp5yeqcWXy?+))+vPQH^{x8h6)$Xn4%Z&9T5|>ic2}!oc??ffp ziWE#Tza^IN7b~c7A$nP~$}c_?j61`X71L$ZW`-#WD!wF4)Kz8sKi#<$pElbZZlFdx z)V--oWsaF)b@)0O$uf~_`+buI)!lx)L2l?yW>n#7CXs4xalB2JmOSC1YuTb7aZ5D{q>_nJ%Se=3vqn8Z)Ah+?~4kR;H1Dttq~W4 z9Tyo!ZtfYHoM7z|QWSTdZpO7FM>=h{Ts(eB3xJzlb`#=4DU^$Kk#(d|fgwqLGwQ83 zj{@vvHf1?64&LRSC##{!Z^7U=Y-(9cgmHE+Y~s*YD8;yppeueH?l?B0HcdU+qf7~# zmtoJcRcBqPV^L|uC;-8#p4N0IF9DF=CE*C=VJ&sWUeFg%c|31J?KfhTIN24!-tk?*5ftzm# zb+Rm*?nKL+(s52B0aFcv&Mc*D>wZn~{sj|KS){M(@OEc?MO)e9* zNO!3jGQ31Rigw25J82+`FP66CsFnr*oVvDoyJ4*(!d@SzVRq2mN(63b znpl1NO+7>s2|3fuSXl~9Z@sz6?#lPK4-I%>Uwk1UuiE$V7#S{sD zU1gKk|C<`>qNxZWQapEqR)IIV5fd5UP(Y42YT7)fOBom*zMA}CwCj9C9V#XaCIYM4AEYk)w zPup=}mZ^)T)Nyoarw|y>_20QEF1&r}NBBK}qt)3xgQe?1jhhQs>V#7`feM|^iNd&c z+#ACwbFO%CsuWvA6gp1=YC)OIW4CZI%l;SXX^f)FTN@?tney*#T<#H`_=B$*pe%j6 zivm+S+cE6m|6}YNf&>B9HCwiA+qP}nwrzCTuIjRF+qP}n_Vg@n%$tb2c-zD-Gcx`> zndkG+$w%EGb;M%q8{TKC%Ty!su3nbQG6ru3 zQH)Dq;A!g)roEOP6^2!uDifo!aphvyzi`t=AmFzCxwtfsG|(aFL<}e`bdbmn^Vy&V zI;A(gtP)i5`yP*;!$i*XWmJqKhvKo@w5)gAbrMXWh`$fFs7U&VHesI3k816J%k{ud z5B2J1c@gw%DEFi4{mj1;HMWtV=^>5pAJtr4EDCe~BXezHtQSsA%^E;< zq;yz+mGUH6la{s>8-r|A!yM^TSk@`0rMN)aS@q%S3A|j8^NW_2r2QSPUUiR1f|yqA z8{bCBXoq-UO*Fl;$JX1)<F|ScwH%a= zy;7F)LrP*O=&KQg`pYK(Yo0(3OMK|VDGxB3yfR{i!QS(=-h5;`4}n}i5eD<3S{0FR z>c#LRGc_4I_JNxzFky3&9o19Rm9-G{Rqs6fA$<-wRE(p54G9PDqrtq~c$o%#kR2ZE z(Wg{2rIOl)EB$HA=5adoq}VSwH~#4Joe$`d6^&iHbFzBH*Us#=-O@O%GboLspRIfD zeGVrOvl80Z8b4kCDFc>Kb(4jK%%hn6h9TPhx25L3%k!x0fw!>-w^|Utpw?RqM$K>g z<@OsU6|QU@S=XTPL>{H}G`wqJHErSqWi&PrIHmnv^2ypamMId-ZRMX9#RMtpiWA6?q*iaxv#nT>;%;v8yK*;;x3z;NA|vo zQiW)Df+M3*ILKXd<_xn8_VEm?_J;ze5LCU|RsuKi16+3^ajx8HzeRbLW-fxGjo7#n z@j^xjKf$PyiHiu+Eb>*qT}2D)SRYqCd0;#Zy303q{x;_7dw^P@0;x`jud`WS#0^u& zwpPt;X-=8US*J(r7p!#G#r}^-$hq;^@$#Db8R7IQbEae0MdCy?mUzXc=9yyI%!lcj9vEl1gk&30m^>nvJ>^jDTv zu$c6?4dlY1@g?bX>^`=xX&6lx^!KvC}|>3#QsyLJ&Y8SB9D zK=^@cfou*kBCUs_&a7W^1^5Ye67Rzf#HT} zrgSy`D;|nhjq^UV0PT3xQI69(vLx=kqz8g%jkKnf3{U4q?sFl(f!aSM438`s z-g}*&ldklQEq&CA<)PxT{LeA^=VGTnXsmXH08Az+n2{?L% z*rPs*p(9$hBukKu1Z~qu5YF-4mrpW4dC?|nX6%)j_D*-UmRZzx-}dNCrhT^Fq~yaW zzrEy|FBn9Ev7VoC`Sb#XvTiW@bT-CD{ZC$5q~DFauCV>3g!|9_#H;BJ&#y@$lA2m0 zX92uM@i%qOko3RI`fq47BSHG3n>ou)#9_B;7LG9nYUis;7ZZHb1pbX{tXV@onuFej zGs|&{e7(kUqIFJbG;u?&k_+L{Q+{d@_NwP;7CuA%IqzJR4bngE9o%WXSs7((%SEL3 z7&c0hc+$IP1?^upO2U?k?F+_(bIE8xT%l~b+Z`DU8FWWhbFW{_oqt4xT16H0H7SC( zqPmVc;i)FePSP$J4=mzIJBU)R-#twHMGcG_`lV|Pss-7?2kheV8>_SCLF*N+OVRAR zSv_UcQ7%i!C_;)er$P?gjz8`eRLL$%wC@q(;NaEwGUe@d=~qF`lvc87HrfiScREz8 zpoh{%fWt6ja9G%lwtD7g7U8$#O_Ax2;d#Q)tEZ=uY9lC67d{hRMI91#??gzp6(gNI zzQKe(ZEg{6Nz~JtgS9$+L-j55RZG%jxz++iAMQH&dV|^As#kM=KLpg7({Wc=YyDRX zbRqf$zA~_cN;|^!)e%Ws*-1{9Tnu_>H4+f}Xs%u@?^bQbZu&!VC@WKk&1bd6$VntA zAs1nyj*lw^CtrqQ-@ZH_t!s-8=>`coWGvGBn>_nE$6BDyC$w4QtlK`n<>)ta- zTgmA2VS}$47n26=&WU-a0jSaG-TvFyb|r2grzu^Kl-3@SHSJZazz+f+*F}}hES98e zWv1T5jDFYlr-F{Yhvv@@74t4u!cvzo=sgw3+_ds=JhQTF`U{#$Q`}d zoowO3WV0wi-z?R#`wV~|rMPaYmB6k>BTUz=!h~oT)lW@$qb*&RiE?R>Bt~_FQ{kZ|L z8n{yH?(d{^xb1y&>Rsrsg~jok57Z@i>3IG@ssztnHYH+N!l_SR;ig7b5-g;==9}XI<^=Mc8cI@tvIY}u|j>hLkH#s9HHU;xmEB8pTzQz294?<^8v^zzkNoCXxBQE($rhw|hLi?R=}5aT znQ3V{k1v)nv%T5b+OT!Y2APtM0d2-B*$N*twR36^ec;^?i2cb3p0;~xJoogb;rZgL zhPlw5D-i754+p6M<%nwA;?}AJ+`i9p%Q9R?GOZu}GADKQTbNF7iTl%+!Xu@IB#ggK?Cvd*f+)q}&L)uFg|Fdt>pl=p(LF|@4*XnPzSZdEY*8vl1DtChb z?)q#w2BP378DR?7wG<*-741~8>uR96b~tOY6c*8T6ILIg7DXq}T&wU}B&1&0OiX?8VyW#AP`a0#V@$Z zfu#~fmIdZb{^p)MPg~)YBPz>J4er%iDoJljA~h5^BPhbDHfvoq7sy7y1RXC{R&}TU z_Ga4zzqs*Fd4yae7qfaV=pCCA==6IO6h@9WrKe&a!a(o^9K7O~rNkDNvPXx&4=^~Z zbhT^el+_rN5W^c>Qhh|=ZDkG-%GZR?vGTC#;8``e*BXyR&pdy!#o?d0gSlM(4n;`}WwBIn`$Dte7hN!y))T4aW?a=3ZVOdyO@7>>7r)u{-zZ|*q!07j@^!e{}XV1m_p!$h2;Wt@??+h)4hZ(-{5M5#BBhS$X`4sT1bpp zXSLL8`NQOVkbp7fhzDId;^rfm1EJ9hE{YnwBmvK1PPH9WtS`CFojk^3? z-D?vt^6-)Rk(r6XO%S`K6TF?-r#mFxl0tH&{o2l;Eb<`f4DpV$dWJ!#XKr0GTqog= zan%z(*wdW?C@9v$oU2@E)(gCCXf-EmBoZGvJa{$d4dn~{BnGYY@QZJX{5pAb44!fMO=azI6VwrP7E&QoiPpjMRaN{(u9y>&?_VCxzN%Sv!9txDyPMXg4u?_n z6PdSwr{?KOwZQQvQb&!Xt=zA#&6Rf4UouDbi{nqtxpew5FzoXW-Z`ejrlgkyZ{H>o zaY$nR8zIjXZ-`_mY0SqSpcILM`oKW!4ye=`4!cu+`V`BIU>RZ6((xXuuAgt2-(wmGf&Mj0*m|p2NSa!HV+o zD$)$3V}uJUPc;>=Au4Go<&Y^e*9m`dLI{l>eEMYhta?VYqHmc!BF|ZRpYYAw^=oB*=Xq6Gq5Lmk6d}SwsQu=iTiRs-{}MArBu-$ra)UQg$~JffKiY> zady+Eac!-JdtLo@U>P$)e7cT3v;)F>v>G)>ZBI+%>Ny?PbRl;_dxrn$exIX@EI&Ml z6I@8$j|X}p^_OoME&fG>rOpIT(|DdJz19@f{)Qo<0d(%J_4&?#T`NAA^t!gs6@&&Z zGk@5iUyjts*u-YF_}jh}z1-Mc)W2qV6Q}{$al_^;>co`1z1fBN;F$5mcZMWcwu?kS zOMx$`TAPSFg7*;USm4d+P&PZ;-l%zv=x=ZrpZJuB-Mwb2pAZh!dwSg}yWkKL-tBXJ zCc+vl`O^h%8wk=`$*r$$i$kov&J44Bz%&RV4p1Vf70NrT)HRsVo|dyrdc-*X6>=$# z>$lNA_vK6@L@Nx`zD=i;BzbCdDT zrFS8vXf%+zTw_tf;9*i$c(83XNh^R+9;Jb9N2s9uWe$Iyz>*kx+SD#*7yO?8S3Dw0 zPwYJVhJF$X?*xPQ8Pk*877R&AlgsV-u!il?eHjp4LPWV^%slsj*Ov>Bjrcnoy~gUj z;)bBqG>G+I7+I(%UR+`>++xy;l080;Coi?(IZX#V1y2kE3);JmM^-^paZ*}hwsNN8 z0c{8ku9{>U=G59SzZ0x#1<}o9XO>p^@RYLXn{6q+IFx)3=j5?Er5Z_dY6!7Rdu^P3)sMm+CLvU0h4)#516vCk;a=}!gF$d< z9|d*jyp@WrYL?O9t+M{P0xJY>1ba%V>Kn}O%MvVq-AMy2W>I^wvvPon+aBZnbxjC7 zN}!|gPo=X7wX^%m1^p2VU=}xC;MgnI?pqpEipIhB6i%W}DuWiV05#o3$6i|Xz=gKT}}v>5AzCt8T3ff$JB{j zB%iyod_kW>|5#17AVzIrq9=x4BF-6Ns;+E-iwJbL*x0R7ZC##n_R{U z|RV)k9{&_+Ud{^E@tLO!dsfV%7$lNhT+}gO*fX@JL?xXAbi?Vn~yel z5J&;s;g+&L*w|c0M^%-ZnR~Js-;4}XyH~CHn6$m4DGRk&Z{|E7x7Yd!&c8qHR{pZG z56k-oC`#!UW*dI+qfw$m4|aF6Yo{1DhOmqZ7bAK~2`V$&vfik)o4z4ELE*BW<I3jp)e=ADsm?@vaI!Fc) zIbSIy&b8l*CpCNMzTn04)ZqUcqy0x+3G9umpm=!x!(kZ-7+Ki=C!=L&XZugz|Ho+m zwf|pR(*MerXqEjV1`ln8ouN0LdZoZ;W97%gx3xI zN8;%xm*kZD)W64jwZmQ0y6g7ws@vbY_j5fnL{U0Td<4r3QYlyvkw-w!&oLmYBbFi{ z_ZN|#zZZZGItmX15b#^jQ4>bM0i6Mc>ZkXg!eOLXKtV<( z__LK;U<45aTw7BB@B$5jJD&I#P&+V> zP6AxLj1>Y-zz;jGvI=a@1xWB0qwX6 zeg*9SSPUQ_3#y8HA|8Y-0O+!3dSU2rXqa>ReKWvN4Gkh#KXN-jh2(iK4T8(hN-lJ2 zjy1^rflHv!uZs9vb&MloYJuuwgVdEZaT> z>r8!8jZUunbK(A(8UoYzUYdA>8~(qv5P%2=r>E$Mz(4{s0P$fdxVyD`UO94kyGQmI z>AkhB$WxF8K{S9z&@F(3zx!UD8f}395N*_BtDpYJzdidvfdJNZs6Ymg4WPn~zY(#) zf^7abV>5&OcmT$JMRx%JKl=XuxiSeG4WWXBIlq5@?dWp<6*mc~K&!WZeoIVD4elJ(!BlNtA=j$0T}rwiuNq{Be{K6eqQ;*f`RYt z*0UH$4F3VR`i0mE)(_u>dI$dcO?|gd`_1g|7k&4K`09OBbO{mgYrN_``weP#01@W- zJxK6!6(wk{g%g$oc2pZnX(aipTjNstx_trXWwiQr`M^DPNlhC21J>H-M? z2~4x+04=X$5|zMC7{HFe8M;Ccw+jV!cQ0fJQ3MU3`OlFA1~&S=EMGMAPP31u21jtT z(J1&u^t%`LGxGa%%O9MF5HLHlI1bm2@Dn-&;4`-TGK+hD3F7Gbjx9ho8|D!9ZA<{Q z_XyPg3-C)dI)Mr&4))Qt&k1R5(?i;kJ}H~>X@}uYk-WJj(9=*i;7`agZoB_kQShd3VpQxrpE%v%(KEMc zgnXT(Fr6LSv}a=7e&TT9@mkR4W1ryz3xqp^Ez4EWaLU#dg=~Hpo7wJ6=CSIKJ_Q#p zVQ+4w3Q@hUkskFdcrKIyiXzk8(J+%$WHco7IIHKBgy#(>lb0ixa2C74P&sIK@O>@7 zNAwM z(#yo+I=#!zHLIc_KDb&^@i{x1NKl-Nkw?af!a%MAX@o;pN##xUk7Uv4aIhu|xMd+E zS`I$Z6>l+St<}~ECT&UNZc+knP_n+H_CYMdYu>V*u%kkZ>wr;gc;!n=H~JpxPSja@ zrT!j^7rSS@JjcQzxqD_rk zA$KV?k%Fa`jr?#&WfB%8C%-kVgz;M8PUm>KI)E0RI+6(InNuoTb6w$0YTwl1Xv?7! zm#X3}aB=h-)wSsk`TZ zlQ9;A;gWS4ufEjMf>k;S2>;}3tMqBQ$6?1y3eelW#_9emDtTDvTJp*F>R||-yBjrs z$6ON4>w+qCz#$tbUf}F#KqG(>SKhE7T^WOhoe(n~Fwmb1#4OBm@BH551u(E#%L_u9 zy&%p}g@xg(l0Lz1=!u{riM#P}Y~{WRP)ehaVnEwpmES`x;=+-VNRS*vZJve3mx+ZmSnh-8 z&r(?mU4ZAU3nWGIoi2#$aBbklPKzQN6qfF|&cm1=;k-89x54`hqc~-g_+Ya)Y3*9w zD_z;3)+6>xST&mZtl=TP=uQy$eDr+7Cr&fCU@M}&Sj>e^f12P4SpkHc-bu88=;dM)(=CUu|B?O zXX9`^W6AMRh;N{3uP|+6r3Y`S9Ob)>1Bk|cr5~~5;!>vJLDs1Ev^9RI$25%QZf_2J zepC?cd}KS1r?dm(-1-vMZd-&lrPc0*Xf+RGFLBxgkab2IU6kZ%aOA?zQoZkABEsD) zCV@;|DH?>?ngLW6ha+b0mDuX!bd4t7=G^xfpJ73p8ZWN!FNmXbabYLrsa5ik;c4Ud zv2MXD&8>Eb4EvR-8=io8li$qMB`ZH*UElueIT6U1p3|6|MN@2}lL|hvudsL5h5&`Bqzue}u%Nwkij!ijB-C_bat`OLP6efU$oT+d5*qd?1Sl8ZOA}ik zhB|mhi&2x{vn$)SZDC+z2dD)kAt7Fuyq3oW z*h0s9&8sTZ;Ck=c*bxuL{k7}ka)Lc6od+swP{b5UP=Ni_i6r9J&YMrErC&1r zg{m`N8D%9_8f1XgZAVN&n5V|a8i%?U!V%;bjN|=vOrbdN%_A#gcaHq4Kj0;TvM0vI z&iarFv!he>Uv+YbD|+_T@uZ{;8%I{#F2QeAmn4ua_plYy;oA5bB`hbU&3B(_BmG;ogqL+PhO z+%)`czAa^6Pq%e*LfCBL#|_TgXs9|O14{Y!F_l7YEdq0wG5dTW%8X+|QwJ<7^k51kWrUNpN@F zHV&7{CFgiC@cTB*odk30+D|}`|2C5@>Q-`!!78Kv3VuP5b1av`z5qqg(L7=ACYG4S z_sT(|QEy|Z>l0|%j3pMhj$2=}xN#ysqSCi>8z*b-iFP)IcPio8?OFBnR0@vJU!Ib4 z!|yycB63vI?q3caE;P>EPJqNlRYi;2M!K;%levzx;?j9Z-f3}ffeyK-PLi-Q(hGU> zj?uKmgtA^b`HnBA96Z7g+-=5P^Q92uo|^q~eSH4_n-QT!f~EV=STk_8llcH*j6U`j zg?n-n+EA(&x0m8RNY~~XFy3v0+E4psC}B*yv$Il^8>q~j*svf;DE8vUp5Iq#$d`Vt(J;;uCVYC{VV9}UeHisDbKgGaSo{<2$vdb~ z@M(1JKI;p5EwJ$x@8*nnPJ%?t^<9&w)&qoDDK?|Xl#G<_hAij>83TE%MFZB~`OUtH zAw+(RZEKZs-Iy2+Ce-Cot=Z$Cvb;>~H34**hTUO#mSw}0mbe}acu@TfA*$lMDO{&GBO+n311 znya&XHJpoU5i+PQ1$YQ;2-ArgRea#m;!Q)(fm9)u(k#2)SG1lae-5s!!=<{p@5}c<&x!j!)GGX2SJPdGaXlBJV)9Ougg@ zykooXQrDZ3;=A1Kxzv?%#~wN)Md`U+diD6O6dS1~nFnnPkD{uu(I7R?vMGIDG;K!> zT#lWL(Cz7Qw?k=NsRf+nw%LAFJX>iG09x(f~& z2h-1jS=O5QDYK1Kt|VDgJx@4w$wNJT$G+h1_|g*yoj>UQl2`2YEbN7wvksU0*#-G+ zwwY}@mkfoMqGV8_ZNbB0GR2}d6bQO%u=qX0uU$}8XUBAanV{o~!WCeWYjg@U8F8P{a<%i8r0=aG+>Y(Dx(6nXBPnXJ>v1!ITOEVMTIBsoa z^hn=#tVX}PF~s@hHPGr)S2cR*WKy!PY_92z8gOjhyY5Wov2FcrGFWdfD&{_&dzF~B zI>kmcI+}MPkE1Tl152iZ}m zJe2e@aNs3y(@8jW<1}!ixjDuM6ByAgPt&&m3OqcD3YhI%u!1W{0;kU(C49ScsV=+QM#`;#5vC^j~KJPRZR5p&yS>t^-vBK~Di=uDmVJX!xYI zSSG{sge4gp`HtOE@4t&CCia%P2New+My}t!uuP(DNHq}jaX{N*b)xJ-=(uX=Qhp3C zz6BHSUALEj@hFTR=hz|}nVOwR|F)pgTa4qzPA!YpL|RJL+Qa+i>hlb-;#+ z0j_LI!NAo6al9?b`(ma)ZJDo}k&D0Zuc+&r>Es2NaH z65rKy9F48ZEI1(Vs?`m>tx-xs7%W&v>zE+JYpc}ct~opxFzP=ofKwm_^|oxnxDna- zu6r?e26GQn-n4Cld)T}$OO#Zpa^5ILb-Q7?|7@&%;Q=aNo81clePJqXdkGfybzQ6g zxB@i;3bxUGMS24}#cf}FvIEyp+0XQS45IGb&|lj+SMu1NHIUto*~~-8pae8tXQc^I zH7v8oXr~DSNrR0P^!2)h=!FXLTQ)v?Ee_@9(E+L9Mq~^#$7y4`nYXivPkp%vGyEBh zpMdFbdhNQA^+{SK&=*xV^tK!2?6Q_FifSx}>+t?8Y~Z}dY_kr%4s8m8g2qfhc*1DF z9{RY0i90p>25g1GnxO06;82rLMYMXyKQ32np5X-B0J;~atBS zZr(Z#%s78G%ZRq_FDU6)z%F%H6MGS|_~y7d`eT!5#B5R6fH8`?dweH#Vo+TzWYP?f zTN%6fyVIN~ZLgTF0}lMj&hi#qD5hWK)EiwutjdfAj@HB*J@<}beUyVFv9q7{(U_f+ zka3x1j?~jF6hMBbm7mCugpao+3jr=smL`Cs#&7T+ge!oY!ud!Xr)TH zQ1(rXOhq6u@MfsUdE?AXfA*_g92GFApMe&HvKBQb5K&0`FAmWPVYyQap6nd8MvElKCB!G9L=5itKGj5}BTj8&u6g ze~H>iv1`#I{c!>}A~!ai`b1_uowTEGL}zOHb9lErRU>m=4hXWdK`P5#w)l=CbVR7) zm23FBJv7rZ_0o#R^kYB$mG}zV+OzVTXc6hlU zL9z~M9#EII23e~=Ij@Lt0vp-$pMp39I_~2J2cugCzDro-uGyKOQLyde##yjI4L43Z>7}amO2N^ zfW0M{J(Uu_z{mZZpKNS)@76;>&&b$q(SMUni zXbo%sa&2$jm+Awz_9slz+P}x~?z|73XS*rN^WfZM#$_!3$f#4A!a1l?>ZE;YYS3SK zyd9Jx7{Q=;OPV1`PrltNFKa$tJ2J<*&EbMJlNIsM*fdT)R5zIEldhi;jiQ6ccLVoE zL%<$!*N3z3K2dimb{q|P&rN|=k1l3k$9F{@N|0?|D>DaLsJ<$+jzeQY9v9zhj>pw< zSyDM|Sp3#B!hB3ultSr)t-LPg)86X&=u~=gxIes4K)DDZ)GNSQ-U!j@jXEp*OWW zKiAIg$ymP$=C(g6Q!xegi>&n16kWn=6MTD*_Ilh|U+i9T52Qz{lW_4(lwYHvzz~g= zFOAUv_AuR68U6#({T)iRj*@sI%q9n&hE8uXd>3NNO_bLmQq}Vr6lsMid3zN>74-aJmgcp- zW7mpVc4t$AYr~u|o}UVmJ}%mFDh~p4*XnM|942Y6l%~PIUwc3yt!vVcP_4Vp%{FFG zBYB7(5j%6hJbDAw^XVv`*=Cw+ghl#M5<9gB9bSB0cU|22<56hS90A_~pk+gabtcYr zSRZoI;QcNaY|)hgmrzb^5!&LGQ|CpDUGcgVeA@;NuRgS`1V4iN1YYSG z-&Vhy-Gcu<&kJl|8#o60*@S2Cg?u)v9gmq)gY=tzxxfh;Q9RmJnq9a6FWqIF zFDj{Wap8tdoK?!uoODVEGr5GGI4o1mZ8tgTnKR~i8hy_?hZI8w8n&gf@|qKM_S8um z%YuLN!F!Cfw=5D+B84LyQo9I#D(Eylsa?-MROe>ph6eP`Yld)Lxk!($1%6btmSc*~ z(D4(p<$kYqof}LR=v2~kyYWGEawKjCdpU;cn1tEM!-OsKQ5$phbP~j4S+pxBtk*zJ znN;lFpdVjBAoy&IP~~{a5mB`L*f|`%nVN(;ess>WOMbHLiUs2}B-ymNDvg!~g})P2 zErZGqQ{6a5KqZ2VAr+H<-aXIf2<{zU$WiRQCYR5WXf0rUS43u|77Vs|g5aMka577d zW7fkb5Yv&JEw`sLJpKxhM9@!#Fy#XEATPjo^lui?oOv~2W32prR^WCWI80sT@_H|x zr4w(AROR3CmYPv+YCkB^<%I;$8g|kQXo`s0MfYR2yIe7Nky!`oPeP&P-W=Tp_VhT1 zutwQ0_OqIJf;`Osn$X`{+l`TiE9abwV5Q66^o+nV{m%HDz1D*(##zFrb-TRYhHKb} zW`A`oM^7W?IX-(En)b2b)sD9)U+tmtBb`!jJJX}=w*HB|>2C=y&dw0|hNus7Jq^Yh z^(5;wg4?6M?>ag(S0ogcg>KWyEH(Cv`Z{cPum%rSXzM=;f2f_+=uRX>)MN?j;S@?d=*J9tiCOU9H1zC4Kw`&-cX zvMkTKqadB^W1S^~v;6ZW?ClU^$LSfkNEvY!7)WU?&}=y$R0zHFivPhz5_HxxB@PU{ zRnMQc7gKmPox`d>axK2N4XdI>_Sq)J>^v)g5-Z6|cyU=d8)=89f+1}RU$!vz>#|qs z9_kb&a7F^wtTQVS%5n&SdBD+8>Uv+~!2xSej@?sQ(v|szD9bUiT{Wf#!7Jk`j0&5q zI+6wHCxf?RhS1G?SCfC=cM_jar05C$dsy7@h$=Ov@b%F2*Hj2yAvR16sjl!h>6Xg5 zBUEst|1bNh$_&`)w%M1J{RI(rxxUp^!Qg}%11^Pwu)RMU@blm4S8@hhW3HC6Qp+dI z{ev_l6|yZA|0X<}nE?FTQH~3)I8>xLCF>MBy13nh=uhod z!*{|W>zkKxf8ukQ6N)1(2)X3syn5X z+3lLfl%wbu!f=*uRjy)sbZ*tma17;MkNx`X$Z_Ir^yStcD^n?-LDskQb^Nt8Ovo0V zJ+##lD*<^=dkT!*;>le18=qd8i!y8zS)b}fJeicuA1U{t8?iZeTZ=hlc2D6Y#=Ek5 z{V9`-IIW+iF87Ax1{ch2LK;bquKs|`h@8?l!umf^cY7X2(yL8JcWkw|DNzvVkr$9* zn8+T78vgZ`I(AcH!p$i|#JR_k(DCtS;+TJ|yw)9S&gp4lmwQq#ZOqx63bF7Q%p@+R z6r_Jjo8fLY6$JHi!Tp4BrZ2Y9c_%PGtHGtU5#u+}BTBP6WGCo2le!%f7tJ6hgGxJ) zMxtaEt&WYS&o96wJc+~*#W|cEwk(brN|!Zx)@*ea!#?cC)W8Wt2O5y$(tHc_6Mewu zw^Ae)fOqHmpyV%FqD>34RnRcd=?7kQR=cHQCK6G_RRW5Zr5O6& ztgKer^H%g|^P|||;o!pSK>^}`(*QcH%Yzdu_M4Vcl<@Dka$~5P?`xd#RgmUKSH^(c zh`gdbMr*Ow>gCijmVmI10(MzaG?m2nlQZDmTuy4P+S~hEvFwde810|0+0Y%B@!PL_ zv}s;0a%zyXgCobqt_jdGZ~Dc&EkqX5KF&K5Qk+M?oT5A{ZU>Vvm>+ShFr11sR=jx5 zkUTef2zYm_WMWqo_>h9Ddh0yd@5@cKt=(wVp@(7@_6yj|!odCCxHjv5Ubn`Wg=)0qe) zp%uh@p%{DtvQX2%MbZ6=#OGyCN0Tfsl`btBb_=QiB8;%z zqg~JlfYVi1R}TRIL@_-$K2zUtC0`zHtS^8$;d$kTnjDgP9g|~lH&IW7~ zatuH~9H<@sXbBtGwbI=%U_$^oWa8#k7W^aIfqnm?fIb%kkndI<0J^rieuFUi7fMHyLw70Ro6uB_+ zC<cvgQ zU;XMH9aQ6Y1D>tV-`KLxJ^~uB?q||jIi6tb<0b6%H~reb?dvz;OFj9QdiJ-KNYtIY zl>e#9AM4Bi$-uk}`GP)5`=Eo^K1~4EuM?{7mz6c(tG)dK0lTd8@z+WK5(7$ zn^~ePlV}Ors1Q_CYx8$JjsIBQ-!=mo1#JFLr=b5fEdZS!!kNEI>!sXDoGPo@%}8Eor&vzgByApiy-K(YNmyY?Y+3W&Rt?rm}G@Kt;kusesry)kX{K3ne| z0BsJS+tu{Q0017XUJ-vHKY+NKUI~c4!e96fQnt4J+zq~*ePK49DiYVPM+V; zAGXPaz5#!km8{)C4qnXOgI-!6f3SZYV+0BG5Ujf*7~7gX>V!od4mI0hs>xR$)UVp# zOTsk@XB-4c9$#hby_-ABr3PynQrSpVx+S#^L-K`<`I@e{&A;_Zs_lWykW+Sv=25uN zO<&s@yk_9FuL+*m9I!e8_lv@ZKRX>aTtrgI{*uw$gBhz64%mKG7E^QbXKty*h_VCD z1au)A84%GjX^|CLBJ(aljB&($8JS4o2>)S_bX!&SbpWRCdV+fI66%1=~KvT87|=!@P_&n+K6+$me>$Y-2{${9l)dAyBnDl~oZ>&M$6 zx@%sr<6n@If9MPEYx|tvKnOBZ?j0| z+zH^+J`rpYUz?#aPzH^DLoSXW~ZUUvwm zSJ%dB{fSkRb04)Y`{^>3fowy~uMY{%41pN4cmN zxn|4SzoZYyTy+YRhKA;e?tv_Neq$;g>#E_mB@sG}7g~CrQvPUT^aeia+&tm;9SSaz z$r@RTf`@A}lR}ku+Fwj%^gz0z@*xsYJHA^<^-|LMnbv)A$~ULqBw!VvDd+B2;yAc3 z&L5v_;$=rk#dH8g6d z84!eVp2Hnja=-KeALeJ~ZQL~ui(JplB1k}LnRbr%+4k(gQ^o*Vh@5pb-=$~e<87oW zYa5?mQ_J0Y>j=TzoA*H8LhjQ+-b&h!%BbB{T}8RA)v>7|7Q58%me89MqA&MXTe7Xq zhGOl3Z0oyz{s-eQ>t}A}#p*F4FT?9Hweh6r((0z8_pdW$6cZb7Q2zNkipBg>!*dEE zK?0`fx35IzL1pdKK~i82&-eHkNLgHRw})`q2_Y$Cy1k3;(NuGX=<23%!M3?jerV+N zp)cBV1?Nv-PqoqIFo~g<08`cac4Y!S`*40s>1b95sH4m>1}yDr!M1qq{>V}_v%HJ- zp)>J64hCWI$3sp;goRZ9o9^z6o(1^A4y5j;F2kD;V!1h5e zqQ;f52^ES$Kn(gd;9V){k_n1ST~t~`BUcwMcnWPgpR?k{Mnf41{`0zLg;d|?HKU+6 z_rSMU=8q_!CKme~>wb43M(zr#U6iifttc`rbPj1zj}6D5^Csi$b7xRi!jtsl1uBK? zD70`oh+7qAD-4^Jxb-}^<9iYyr2Q~+Zrj2rIex^(^%g|xfrO;Hk2K7cc*Z8KKf=kQ zl;Bp9UhusnM8isrw#i|Jr&HyM310g?8DB|QQ(cz);!?DbE&s5z284VNoM~S98+EM#6YD0HLZ22yy|H)fA!&KqqT~hXUfUSOf zHg0Jz$lncd)OGLq`MK4}Ttt)3wsvX4+x<=zNOITBKBwrO4d;=qT_{iQbk`^rSQL|?c=Xsz>QZ2@KrNz9 zD9)*j&&G%LRow2Rd8fPpCuUAGQe1*&}0nVF{yriT3g@8{{_0%%47bZ*p9iX>)X6CkM6XC;-- z8vc!wzuA!y%$+kcam{(joY=}A+|Zl03!(EGot5+Z^>$4o4`R2ss=i5-`nRT6Rd0!n z%y^t`wWcj~|`SmaDpJ8NCK|(f-vUO=OCOnoBe=9z0J4)=tS(3j#5qNkhh zHsdWi@jiFVJ*F0S)s`aR8iFuiZuQ-^3{O$KgtpF&(5b&=3*~9ZvWwV~G|-qT-(ce~ zQT-3>FsRq^Os9q9nscNRodmPPTB2^4yD5G*N0l>=sp`pev~kx{=}e_*{{*ri#BfZIAb@ zXqkFsa%=LlD{&gl^fBh%Dwq%+Mi?nbVw}4@ma=9g(Vt-x$F~;~);eyG{pW9QH%TDT<2vTfV8ZQHhO+qP}nwociuQ?~6sz1~`{dvFIm+dtvknUN9bE%6se zzI}a1$D~-m_d=hyKDf9+b&T0!WmtX+o&34rAW5lU`SU9Tk9XA^*-HP@pCuIqhd?p+q0Y@tZ@jG!p!z>5KF|@=!q+zc_;v}a z)w+kor^v%ECjW320<@$sG}q@3+iCViYnCQUr37Pb^dVn`x^DkLSbE!FGu}EApEr(f zSAUrZRw$pQcRj~clfB?O4k4U`E2wKHTOJceE970PJOlbc-U(z*V@ojK6c^3w7Vz6 zQ#cCXDWFAp^WqHD*%3f|)q9)Gx^OEnx&$!p!GXsF9<=c&=EO1!A9y&(rn9Xw!#o7| z9K)gHU?X!4%s10Lm^I^p4**j?>M9f14N`%9WeV{qfz~V0= zWgtUuGKEv%F~c#pB*>RVB8yC#xd<(9Rn3Cx7u;vsRd0;_vGlIFkZ2nDN0d>9p6;|J z)A0!LO76MJuUExTNqO7dGYO+6w?-nu9lL%Hc>lQ7GuZn>>8YI3kv6e7Sa9u&QSjC%5HPu(wSj198}F*#AqGBlgC0?uDfpMudU*d&$E=CHvC8(?A9@TijD|J-I={aRgla{NKv67k%SPZ|CWpvgXd_y6Q zLD-Zi?gVEk|0D|QO$v%x3LZlqzcVRNriy72mNbfIQs#W3F~UA~)BfD!3FM#=LE_L) z<@{8W(kXQ1NW~F4eb$W=TY*v_Jt{S)1)E~fd5|}YxL56;4C6P$Aw#oJ-mWWVij}5r zB&=E+)t+~R8|6naQdcnoR(o646qTGclDcJCy%`#K4F8$_tiX9*|Pt!DD*j zyo-STrD+Ic2gldwwOLd!duFcN*&y~VMu)@Oz51P-ilD>iH;K#eS&3uP#Q{59DZP({QNb3QhyB*sD`J&tjTcTHz zBw3@8bqH6M?{)i?q1Prx8bEwdIVLV~J+kZWhU6|cF%ORiY6P}(JgmyM{W7o1X@>ek zimjV))%}0c7!oRE5jgHnX09}qg3lUUeArkOliOPu_>ut}H%E&{;X7ZS*&fnUu%brX zaFFH=4%cGuooE@Ctqy+*t>%xWswC=31LbCJVu34Ko`F!_s zF)+kmO{`B2Wv5OR`0ztyna+z7@an$~tXpt)iVI4q`7O(InJcr3PdUV4zL=$utge&?p;WkG@vD+6??H0+V<=J&;N25LqkcV+ zOBf$;yU1~Fq*IgC5se=X>@n`r|7x6a)gIQGD@2h3O4_Ont(1X`b_|cSX?hf{?vGev z&f%)DU65x}Q|{2g*qY3Q+ciY^%A=Yi(V;(4u@BNgW_@{tRhW5;U_`&bV|_P~FN+%QOavyyZb_6qSZl5)6BPFMgL`JQ1WRA1Oc^L`0q7;-aH6A%;&t_gbOLSFr zntU09~jl=bQpj+JZ-ha293;B-x!M`iHxmXX1; zJAM)t#3*jJA6d$T!Q)aD>HWoK0&BeRbwC{T4a8)mGp)nB3i8h5XnQk7MZ5d4Y|Sws zBFFyOLiI_WN9t^7lOIXUYdO`S2UKsCZ4$u&|B&5gO6Fd=!LHIJL0SXWl_hse)xp%rlM7H@og?DG$l-So>y?VCT&H( zRxpsSHFT~sisf)U1X1fJ%Av}p0h^*e;8oq0^yqfWa&2v5@|$#=TkRp4XuTqwGt**u z4dyCLZ*eGuZsDDFRf&&0y%QtO(nNYL&WL~bp&fo0z1O2pyKM?Oro`dfvYy2y3Gz}( z5)*|bM>i$Pu#5XwJq$V@VR-J+lfqLRz-|{h zsKF|mb%+4_K)@LFVkFs{Y#rm4fV3*x+pJmU)x_=1_7>)^nl37%`T?1XAWEhGUT`n` zEH*XB{xRFo6Iv=AEG=(nMzTqiL`!k9^{9O<>d_??T!}+;iB5zm7f4ISZYJUAjpHRj zvr@!iN3AMj=830dI_1|cmDtR!3m{J(ZN^T->QD@8_Y_MJR-Fx z1$)P&ZSeDIxcwv3j(gKbUo0G5_{H}yZ#Z6?TUee~NfMiIb8M~j`U6m=b>fS2sW(iX z&S?B}^XFCEk?Z48_qz-hebZz68Radmj` zjZs+izXnbKEq}1Cq={}Ju3`W@U!vv~n4}+x+NsrrCt?ymIn^0n=3bw6wnT%EqAnXm z{0*J%0*eJC{#veo6Y28m0cD!nf{n6zfd8@C)gXOcv;pr0BzC%qoso%*O?U{ZtL_tVpRjBH>+YxZY{v1H2S5P;z0`9pjErVSe(-!tDz! z`;kw32L@HH$92&sR0A{4`ycWV2hj}r4E!9^e($bYvL^24Nw-RZ49vP>((Y$8>*KA3 zYkr$%72~cqNN)0a^J^wWRAe>nN0yaPY$);bZwG)>!2JFUSzH&;9#sC9g}?=4PEwQiZvbnl0hXaT6g`^n1VEf?7?rod z-6JZ{HI{>t-jn!RenPH7L#H56a}^o#WuFaTIotW<8K=p;B{-&@vts-u(p0sbc3h>( zhiIaKMK%YzXzVDo8kFKjR;FJO<@l$}C5`Nl%B$p$M~_~ghup2ezBo9C%VSEd=s z0f%wzbSwbqmuk`4f#Hn&_!|*ecKFwkyvM(lvm}mwWZ5`lD?JVCu7SU-%N!k|bJNRE zgzY|T{QO6wb1jsv2Qb}7MJb+g&wo!qujlMk%Y|IpgRTffcjK`ZCjq8j$o2R%INYd{ zP~5tF2k}9aXp-I~KkA{H36)YB0=%;yJUfVwa|I5gKgOuRJWZe(wMvT16Bnrt!<9hga49%# zqYm3y==Vy~+6mm~l4t2AD^YB41xzMH>X;N|pj_7-K?2L6N|K6;D6%-5do%NnoUb2U z<2>6*>2X_MszzkKTk~P5>+$jjY1n8jH~S$Po<2m~$kR$duK8hxeE=Mm8`Cq!ukfdL z`Tk^aCe!^v*J9lz{5|(Vo*HAIPS`W@*+ECcsx`5?&*|BII$TVzrExcVTEBnz)1$eE z$%#GSE|G!KLc7G@p&2%$WsdoFQ4R|c*0bO z)$^1Y{^>M0LK0iwi#q6;C_Fx$lXS?mn+da)zo$#+c8di^vHh3D#%9&rPK4h_Sa z)1deWvcqx3jr?6A_Jv`0a(4Hej|Tx|u$GTN&2t(2D`iu~G?DVyYI`dEW>MUTpYU$; zXHe?v@Iquw{=OD$TAeuq@4^BA^6w7;Y$x&EK2f#8cn?CFH zqZShTv8_f#lJW;kv_`~%Qt!s1(hysCx^!6SI1*51a;T4_YEqKbQsj+{D7zT|Hs6i zGQu4{f{p%{7I)M0qiLZELblEG0JF5N<}f`i}71S+Jy$8Ve{h6Gvj2EhMO;g(BSn=7s^FD{mc6lVd;^ znifH{bNKu^+1x+NTzHkJl@i>D{m6=Cpoq!42#^P_$6JH$`x(qKaX^Wz4c{mS~t={ zQ$q?v3Sxtgy!qa&g|BTS_I|*jno-D^s`nC#K2BLzJA67zMfXhiT2byW$K);jI^+E4r>Z>TW0s<|n(|F}zD)ANdP@RBbqa}PbYksqAt`w<*e5&5E)9=X_Tr#4*&{!zvje^;I)bcUV zwsJwnqFvs}@1}{^_uIZNq;Or_`^%hDx~?K!=0!B-9V|FmAAn$zL&h>}Gg5*TWtT)x zEiy~VIfLN5;elKqM!fB5cCm0kx#-ACHvJeDqWeOoV?oys0@yS_2U8p~M_#L~Avbw@ zJC?e<+q~LdVrieagx3IttgP#Tem+E#&#UA7iOs zH+gKPUm3Ncn9-PP_Wb?YlZ^7cRNmvwWFRp?%zE6)5u3}DNv^(=e}C38xk%4woFBf? z00k;k*qd3A!KGKgwMJ6jn3%q|`G?(6C#&<8iqq5=-U-zp)E9YPOCF&xA?8i0OKfM7 zhusGS*_lrQL`XsRCcI9z6H)9X$_iNt-?-(|Nix85r{;>e7)YseOtd`E+mO>gZ8M9i z*Q&nngRflS*)yQSO=SGb*dFooX!XdIK(B7`hh!lY%<-t&F2vdGT;S8=!DP_Q!sCeq zLWm37KTDB9i%12UU{Q6?94G!bObm4LP4~6X@+p1+js+L4kC$&$-*1Q}uzqcgjW44h zDr{VvXk?0&95Ys97Z#EEhv#@;%mOuEVS(CZ1yNI4c|S#-kw57O9JP~dOL~?YI(94X${W#w?nvqsI|5$|)|%ML@d_~6YV6)I?EOEstx%|g^w8NC#BF zh9(gR*Z+m~8k_6l_#Ym`z}Zz)8O1Hg(fO>QIDoiZq2Ot=8;7hQA}A^88XyFRp(PMp zhL(fh^1$PPGOor7a&N#QE}G(dvm$I5&j%fO1)oOZcA=WIun6vq8vAnFIC3Jh`<6VG z2thvs*Q+z3Fx|#x{CbB0qx$p)adG@vH6w0v%w*yHVal6sW|eDo?Q6FdERzouWDNAv znpy7iBcnaVhltVVd?GGTV@CZ5V~DTdw89iH;jBO$g5ERmll;SUu6hMgA2MIw+nA|I zXD#%nx-bVXdkxQ%&K171w;m>mbzGYMN)*BlOx2X5=1&SAYTVzF2p>Cc+Ze&sT9^F4 z+m)QgX85p(uIR0KQKu?g@uo*Oew3kIXa1%%JV`#-RluQi<&Q-A<5%~#^I zcW_ZpNGgBXmp|oHDzPk--Oqs3l*i9wNR%y^dY=|1$&pbcH$9m`y5N8>-~?EhdAN^(atD7rTj9I*J^0O-J-?~Wg;*Q`|201}YhTV% ze;&`)1jK6HfPCa(-E8Gx4tx>hfcc}i3NFSDyTqNkJM~JeE3W(yw@NJ~AH^8l7_RcC z=(ETk2w}i#?b3ORVZ__^eSL-_RqB{Ww4Y+IOB`*Rk(V-GO-g)I8D#Qx7atIhFy$~F zdkW%)0@(HpyLcjr|J-IyjcrCRX(#g7nw_xDdv_V<0t$h9MBa7(5}E*ftEV}O*vqK1`Lw|xjnlCY;6+T$G=Vy z4O&frJw!!9IQ_$cmEsW4D@ZZGDgqPR5XQHVX$Ro~;I@&V1igN!L907xk^VgpfI7Ro zor7@^w;j;2A=%^xaHCp4TL>W{qOnJy-c}e2;M_vK&SS8{A?I5|iGJ2y|E@~7H$a1Q zuyP2{5;Ba~hEPvK9RO|P!JSc50%6G^tm8Lb-~*BWGbjS++3of(eWQL;Bci-;W7t?i zy1M`k^As{*4`5vY0J0pZb|&IU$N|7Ke~5x`6Z~zzH(+O22DEXMe$(Z|NJw7<8fb_8 zS-~4x1ic6maUd>$>(>JLmU8Ac<;+Nh*ZtQ4Dis{4Du^odv*!> za{FQ1bNhRE4B`${W1k)HCQwV*&d;F-(SQvi(A6R2)AO@+Zyz%J2U^d|N5o;QcwHMp7?Di@Kgs6-?LlY`}vK> zzJhSQ`-*m;)>1#Z2C5jeUjumQR{@^LFI5fJ5Y)x}U8@cO*`o_WnI7A}WfVeuP=Jp} zB}1{a{1m74zw+vDn+AjmwirU3*RMwnh)xeb_4jG7HnsHsyb#!ydT57s>gN3AQ-U*t zZ2xFA`fp%oeH8R6(%E~mT>|0mpm#@{<4KL6WmG$_!0fX2bJ>@e*p&~+7X_FQ!d#ZD(% zcw?Hn_OMZ`mk?;;G;@8Ajp}U)wG4-JwsvYVY?;L@8hE_Cw{nv|FaABl`NY#_N=-(v zNaCetI^WUMb^qJ0A#h=1kn(yARC!449)HAA5g7yf7=xK4Lq;DI(#_Fc-MW|WPSm;| z)dQvj$ZihOBB?S_aY>Ulf3YaW0%IK!c#I50aimU+vaW0DlUV%%QNe=w;!NTZdfw&O zf+euDv~MQ9m(pARaixnYR4O<>0l8nDOgb;dp0pfNdgXS2fMFUMPby^`EUjgJKz(f8${HWABG!^@#@k^^ty9EOQ1N@%zjOWtR6fsnb59K2Rg8-_$VKr_ zxt`dJvRb_O*_CM6!3*M^y`S&%=Oa>er39QIuX8X%#ALK@+kWjwh6TK5;zYwjuoa(t z!8oSs5{uCtHNcr3fm9J~>hHJFOWA3Y+vBG)bL4t+wHnvB2ZKWJZR4fS~F#|K7Yu6_1=M4ioM1*AA$?YmnQDq&XT2Z>5E zucpH29>wD>-v?S=+*!>t;!2q*hn;&>Bo0mF)@0lu&6`isL6vQ^~I!5euj& zZD?o8NRF7Eu^@GMi1e%=hErBOY=s#V%mw%k{(t5JLnTI|I`t!vT2Mw%nv?s7?}YrN z+-i@ptF{Cv#NBwM@)@fIVsE1Zvm?d-D&O-QoJ%Ga#r$)u+x$>^5!jDGoV7hLU)_6reuXAyTg{PipQ$C(ioP(y#_g{|1R?P-YrJ2_9 z_vk0apK#)samRvUMCyRyu?jt~a9GdWnpx?%-T}_n>k#+zza8Tf3Nk$O_g4Z`IZP5` zsOum8X7UEs;+SkR*lB2hAfzn(LDVlrh)4dziSeNmJWklz_d?Pd^D!~BS6wzGl5i{| z)^f1kJQ1=EICkyvD!SKwyF3&!$6wUE_**f>9Kz9IS}671k*VLnh@$M1)6|u?!^bZg z1Eh{_g5`G*Wq|s#&s@m#w`bk7eXJr~c+g5p8#VuDr9rOLCsaS)Vt44|50u%KHOMo! z=9Dh=AaN(SH@eu|@R3A}XLc8KikC5Zz*D1CioRoMiKOLmJx0G zsFcQ%IKicO5S4RrN&}CvJ3zPhYwBBDQ1}>fA5e?gdrPwj0#QiY8=ZDM;>*h1uE#um zZBGqp5YWU6Zlsn>%5hjdUdG!pwm1U`VesU67}UcG8PhV&E7Cr1iR;rTF!jYWc|`w` zx84)x$lQMmJvEG_8*|BA3)0%VCgPWW^u%Lq3(VODQ`SEdpgIv`S~<<6s1V%Hy(us| z7Z1enNZZds>ihrR1dc1ItW=?IIfnZ!#CpLi%v4)s$s;pU0Y|>$;CP@4nCt&KP*wlh zMa|s)b#$@!tR}7eN}<~6Ei?w3W>D}_k|D(vjD}emh?arkC=8*DI2P|ZdU@z?q4ZVE z@%6;497?Hs97)lSBGNY4Dy9p(LI8Jz7!omdxcI6 zA0Qk<1;;a7q!x$hVr%>sMehZoQ?$kxZ%NKxVw7;568?bw3}ny+udEh(Cvtieh1v9y z%}S;tN4u@-juEff-=`D@B(SBMP-e0;J~$YkrmE(h#<`Fs)|m>h5B*?TP$~l}{}x>8 z)C;&Xn{a-mOdCDV6Q7wYc3*wKn{=DSw{MC@+pdN6BzPozo}tJns7;l5E0(?80=a;= zB8CL?1&h8w=;H*ca&zV;8I}~%PpyDz$m4Jnuq~3eK}6_^D3t;`?4ToAX?E5bBqO2l z4NB*3L=C~iG#fI7Gvm#>&Wn56kmB`&Wb?MSCkiIIq9&G1oEhJ1Vf#iKL6xSSI2e7y z`jfTzPl|eXIZQqFtkF~GmiQA6q#9v-3_t1cAtVVEuUMY#))N@_lU-*ozllW=5t)lh zNp)$(W?zPf8%TdkO_#;X_-ZHJHryztDtCL!3X~{TGz|i)X7AUv$gU`tQ`AXuv9Xxm zOuB>B-<0}hb5MgoY|U?sNBjW7(^o|=BlH?aVA2-Y3DJh>MUzN7`_h#^ zJ=rf2+}*xotZX% zO?=FSL*FB-D%Kjh=d(z2-z}VuT=#S85Sjyc(bdjYMEar2j~!?GPq7RGoAberg7LmM zg^R_nrxD`?o-;neev>$Lo1M$gn!$QdUwlIpaw>>ymx*$jHyGjBkDf^nzLK)f0n$bJm&Wf9`a^s|p zw|Dn{;u;iuiO7a#xQZsli?W6h$TRHVf`(qaq5Sb5ceuylxjAC~cUQmBe;0QVGMTLs z&Aae3MP(wAZHF=&l@~9}s*f;ZYPe>9Z%O#-YnD5em<>>hnp6WfTuenm5oaY{s|Uowqz`s`D~nSMZrrC63A(sZ z`w;$0BN=*LlX!^2UeWHCpk*}GB7Y#3NZoYN1FF(A5Dn@xOrCdzoX8Z7pM$lhe~3N# z8(}wW_k{K#IyfZ^A3ex7H_bGys(Y2QJGM^~BSJ=0`AIy(Z>{$Don|P%x9EW9qsmXk zF9rYdG}qwc0l#rkc1*fbMC^yc>3~o3OY^T(Z?iFz+vaPezyxw4>E%n;Dzi&q#x0AX z>k*Ch(yKgBeqmZpFmbt3JrJK*igt}t_myeukUVsg;SXA}5#ORmIgLopD`lEbe$u6T zz}2Nayr0Z_@K)FmuxC?9#nm>VbFSS;X{q`L{)qa3`igE%%N`IH56uY+rCgye#l1O{ z+vjh&rJOz4vQ}3zL0!L~gHt{SVJ{(S?W)Ks+*;7egj-#U^$Q|A zS3jdvonc)c+G>U0>eep@=sa}cHjy;Rx3EQG6N*?;UBM3e=|Zt`2u0&H$*)X(lvDOs z^I;^aS=pe~#x=eK#uT1_kI5{rW>H*r1|)5bQp)z&ivKaJ+*yTIy9(LXv;caFnXIJd z+y(rLP{r`5Bc)%sp2s9B>i5!IZ<&}Ul!7vIAv^=`cJJVF#$_gFbUxo!SIs21nfGS& zL5mB-Xy?^J{crUTES&AL8FRrC0ge-SZQO#Au0BR;0TS{ke_B01we=Qt2;Z592QB3C zD*Gt4j$-w!C4t6>cex0=yIzFS<~eGqKy&BLq$TF>O#6M)SVOeHdbNn)brSD-Un8{j zp&+Y1HdfemB=*Kh@K5x!8@!lWPWZ{0ErZazx@lzEQ?`8$*Wf*8c&i1_`ZdpkooGpq z^kZt9%QtRTq4dMum4k3qBz@dz#I@h|=Q}2uVkW&efw_It4*ED8$o(j8^n~o|y6V~Z zg%0h4<|)jdt2EAtT<<#n=#Ppyx}lh5a(UoZ2hZ%xSOZV~FXAgMSj`D3G@cZ=w;>8gqGGrlDx7RQ8rxbPrAjo zDj-*NY#4adQAUmSz_N+BWwZX}DcY`EliTfNSZ8tOk>1-0^tEG*-LgQ_UKe-W`IFq? zIW%BH6;bb$cHY>BM<#AdvrMs0F7XU2+oTrqvkBU7$?2lfP&?Hmg7PDtx34qk4{JqIo5{);pBbe{P|d|Rky8Jk6hGO+KQ#(XJ@lYs#?|O z=a%RcxWiMowP0>+jD0z-At^c|vNBv%USlkbg*$e&n^U;#1Z*TQz0-IC*n0Es^!94d zh6s}Krc>fh?N;uy==g7n$u+b(z6m@CV?`NuCW3OMJL(gSSEognA9wdmTSOJjjjeSf z)iOFCP`zxTR(n8#zAAVdM%glrE>9kr`ia1 zyCw3@&$*4M^ezbw!{0cdLX{(}aC!JM+n z)b?0p{wQP7;hAqix^j7S_o_=G66Rfe6W-UjwcPdXL>s^cZA3!UHgkRU8AelyZ z%y?998|b*-3{nd+djYF2g7d2T1sdreU5C#k`!*Zv(?eLc$FX`_C=Q%QDs|-^7=!hG zb_>E=?^$+wQ{^~oqCXW5 zKAA&|0=a3bZI`FxiS>D@0(ZN8h``Rovs-t&H^m_3ls6#th8%3S=AOP7QAuU+mz0Y0 z(}+x`UFS`H33OU&Fz)%;sYmcODLeMe%P}0bq-v_X1s|lMx5?0VC1#_K-y|F&j`&k1 zsjcNT>``q?enw%cX(-rKr9~-f42-`` zeWIgj@1m{Ug(~JJUqd{)ri%Tx0S`SWQr#0wr~xJ-eh;PphAOLhYlA2zMjtrs1I%y- z-T7{cBC;s)1lNhe55x$*yMGfthlAP5bW&zCNqV&~ zYMn2w?G(}H=bTHeQN|bOh#>TJm!ci8S|n4LkfW@RF$Ig9oHPCMP_W5Ht8Zm zkBUg>hIbh3im^NPwV1*9>$%8~dok1}Hi3@4m=19M(Y!2c*n0pmN`;)2@Ig-vBXvfh zf^I8hrnvR_9I}Jp7~aVg+G!7nw0ngfe1yyir-?F*7Sj4}yHDg1y4ufv6_~OGL;ZQH z_xA+S+@|uHLpl0iiWAM{*pB^b7)4s)Z_vtrv~Owx(qg!m>8_9FcA!QZQ?BNq$(Ih+ zCYmWJ*gXrFU!`zGu1sG@ERa+G(k%R~q<&V?~8!RAZA~)KeRO z%Fr{oYV`8Aje;G9ZO^x}_B4R&*U^*C~zj&Untnz#G)^b`&ljFo}-0glQh3k=i%O=4&@KB; zn@u7+h9A-47MVpfV>Xh4ocAmmw9dmL>hNvp_tm}bI05BylLE`=fLqgUqv_)l-8ZuE zM?L$Qhx@`oS<9an@3B6La9#*%d!}O(bRX40dkNA&y{zw5&6Fah_>ZU_hPxW#8vEPW zh0Wu<*2bH&07q*lSvD6YmpHy;v53vCRO()4ySI(edGhgT(gPL(k)dmjnn{-B7&R^} zkBbH4thrX@MJZn{@Y!O12T`1)vjdi%cli`DMp$c4<&tZPAa}_-5zDT*!$-KoNA?)K z{w^Q)X;kXsnhw957mp1a&O-m#8e!|t?EEhTV}Vy{`2|7qSQvSFvvsEs*gHl4UX$-( z28YpRJUzl+M@d{!##n#4YHi7T!x%LlIq$Fjpy_bRodgOV-z$hxI0XNxM){y(%+Pqr% zkdg zqFcH4Fs$n&YL;&B4`=RIq_tNk!VtSxMti00Q=TbvPf|$yKNCmMl>y(5uifr9cbl6nt7hR2m3aE@k^s>03Xf5) ztkxCxR1MIZF$6s&45ofqy&g$|n5&J7`Q<*B+BwldxiYFyaEqw#nHuOpCRP6@fCld`6kM>G5n@we9UeO zmYkj7R*HEW#|d=Cbg!RKCz?+H9Pp zmBXZ&W_qoYf)x|v_YHN_sd}hX^VHRl-`Wd`t_=&yq1ersp8eaeK8Q7HvJ$>l#B%MC zYha-^Ys;_V$vLeZ?B3b^OYISk2uJ|o!Epyt?2s4ckSXmrsb#$`GGD;#-X1=&hRPr7 zWAS#rUUwMW{Fv}8`rBr=TPn`chLq``pZmVvZyz z5MBsKe&7Z?Hwxz?SWYbl*dTdQoR6qn`1Y9cAD@#&8tCYC1!+0*;Qqy&?ugYoKuy$+ z*q=Bd&qJg4Ucw>N#;ihs3k79SJ4kl0NKCaTmms{@zkL6Q@b<*JrK-5unoM__81K%2 zq$~EeQ{{zYD$nG4D?GdQMw86VK{mJ=LRG^Ny>S%2`37!8w6P!Z42|A(^A!Zo7f~c} zp=SvYNyJDUbzP@7L!#^bc1s#Zd%)! zKXpchrdLEPmP*NG_1O5t)M;z)c-e?*w8r+Oq)lg=&FuEFlTEaRJlAjEA1=-}yEDZ_ z%(=}*JJ3{Ke+zh)Eu*x8TS~1PUM%G_>L+33j4+qYp1SbETQM!EeJ%r6&ibYh)Iz;> zzg>%9wX7pHpFVM=!B1!u3IAIi!pOkM`2RozD0(qV8y8b20(vnULl;vK zQ)7D*Qz$+@C}$TZQ$t%QkImQ)P=)MEG&&haiW|9tj&APJogGL72oOlUC~L|a1&SNc zDDV4&T_DjY?LriHyRPG_=DFXwZSG9Tro7|Z&zfgbA^GCUVLDsrMzASior0@v933J6 zO;%iDJ30V#bb4@fba*b_UOEUUu%FoCrAy!)UI7I3#NVa_S8y#s!*ywDifIMd10aPL z5a13^z-=I+?I0o@9RNH!y8V7w!W*K1bTvH!?Eoxr0n7uSCxPM8vuKRWxxX1Cx9+*KwH3mZLkV$PT*gwSm0c|f=hshH|0X$#$Ye) zPGCR=0GKAgE{_2n9$lNj8vqTrfVdo|v z)`xWYPaPsoNep}s_}2Ug0lXdx+5xb=y$u8)DCj3J(0^#575Gzi*zc37b438K&;KVh zcWIB*`+F3ytRE}{^X^vT0s+H05TMLI!ftAG&J@`D@W;RG6QAzapXL|$_*d%bw^m{o z8RkcL?x}q6SKR&>2I%w06wtVi9EAa z8WiA?5x@Z4QT*4p5$Fp*?tE`PKZzkg`G!BK03bm586UE2=`EgQ1oZFnm-qpogVitb zJrIZMPojb1)4xwgU=Bl7p&r}XL4T$rdszB8eJqCgn|_7e{&|0FXXn8pE$z-if z)o4c0+O_@4Kgb#ZukffgtivaZ$Y1(9j{|bUE4V<)ULIQB0Ke7b8AD5;Pu_OFRA2g6 zjrT6@;im7#(}s$_X}{}R0tN8%V7~XzBVsJm_r@CIuC*+k?4K_j51U@F&#L77wJnqRb^17tGli+Xc1s&9xaT<%*d$P$L(}}66bDY7z=RM`1 zRKBc0D~N9;AbwLUoyC&EB_WZVQ!{uDFicqEM6CSNuCpr_A- zNQ!_7m)(85l`}$8f9hgX_S3VB6!pR%vTkJ(b+r3tq>fG)rBT75VSVXS5a&UZJL5Jn zX?CJZf$k>H?pae2mMwACc*XS_9Cc9O0;pgslXYibwfNj)p2YG5&f+K`&4Z9(xSQcV z;gPaa-{?;ro;8m^3S6o533t$@?>q0C12;YY4VA8Sp^C&62Vr3sk4bX zwNKW?B#BvN#%xi#=CBuZy~S;|=IS5(%hri>yanaPx@WEO?*c-7pX?DSAbh3+fo{56 zvleZH;3_@>u@wIe=}`y*e#xqeG2}EF8ayXh7x@ECL&-F<;_rOOjjS|`-FI!!<)kV8 zD3qTzE@=5fLjDY@kYhy8d~Q0~?Ss3VFl^vl-(tc|*Wp@tw;7n-5xwxTR$_6?+p5$Y zk(L8=%)#k66b$RJoC>}N8?A>{zIuC_FpBc`LT99}nFM4QJVrM+kEDL9Fhf1PJ?wHN zWoz@1lALrLllGzc{@g8w5*zct@`JocSzuMi-6q^Ne#Mb!*AFh$SR31MN4TQUi!Rk--_GoZSGBoubq3dQE3)uX_56>c-sX#{N!r-EgMROr~9 z3{LFg<9d8E{;iuNU5g_=Z9y|wQRa?uKT{Y_X@;JPfaaLgy?Op+@7%qAtEXcjs=;VC ztv;BT#5EW4gJ2DjIAvBK86}oP`m>sS#Oe~wdA%I3(NtS3Ldb>i#Ztd#>+z37(p9Z| zA)4%DB{IHlngKNSHcjmnQ)z9FrPK^j-YTEa!(y7OE``|ig4e%2W)v&6>0lbS2uoJ* z&UuLV)9NN%4|T0ettFhkV*Yt=MyS3~HA~Ie>4=H*DE5VOx&SIO&X=&ch`e&;?ySCP zkdhk zn_w517I?1`>*V8y@Z__JdK$f9ke-HqR8lQ)PBl>xalY9;x7cK%(N*rnYX^hT>F zz=Th$6p^qtYsN4ZLv;s;xx3j}YKTl z-9$+oo)2WlH%mvx<)HC6h4)E!m*PJ^zu`-8N+xWof>SPZW&yS_#rZ6AYK1o+IjrMB z@wikay;nd_dF@)=6Y&ucoqucf`ATms!~Q zb+awr$(CZDcs!ux;D6nPJ1B(4ewMBjBGMWrLvR zEdGSeKXP5Y&|Vw%Dc`);+CEhVgn-X;!CMEFdcQg|IG=8dR^vtL2gy*e*P}m6ffcO@J+$$3?a={!Mg}SACs^^`z!<>$cp?&PMQk z24rlKH;~{j4LzY_eHO2)VFu=)2W!{JTkgMJK{OeQhsE~;oviuB5hd+cP$lqrxEZ-9s0{vJ9hD&3edyrVUP9MxqG^$5hDWH^^yyvw!5bUf-$UFC-Tcu z`pE)r(?PMdmP8tt(BSYl*#hx`{cjEV3y2u;yx1d*p%)X7i^Z3mi{9sge*gj|x@m>X zN7}hV`+k{SfiHV_tFWk?z8%P3NBEE_-yfwJiOS|}aZS9`+fVpkHOH?+47MGGl~rgl z57lof#~5}8Cjtzl#3cxXUPpBUWn?^Ii~*z@v;ZxnsLX~xpZXW0olP(xz?ftLjB!d2 zj?>ufk0lW({KKVnJsZ`g8lAcOd#J)(M4|Qwow4bt+u36x{ma)Gdu$Bo4cwVNt)3y?K75ubWV% zgk7q~rfI{q(Gd!Lc$QyTf@7l`_nF!X8WaqWmtw*&uH9N_SUK-1J2eMh#^*ISE^X`$ zih3UB~jtN&KEnhp4W2F_F_AbM55D@>#NaJ-j`$8SKm zZaJ?_P_vjXbVVQUc(9FPRtcp(1w+v z06|$|j?o#$8ojXm*bMl!q1iCQ2<7fp>$P1j(}w%(qoXkUZ^$UmwuP^C@FHpgAJ6Zn zWuI2&vLJQKV5L}m{Rr?I$dkb@j!_8O(}afHxS<92P^c`2 z1nGX)il}EN1pO(x8;(^+Xq;;5vBR7-VCv}kiJkWENE(#h`MPDUQMR!HP1GE78)dJs zS!7B-YaM?zMb+mfVbvavFK(%i1hajR`2Hq-6^1iE9mBS_$CGNTVtb@cG(>eQ!`0^9 zU^lj)&)@>bH`Bw=%HoXI%_o zr#|e@F8VyCd@)Lz0r(Hh$skpmZE@|pq~Y;^Cs{(f^HB7C-&VMkpLeSc6vOsF_ChcThbIoRME{9yx1SwJ zHuSZxE{)-b(U->UXYX4R=GT@6Kgl$;gKD=9pKM;N;Zpt2ChqCgaAXlQt^eNI)PKZO ztORIl?*kB|AITy%- z4n=NU32Pb>gM#aLoO$Wm(uyfrXn2z9>)Je`K#>{N^}jvAldWg*o46HpOXEasUGJph zFu>vh)pFTP{!w;rr!u9X76J~5kpm*+qb;MOoTf>lGM~Ix5PHe zA~pDNe%n7M|00t+3SQOjf;It|sYj%;VJYo#+QbaaYiyo6sj|PPA)CwwMXeXtf<0F<5>| zUd1n)wUiLWDLg>-3$DHV+DQiz4th9t`lE_3Gum*SgFh}i;Z5eUWdta=DJZyEdIqtD zhfr`jQ@=K`vt6}BgIFsT=Nox;+|tf#mi|^}zK(12WXy-JT$%=F*O<>@&+e$4k$1X` zE5H(I^UlZ|lDl-`9#~Bm@S<^64ZX9xMMO@!of1Rx3$V8c7X_%Ssg^0i?> zobx+zg+wOIj&kQX7D}f+awajK#>#Kxw65Tm)Yzfwe)jy`_*Y+S@K8%QN>y?`S2(eM zWOS$b2;ghoOsO!b&UEv8RI$A-gLd{D*B1#ns8%Q1rrVZq0BN`-GH@bAur6`8Nq3lX zrx{zQgFm4<*+0`-0zswv7-N2oauPGOfn8;Z58c+|k+N!s#wB<-au~PoD6$^lqb#V9 zfQiZ;)HnvN`S6HS|`|&#rnU=~MU>xK&GS)-WR>^=q#)883mO z4y7Gcnn-txg0CkgnOCrfZ}1dAz3wq#QPoon1=0!V;znuDpxoR@DilyhvUD_%n(xsUmNkJ!t|?sJpw|y(Dkv z@?}XMjt@+G0w)4XMZT?`A(GHVOOT?1$_cfWQ1pm&hB% zUolKaV#A)%xF>;ZSWT`FlS>2_8wj`lxZqQCf>g1Dmo*UHb_!+(g{ss(2i-ghrr1IJ za9K81btM`c?xt4dWNwFJ#z!LWc`8u^ADR$i*@hE`j4M(Uh^1j4j5=A=&_qa|%%7o) z0dwYZcp-iZwk@%2ejsL&^4nlOzI_oz^zN(3#(TwMs}5*%kq4TxtZAbcT>eL&QaJnT zZgkTVOszTdDJFXQy-YoQSrXZ$3$tPVFtQr3WC$U`42SkUsotTe)t~BcXZgyIZCs7Z zb{a5vcK%DsT)AB4@^thg`s~ne(BW#zVUfyJ7mO=Pl`+D=`pI_D60>5Dj!JelX>rv4 zVth**{kc#oM-W}N{}Z|q1Kni^_b1I*|rhu-$ZJ;9#Eh%*;Rz(NSUswqhtP8VIp zFSN*hPMka8UmnHPvnJ$Y0=IsByH_(}(Dy-#Eg0j2Y+H%`Hgf`-GzP-hBmcn6YlO1P znG`2YHo=cv@BO#7yww{6$AFq$qLmt)an`~`N=BgkVM1AkY|pFzbXsLL4T)=fUK7|H zP6**!cmNl{Kzh@ydc4cTS`+lRLyL?l^j)S9vv1%uv^o=6h5qH#Li(YTII(*!IRwlE zk1qx~ONdqiU&}n{;5G$25V>C-qE&tv{5a>XD3mR_fFO=aCfuAAiH#@ckxST+dj5v*Od`l8wE z8pY&LS5ywB<`Qo#4@L^pYUC=V!b$hJS%Sxm4#CCwLag9wS1D__%U!a0K>(b0Q?|%| zf5>N(85VLMy=?jLx*J+TmtfM zfw>&4*20`s*oX0u1m?jKnzLO%`xwr|x?ZxrLImN3=_W9v>c=ei7h6RmgQw-1Od7KK z;BHPdj=+Ggv4SU$s`@@uDb8$`&5NcW_|5<(eO~l|U}k7zr|ac#_w|*A){mD?Pa69W zN!IOVh~LZ;^>nwqVbvyF4Y-&IYP+W;%If!*rP5!|qI&nf-eJyyugr3gEQKYcd$~Jxs zR3o^3%&eO@@a>`;@L*g6=Qe{{Ijf?i7x8sN##Kqz^p*4zi-eaPJct3oh~wn$l}d)O z%%i7T!5-X#{t3D&g~T&t!}(yZC#=|Ux$qEDbmeZH)cRCJkprn^E4n= zU|U`1BaCcOur1jykTbD1K=f5=DRs!kXu76$X{Jx5YUhLQg20dJi7FC){jDq~{Cz?b zHZ=%<>3y&%A74B{a_U=J*sys*;(PhD#myLUi5Iq8Fb&mZ^u&wrAtp(3AM+YpwNK|@ zAW`uC6^4p@@-FN8YZmjiq8-D~%FDT7+WlvZKkpS@siLR}LUFd+^r{S*gB#x&f5WK} z(5vp>Sgk<+Evj?Loa}uncicn#9E~s9*^$OoVaO^&B=Zb{#ov7@Hx2X}BP!xj|RM zSOz{k=7QG+L6#iS^00bOi+1m&y+JZTDb4FI%2r!cw&*H z3>dQSZRx6Gj3D?u6#P;|WEc)7ZQOtOcpL}LV(py_U&!>9r8bW}`qq>^;V#Wcfs_d1 zJ}tWP=HW$8c7GRQ(hbO)qyUlx&_!p~h)mcgsf2V8Of_HcJa;R%CrdL?B2m5J>UVE6 z=`3W&-E*4Hc6hh>Bz~Y2g-=A6v{p?Q)B9@kPCsx&QAEoO!#!+VrmwWpFd;>l$0h#& zKt*MM<07|bMieC{UW1oG`x7Q=numC+w+c>b#H`|n9AoRnm8h0K>(Wg&*>&FEZEv-EIC&2#uARaomXstgMeEvT&shgeBPEUiC6?VIod z*MY$~kYhTL3Js3z<*t{pm5gccdQ+$RU8td!Xz-U&*t~B=XF0sKiL=hW@b%Z-JxYk) z>9FfPXT3|IE#vBmfd8n9=%MF*_Dsu(9Tas{g@sQ3eXhwWKD*Ve26WRgRnj#3gbJ_T z%xM0l6PTGJ_O_bXSH`P7O>E73lU*K8FjF7Rw!Rk3{R%pwDyZjJhd~sm$ zvSFO_yLvAg9|kF;p00EV2E5Tmov}<08@SRa>Gbg_9WsAp@g=PCqb>kFLm#KT>@}kT zx5tiG@~BiPWNJVJ0XHIn9U-_EMKOak?r63=-@Ec^J-=nq7WZ?5KhR(Zp^NZ%T6m{8 z)MJ{;oF&sCMW;ugG!X>yL zs?MN5Gz?!#kd3L<9od!dK!1sP$(FT}+x!*9FL2rDF(StMrOK9$SaBGSH`2x6864lW z=)O7qc6G&a=QJj_rDefxX``xZCK#ZovJ}T<@6S2cS1h7+Hk=?q7maP`i#@F7Qd`5T zDt=}O3zGz(pQ;x)VWCavzL6zB$==^L8w{VHyf?+q^XGu?%of{>nm0S>ruWE`a*wpj z>XAlT%;4!mi$~1z9XU8|Eew%IqjfLCi#XL^sQ*J}Y4 z!A1rM>nJL_S2X)4S*cU>Czo_4dPh1I%hm$HMoqtjb-fp@&gw8zOkbFcOE=GdVbWHW z!@9;ggM$(k+6!y#_U$JYGCA?hU5unm}R^?f{VBHYm=|8 zv~Q`M$ep9lQjVJkB6wQPXx0Z5uH@A<-fuMB3DTROpdaEH!X506`x!bXDYys-E+mJ}I78TU4in+ug0)a!Jh zs&}Zgp`O`r{wk7E)~n9phZQZn>Pgzcr=?~qM6&n7D0peY9LxiB3dm_?G%;!z6CvO} z+d4fNmc*{jv&J4VQ&~sTt-H?Ewmi3HaM7mr#TFffHFVbz*^j2Xe4UDdwNw+bI#Fm= zN2R|f5{pXp=^lxV@>xEFIfG-DeVKlXzvh{=3&aq_?B=}KM9dlqVv|kw3z9Go0kr%a4wc@7SUzX=kIqrh=fK|fuaa2hWXR5Zlc|n9GGXH#f z@4tMvZ#pN}DoEP3=L2|R{nxisUF2B}Gpvc*p7-4o7+5}B6*Wq~AwZ1e$t^7H+ZN$P! zr_M}z;;MyJV~a?AbrqpmQ1^zIXf}wpYPpd~Ee8oV$$1NOyx(k ziZT4wU!0D=SjOe+vb(16CJ5XjN(LGP_QDX%2=<0KXAiWGCe3}iQ*dyEqxI@<`HVvW zYWZz60OG?*T|XgHi!t&1o4BhRhoOU8jwW*4eU&gAdyTAuL&&-Rb9$blKv{1`my!cUo`EwuTgye z`=y_J#f>Ts5LZ77@rk0j7b@a5I&x}{trrfqdEh5mpIAZ zP)+~zqW&jFlO$6tn9A|>z~S&$Vz<5f#^7EXIj=TxcW8HzDyLy{Wj`5eE^fs~PP}hT z$ytAjJAwP}0OO49fEI}f0}4E9nTRXw7o+ZK_yrMn6Lybf6W)oL;KUVUY|>9f4l?jo zcfVS_%GnE@f;r2NRYiEUgN=#-T9&Mv+yHp4rb~uzHhU*9eo(V$a%W&gB^m^_p*pbc zG)a8Ph3Ftd#!Tq0-^KPorH>SZKIBlAo3r6!1{poG!-(l=XMU^ShQ>Z1FfF=pNiH)V z;#>RYR+LBtI<^w|JLu}kMdBmkD)+=j%%3_Fw`cJPx1DaM!Qdr8UvDbqnMHEpJXL7{ zh>MR)?RPV0Q;oKJai#&Gb`hr5qL|~MK~-N{fU&89yR$jNSamebfIoqpSolL&o$=4@ z;I?t{I}?R@L_{BGsQVE)rUX7P$}IHUhnOoj(#BzQDjjju*4LRJQlPT$6%;3w&T_)Y zPM_cyo*9oC#BxZ3Um?u1kY=U}Bpcggl*wdVq+?>=h+bD}uB|EaCk*Lf!=Vgp4ljZwLos$-T4-xzA zUc!dCq9ctddt0|JOe8J0N)b|HLbnK`grKNAuUMhDo`BYvrI9J>R_bX6;kp@KI%tg% zEIF{_jF@ihP_Y+!I4<$@ur~|$bB+T!L$o`c@PVCDsF_1u38Au$!0twVoPgY9V_#inoU2GTk){d@?Q|52q0ai{1ffsTh7 zs)r|a(`UW&Uyv}vtB;8z&zrZn>j?Fco-M~{*ivO3p?I*;`=AVfzR0UTHe1+8rC|MI*b_?@9AdwT-!A1bl&yWZq91QlMWK{ z7@Iy{_2dV`im*uYe2)8?fxW)@RN0o#Bl)dsWsr$*k0kf{lrnzuZ^5(80gJtlhs{B*Zn=rs)plmeOf5NAZtrBB%0 zqG2l2j?`2i1ZbtbRLgpIalN+QfJCipY`qlD2(iOO1{(3`dVOF9CUt_#%6W0I&ACpR zK*Dl~oL;(T0CVf)Nz6Uumdt28^{=WyqQfUNi1!xv%O+`h%w(3PNy`k-Yup3jLS2T>mxUAa%z}n+o+-V zh~{_LC#cEsH|J)h+hkoiTV%Q>{eTehInA>Qls5o>a}AJNxPx%|7x>HSCmajGTtj>S zqaW)@SuzC9khR1>T&F&PmH=`AR$U8fwB=7?s~+;qbKy-0m&X@|qlD>k*ZChl651F9 z1`D|lb{(q6GkY$g_rUBmf$3q1&Zvpl{ZEQl2khe&0`@K4s2&gLS7p_whNczbaW6^P zDC?R^cyh@sA5_v7w${1w8nG3FXn6ZH&0FY zUP7#gdAqILdz#ep;cBISL<$=g7uR8t$d14MIC^qRn*Gs3^ovpZ3X=LQYN#m`!&>x9 zTSPo^pv2rh{X5oyoXv3OnuY&}7fTwyBU#;?Cu4=SZ1W&JXc6gzW=pu8K7+f3kK>Zb zPoZpEH5GQTyh~5xg!t)Gb!Zw7;Y(xy7xRu}`6##9tO`IDjkwaGD(dmR51(Be_Ax8o z6r*wM36GxA9>g8A*^h}+i`~Ua1l5zRkwqbWpEM%gY+Dqp&re27ssTLDIR>*_=_D%9 zidlt;%7dEKj;w%F5EPp%tuweDZC`m}1zZaj2p&gS2AgPkL&kLeU)|=$teF-gZ+AF0 zP5}|T4nrbYxPiPKRM1M>d(wPQcX;bCgpj*vs)q0oVT=^PZHTbs^^^0VaAwpIUOOcP z9s~grNO2c9lr~XS{pu}8ZzUH9UzmI2zZ&5|oz*x%_-62T^#opM~8L5cu9){CfEpsr%u%NAbj~@r{0Eqv_X8 zZq9P7^m|U_d4?#1&hxc+6iye^_@Ctbpa1abLCp9id6R-*YgDxCycm)asJMUAzjq(g zjGLz5bFW&rX&THG^W=^^;;J4u1Gh{9|9XV{u>t>m*Z%b)^W}cl%jwZe?x;$31V0 z6?B<>{|Y&88!-m;r{(&c?Kt9_>5F-Un8)sD9I)h8d^QAE@i+~&Sg#*k3srDeAa6ahWK3$9n%^Pis{LEm7ZrY;;tccZXHV53jiMN=Y!3uHF zGR2!H&AV^z@84*%qd6UQI90r-YjB=ZWZs&?l?#Mfpas!7@=dN=!DMgSLfh*_fD|&nSLl@>!CZwbtVLmA$P7TmbvA%q)GQ@^uq( zkocQq5B>sY^(cM(FLW$3)Bi@tvT$(x|8y)nBPZMcr2m_a1tqr*vKfLBI=_jB2QCqdmlwkZ1_m!t#YP1ryv@4jp8k66 zyk@`h@tA-5-h6gkIvK3KMyE}Vsj6Z{ItBys<298S5lImgmpPgfA^ZiA1ra5)1JhRF zVqL<%*;MYd`{pn?VW@t4fhsL9!J=9#gzDV0`C*{v?j0fh$$JnEu(p}-I!1CD;c zMGKCBEfMWFQS%YN&hq0K;lOLDCqTnOJKK!zO@E%D5bcq{{Ev@JM18|RlaUx%x1a;T z&P3{J7{Pi9QHJpTMmh!#*0uQ+BPu!%8}S_t4|#ig`y+xugbR0Wp`rr6_0`t^petilARDFl+ z$)Wz%xA)ZnQN?FBH5J1Fok07)bGJu+OWTD$`hU9y`eodsfPHx7^8(Gh5&aFYH^=-5 z8z2C^4+XBXV9?C}{dN6%6a|I=&9|qZ?L#^Nkx>4k;Msw5`f(azAo%kNzUF_o1qv$I z%m3@!5mknP8sa?q4f*5MYm|8snrhZD;79(`svrh_410%>0v0|YHF0oGO%EBLkOBer z!xDuA_R|VG=O^5pFhl9Tm1lO}`cxa*{d;4uSIZ6t{5xgpKahkDf%t|nY9pjfNb!a7 z_M3IyEAnFp_M3A2JAD6ZD>*p1{oSgw*ZTWwFM??Awq*d6+o-R}`}?fqjz;*4V+s9P zq~(K&dK2UNX8;n)J zcxO-8?mdpwiC@x};O30fvR8kD| z^S2AXfN^(M6e1irfs&RV%s-C_aW7g}`EZNGKzV^Zoq4n$txf>qzV z*TcW|r}oiKY4C3kBSmBeAUR3+th-~brqPyHXi$m1tKx@_E#<@3a0Bm7il^AR!wgG0oqRr1v|##xOM zl%^;TUSmhmk$^@;IaZz|)L%jDLh%`(P0V2hO`L6)3#G&XZD!t!^c#s543u2Ii=!MW zp)W0Ng+{5j+o4gtRGQ8jo$lLko7U39pL*HThxzRIqLkjes1*_Ss|O*Hm)ws8kFfPbB@akT_BTBBrOp?oh*!6@q0Y3tmU$skMfzd?r;B?s# zQE639xF)Od!m^ju*TSKGc9;25>Y!^X?fOx44CCD!)atv^^F!KoaVBasc!Bp7YhhEC z8b%#)G*hNR@2XEYoZm%T)>uX`@0vFO=)D8n-F5=a{nv3lSNp1brQpY7Eg23lLuW-* zQU+-0_{e}@PjM;1Jg0!QaytT$jBeyfhM+}TFi=%?S-4E?m8qM@GG1AaUs<3WSfglc zD--H;4&(UW!1e3>r{6B78@i9cV8XdRV?5~i zyQ|4QYXHMl380AgdHvmRFtnS2kPXC1T@T8$J*k-91skFpnzD-=m7>FbM3%j1#N(v> zTDPU-%Hu2T1M`PH^-X25VK4Hi^uko#VGhzE`YhmJj)J_CSAVP*Epf8DDOFcs?%D@Wmt<5s|sbX2>rp z87LPE*f4p>FC{H1^~>|j=mR-kwK2fTO{(P2KCotw(ZfbIEhkIg`%$mR2x^0d?WrdT zg1)cy3JlBK^3*asbR$PhUnqD=b24e6?}6AM(op?WT5#oPF3VJ8j`rg^=r5`u2TGWF%?4 z(Ksm_gbB6>WyI{2@DCpfm+Waws5O*U=iSNsi94TpPDAelYWdF$SJFc5z@1qE;Lq=q&NlF(Z-?$^HGOsx zq6O096g1mRJ1>b^E>Keok+NdP3m?Z!!Yi#G1+mC4{ujt_kDQZXl++!U&-OZZAxO!8 zY<5;HPnSV+#csW=(6!>C)wkS#9}L;avTwL=W)x)p>3vX;Jd2Kr+!(=0w4f{fLwT-T zWNBr;np2ojBWbU`6*;dh=xdu8!Ls>JP2HK?G0L%;R;UqYinlGGYi6)p`-4eWZO}M8 zQKn-N3p}C(Ga|s2n5(2fm@#ojD~%a1+#jyhyfHa|;Kg*?C*o^;TP!b3Am}UOC}51*oA%lp*f4JL`mdh@+o}(6XKkg67~p=OJ{Y^W^Cr(FvbyyDbTq)RR$r0{HM$7Do*h@%e|nB z7!5%pX_7(=`ooX!y+!?Nu>{L@mi8Zk?_XJ04G+O2E7c62yuxekS^JwIj^>VnuegT9 z0F8FLo8&59LCPATIJX(l0QLP%)0<&I`>Em@YR)T74`bg~Wl0pTnFf;xMA<`m`-^G7 z>Dyl^Ar7sHrq(im2Hb%i=h&BbLIOY{OM4*y)LZH8MwFk8e6V1v`wHei0mO)6NtLdT zjmHaqE&)rC&nz@T7LL=Hx%Ilyip9nH@!5YH9~^nf_sM6vF?D>FaMbtc^2hp2J!-M) zCrhi%l6YEZ#zwqqD;6c69a+*{HhULD?_T%MEhfTn?zcp>@Fshld>PJWmS7l) zl|CrO1N*6(SlW6Pc48C8b8j|gIilJsuZ*GlJF$=aVw<`Bx0RuVo>lQO>reyH0*j#A zN66HE2ZR>cD%RKy?-gvA@G2C6!r$ZJJIV~#jORY%5xtHZ&Q$M(|gP5GBXZ&ISRjz?Dg5h~w5Z|SRO zF5}KGGZN#|kK@rQlfW-oz@s9!-6PBbI#=|z@e|sG(%hvcbc@@$2*bjgMrg%v6S&^H zWVc0{L?O&8P??!s**=Cv){#rksLEuSSQ^OKFeMZn@$r&4m*}$rstU?6P-T}BUD~&> z;rfC(mL)m_;nr~aBo|eGKKqc-yAQose>)tCIA^Z#9K|+!>kjuPP#F-KR7wA3=$7%Nc^co!8Gy_fKHx08 zK@Q_3)6cgPetfI{jOdxXjE7L4UhivVIcT*zy55EHp}g$s>CEYt9C`Cbz`I(Q;6!Vk zOTtGsRhvb-R>XG}Eew~mx+f+K|4aX+WRxaG%V8h^cW_SIBk^vzawoou$UIx5*r;UN z+7%GVb0%dfSUEHsDTo|^Oh=rxb>}7Ov>YoY_%s!l5^4pO-I!V%@y~^|gfn}=HM<)X zo6mjdAv2gw{o)u{>N^y&6k&X}RjzN#jj)O$?m zsbytqiV#~`yPDPXi@@vcUoC=|HgGJJ+(9Jr4?)o^~cpf9OB%gyiN$EC4rXb7h(#z@IexW zu_d+otr-4z1}#=o7rIdO?i8YbAdr*wW4|BZ@*WJbSfb*k-LAh^R>);r67&jTzUBR+ zNfgz%f8yGRP7Iu(3Ue<)SZyvlxS)qy$58%6jYO__(NS=iG4tj^7MkMdNb1=Jmv>-N z6TKH(KvbllYn0WwrQoZ?U_+~2Du1cQ_FXyA-3*6T=#dm=2L)1qir9qtl#i6Nx>R|n zL?3>knF#1&jS)Y&{e-0xo>1lz%Z zo%Z>Yq@G(Q5kNRA7teRI(YUZeGKBPU90Eu_YYlw^H|{LcE59Xc4j}_=Hz}>-r-t=Y zCI`B1Ypo;Ll7P^()1%9y#hpA`$;?`0SjJB7vd5rE%bs-fdru|P6mlXbSyjx$_=>LzgEfs`FnqqAo&n|b(zx>y!ejlVhp1=pH z0FMf4+?Y2LCY0V?^!1AG7lUPTgK~jQ!&YZtpYx|i*{~`1Ez$Tw@?rC=!0);&h zKo6J*FSjt74~<+5{XU-P^V%yL*9wqKQ3bz2J z$0^98(sL!f%+aC(HYd&kwGF`cT>h z9uf7#9TwWlib_ZsyEw5pMS14dOcC}UIUVUlDOE)& zFqzBwd(fJNE=BKSvVT8iTom9(Ylmy`J)UsS2al0|X)Y-?mmCf*ZJn1Nv z3Q1Qv^QVy^$T?hGWCZhBkubX{WPt?#2B*O#5(pMle5}oC3xBZQ=!nTow*#)vt9`#G zLiXZ$#9L4Ra0j_;rcyz|9FC4)HdhIhG%J*Q!bX3T!P4*gP&UxTZzc7nLJ5eSOdvdD zDm$R6Z|B82fK{lSzF|`&_q~5hTNs?wILzk|B>S_oJXc4hURGy&TJ~Z~c5hY{tNw+p zH6ggr=VQ~!9Ik&Gp>&E`ctfs=NPgGw>h&qo-tifw&5>HO`y28)di8p46hyNCUr`+A zp5!;zbPl>rzHQo`@~>mp)dEjl{B_4N|Ag!hN!&}PoUNb5#_RAp(9`!^!5c}&mhSTt z#<>|ryP(Q5ZhO`AicmQ5Ekhg3Mo~i0ZTre?u_YlwI!sXfiIaeAjf7e%B@`xuz|QhO zuuR4%)8xw^C0HA09@L4jn|m~wvW{nMNF5CyOh5(Ih14bt62$c(+9WElNkI_HgIo69ziS-nMFSRyttd>)(Foq!4yUyKHWks18c9Y1G zcwg}euv%%m;aY0{m>Csn`@uu7P0DKIvkp}$Au{CSJlMyMnXmZUob)N~|Jj+w?qyP? z;s}4njWxYi4{utT^YKE~MUs8{tB=KZbKv1zK!fNk1|0vNYY*M=p=OhzOKSOXRs-3`8zW>TY#Xuf}h2NJke{VL^7#JdC^)j zDR6G&J}i6rKn}C4s`=&nLN~i zj`_>UTu9S0D_9i>K%H~Gy;IYi^;EIpp(^+{NpdT@^xi^9p{5eqQKtABWL~>WzJn|k zKf`i&!v3gevkTVo@u#}0i{CqGjgKb`VlfB4rDtyk^xlVY9-)vsClmOBx%kR-_EvP_ zPN$n=j8?V~3x0sfUz5M|1PNwd15`OMgkvbtoJ1OMGu3Z^Wg#a32t@t)Z<8#HdjtU5 zu#B3<#&R+sQ}l5yjVQnXSg!aQS@9LRD#S&L3`)J4OgnP^LJkz!-Vj2e3if;!xmtuk1i;D;>?c ztd6A&(sm7xFkWrL(M`u)$=}75Uz@fDuhlQAhLlRrU!RJ6%QUcd{{`F7_0%vRPmELu zUum3?*!iwWUavRo@nf>^qLOE@iZXc`fSP#7ynT>%Jrv+Z2een67hiTr=UBOFLNqM0 z4pl{hZQu*+Xr9n8dlLMHrm7q74$n$b6uvMPJa}z^e1|z8^#zg?-JuAxsAEB-+co@EX-MVbr zCEcizPcm3j3tSX`tyS(it4(mRP$mCOrdKe@;AyLAkrT%MO$!#5FH?jeV;Mjhz_&Kz zdZu@0mGF>ir|YdjEk%7xnO7SW4twWvan!P9kZiJvw~iq#J(Gx92kwO%gIulIq1bdV z|H~;&pP!ztuRyiMV`}gxuN{xxtq(U>rC|t3 zB#Q$*Pc69{Lt6yYPT%;CIQw50;#`5Y3<9r8ovGA{OnX*|haOVt3IRW7?8)OtXHr^) zz}>x5+nh;lc3DwDy3Hc{*P%NrrkC#NwuC*zX~o&T`G~+gvhT$Yo6^IN!Ogi7W)^{z z>+We_mZz?nmI&|(v4Eb+7H?(y<2gu=eb?Ns+``c+(u}AxW4bI)8N>`RV-o(Yp=K@@ ziu7fMR?_>&H$u-vXQf&}FdU4bul>gw4YKETb}Hh7uer3ug>T1>q&2p{4QTGoKWCQr zVf4c|%Cr5vbuk+L!S1IwnTKIP#o-RBeN|Sl-xGv|moZ_Tk}0IjH+U&Im$}!t~>9+5sW8iovE`( zbMzC9&oBfu+}FIx(%fpi&woH=HI@euKm&f#FrJF}m|Ki=Y;G91(Icunk7G{nj(FNv zyt!oXyb!ti)jEpsXpdEQ57UhP&Cp@qWVe-KDFBn5JGcqS>ojn8*Jta%>kX;zwVmsu zRG(jloz)ckkx;ICSo$17ZUUJFi08(r9NcizQxM9^hI{Srd7`Qz({IfR%pcM7xN^F6 zsZ<@56TPwRuHJ%ph2>^H$1!2iKYZThgrAJ1{xtrr%QcHkpy{`fnf!N_Ws91+eS*^* zvx~O|*2?QGkfF}mGSMK;D%X=XB&9}VTb*I+BYaL;jf4AJ;3a_cxw$8bh16%!mXM6Nrv&096+*ZIrFC*8`B zh*m0}A?fwGGL>V0r1AR3=DzcT#nGvG|K6jZlK>m5Bop`+WIuY*x53K_22#R2QM^ZIn1>B5~<@0siBFd#>u!(>N@gfrB_Vrk4#pV8A?aLU>1X_vA~_u*!Zr9Dt(Kd%y zae=Xl71~s|kF@tD!}eSbbiobJDP%>Ya9l+WopkKl>YcDA{5wjmW-`#3@9npnOuEbs z>aur~=~Q!-EoU+l0;F)>%9dLOHuCv*k?nn3tfQ$@>3p z=5hYd%yaufzfBoa90YBzj+z80|2PHz zw79su@EAFSP)GltkchRX0!a)^5c-#1s0K+&%#e|zK1x;vJYdN3Hf|t65TN)vOi9t` zIE230(bqErQ7~mVLP*yjE#M^&U}`ANrI2tr^!=lV;Pth_xy`Rjv_9Z{Fn@eLWREu< zJi==zu>r!roPv1K4I$ixZo&R|Kpjv}aFGvR643AtI;ay;a8S-}Zl~Zp9e2d>?1|Y3 zz&DJ^bb^ojSz!dmT_#$vl|Q;Q7Fim|!q&LgsA) z)%lV@o=*NX4cLN<&@jIu>YpO+P;XxBK!^-yyT?Cfztmtszw!_wgisO`j9VxWt-zXt zxcNYG;B5NIkQYQiaIODG%+p3V_x_)lXA0Olg8b_S7fvyL1;T$G^apPiJTRE6K-XRG zFMO{!*bj9Ozo|!C3hlr=5{yJU;`fCDd}y%n+~^JTms8`5eK60~=j{MCL~Fwj+y4>s z+)?44T*0WNe#!`mi2n9l=TrJ4?vfZ93Mm0CVF5k+uYvfbtUfvi{=f(N_Mbs}cjq9= z16e&GDjKA-p?*vJC$(`60#TIID6f6}h5vFA0|Np6_ZJbh57`nl2<5ws8#`3{SF4DD z0QwOq{jQ)H66EgtFkcN8 z-2O}AsMK578}SMB>6h+#m+F@zLP?~Z5cp6ACexIdN=a<&(&$wE)b z0<@fDj@|#4V;SgUwCO^TCcWy=Pa;L>Zxd4SKD!0eqd%C(JJ|0YBL;Xn6zP1e)iEd#o}s-)ngQj! z0JVs2f@IU6FDy{^b?-{=HC%|NAd%=|64clsV&_Es$nEh0+zs!ghre7JjF24*apHq_$`_-4*7*wy3T_f)1Voa6h}Z@=y9~!eZKfW348b+X|Hs%l#b^?> zYrAbu+qP{@+qP}nwr$(C-P7i@ZQI>%@A?0oo$Q0}VAsJ)DwV8MrD~NLE$nJuD^C)KF?%b;=mCZik*$N!n=reEHa$ zxvNJF{IOn%W8P0O=K;sN+WH8`SlMBGa z(lq8df5Qb9jd25AY=D{uD><=EO}q#jTM+Vl(Q{18*UR&$Un32(Vc2l+Zt7zQr~%K<6Dm)Nk2TW9L{|;`fVbp6Kh}|4*k4*I`Ppg^ec3Ph`)i0PL&7 z04@_nhgvV+)=w0vR-=x4r2XvE1}^98^HL-T=(lyNi~tytz!j4K=uxpr2-NLZUyNz9 z9IIOB>(ECxkmt0Nk#3dT&V_gjCjj;PaRAfjwF@N-YE;qj8cb&vsG)J;!T zZ&tc^8qqT4J_*JOBFOB)qQ`BLH?1C;iB8$m&w{eIpg^|dT%ZI9Lo#v72~8w%=F|8q z#|Lz{Do@7CD#m}4$Eh`F!M%JBv)kZ95=F~n#McOpuQOY z7N0tu3E$u*ZZuj)$x_F3o`JJ_rgB;R`Fk2GXC#4oC2AYs5p`#0qf@Ahr*?e#3w`0# z^60u1k+UZu$%j(lG+#+>s7oIG|cW-GQ4ImWt|F?kSztvyW`v> zv!3hYv-M_pG+Ch~N#6aoO*@^w{&F{i|BneY^xi8)k{BNBacw;p&(12Ua^rBe`~=Kx zNDh`i_Dg*xmnoWx)w0tR$sX4t$9};kNc0mel%{a<3b~OP4fY!yuMInq;feh+|0k^m zQq+XtOBRSwXLK$iE`!mn44A7gIjt!(;biS^AP`#BR;`^`tydOl>)BC(`1=zRqx0NBBX<;N?d8E&1+08N5C@2;}By z9X8xF50QmPGWQzX+BU?USOXs7DyY8!X~m4>&tz}VCi>PiuU+F;z%urzvjDt6>wGcHYvOf zKIw{J>KW--r6wg9oW|^-bC3~^b^ADRk3yG0P~IJyO7YBm-zenN3D>|-(F#Z{p&m^T zU9bCnEFg2#0a#Y)kU`8<4Nki=#z(|C*N<@Tc~uOHK#Vem4h{wqW?S5fyVo()WsOo> zI90oy?pVN6)_k`B4Rzwrnpd@4Pt3(ezBi;IUY^g15RIQt+a2}{cgLY3%V}3?i*)09 znV&U7B{kyh>v~No0b{Fu4A^{}NmJ#WIPw&1jZW2KXTdrGMW4!;fOs4X1W4d(i61tf zcr9UT*%fPQ{PXnU3N#sQ#cOmK)zcybLijgJlYVi8szW< z&{G0PyJm75mhPkmr@RrWmgG2#uI0!k$e49In+$sR9}{nWRNYpX<_;y)thA#2e&S@F z+wX=BS4+PR;}SfR;Zo;hH>zAh7S?sFsESN!3RMq1UF6@4CX>A@{dMN1H}*cC(r8q8 zm$dcT1!RhR5~uUC*wPjl(U$Q1RGb7JZU~*#cH6>DSDOur2Pi^rKb!WUZm%L8vu@b# zIyVu8HFQKP_^j0!M7iJy)Ioj%ucnR(IJF)=>D!yvph<7-OR5pEVbUiMn-a!pa$%5d znx_Oi`X8CQ0uZ?P$>-HY&UlFznEi^oD{%R!@q}WQ3PgM-ux_s39b9Uezh}2M%WC(l%bXWh`zcj$8jL2mV=s^4F|Bx9n&zVM*~1Yq3+w*G zO~=%>t(KeBj1!yVd}4+TyLhh=(4ytPf(e2nr{kN8*VUN8axNfB6(q|Ghe&MgoDN9j z4GF(5k;w2=Olgyb#KQ$cTBaYOzN~+l*WB2ds-V&QD;v|~Eg64(_C-0Tm3Q`zAo^!| zW)^_6X&3?NesZ!@`~FJ@N_)6m)N5yO1z+&zDBz`{51$IBM+(nKie2RX?V{NZdb!H; zWNr=B51(VYd%zC696cu=SBTm!Lu3ZG&hMFp^Cq9La2&Pa*DWCpeEL-ILXiyJ&?Kzg zSCDpqy)*G4B&;!_-7#v}=VLIJ(;edTyE!%+R0!#@+)AJJ0zyZbbwtQP#Czj&&mD44 z8sR*!pB}5up2_Hz5+J8(NI0f|O|Ic}$|#v47UPqNR95UBtlGggBsSES!Muf*i&b(! zi`9H_-)WQjt-MPPV(vw_uN?-%ftLJ2CI(V$7j;|QT^Eu2j<&@|FtE4)5r9!2CAHgY%!s-kvV^fXLNmW z_CbrN*`X5-qotQ~ZsjSv)|~WjSSoc8 zz3lLP!ONZFq`BH&y@p~^$~GS;{JR5)Wafqf=i5aQYqg%UFMm+UR~^vq*e*Qww8LIG zlUzwoj@-pJQh-_=zGbf>SKXr z>p5))Z)WVA=%~+Z87^%_bbG^mYxFfsHpao+S;?SU!lZ|s>D&Fn6>UoRBl^}f1tM;K z@0o{;qt+Uxuq)MZx6?7I=UnLe%gvE?|VWq%4VSUA|kULJNw1j2BAL zD$UnOs7VyRnOBey3KwwgzwW=_&iQ>51hjR&0x=lsGvhUJ1@S-2qBwnsw5-uN?tK+f zew7^`LjIP?kDs07k5j&pPk?HbuGZhZ)%XfgeUZdaUTyQ`VykB^#N0z)*_$STBrs&1 z_3qJQQ$}g{@IA`?FvmR!7*h{JS2szWEKdFBiaQ7YUE8_GQDVs4ZL_V9-}|0u-ex@A zB=~K5!ZdI_`J>9tCFfaSl|q=CM$&hT`IiE_ZGDK|o>z|iclt#N+NoD*I+^*JVrP8t zwO&{3&iNxh=dSis-u3}>`>EO@DbEOKFJS zg0aNC`HyTNg~IEGg;*tQ+1O)@rEGrJ;bAUl+EA5 z-37Xk+OHyBe9u{_8*a{4Wi>5+k9qYJaSF7h>a~>`+8KNE4Dz^nJ2-|+fJN(FWa2-< zB!dAzQl0TirorOkS<;u1bkWvRkHuNOvw-zDNe)~V3!|OzY8thA0`$U4I>*v@ct3Q) zXqNin>XCkB#EE1ix5!G#jz!!FWIIZRyn8kZRWQI-TJL6V=Ly@=Qq!}alF8ACfLB@Y zoULm3&XJ}soAcA$V{uGA&eoWvW${;C-bd5P=8->Jtn#6cJw;ShTJ&?8L zYfFZb-{I}KMLnH?O3OTnf^j}LyPVIB=g`=`d6Ud_w-{=(D#K{xJc)QGBqhko$R^gY zm;;vbS{t(-i|deH&}a=#j>Q}f1@Caf&4t)voeAyTTfNuellc4T`f4aBc^u--7Wni{ zM&nZ9SXEFLw%DdU-Q%-UnA0Xl-!oWPW~(af)I81ve|CQNJ5^IY6=NHzcV-kS%JDs- zN|fK&O)86rbUHvN-$g0gYBNIy6P51{lAFy^vd!$BWsIv3iUsAiC_yvnF_$^JJqvT9 zTNRNVy`%EJ#Y&jSI4{fZJZ^?g=U}qxp<@-g`?W~+Syuh}JkKl;L)Z+Y_Akg+@3?nZ z;@tvb4^nNIL!8w?9wLWJ^Q9dZuEK za$^`SFl96zK;XhLi@sXp*bF6CHnEfP>pJn%b|jx~qVPepN_G*cOWgBJd=Yl9yjbqd zp$HXIY=+G@u^YQv4v6#~bVtKYx&;*W{8}){PsPKDy%-mC?iLO%3xhWp|6c$Y<5ELW z9NE!$lV1KzzCc*mwA`IrHsI)dyg~t&o4nE9bbkoliDdI5OIGmNu`5)GL_oiKszYf7 zP~OS&!U09^;HP;~kaqlFl;s7S6gfefCDoqpOIov|_fW~*at(#5a3MOrx#NfRd?oY3 zeA=bEy`%MsjHlUKT94gr;)hgc|72Jo-oRyMtXg2o#o2ZX`(lB;o7Y#Cq@hyl*^(<| zr%fsu9r0b^?cPvGxjh~7gg zO?u5qM=Kq8Q!#D(*Llh8dNFbYjB5nzv?|`0E~D~SBD&3z^zx(&_1v@?f#0V}c`I)x ziok#u1BicGHxX4I7j{eD^5XEy9{(4=#lYj=gwK>`-<*HBON_Iozx4S$tSs59v-Yjx z(*(?#6^Y&2&bAHs#hc}M6B%vfv@AO4{aK4Bi+3jWUBjv)=jixxd6rJN?vp|YG?5R; zQ#&v%z2znT1*`Ii@_@u6Wzsnx0f(^A;pTiw<*@(Kw%v)|6&E;K&X)Z1dH%kN>YSrX zAde%az*`aLQ|7+j)U=iVKz^$SX;}Y+!rfpIKwo`x!aR4e^fiXSUbKD6nAZAtinrFl zcMjE_^-rn^>DN$f)u&RmUvh0@5&>njM2Zi8Ff|D6wD`KUwzYRwL)KytAW67pSl%w;f+Ei7w+;C=gtmn>xBejoA3f~Zg3spHegvL4+1H0v5Os*Ghh4SrFapQMlFf8!0@4X zD-!`n%Autby#*8~t+}Pmj;v@h)CF9gw7t~Ir}O+5z553a@lJ1;F7zN8cx|@?#qAN{ zb+Hc$TO*d?mZ<-ltHFHAhNlU=JQI9Shrz9HQr7B5Ve*ubwhybOOSO!W6&MU1MqsSj zsIVv5hJRJ6!oWb-#lIf(nuqk!0+X-&K0rBEPt+(2qV)VjWn~J0Sa_%AQ@UhG)*oJp zm@`GWjo&Ck>74Q*>)3wYp@!#r7(MlFbw?Mg-|14QV>im{_w4uNkd~^pSVG3Wry})E zOE)(t@z|a&Z0at(EfHaT6=TE;&brX@(Eztj7jLv^d5pvmnsPk66TDx@^aKJ)`Zghh zLKd;}QPS1rgX}+z+_Kwj!U~gPIBmtyj%$8#L|42s$H1`Qh@3TJx||oUCGfjWZBAmt zog%DKJKIov!1j_(^=}YiOlglIxbZi`QyeTSEt1va#hK1{3o0G=K!1D(r{W{)ODP89mFLH}QGXuQ=iqgYe*1C>FW2rwWcOS=tu}g>Vob__Qj;S|;d?NXd z)l#XA$cx_Vxdm9XSvcSvEjz6-Ef|}m){Q36xG7u(%k@S@Jiv^ddW|$%XxRTP#+_p^ ziarn_4%DP{$f5AZY|@eKDk|}fHgg10QT;BvU$B|YGEvyv!)JN+$9GxT!akK)p1M&x z7>sn8@@CXhI}*9-Is|Z-6>xQVSij{X#goS!ih4Wc-1fKemyPux?YFd&Eos6y^k84S zX8Xh~LDS{zc=PaD)?oYAMzUsaoSoHLHU3!jBAsedXSUWqB5XiSc;gNm>fT>vbLEk32r$O_mds1Rw zMYMZd{s1$K<&GtVK&CPPK z%V(Avev?a7tZ_f;s**~sA;X#{OGWuD3(m@7-St6KnE`o4Hly!dMd+7IWG%*LrK#SL z#y2?Qswd01h$6_$%7|vN+oQg*_06K+p+ne)zgIA9rh~!4YMC@+{U{K{EP3>wxEb4j z;%2Pu|BIWkvvd7NlLQAl*Z;j&&h;NRvtDIaA`W7efD)EqaUt0zf=}X}j$s^un}*xo zrqY#?aF39niY25Y3L=z<@wTjxZ}OM`aJDx#wW}V4mAW( z6%|nY4i*Ta1Vn}>%mN({5WuJ)C>U`E@Fl_AK>omx@z@265*b8tsQf`I2nh+A$CSYe z85=n#+yjzT7Y`_46jYFrQqYkS1R^9QBz(^o1XF@3fO85K0^$}7AO}GZMcz^W&FM{S z*yd(2&Gh35<`C2g6qJ;7(9^#J3?rvN3I`Vi>LO^tQ-N|K!Z?9!5CJq?U@_;Px|h-@ zT%23__P)Kn9Y{!liWJzAdVB!hF+A@E1WE}LAzLcm^F zM7f1K(l3|+pg2ZG)VLv&jv*oiQXpr}0R>^4f=js2ALzdTlmXZ`YYrfxg1vv-Y!aXf z1@V&$2NH}dH%r(-0cQv60plD3;-cCbXo1Me00b8P1p~%agp}cvfCF^`)ien9kDGxh zsLn$MFoFWu@L^&?xQZ18QK#=$Bfk144cclz!YPSzata(+qwduGCmBgmV>G)de)6+j z14y>^cl@#B4IL(y^o0d=a^GY388Or=!XEUAFeB~=rqQDyB48w3xpPz0bRdw<4 zUAx&`z|SzeQxKOI04;#1mIj*y2oFpQ;_e55Efj!Ui+~E^?D}C}4}l$m90an3E^Njx zgj^6XL4X^k6zVBR4R1o1+HGrIg5+G=kn6iYy=7`G<}eG7B|!%hYnm_CE` z_*Z)k=u(5jd2{s_>ZgMS2I}A0O=Aoo2pPHp4RadUO4#1zEW_}tAbCm$0LB;-P~dK& zK5&o{f$n%n3^RHGzX2;OYiN-pM}RVPvx1+setlGh3P@BLPM%O8>F~AH(3ZTvl=Iz> zzMlalmgEbVtQD^wShc@Lt&B``9q7P6cj<*L{dovds8mS1pZmyRM5vSbO17RyK z<`y)l`KF?}eLO+UA#ZI8W01gduOmFPj+ocwiU52`6zCo3xN|b)^unV&p{rGDu=N{? z|6YIDKoo)tOjOR)eB5e8?Y<~H8!?%-(BC`LNvv_WmWel3C5F6Cp?L7oJc>54g?x`X zZ4px*0s_1F+ndScJ^A%3s&r~tVnWjArGIjGGM2@qQ!{GULxzmhQTf!UWhD&?ea}(H z`T{^N&a+w*Z2Y>>St+{F5ncFrpu^kQS(7tobxhJ*|ckC^ySRehcP^Q+!)itQQ~W_6lVO(UJbn zi(l!Nsq!g!aMn>H4Nm{P8ksGAjm$3zFRIa!Ge}aQsNu?w#p63AKJTJ6Y7_2zjYJ2) zlRqbM-WwKJ3rOW zI%u>rkeW~Il`A&$R*5#=9zFIkYy^|vzvW)PP?xz$H20FIx0206&w>B=7<_6HkzFqD ziLN)6;Nx)x(Ury#t@lMb(rx&~4OCzKcWJjxIk2aLFGm?!;FD;B2ECiU@vWml-2iUy znT=Gxcj;!>SCRbXx_4&)=7+L}up6upUlS|R*6&KHu=xJfepw%v68^6rmdCV;f0K|t z!Aq?N&fAVh0Hiyn9xw7_Rw#LNHlKD`)VSI@DMPEnc4+@}@NIU?hNY2olnKhGUZ|n@ z9w$X3MWrLaAxaBvE^o9M%E_qBF9DaN8~R@!stL5$fYaxa_~}kSRVJJ3*c1&{3GToK z0}=OeqbQrLHl(IlLzDVn9*&_J_F+0>^6D@`&P-?q$WmwvLU}@uIr_>5@inQg(Yl@Q zGwwX-rX6(elN2iKb5-2^0uC7|bxPva6d7SRTK-?;ZeXNP8;Ypb#f$kd1$ccqqwp!6 z4w^ ze5Lkfe2zl{T_?C-vx!h4#YF+nDSuYp^#GHO3(a-qlC!A$JXlf_k8phG3hv|~osCqq zZb!zQLF>SS+ZnJ?`+@t2X&*To2xV7s_#^fFH7aV`@($*u)PF?Ltd059+QZjj&fe6c z01CAxaz4G;wxgkWf~W$bkl%ft<`G|NzrqUC$M+nyL2&+)<@~M{A&1phaSy4kN4qYe zu?i2nDQN83O{7RU-{hIdsCWq{-M=OX=ehaijCyIhyH}=u+o`xp)GWiE2`L(^wj!#uD!j*hKHY;_N^ zq3dc4;p}d_YPoEt$5#Vl)|YbuvsugrU*n8Tiqyi9n6By9nN9D`o5T2f(X^-{mZ01a z6N)Fd_$V??-yP9Z{*~gQrhAQWu>5`%HhP_(RK1??z*u>8mK~Z`Vg$maM^fAKN#oPm zD5QYz&0My;17BnkxVtF&M~QV^yCa{Q3!-_m!AD!Nd6I>QnlH5*!vPie{$(Cye*Wp6 z-T^Ix^SJ-F7?^Y2vYOB@H=GIZ+78n0!c8%E<79gh_}d&4Y`g}Dc9~@LoL1~Rh~)$1MGh-nBpTZOkw+8`tel)HNmZG9P9i95f`Ju9^j|o}9U4?6k2OLjJ!zBh zcxDLkM8=AG5E9CqjW+})ZcYZRCK=SoiyB@3n^4b*ok^Jy3SoxGwF8It>p>DZS5em{Tir=OeMk$LCdtuz^L% znT=Y_y{AVuy)2;4s-2&c%S|`U5b1%wz$gD!UuViLX;=F0d7;!vNr#-VhF(R4FOsqE z^^T^|=7oFK)CmuTTV-Go1CiTBBBNI|+Tu*u+&>DtgEj5xyJ>?lrwg!F?{3HyZFh9C zciyD?+YmRLU|a--;~<7Yq|U?QaIXiu{V~q8G+#y`tw_3O_!Y<+8ahkJj{9oV*}ch@ zxC{dq0&a6!isopN+Likj`(SObeh-FL$m6Vxt*KTIr}n|^@qyCSvXn2iJ&7T*U9@V8 z5t<3oati5CN0~pFkxZIzUPXTRyLn~Uf-CM>@^(X7%dM+hM!=t6{>A6X<9H4HA`f-4 z0cLV7QW0!6iG{uy$q~z?5xQ?i7hN;TM-T0ziCVVmG4XQcP0DlTVslk5wu%kG6}x}{oy1`*Ev%YEEhVn$Tg2Ltfj*4QrD#N zQHHZR>`f`G|6+*DUkOdVk{E*4jx~Co`L>^l&c5r&SbJgcTB(&wT<449tx9XjS^^>d%C! zG2Bww=bNpRJ6$LZ9!?pjG5C0+(~`d-&K8Wi{i<%9<;Knit&@1;4=uvo>5;sA?^zl) z@eL)3XNUq-t6wimq%v2Kjq<(QE?cs-;>EC#$z}_+mlVO-0LcdRHJbJh^vXWOu$AdT z5TnynS?}Q>y=$`ugFX&%M(vU4anR1_fr_T5RmKkP73brcMB%yY) zJ=jZdUvTijEp|~`#+VRG=96m^G&^Zaocic)s{RnN`&(*zYMQv{O`LTzx1rr| zr5trk1Vs2{rVu`|bydPk8#fx<2!-(*d2seDLn;+H?Ysw*m}T-*4o$xNDX9N~z>gs&$o_v#>qkX=oXfFUk}A203ty}2`nrbcz1Lj}Y$|NDVp379sTGg? z_HQXf3t?Dp4wl?7x`(R)vGsA^rr%OlsZ(Yzjs7}~fYn%|%|#R)Du~(TNx+NWJt9;M zP^D)U6|X}irLRZ=O`Rz!QTvD~pY~0$_fRJ>@dzut{}JIo!mAV%B7?itl2Nn;va znW+^X(TQ4Hb-+iL6bD<@r2fvzmVVBTp#2^bQq?J8e=Y6tt0HXtK9eA=Iu95(4i}bu zk$g`18QVY^p5nzpwHa=zSvTBmKdFvYmxjPckx>g-Gs~IVz2H5U&k2&|)zt3@ytXO| z;X5-T<8#tYReBS@y#VR>i#02U-U;$cS5+Jzsso&v-gL)9agMWonb)TjJS7||Zuu`l zR2Y#0UicAF%hW$(aV;)nv(-2D=NcPN^fm2%7nY|pHr%0gyJl{#G~%UR>52t%8A`>~ zW;6KDeKg04+VBI^4P?#AayWgv8ubNHsZTNy$0YFsy<(GBo7d($|Ws01-?OP>^y_qjP@50Wl+~3E`l4`!RolOs?ANZZC=cDip zTWc9wX^vf%akNq9YJC;)*|Ce7UgzI%phH! zzdP6&)B?9GU|h;KHdtgYsD_p@#=m=?FeSgN7OorICm4$fqaV}YkgHH&!p2!^8Xjpl zSVq0$OUnItTjpFZ9SM_Q*tGI+Xh-@Ulewj&cAIw1<2<+yk{ufyo)!A|ufh-=*NV(U z08LS1`Xo@^gKbz8E^s3&LSlaY$v+0q*$=ttPJJXN9_`q1rrt`rkNA+SQN3-)y{%hY zHO5z4EE41DuwN>;7b=ANDhlp&q*SiJ8!vekwRZw81nW%7bJdz~wh}N}5;M|BW@xqx z2to?nn9W&Zu>n*`l^a1vt|;pbiF5Y?t0yY%aCeQ|w@(P)6;eyI#b62bm5eCM`ghY` zs7%s7tV4qoMwWE$#sEvJX^8tsK45Djr|9Btcd5!9s8Fh+1sWa(ObWry^c4%p!CC4>YFJ)`3@9BC_IM9GV z_CSFw>-HmPv{7f?S{Vl-Ll{@SZVPTNS`!Cf zt~ap1%oOk%JxB?+EquqQdpG#81vDFH6aJp~Xily+k<4g~2<(0gY`*{GCd|Tv1h{B_ zRg=3qQ>q0|#fw6-pOEH|xNEwB%ch~5_R{2cq3ZUuo@^bOUuJuvr8VZ4))TXrp1@d7 z8pViVL$gBd%)*hq`@8F{i=3Kp(;9AkMxqPCqDA2*+jiHpqk8z^eI7xz#7#;`dmE*pqBNg=Sn& z5X(n>%7O|9ORgv9f&KX#ogB3$eqV!oC!@ zwRESJzw*H{I$6kSxiUecHF|uQ^}-P^DVx!7xkOzdaRI(Hl z{MT$n2jWp~3ns|YPD}P<#a%%86hDbn=DOixSUu`0bMjRBbDC4riqXdY{=QDQAecZE z+?iPbc4y3F@f>~-%G|3cBcpnFB4-;o`IF=2&m{C**(Sjy%wavQENq1{4aF}XI({ES zRFZq6QSo)>_*o|Bm-tV$F|d}Vs&>7OQE)tu_c7JOM0wQZiqnz|AI=^GjbZL#;nyv1 zfiYN<6UUrC?3kB7a6rUwv7{i2>_gA~dV;3Cv>9~ye3JTU4vb3FQZR3Z*RQ|Uwzcxz zzYa?A>lU6?5WRyRgxSqkU z-2Ae#TwCB}UtmHTBIzsaEJFHOKFw1}6yxY5UO{@@#^P4hHdR>{&?5P5 zf({j2-i*z2?-Xuu7$5Z@raoNF^IkiP4(VI=tf(!k1Bfu$jY;Lz&6~SXE0JYIZEn}C zrqN4by*ovjoPadSR)3nar=pCzs_?VG{9Tv{vx*w#3tjQq-%cToE|p7<^b-_e5 zXEEm?k3-%H$M#p`b_<~3$!ssd86JBh-EcTcZqT#8TakKvKZRfdSd{0W5~};v#iCsN zpVCpdg{K9VQwIg1tf612^O`LhIR!V*T6^1KK+-MYDcShmwpU)CM9!o5LqiFeG)R=k zElQaY<>>N#Z|F&Aw20<<&i?c*=6%V;T)3vqdfl1z3a^f<;uj0Niil6Se+^~Ql0$YX zfz3!neTxp)Ym}wQ-eBTLHx64@JjvoTZxF?ng-G&xR3yR@v@RO)8v}<)xRh6wpadq{ zgqHUp@6UcAgk`Z!qpQ8>fEc$t*g2b!2HgTOL!P{0CoRU{MYkbN@+=4ZE?Y~t?uws; zE4e4gqo|1|RCyhh7ypMDnXxCEUAxsSe+PNQhMPP&%zXCldWU84yw|)&Onj=NHvgL< z4(w2OTj9;MXnjLr-uc0FVo9X$DR1Ry(Ik8_7|J! zOApW7?F-B)6Fs(WV*~&SIC=Jp+Na#ZYrLiLlU!^=`SOr zgp{%Ew3I-ZR2In_j#W-+Kx^5g;cBtY$x8HCr{(r|KEKmkcW)4@O>N&UU|zig`K{HS zCn+skRzVQ=w6K0MnMIV=lf98@=`eDYsJ^f6hDfz^re6}PB0-h8O9*W0(U(o{G8Eb4 z?dfzguQ2z5nScl7aZ~UO?!cRb38(rm z?@=0Yo3o6LqOY+LVOEhZKo08SL#}S>oHYYe%qQ86h);h(GE2(^n|?>+{YB!ol8l3yr>cch| z2$tp4yl!8OGy}baX~?Pvyih4NS9B~he=$kB6@0wMcF{*o(8O~=&fuI^Pl9JGDIgm4UN~^r+S_?i7vTNf4(_s8PM52g z!`XyWl7b#x>?ItgT2(tPYK0CX*@Y)c3@O03CQ}T|O;cr9-ljiwB+*@FkMAJLRzB7d zpK{Km=;VyriX;P~k=A4y$Ayd z=ty|J=65XWK8xWyh`-)_>ViL-0KMAS)jp0J=b_7k;2<+6u!%@L$WX&#cLSz9$%#c( zkc|92#EwHuqd&LeWH+x3#7$z@*!Cw2C9yw3)~j);^BYUV9v?MeeHC5&pm%+{YLKUQf4*%0S|L3dZLgYP8G#O#emBc5{PnOoqAI zl6HW^B0s-tWY`GI=&$hS$u}~!)9XJZ_jmbe^^kY24m-a{{M|x>?o}eNbf#4>u){hJ z1=+oCtWw!7E~VD}qkCLb84?#=rNMgOyu7&4qrZkTYEul|C2M_UCf^flmuL z>9JT+=z8#zhG6%CQ=uc^QzcuT*4Zb)>yABbV0q+EAhRe*~weR8sX;uJuvR9tD2M1Ueg}T4*NT-deD({@#4-Ys~SFbjt}qe zLTuelKC2!6I-4&w!IgTJLbvDi6x^cQ-r@qKg{ld@z2A=o!(T&d8C&tu{TW zVJtFZ@Q-s%d7QCG_tM#CzV?-!fgI;NpcDym%iQe*aSsU75$Se97-;)x zT~Kto0T^ryU-SkR&WZkjhJW9P{3j00@gF!eGZQ1{|8#6J6Ed+gvorqB?*DG!Wa40B z{cjQt!ysm9<6`PW$RK88=wd2jYHV*}3d7G2OWX8-?}X@c%cSbQW_nr<2d0D(~v*u3ht5^K(7xepxBXYC)0$`$}s< zBTGZ^VL|CRXrv@HRPzGJ`6ZTx#U+-Zq$FyP>uERuNjRA*dqX3*mX-q`n8?Jq!1$F2 z+ZBFv7fy_-pW8{jw_%F^CgTZamD7ZhmbZ{QTr>+Fx{w7xh>d|IT-dM-EtDB57x5 zP*@)Awf+Ybhz-m$->2=&o8540E5mb({SN{YsFrz-uWC47ZyIhg*n-yHz>d3qE&}2; z0cH|V9w1mgK0Y22o`C^ugS`@SnOAinQFEGLe=01%FaPDe8!J;waK4gi@ST! zORMTb1E7ZHMh*w}@A3nGWZHlEtOOP2kn?PeAX)=}djJtT;}?#9PCxuS^j$K*_zM&0 z$=?_7S#r;0_>qQgHtT=(!)?TExk9kJ98w9;eeh2W3QCg$QpYAo2ZASol*_r2)nDFMXda`761-y}~8@cneVi{ajvlACDhl15NzEb#e2- z_U?>h4F8-u{-po;>8JXcn){bb)Wb-uU}XcOAL zH^QR-q*oc;yicS z0BJPet@dVuu%MdK+I#vO)8S}*-#-h!w3&J5<8(%!Oj$olSUy+tKV#o!h31t%t4&Nz z4InNyHZD1a0<#~WjX*kB{B26R1Lyj~a0SJ!4K1IHz&^`o2#hT)f!~(~nwmf^ApVg6 zF&iN6LV*Z8L5eR(N1(I5Z;VDJ;Iok*LNlP;5rPqzg5(bdqjJdkKL8;DHyHo<@Z5hl zWL(@OSKy4K>xSS7S@*U-;(hq$FpueH7*Or*^6?V@2gDuxGZg=K{@d5fA1VfjdsyI( z0qkq|2Ucri^D7vT*uEgJCk`^^zxZxZ)YzPQKc4#@eoq7n0QFlo`#^kC%vt|;-1WqN zZybH;dw6BMSIGRGVh!^3aOMH=S-eTjf0>|7jUc@{G&68N-zoE-QmyqppPteGY4pMv zgYIXcGu|C1_xax(r9Wq&!{VB4{vS_;E#DpWfC>Rzfa6c&vy0mpJH!uXMEuniF5Qpe z-2%p^|3G{n=hweexOj^f{%8P*Gmas?&i(`t_&U5RZ-i4|fM)j#f6q;xt#A6U4&CUx zZ;tbSL;hTvni}kx6hL-!npch=n=2i!J}3|cTuL&=#3Q{P zvMIZr1qqH%>zy4!Tre3AdiPdRL-bajbZHw=Qj8hN!j=@?u(xi4QO_+k)~4_s=9zeZ zBHEI}`jW&vLc9Zy;{YsSWUMZyw%1dH744eBnm&~dozLoBDO&CfjQ0!YHLk8??hO2D zL8xj?gHl13SZohS+tj~|%bD~=5C~Ab!?85mKGLEpgaWf8JH*AI)RH);#CxD2b!#}2 zHc$dA7jx_EHRk#!eC@Qpdj7cDile?obSjOylh=*ZxebjT$6}9sa140+2gBNa5qGTx zAWOI}es`7r5nZR(nMZBHEYG+c8hL1~sP51}jTHxjs51u^GkF-Pk7CCYU6ieIa$wd{ zUSOwp4Jj&X-w z_2VZUD$*`)SxUb@oFxo@}e! z8aQj}H=WYxCQ0WVK3W}d>^T()ld#ftSw+_=u+C0NIWJMe`Ns+^`(?k;LdMeUK z-C~+N@1c-Ps5(NUSSR`-SPb4}wDDM__KLfy!cL(3ox8d5j@y3Pxs%;ju*w6s5Y^IO ztUL+cv&WgU1W^K5eZC#{|HQeUz$KCjvD05HDtjps3ySk9etWZ-ZVra(j6G11y_whH zE1Ka_9?ey(Jqb)mjv<(<;yF;N4%rOHn-cAy(@h%0sP3k8I5=5)yF#t0e3okr=qt$vzys^)Pzp_1lmOSq{xgVWp=X^F?nOc98fpWrXYiWd63v(-CUF z29WxDJ3ee@;_QtQ^>*?#`V*Q&;1ZvTl#Jt{H@Do}Z+EbUqyTihsT4X~+=HnZhd!>H z{($7sMBO~dwXzoziei^QgYDdh`++B%_egWGI>tvth2)r`FT}E|0RXhWy@j64nYxzV zK=N3_=`?i(aMIY$DWvk=yKojWR$WTu=@-_2$q<8-jDPjZun?C)vDcM9o%akxu`R~b zdH48YhTncXcC6$FORgHzj;T)g3N)`p8`mVu)}EV34s^#RzPqKtWBq9TgvQOb&B4zlQOliHMuRX2Xc10sdeZ0t})Y7Pv zGv~E5EM_`YnGu4^f*JXD*;!{~o-(`Vfx>6@{Ywlv3Omb`)il#;G<&@T(}>Y#_|gzK z?0-*Khf-OjCf8R3+dlh{1qOtcC30Y?WjrMp^_P=^x{j}E@}MdA5`h1_hh1IBJkhAi z4cv#_I58t;1Pb%Lc2iN|l*FZ`hOz|755lg8Wi$fN!IlXi$bv4k4qIy7CxDAd3#e_r zJqpVoWi!P=^`McHw~b9~%vruz4BjsSxe}ljqfPTKr`W{2Nz(rewT0Z`tPKAyHl;Q^ z^Hbyxb!M$4=h!I!Q~dObC0l%JHkp$y?q}pcBY(t|pASVYD`DZ!CsqheXN>S5$9J}Q zRp3%#+U3GD8V7S|@e4UYpdOt|9OD5kl|f(~Js4~a=F@OJmFci~Pe}TJk9WF9|7pf@=ls(aJirW#4^eWixps5$u3#?y5WSjlYA^RH zdZdQHeJ%<=ykthXBFZvcZIeZj$kYxl*p3Y(7qAaQBUs0PP#F|=AB$HI;Fr;|spBD1 z{_d@%uZaX$lFt=N!G%u0fN0eCkoa82bE%(%4*f8lV&rDCJ*yW(rOgkILf~4V@rg-X zTrsj0(S|<6&IQIzZD!vdEAXm>WaDlXqZc*3Oj|pICjAn^zH-ncJAe3R5wbI}x1v{# zvGx)~7t_{d{V&4KDOMC%OT*i?ZQHhO+qP}nwr$(C&9iZ~ZOq9eH<^cU5yL0ovlgk444y5Zc@iX^?3=;fjOc&4TD#If)EFtCs75wnAJ7MGdP|7%*hQgIK@QcgSvHrGv%f^XC0352Y?J@bD>27fUt6J+72_Mgc zd`j?<%tv3i^`J4rDBkRnpo6-ifs^}C?OdQ<4LXO0o=uI?kckwCl*V#7k{tVwWsTD^l!R zxfja)rXGpSg9hL4G@EUClqTg=mn1Vjnfkjp3o$zs46aRSRuA@2`+A-zYsr_Xv%~W} z-}Xlm&@z@~SaX%PkBc1kOTpk+_-HRQ6zO)k(|rx{Z=(W7Y#Smx9GOezAr)wik<2RlBEbBafK0H79hu0gO_#64Dbp*!+23^~Zron@eCc3I=YDpb^WJ%0ZRu029T08RUb36lCm9G%JG#<7N zX5+SDr9Q63nQhOGFXH*efUFyMXmjzT3<91%Xgqvcqb*O^jO_H#^f!g!e9@$HK?Riy z(}gMQNl6O$0RW&MMyleY5Nt<{j(mG|%o2m7gR2=Vm@R~Z8s01?HG~h+C_3K zn%*kTNTE2v7N&`|^PdC+6OabkUP_w@;9hH52z4$#C8`J^>bqIeNa*h{`s~y+?T!Oj z{%U1V0J)%$8sjX@j-s8a#fo(0B4BshgS%&BzH|m1tA5YoIE037a|^dgnv@?dl#RHs zTI6nSJWmg}9E-Qm@f{bRi57QeNHPR>KU+IDrqK|GZCAm4O5PNRFWf~~re2-mHlG;h zt=;<(JV5TD!Ozn{d2!6R8r2Cu$fz8+ay+=+AACGedqU%Ckmjk$k#;D9bJ{RyAqnAGlW!70D; zn3f3t&>YV&-qU5?Z3XU^1fJjc3`{A~+qjT1hn_3klU$)9nMd`4xnL9&O?_79p!BY!>hmawFZUaO}Tt z_LP>31M>QDRv}$hl$1C(3j=e$~ADO^f-5L<5YKopFMCG))Vr zReH#{Uiw_bn}g&@Pmhxl*iZ*f!e2~R)HMJqG4=ZmnqciqOnfh_(7<>5^rRFhR-YmP zM~=A_Z?MkpQCIKZ=|kY6vIO_M*6`35Sxr*nEP2*~*@HJE6bFyQtB&wRZV<{bd$@QX@VkhmKq>BKtgc!#1zu ztYo7C0f~X1@I;u{Y{KPy*Mtw<(5#I|$Oyy#hd>Pl76ja! z3T6^!0Gi|OKJ+JUR>M9IblWaQiLLd7(Ro1^dmaqb7~iED>s-x#mB_U>gR0_`l-PL? zIb2M>vsOUGQ^`)%1BePTB%j(`xi&~fJQ)U}TYR{ECu*+`rv&Q`L#mVT8L&c*l8q)& zSedooa(@+l6~8WmTcr5rAg?jk8?#8FO+|T=%^a%RgkekXMF_2D6_-)j70;`!74+8W z0ZPL|iIP?9IY8dG09SjtR-umS7)pJnA!-bq zka*xec}E#=us&*O=Q}Ey@7K_@maOR}Y-2RG=X(@?>q~@ABlDj25z{>gA`cO`lW-T|s?#ioi(%eS2u_L2bFfv;|JbmaZZ|hWu zEd7cV4mXf2cRUwq*faAz+lv#)b$PRl2eIhLbfAGp+xwO&>ejsY6*_@)C%*a!-Gr!E ze+RAG1g)f=X^76k8PVi&;d=W zFyZgni17MqFNH_Dz-Eam%SYPCVsS90+(*89TOJ>xZS0UqXq$!y`%Z!^JZyRU1#{YY zFefJ^oAEP?=vnn3fNIkOnb=63l8W=rHt@!qWOI4IrQu{Svu*}6-V`~$ReQc{RJb(q z$X&CPWlUq=!|{hS@WHpM_`?(Wqlp3Wv8=k%>1V3nzXodXh`AP{zC$z=fpXT7f)Nj; zxK`2LI}vGDGs;&)?cce5v`^Zpc8)Nre=OS^N#oa+v}^xq0v_vux6}=&_oc2zKbl77 zNcFS(1FGn80JcBvQ< zL4U5gb%9qnZ4VpN4Z#mRm=+i}C~46?qpQyldzu$!sd^IXw9iGb)hKbCe4UBZPuy^@ zvnLs^MjdIS#EWuP(~9v_SepWX(EzU0?&wdGzXkm<10NMV6L7)Lr0 zA6dS4i4~Dgo1d%AsQG2pCY4_Kj|sx2y+)`wqL9q=MfUl(Eu0&dBbhs@EF(AzXjfXD zC=p%A6Lj3~LVU)>u66=NZg~v)q~b92Np(W4I!_;e{8N8KvMwb_MY*`7p0yqUCWx)+ zTr-?U8{O*J*lHgxD+do~wi$v{*2vaN#7`=U(GL;-bd%(i9^b2G28hx{wb2$ z`v+k9@duLJ1R|jTxGV?8W};R6(*k3hVN(%N#Sl*=Gv!VbS#Q(^e$!Zm`}>hcZ<#YJ->y zrh-RO^nd>O$*8==|FT8V?h~uj9e!>4g63jXXCn}SkejIIyuQd1vU9O9~IusgdZ?@2VNun(2vh$bs3hfTIB@6|m^qN-nnQJ*}tzW>a0u_|->vc+t{ z$)iz^kK5UG0zlxv;ste(Z*LdDcNgaILz*jgCUkBT zx$4p)J3LLI*`a&Pa367eFrUCQT+b_&vazR`l}yNGkw?)JY5Yr^$kv-zdOLJ3gl_F@ zAP^+HoiQ10+uy}eQ$^5l|A1d{1ys}6n8aTa>v%ldzZdyIuosq!^Ju2=C!)G~@UratCn%kTiNdIx-bzFAq zaB;{Ml{0$U^KdeV)3UNW(inA3ssYKXT{YV70ry8u+kN_Cnp-4aGUAprc}g#whiruT zc6PfRMnuU+{^!@1odm@P+bbM&}%T z`Lw)Te3)b-n1}j+$7%v+`VsnGVfYr3v=fb#O!i_m| z_!CdK?upMz_cBF zqVkdDhmYAD{B)%KQa}R;I-h4fS6u`65_kaGYFetA-YLfaJNi zhG22!4#6c`e*>E_MO4WbhI}n@V7NHytgGy;uZB`JOr@hRuWxIW8EkJsKqkeBKYUyU z#^T?KCyymum~dx|Y1XGus$g1cRE0{Bc+SPLI^J+H7iTTPeQAC5DeXtJ7TKN5t1)9S ztq%KMh(w)578#Ao#u7wNXwdjz5DtCu{*Xi2uRdK(`1SORkZTlS{1?&pVmN}gC#_+( z`+wUnY^{U@AtyTGR>+s`i^V<7p!8x>2lV;*HQ7grUuW)L@zN{F_lV#szbmP@gR42X z0&EDigmxf0>P-hvEj?hz04;0KDv4!gXr=@qOeqVKrRV8YJB~}%cXV~d1wRARh-r_4 zQQK~r-_V+z^;?uV{$`k*Eta5GLvm)eN3dm`a@ZKrPb+B_hN2%#djq{3%@VA@HcuRl z6*Krnm-L>?xWxw>lR|o_v7CY}(z589*qPpKLWkr7$d_#ESu^T>9}EanG+&;@L_%T* zm=c4eE>-uXE~G4W6wm_e$R#l7QzOJ|#%7RYJ)1vY@Iuq<9xHG9cIbk=o`>3%^+?EL z_t>*|A%LnWtgMGqt00y4A~^kFAwwGNNeA-ftZC)RxR3bWXTrK(S8POvJn+&a1gjJQ z(hMQ?lp}Gr)~9~UAFOSN_HTp+WqiFp#{ zxTP&NY|`GsYP3ua7i}6;s|xLs+ZlJCTU>a;Z8g=8o&>wkrqx6^P9&4DlirAmzRcKE zACArrf02V3Lca(&Sg67mX%XMZefQdbo}&3u-}a$FT?_7)IGRp5g9seIl6UV6_HWPU zTs^b}@;ncGUeQZNxmGN#5(+$d zX>cv%E)U33n?<)#&(Xar8sYio^is>j)HIKXpECT@?6)c9MP0-$JRxdhAn;-iMbeY0 z{tQ)bw(Y(qIE9w-`qvY3XuaM$)pRkCd(StBS9X3qoUUIs&)-0m(kmFLR!kq)Rd0m5 zV2`@uAe)S5$!|)mPqAK>>@w)P<_p_^*9P8F6D*K}hB!5YuK$Mnou{k;8iPexMA$u~ zz<^ssv2}t4p;x5x?ad!d9y^~_7+}ar&>$AYwX-qV>{;yNbQ7mSSXH7q6h8pLBVMyT zYOr;Oi(@5h1t0@IFpEE%m@>o^l0UpF1>A>Q@%;!Le|Y=I?3!1NUj5}!(HW#A`gE!$ z37I4?{@e(pacaQHTgZB&bR!^ZK-+q8_=SC&j|gcFcP7-hjX@p8x(2lONq&MHCn7tf z&q2=d90C3!3xV)Rf>p$_*Nx^iA*21Nfipn=$uCahGLD~JtIT5Samt$b@0>i}44e4l zIf5=Df49$RH7>S~dIG7(fe%ASNx@K3ql-=exF|XyNRG5U9>OLHnAg$|Y4iDzahM!61IJ>fH>p4P zM@Bk1A@i8GtJ|VVc5Ce@fM>bV!(ioshBjS_y3zKvb4}*($c`{D{krsNAVq?H1bf zH`__SJ_mN=m#9ohr@;k5x}+hIoj7kujTw1St8B6)sjMEZ50P!AK4tSUR9f56Z6TadzV(LgF-_Sv zoFFon*>1i~VCa!?^~&8M41S<1==^J(q>W!Oifq2^goTh>7#w%go~0%n?3TaK#Bjy#I8nouFls{}tgE)k;-N`PdHwutkr;$=YkwG33jg#^mHFuxSCubc zUIF*;kGios$5ZsCunsq<#d-dmR&VyO>?GOcwcM^QF#UVLwCkr%U*i<&e}P_gpFOBe z&(*`^?bb#9)UxY2%a>2Bt9Pm2bWp?tChFzQI(qmpC zP7XCNKk#7gqYyVtHgG8WaQAH2DVUAup=cRvQ>;(ca`!i>8#(&08QmEe)$>UVUCf4^ z!|5)aV%JL$>R~ATE+UZQOp|aWqptimUmPys&f9j58NP{*E|aLMfQ_y~moWd!WW@2* zhAf7VsxEn6A0vR=rW5%3P-o`ym~DoG{(qA~&ZbA+d&{}3C5hl_6&t?YD1%Hk{2EP;z`URWEL-b(PQu?E~d7DT>`frak!vcKbv)brZgL?V&`e9+>gWuF z{;$lNSXn~}inD@v0ogr(eI^3{pNKE{|qQ@&b*nQJ4(2jmkN_xhbVd|O(@OCdSSM214$X_tYTJ`Z-TbpQ=LRHCB<$~J;#Nc)}HKTBzl3fBdU%(lKOge|; zL7R~A#N4dE*rDAzT12Uw^0AJ>>m!foNTJkti#9TyiBI)5W`OV7N^S;(Nm*XmAj1gk z#isRggNxvSShlN)bJ6vwr~esv2w}moN9J4ihF346=@C9jwuNI1VBr)F?!{}sB0^|B zq3twfpY`i*q1s81?AdH1pNJ%a>g!iW7NKToK7=_P5RKw147aHLhc5HMOY9VKmLMmN zU*27W-}cqi#mavu=lZcoTiLY*L~A*G=8n(B(i@bbs6pd75iX&fclE?_T{JFCJ{1sHkjHgKLH5|R zhU-Rfg%Yb^8JxUd1s(#p0gAoGf|=BRhAH=5BUEkCm7r#8^X=!HLu0-Q^ z`EGf!Z+pq;y^M(&eCb<)m~GDnw=6%y{4z%(yzNg+cyHhpC6Ol1^&T zL$sckCo__W7*0;{)st-K&U!F1U5C)-h#wdysEwbolL;@l)XSrBlZv4h)Zfd;uI~&N zoO5D!IITxS=i-5~6t)>KwlFUn;5E!T#$RzA(hP`&PrU!{E9Uf)@66j?<}^|`l`jft zrM{Q`e9M=7*1ZwhGnlsIQre$Pn{ z(Wym>%aRRy&88KiU!O^VLD=(nTe+{qg;|OXn6l3MLk4)E;?6Y;3`?omB1X?BthCCh zBwxLW#uGWmYRn*7Cm>f&B@fxnP| zPR&jhxquZ32el`ELr0cNtWKhe(ikGRRA6Qd$9jxXz*WySMg69#7%Tn(HE~<6Y5f*) zJPxLrB%RJ*K=%ccQIaWI6Hz-2z?wAc zlF?RLvT2YpeFTY46Y|#&qfxQp$rPq#PO4%1-7e8*d^cdS&f5no_#xf1dMXvH^*@I@ zBg|6WW1w}nTo16nxPCMoC9Oo*oR87rYODAhDLZ69eDTwk6P4#~4=LVx~JBb|BeitjbIwS4gF8$9z*1?V4{J|1J@s4++lw;jvCdai^ zucD=A0^vk5h9%8XOf$%*`vH?0xH@Blr29`w&lLBDE#A%z-JTmnVKS(%%{z zpEnSlbFl1Be64tLw3lS|qA^QR%Qw8@*fn~0|JWgJm0 z%@q&n`}NxWKzalT@1{)9y~VR~EMG)Vz+Dj=N5OxakF^wx$>A=%h9>l*D)#<{r0_Tu zpnBX^rGW6Y9M}LkW+`ZhRZ5OjJG1U7@OS3?jkd=+97I{x!qU-)szrn<>4f`);1)L< ztF~w$9RoW>`Ddzp%|os6uK?CGpFx-Es|b>^;VghG1B+;I&KCUa1IY~z7UaEsCDQow zA5$oTneEPl8mUs4!u27T&$kjRy+#DFdX%Fi3|4k(?{ukM%C!udyp_I520e52y!D*n z`(0k}0wwa>j+OmOoBKl19PG9!)k&6dwm!VTgIGbBfvj`iVJythE>7Rq48J0*1gM(p z?-LUtTjK2%<&gEn!q|sTzmoZs9dnrtrV$%Uf647(6(JF;tfwW}yVqJMns#|L+N1k7 z3=bZ8nu(aaWyJ>InQ4>>Hnu|sFJP-|0QzWyR7_D==A!FsMj0w}a9Vw?1erOKe@ zkgFjO7zHMr<9VCO`UQPFFa)pOS13sVKE}O)OxsA+(K~Zy;?Bq_v+?~nJLLHGtut!* zA<8veqo}7-PPI}z2+QA<;Uvp=n<*&Ti11`P+=UEbKdepRU3 z3B$jd)Qq7CTu=2g&so#eM8{nxQRFvU)qF!+z;pExMYYeU{k4ni0vyrycb*@-i3$S0 zQ1Y=(1j3Q|YvVqyB2saVd*Tv6nO#L;TzPL|(2+&kB70`s*%bP#H33T<^7MO2c}x+X z22N8lp~=c&*lj~xV$ib23YSA-R>Nk8uAxEyI$N*h)sA<;lgR&ybWY(zt~r#t$$M_8 zm7)(h(}(2*5bdjhgLS95!D$eEKfflF~n2v2I=+Ju>S-E zshX>WN4?&CKgyjxmoVs9z2%il%i`PHMH;?I7P+#3GPdxMhOcVHwo_&D_Ec3;I>t7W zV9bC(ziSfJO6#s7?~kyD@S5;6AF>|lYz{)yC!ptDW!x!-W3c<}Ple~8hO|2eN^1v@ zUUmW7R(-$g9o8O(E?)2TC`71h&`a&{aY3;hTS ze@P;6axVBl1DGBA>?fROT>|0#rogz!ymsqh;GubNy_e^zTS>8*}mCgYSyE*?AC{>tjZi>y*P~iQAUNhwZUw3@U`N)L-b{TR2VsnQ#61|Zj zOnHp-E72E8dlcXJLn`xq{FH?ba12`H6AV_B&%Zp~&dB%N^>ZnyZ_+u3(CX?u;z@RGrf#IhW2<5H3iJ9Nw(0@OZI>fVkW+ zG(j%Vn4xblqE}hVZxuE1hsI=(CZT@s99$-ch$2Sct;jH9LaQQquYO7{FbD;SzR}`b zX}bH4%ekP|AVLg#I?(k;)pyjcxC^CDTUbla(2Q$@8D+WTR{cBAv}sJTqSwO;>O<+c zEh4YWA+mAgYwM+1O_(08FyWREUQQTcM{Wkcr%9*Yopc{5`8o$T=8<{uLk)qrx#sqO zwPI&}Lh;v+5!dfvN~k4SLH5PNJP?$xYP#Ddx;{&6gmt4HbRZ>r>CWLS1ORP z03D5J!Pvx;73&gY3_U`AS+19d6Uo$TVEk^2vWl%$Q)dzKBeUhqM9?U2Rf!d>uaWNo z6LikI;mA5vGH8*qfARikJ^AleETd^u#N)T}de;OI+&HlA#$2%ua#4^hPKkO{^sM1W z_Bg(YSd1YUlz8jh`0x#mbJ1g3TVF<0_&-lUkD;uI<&MkHa8aNd@&QEsWf%W|QKdv@ zMb;3Q;WNc_xiJ)MKi2Bn&9tT}&0yfyO!Y# z3v7s@DX`(wy9E}EW`%-twpMdD%=f=(5el%dgG3}S^L}yiPNC`QTL;qe z3z`%N)@f~1TCO33ZWw+A=@v@U(CDCakSeF=on7)(xnN!cYHzWBfZvk3Um2_$akRRo zb!^h0PEv%z zhCcIPdc&&`|5VY@DBGNei_V(AD8$yuRh1WyIB<>CrCzk2_&@|KUtYuDsz^1CS$0&_ z6(|RF(>e2{sTdEdwiLU1r$BUd8Dq*B1-(@#(wd6b`Ok=oy}naguG<#ibEMgh{>Xm& zI~bP6(W)AN_?CMrs6~-(|bX{}?0arN4 zI|m(8;Pl3+XV?U*2*<TEm z2l2hSDzXstOD7DH;yq%mxu;l!$WGv2ba6DXN=IxCourfwzW0Zq6bc{VGJ4w&+ANoA zKQJ57x=@py;orexSaH;3AJPK5-o+q{c4cA;=v-$UF;ai-BZK;vQosVj7_WgpL7gYt zPFcl#PpjVdlu}GEhkUKV`h4#On)Gm=s8iJBa+j_5<@rEf`Smw-u%v7kMw~~vENk89 zLG4SVXTYcA$D(A^Qu3e;Acm|^FCvSNzl)igm_i1-pw4g^pJ(2 zyVLovi%=aylNVv47Eb%fX+ECVXLVEVbK{)Rtq&nmMmcW2R;J-7v(wwvW5eHKY)7if z{fp5_4BmI^!ls&XifBsqF4?q#uN&+AiSVjKNpOAF71>|6WC3J>GmwazKj?Ge2~_0;r?z9hlG-tq>SP}0+1#F} zukdDx$sskBT+`Gcky!2JpflMaby%O*idgg}P6l)0H*TJNvAob@#;u&|md*spn~RWD zjXy`+85~?3KGp723+fprqFEn8vct{@wXgv=-z^z%dEE*PPHaI_#U0DWtcg`xvb}Et z4v%>AmNT`~nBk2_=4EOBNznNg3f0Hu#iwZiWGQ!H~B> zzmfYphVbxn!tL+#5e%Ic_MCcbW&U8@Pfdu+c?m45fS_KyiiGf{WJ9MJ*K5Y`t^5id z(loi@TbLyVObai%iX@6^bCu3n9Ql1`9)E@iE(bCWC#7E0iC(>6 zehFPi6qGmFUd_OcDGK?Ld6KC5N6~xegJJ?D$`vIV5&K+Dqv6**& zYK+9G=xU*gczs$}-x^mZw60>~1a4nbm@Q)Gfb}ogg7rm&p2&I3Fa+0cmyV}rAR6uh ziR#Uxpi|yJHMH%00)=oqOkR(w)AYf9e;uS5PdbLnA|MM_wQLHx#oO-1;d5jLOCp*M z-QYTo^-ad5Eb0AZ9^JX;RG{&H>5?HIt#u;bQ?Jsx(41fxzBL@Nu<5E9Ij&`@t=L$J z!iF&0)Gybatu%cYV-%iWI(iGX_GmUoD1Zp`zR}bnkK!*1L+Rd}l;`x?RdCa7^6CR- zQiIH!u*HOB6+dsuqeq<)fQLoI$81oG^Vbf;3JOuIZ zY-CI?qVU&oaD_*aMx>rsOgYWpUVeG5ayhoaD)Xl(iEtW9KV$%%HBNLnCI#Sn0XM4> z#Jl)W8Rm$l$aEIE$sYh*GkMns^N~S#5&fa$pw>kL(TGp?+f_J1a__)AoPv|uGKr+C zMM|}lp$K=LL9D+aPti;U+L@a0`L1=KZEyg}BJhu@oJ_Ri6!oRHiYmM{^z^;Qav}lI z{8j@!dTyuSiL?!Wh&`u-W0~niF_F+1;L-swE zR&_)TU~KsGCz5C!G+j9QUDZv%R2L5w&0wF$t%uukaLP;ig#L&^|2%nV)(Wu2DZjYU zwcF6u1WEeGbRp#KxYC!18zh6AhFSDExAy5=HaMg^mtzJK%vKe1D3z?C2Y-aX$fk|} zXksliXu~C<#R+i`S2|VjquNE&32ei#vLQ5DRt3?#rZ6U9`WzQo8fY|QileP|&Tl#+mrz2c0sE3Wci^3*;TC#g}__rYSQ-DFol1~{W{fA(75N#-3DHum#x$BZH z!BcPy&%fI1%l84eAG+)}^@~yKfjp)Eh>p6j`>804{`D>P^H>2JJ~OqaBEHiH2lL8y;XhiNwP{20 zaoBB(-T7!Gngb4uv?tXb8p~`GTX*HEBIe$bHW@f$YMn1uZr%k}YsSar$h5whiLf;! zB#*Ki^`}j0&-nuN&Z7)fJw4sp!iK8bJqjb#^5TQS*fU`aCxWaB-~(mo&WNJM7@UG@ zfwQ%PkA&<>wSs`P4kDT?BSiJV4NDp@R(x~q7n~al1&dRd^L{}ms5-j?DHmaTZD7=O zahlP!qvi-=Z}+quOORturSmEl3(jJo@kgXJl#A}~z!=N95SG2ZhG>^P#!s1Ep1w|uA){r0#lHc+mlRSRKh^5_5T3mS|4(+rjr(~0~O22n5 zB`9sFcmi;%lm&)Q_cZU8GbJ1}jYpFY)|(0IDI+=7zJhtICQ2c%M7R+1Nd|*sTC2}G;%crx z$V^TsQ|KX*3jHBK93MddS&pC4?a#VjF&+}S=3HMrrsBPfbW4>BkzVicaws4VU3W=n zo&_%kgN{|RkQlqgm=`SXhQ_A?gE0i^G7kBU$~mZxoGgj;>Gd!y9W6i2cU^Zr0>D;~ zX87}p6THwuNOCg;MJ^tELT$xNB9G!Ti@G09bdADTA!&|k=~5X%Z?JvF_Tc)(5MGcU4r3V%WeUk4-q1W(ph!n(rpa4 z8gw#unRzX{8IVWv+Wchyr>jX5r8TOdD~7-Y40zbPpv{G({arbT)mG$a<=__?3i$bp zNJ6DSiL2;W8-0RIcT@y3coO}T6c$iCPy4mkX{;GFu7iq}PFEmjQy)U#ejKsJsPIV=FbFS)=t!|ju)MSQE- zS?Vnz!&xB?@h&A;`e4B*V+FAmK6&4;%Sj=vKz)k9ML-(gDFqW;`VF65){NyJdEIV5 zPr4FzPYuuk-umoY_hr`op{BgJP8}ZS;MgIGR>#&72G0oV|K~Lsh?#>an{x1d*PbTAj-Lw@1HN?zSHb!-8 zZ{?s8&n253t)o!5`|ZcCvHMygeLa?M>fToDbtR(^{;Ge__5B^*PMb=4P^lXZwN;Gw z-ko-qJZINk+koeEb$2H#$8|K0uCKE1TAlV(IKhJx_uiFySn+*qo=pDYV(z5<>LA@x zWNAGBQla|z{ss3F=*g&hn0|^@PFxlkDL2njj-LO7t1nu7kzGml&b33U`??;k7rY?D-iXJ`rhLOHqMc}&nA~V)0o&;CvF}U)96xDmR0U#S zkib?F4MR+wh-6z7?J&3n*tqfzhZBO5hY;wcMf;Y50jYWZ zd6^uh8vx+tOndjXpo@B|8@MV4%*O|3Z1QMe6m5%|$dTD#1QZ2 zmhyj#K;ir^5h#o-?5zLEJYgc>U}j|c&+Pvupm6?MC-~p-|0kftw)|6Z=%Ta95LdED z#7dkkP*Tn=NQ?sj!>}^I>=MRGNJvYvLntln;vy+65d^tRg!9dR_T2wk`|fwGcinp5 z`d<6KxX!i04>P;c!cj_RaEid4MjRZRfIojuDP$B+{A;uO+pz%((C%X6q`g*`IN_5TLSpw$4_85#<@@?Qc}fYFSy8Eg}nlWO72uX%OPw(#LCf^tg{il{kGdBe95JqGJkd1%_xdCeg z`6`1^0Oc0=DI1HN1z2bc?D|4o2+}0n8FYX+7&L@$4dCeFDe2(S5;_P7Uj@{Pq5@zg zk2uyhNbLu>3g~ACADEr|mhbFu_D2um`jG_F))>Ia8HmeM5VsCMUBLrVNLD@+@gUd$ zG!UQO7ij2(z9PiG0t?#;p2Y|7*8&IrQ^Epp03PQ5=-e0}oQpu0L#Ob$kJIUw?zv&6 zDK3qf5ez(nco_8E-Jh<&F>+-1FPQj;PlsDPqr3REpN6`kxOCMXE3HmWhbvCOTs`7w zx}OoA?#lnOh7baP9{~d8=mbz82M_@s8oO>E+QOSl@CT&RzZ;+9&5L_52S5#;g`iJ? zdSV!SdmHo$43OZU4nhC+7v`%ybaocDAw-J+;1t2Sg7~Gsn!kfk&3_aA3_jco045Or z;TR0Spa1s@lb>P+>D1!(-Tp0~_*4n}ujNI*(P#EQV8GHc1AHJm7=B=IcpCcP3>5?d z0vf>Qw>ydp(!$H;HNu{>PoO30pPLU)DGki zxE4=O$8Y}V5BbTj{BeKvm%iywKkj7b*7k2C^DpSz@5msWg7Y;ye6vW*yjRdFG_HT2d0y<{}_=pm2R71miacut%SO3`z-UWnW zc&C8>J`Dhz9l@Qy5&W>J;j4pp2am*qJj4S7_it}eO)==q!)wC*9W=mz1IGq3_FDl7 zo}XU;`|B`^mJrWR1jfMVUkG8I5&*o7cc2;rhpT;YstNc5rJw5$W5|2Jj+WoXkU#*s zV1Jq$@OMYSAI5h)BwL-*_`CPz24*{Bn{+S^f5n&r^AwGRINyRQ_o4CFVqm30=!^68DAbZE&v2mYqrA)*Y zApUeO_Jwy7OX0M48K=OPIq&{PH0Byg!Q%I^vo#huXQO6JZ@-)tcN%;B3uAazG}Q36 zKWX#1&GXd)+$&;#*hs@{%+(2fo^^_(U+p~O<9SJ=hYOptv9wcxra0P3lW`ci7R3TX zmti z+-`p*QY+L$qf;9zPtQcQj#=XS$K}jU?dr<$sn-@3Y@1Z`!(0;K%$Y$tMUSdHKmC*c z1-X^=` zX*?-hUDBrd+(pI5X1>+EI3^eIw9E&=1Us6mihPS{pbE{NO>Bta(trV{K>Oo@#+e)6 z?st!S3WHq&LAmeI*{!_lxkVm_O7W15ytcfhHG+$kkWzS1F4ja2VRkYLmqMW)t;3hD zqSaE9kYka?Er16YYOC*(k@jat1Bs{Ith#g0U$LLK4?mL4_KpwcYF{D0MjEx!OAM!S zpnkr!fzV80IlDbM9eFJ|Kt_s^JIS7dWhw)=IAVFSo!Qwj4*eUJ_%CB;Y;vxuZ%37( zCm3+$)AhiCWT{ShNUK?u)4lj{L}I&S>}E$8{7KBg|R+Aey<^xG1HofSG|ZeoL7v!z&yTt?M zlN}40x1mH}o86`(^_akx40h9{==*8gzgK$@=exeKyjbB5^IOIMFmG`B|39QfN z4KaacS8nU4-ik+jb<^sYxlPVvlY%nT^`$`S151@~*mvh7Qt^!vfaZ!*KD(^%O*}Vv z=S}&vcpxw&KJu#=~K23kD8iq-5*oCTj z)W_P0z8TX0F!l{G!Z3l7W81cE+qP}nw#{#B+qP}nwmq|dvU$lK-eHe*?{vDV3-%kY z=T|usvmO5y{eJuR)~=i1)wCX*2ejmh(E^@ZIke{`i%7%ZEdr|(xOj)&hF0zY=5+f? zs3~ckpLplYg@@md`wbQc!_PNiAhVX1$HAUrTJ)>+%N5Uo>uJ58W{55TFY6rj%fur< z_zIvW`RL3%m9LAK9hbGc@oh!J995;B8|mO3KTI~6I+_^1L^Zv@{>JC#er;0n9(%W- z+!(HfN$-*+*CIb5H7F9u2g92?5=L`|@-As&r*72ryKRrHY{V1%l#%@*Mfd(ba6bo3Hx+U7U|Wvp#{3O4XU$UXZbnb7?{L>~P&P63?VmqQaV7@iEmbWtG<&3r z%c;N|bf^C*X(<7fG)MCC^ygs_?D$PIw`umBN;?&*!VecWmn*@+TScfZAD=eIv=ic#_RB!XiZTxsDKSSkl50alhl}pDs`8;{IY)|jf zcq2Kc4w2Gi)pXNH99H;~bca|!NO}M;9)EQf(wAMh5*}8-8;5Ad?hUWSBI_aY&!U1x zxAV&J3H@i~%BYFhF$fZw8LL?9)cr(R1_Q2QZJA?ICO&no>^^aFex@jV&1S2r$(hg1N&f9-?d-&_zU( zqisgGAUt^7R`=B{b8aXlkIaXe?Mr&e)(3&U;7sXcWJ{lq_nrr3oRvL(QDJtw~5NXOdBmuDS} z(5(}va_IC|?+Dr(CqKreKilP)_Qhozv{}%W43MCIrW*UP#N$xdBS|nqM8kxsLt)&f zsekA6AE?l3S58`^@7Fcz=9~fdFS>V6%*7`sxI!lVEx&NM&v?}MUU=t(idcnkH{0=i zW#8y^<^*URwP7W%HT}=YO4icai{QUx(eq#xu)!EwPn6a-Q~C!*QBFO9dh~{kRpvbX z-xGt?D8wmS-0U`sbee1kQ7Y`S_7436mTG}zo3lGW*%iRl!!5!}5??M2-tL~}pN6WS z_%7g(6@)w(PhTUy`=P*ce(W5tGyk1wXv5m>CD{Tezlz1B$B)>z#R5%ON1?tFZ=#Yp z9f;iS#U6BZ-_bZAQ$Yi_Ym}9 z^%(<{*&1RUK~HPHu6MjE$p)=DU#991%d<(6&6hY%-LDef>f;$xd(ItKQG30z!S$_% z@y8O?>+6)O?*9T>c$(UKjl^4bpD9d!r7-CvW8g2rn*1_LdEyFf9lZLVBz^?t&IS!v z{EIJ;O(>BSc%dBA4T3KZ9+wpEE}L$Fw`;qL`st$jxB9dPXl3P{M)i<;^gO4?BoU7( z{?j}QAJ(RzbrcA7O}t69+tv=a_@%3sndi!8hWF8v(p>h`8jZUI0#R-qFIK8Z-++gS z&61XM4{qAob}7fWk3RsVeqUEbcbH8kh+UhYtBWnK79#paA+EQ_uPIcd=MbZp$42Ot@ zIb;S&Vf~CyeR5OoW2zxB^xl9Uit$KRkwYuNMP98A^a{}P$t&J^AeT|K4_bFZYN4TQ z_7>V6RfQ9(toVUiqgB~9cEp^Uby?jEVu_~6>EnDWu1u`Mqte-%!NSD?vLS6#?c4hr z_ceAC#6bD~ILjj#i8gWa?PSE(nH1c&B`PDgU$1s=AOp{^PZu?f zi+>vXI&G5sLDZ zU1*V}8dzkvdzj-QRjhz9K1)pn#Srwog2L3U*OTb?@$`$foV5G>P!xX&(2DfmOi!`i z)f9U&sHwoCEf=j8x^KYrN{mC;@0AV?sq?H45yX^4=vuI#l6#;;jMcQ1nMV3WiS$;; z|NXRJO6!?_Y>2RbWJ7q7T)efqgN$;dWrmC%IYpsvy5aFJ+Sbf4qaxAZJ2FKZxL4z+ znkhJn#TStttO z#|aixN;SKB_Z5!HDcv=mOy2z0;=@;%*Ic>LO4`O#qoKKOR5h|3%9DfXP6;;CEfr@( z*&!qOxzP}%S?E&zcj-Z)TmG7#({%(l^w^OE#a;n|Sl+516j4jQ)9s*91e|wS-m)-R z=;uQPt+%BVNo;0&hwhw$GghZx!ShO_VEwf2v4^D1#6#bYlI$-wV9I)aY7<%P9)D%h zLus{si@MTy_3wrKZ@4SFeB(~uC*%EkFP7X+N>8;lqmr_g1xl$n-qEvx{szzoSPd{v0wncy0s)v5pVA4zK;zV|#-Ci$^-_)?o?svS79l;V0 zJ6(Ba!&Q^vP_(0fL-m&iM4r2yP^u}!FN!yhto%HH+FKlp^3H%aL6Hqqxctc2YL6CT zO81QDD1hG6uON%p7*z|Q)V_6b94do-)jZmf4|L?DVYTngy3poL%L?i160^LCl$j1Q zv-*!Ph3TrqacMp(`08t3F%Vn7MHfm_&i085I4DD`j|E71cU--rWskCv(Tehe#r?vl zLh{LK^y<~G`rzmYbx0^RY46;Pp*4}^^BjvoKlD15y4(O(!Y(uxN~_Y<+aXz z6rK4pkASxlQ>0ul)54DBGFuLb;iy>r*I^OGlI8-3SB+%glW_y%X;I$q8t(Vw4F@4Y z0ePwYKoJ>7N^_~kzX_Yyl1-Og7KK16W%hUK7SYA+dGT@k8iu6SrXVSsAoc1FCEiTu zN)>WA?=lw;61i&AUSw;Ke@Oi9gRd*V^wdZ8}MVf*4|B6fGv(<78su;=xj}@O^((i^MR2Qawu=%eJXy- zWS1*yN3=dYUKtQ-BkL&O;7Eh~Pcx*`5=uInt^Y{eN$k2*ACdhJe8rN*I3-*<3Q|C2Ran;~G{8snEHq_pa zoFY0~9h|!=dph16BY)U_HDOOwFTwKv3eQPIqyh4tshqCBN$$MGx0v;g1H>0UH&^AA z<{8i!Dt#qeoSO$Iiw*=2G)s&r)JQItRy9Fqmiqk8QPUdXk0deo!n;@N0| z&MF&^q@uf*HCe-=l3Pw$*l9H_+iq0hCr*uLB5lA2qaqczwRL2@p%f=70;rF>KT2)k zbmN7@!R8d*FK+05Ez~Uv`=Sq# z2zdTnv4CohRsOd8UZn{`x}?EIJ`W?Qy#%pLKwDDxg2X;}3GW$jA8d8EVaiNrX|`-0p6~o zf$`;WMJ!yHe!`Ml~O;16&lyP!+3vsTsg6v})079VyWuRedwx-y=%l%s$tr>sIT zyK8>mMq)BS&A@I!ER%G`d5cIkvg{10VSSlQeeg@nn_k*|f<(-!P(b;_3ra2v8!|P< zY%BJ1xjvjkmfo)>7&-b^Hi!2wYx@cVow&xXk{pXJ$hn`!Grtu)Z1u&VZ=G~lq8Bry zJ^JFNr+%{Ni`r|+8hnLaK_W+GK-{c9J3>p@YTN1{$|5sOG$gn-6EXc{@O*WB875?u z)qU#;f^Fo3M2mhSeXiw6TD}EYPjbwK6oYH;t`oyM&>a-^&svO1T$$+%?bN4U2{)67 z1%MfmF6YS$kYzAvrnGDniSRJ?I7l;yV9pKK*|+coz+PecT~($B`xI%%l(8ykbl3lj z3-fNa5W{06<>yxldqVOG2fqZ=z6bjy&=wuU$ zT1gAuzD5V#OiARNi7l7l0hU`FSfbEf6mK!1YE{s4k@JE(+{KXFlgWzUJ)+nztvghg z{DvYTP&h{T6Xi-Sf5(mFo__QMD@eL`A5|IASYDf}0yO4bwO8ZQtY?!(CTK!UO{ST+ z;L-{U@)=`!(N&&VQM?MC+*xd;C6TESbo&OYw_9g=V6QB!jvq)?+bAgaGocQD%sE_Y zdvg6EKDgGDZ=6zMtG)q?Q8WpSxFl6jZakgoKd96(lfwv&Zxdg0_w>jw7hQ`Wq%or# zvYUX#SIh`PB{62uF)$XpgP|8`3&cHXl2IW+O2WGE3>g&+>D63l%}AW?uWLv#uL!B4Z+*@$#^wc&2l*k~$ytai?mQ&w$%ASV|WL4;#(@@U4 zA9bG|ejm1M0p zxW?*XiiUL6l^p)BwX=<2JPKT!wO1}gUF@``d^1^)or}?b@i$%xX^?)ibg2J9`S@pd zcx^4e;B;L6ET)%AJFfN)`g`K(1FcC96Rxh&@ieoSZI8N8JUgxTWcTR%n44jXeZ`Q` zQTyVjMn#5Z=Vsri!Z6fj-TIY9<{HZ!7D`Sh}u(^KsU@2;ZXU~0IKKg z)?>nEwhB0)!4Apu?E1L*J^Tcgz=~`jgTKZr*TtuYj=CjqTEO1xKdzsvW&p6tcYkEp z55w`Vn$p6hwFlBznYuXB|9J;Ayv{&mzUV@4ksb&4qS^#D>c63>-R+;)0;NPsYOtL> z2lwXh&5xg-oDH|I=Yy2Q+JM>D^>gNao6tnyKdqVy~sc{uAMu3TM`;1&Z@NbjN0NF`n3d9viCsf4jhf1@ z$=diT#=q@D+CVhmj}x9{18f)Nwo&_DRN0vXR*u7G zJvFZFaU5s4QV-S1+f7Dv|4Vq`+)}Ig$Hb{wbEi%1G>~0);m}lSG zAY9(#YyOm)4~kcC$i$&3CG~bP{p7hAXyG03)Y>Y9<8;7?fWq*TDZNC6*hf<-x`Ugw^7Z51-{PV0#? zPMLShGg@Y}vO!V%LNsX6#W52b4WP*g?qtN)BNXZuXD{~we5_v3xF+4zo!hXt#l604 zo^%{ms6V_e3!--2YKw>oAqJdsAx7fQTk51IvE2LjvygWa)oMA)2N>AS>@qo#ZLvYD zk5D%j6yWEKeB?|(Fwx4l5|rlfr0wk1qp`&V^be6thj9^Ec7cynkGpf$7o-+Q`fH}G zQCkX%`i%Rj zVB+s2XH*K~zT9$AR%dromYZr<9m=ci@UW|H4+Hel#~WiOnMvsR6rLKqcVw$n)!eAo zCteg2=-!AU3uC-nc}4MMDCC~Hw)UEZ(PMb}n$b)jioyzfOKYP&2HS)Eh@f;T2No7arM95h3wby%JNC=s$oEN@e}Q{v}1`XJH+ zJ;74hT#HYZNiGpkCC*wMWs>Td)F!6>HX_FmYuBfGWv;O9EMq^wTH1O7M@6)L;zTus z40ou-QGHfz@3)-Ebktu}Gr0CUS5mID;HmxFtyn+{S>x3dJXtB~g}FOGPD$M$Ztg(< zcCPRia~rcjg@h}lX7{@x(0lvzDJBORsTV&pH@`+qJqZL~7AX;0$?^D9cNeqOrFkSY zaK@Jd?VBzy?$*`~ z3^yv*!}9yhfA3X-Z;{)93y)>mOkZ=_#d3PlS@OHlouL4;Ml%AegP+}>6{ykWG9&av zYXVvPE&HwO=8draspqQ72vrOE7Q@A0Yp7Hrlj2<`=2u{^rCl7i0MNYdQ%<6Wm)^Qk zK6UzriA#sbRi#~zw@O6&n~z=>f_iMf*`c@sTxu;K=TG8IOA|G~Up$@S0IFd^BY>EF zV0XGSUHXc`@?b3j1K$Q_nX_p4Ddvi$h5=iaZ@xR;Y4#2bia-ETiLbwvFJ%kgvq<&! zcEW5ok2rF$BmPlb0pu_1^fbc&!F^?fi|MUG}iF9#t3eoGe0ue3pI$ARj9az};b|J8Dw`5W5N!@) zU9;9y75(tr?!hf&8aR1b>x=|r#xEW3Fy6(y8sydQruW^$2usp(<>$3){!6iU4o z8uvKIcw2-B4>!k~l+0Q^)n1_MZA_ko!|$(M_4HOpr_af)OnXDNNnIH~Y#o5e(AMgr zT%2H7(#N)R{%`r3EU1wdJ@j+84yN?<^=3q)I-IuH5TAVkG87+X% z1Rj0W0BY|$cV>S)d9K&kJsga`&8*-NIl^R&QNh#`L+o9L7NgktU=#Rd$)9)GLGmRv zat@7Nv$H3p*#eC}Fe;8)(W?R=9c5iITTGOU)7qP=t`Z?t&=)!`L};ZBOswXtv+?h) zVE_{h(I!3|-v7{LvBw;+v*)&4&dmSO)?1ZPjH0I%Fmm(p86~h8xk>ZGNMqkiEGn;a9u#M9A=#OqrdfBPkOkkvE zZQPYv8${*><65*Fl$kYrs0zo*+7m#YMkD@nSSBKt!9faVzv^}XW;-l`=?t|J*c`JEofN@pvlrrLr&su z0rt|e#m$Zy<~3sCpes2sx*{L(dm0}{QcN;_KHNhTHoWP`hTWqr zBEKUk_v!J&opY8gg&QnF6K|dT)!QnC%Hp}4kg%o=;hB#xS)Ff#$D3av@v~*S)Xnog zuAA6Jpff=4OLUjKlrx<~NnzdOy&{%`YN1u7_FUgD{+kV2-v8ojVrKl`d`-;EEdS$V zVj^H-WM%#z^#A&rm>C%V@30fW|A((h8InWA8ynu$S$y+ZQj?38Zm@b(ZdGuV#!}FX zli7JXeP|kUC{;*_N?R(iDpsUjMas0!1X>hb)KpR=lrZIeRB7k-jd$*i_ld{+_wMU= z_veku-oee!VMw^MF&==}zb9(#fGDtL_qU;-?Ic(B$XoP=;C zw(y^zc7))_bKt$a04i13{)>wXi|G#F(q01e!X(ZBUQENF-oh9aCTxem3WNxd9~7aL zgh&FvPmCA=0)b>-PmA;^2DK#s5QIUHY(cznSOCj@o2dZc4Rjp~*jObnoeTu~-F=`X zM8FJaAR>2|$ww+0qCA=aOW=qzkwJnoEZQ ze!NuZ4?-Rmc%!-3hzwX_tR~WMGq|tMAe@l!%$hl%!yuJ~?O+DzDN{!SK=Su2k#G){ zPaABOUi{Hm;EyLk>}b#!iO|mi;{XVOVI&8yP7i=*@O}X*Wag}X)^Yf3=0mfULOh|M z9|QgXIxAq9!1Mfbc+_cP@Bo0plVHLDfA(*h0tb!&Fh#@wASMF1OL3P(tm3(;!XM`X z0Rwt95q_M2oF89*&nOJBV9x7&{QC}6<7*vTcMa`w`sVK)3o|fqpn*&gqWp?b^oNvu zQGjQW_=LP8j>LjcuHRGZp@N}+S)YzVPG4$HgFxU)Xz)U?Z=5MWG)8m~6@P>sVCi8c zQT**c**DI#ztP7HiofLZzv_r#^QN=w%BO(8`ea{1w|Snyq&cvTKalp|Cva={MS|Wt z{u(w2O&8!&9I&sicLF>}C=g3I1R>MeB@;RLsP(_5I?`~cC9^pY`DL(i9Xd{kP*g`i zdMynGITv{r28XyX0I-=r#6;-*F>xUPu4&PkS`lz?gK9I?rzE-n$9K95L%``=&Wo}X z6}~j;&g_nwsLKzsxg1IeupkwcEq#U+g26K{kp+?5if(}?SzbNqPN(Db@?!7!lg@xP z?!g&Y^3af}F>TqYOz(L4UOnD`J}&317HPDpN3RzCi}qfdt(ihn7pb<<-_aGZJa7*; zAD=o~%L_bvpzzUzAB}SzQVdp0CGDyM{_j&wslL<$`_PUf&gpn*RXf73OuU^`BV)>FCWrIq$(%Dw;7K$S2RH5o;MkX%ETyMljp}GljJ@uk_t?Byar#{@{Dt0SCCysl*?Yi4kdkY zZ+JedF6v*9Hs|jW-S8vnYj3Sn1XSy*w}kA$L{%Z8nYr)yth^O+@>A-BDyw9p1(xMr zx$x@!elL@|+CYHnsI%_dGbim{L#Qe23}3y9E;e6+)4r=5!0B3MLz0iPk?UMzlAh$Z zE42IxFIE{Iq}}`rOaMRob774W?MGkD+gH=W)!d3*O`^Bp}-nJWlms2J#@wkfSpqmvV8}l%d}btU?A( z_7V30j)+|7(w z_s?|blpWhg?=KGCNOwHBOSXzg87#-Dj|<0O^soKegfh|0eYMiJR;nD@hB5o?9cNS{ zb&HGxX^6Csr_o2mgegTnC4b}4%Fj!Bb-8=;JOimZ$uVIzI1 z{4=>Ot4aCq!-|tDD4Y3|9EHEShFDfQsv(wUX08YW+Ziua5MG0%4&77Qn#tY!w*n=n zqntbNW3^_h=0m+u+=Hej^jTVuKc7Vfd8)iiw~c~yReXc>rQPFk9;oJa5BVCo+6pWPl|1zh~N$hHMn!*x%G+s8f%QOq;Nud&Re zZJ85zQjRGjZ3hKh6$U)%pZa^Bgrq8rLVjs+pqoaH?unCuU^?HF7dr?9(}(kOO8TjIn=7dGcb8_}tc1~)$zIy>UM}^GNKS^Jcoxt2Dq;+s1<5DUy3DMH@nPu{<9yT2G_x354 zab5QVvW+?G0q~kzG zHP9Eh_pPIWpP<%#r*Az^D^)g4%GrmesCpiQF?=NoSfv|LO7H6Gne3uerHbT!+(E@Q zTq$PXhv}W4un#b=f!;gfb2*G2=gy1yJHykIP2{^DPs~-yvBulA$*WwRW6V4ncfELv zj85E_LSCqV>LMjfi*HcSY=?3c>6m;O@1`co%e4;{(QBf-itfM_rxwyDck2(G>dn__lkxEWBLwsq=HqA36ABA=ojkA;O zqtPBu-0ot++!$B+T_@rp|cW2?S693 zWeAT7PajM&8g-Wr>{!YG=v?Eu3R;92-vL*4+MLU@Ig`z0w^K566>EdF5q>{|ny{PK z$Z30JR{I`qzAqO@|Fkc&oVl6D8ugaD@KK<}Gig6AEinn%^=iFqtWS#hyc``9yVFF= z+6O;QNy{j-Z|eZj};_+R%@8b+)yth)I60I9>cqRe{=dI-%696CtU% zH0CU!z8&u#DlwyN{eaxYDCszV>Hg*Rq)N6*nKsH=E^6Hw8_B6h%EQR{-eAiNBb(r- z`Nuqu-xO^XWONM8Ey6i&=}YRcw;xqaCsq{El;##m+utt`&sQgzrRTs2q|U2Vi>Yw+tutJGpdD9 zF(pM`!@cd1ReD**tVDgK&-qUwVa;i$`CHOHf)HDLC?3A3*^GJMn46B3S40Yu$P)hw zU&kh5^Fq6F(Ylbacn`s_9w$xPA8L%*nA7moM1ozoTkscL0XJTIs1K>xnTWRP!OK); zBLl(M@I`X?V%!=YdDPonPEFhJ(o*Y@|9jGR*dpua_>K*3Q|;yHX_$-(>G{3{T{OHl zVxl4)aUFMqr@caAE?ib<{)1(7dIS02_``pUfWY3!3W|s4e>ego0Xq}p{{SWeHckf4 z|3UvRePCzdw-ubR|t+x67WjD?7U2T~2oAX-#aA>e5Kr~nMk*kOZh$v{F z859~;(-YAU10tZI5FhR<2nz~YU?4q!goI53(c~ipKrSN_5J4fJqM;EU0+bW-{wOk_ ztFr`bMV{Fs7{GxG8sIUY^y5H^^M!JD03GM?`Br`}Z;2zE6j8jFO@Kgw=Ia&!m}B5T zDKLX*Sz=oNwX?!b17s?IpnwJe2k;qiOhe{_I|mVU7w`a>lRyFdkpprNA|kBoXLf`B zp#^wAi5C1pIExYT0(@SUQelOGl>)f35-PI-KmY=sTmD#z=l7WY)fdQ7#P^jRf#?54 z@8|y!-TN>2tETMnClVw^KmY(a3jy~lpko9{oYU_$BhE?$9P={_k3H=z;tTHjKLfV^ zd5i-G2;}_L`|djcCxD~$*MvRaKMM*g0{D`ELqP6cGFY`lkgFtn+U@)usElK@vRn zb?`yN27Lh9gn<5O!5{SX`V)8knO0O1{Q!ZA6qf)385Ir!AUZli2E?>m-2WwB9R~{u zBA)lb{xMJWee6YpgnkOF5nWPQ`B!a~c-B+t<>-JN;mtyi^J5k~DMe)Z7nWJxVzPN_lVm z^~iys+F7=k24+Lb-JZ7~H#hg2EK4z=knNQ|xj!B9%Z08^NbF_W7hw6Qm7d+g)uN&S zJ`Z{}GW(KrvnqW+oE6OAYC-PGL1ZVAysqt=Uou5ie+DRCV)orhVK4x&@AaEYDa6=7 ziL#7TvPGsVD$Ky3VsdS7Gc28=ok)8id=pE}%B|4^t}8tGF6_26K8O`5e2#41Pp!Cp z%KmyFw`%w5;VYufF_{vH3l2#Md=VO$llI8-Ay;G5_t1AIuev!K@3CwX+9gZymvSeE zK&r^T7=n;J!g}1^p@Q}{nj!`v%Gm36$PGP(XQzC>D^#I8kn1a*y-eLCIz*~e)GvFDeK@!i!tZAF1KRUg$myLsk;iEQVL7TLIqj}ka z-vtM=SYd7%t&N_&g1uC0;{$yfMKZI63 z-$j!%s5A?Xo7No<2Ku7>N7{fO89ptxS=q3eDmqYYNn=+Htn1Q3sFK!u#pjH4R~?|1 zJm;Ys+%Z!F`ds-E#N(7pY;&{JciDkJ!5C3$8_$u6dF}FId9ijpJ=`hQvhpy{2W2eH zijAejH>Ddu5#ZqAT8+)2x2z-D^i*iJdyN~U8FJTNhb}L)k@XyBm(Ebde=+N#b$znB ztZE#>-i@4lewv81NFtuGXHpZZk&QI3a6xs1iN$Lz5;2iq+!O05bl+u~NACoR&_e6f zvpIK%%Q5F})8UfFc}aEI`=(tE-G4#J3*P7vmezR**Tf-JeFqhL7whVoti)m$GnxGui)fA}(O{+k3grT$nt% zCmjMLoHGhbeu+B}33v-`x?BOt_Ih2`!HiT!5FST<#PIlvVd6oq4&-ryEIlYrJm<$l{!Qcvwu4kz>L3zDxe)7xfh`KE3KnrhRMKzXEyd^ki zugtoTQ?nJeU%SgEcFh$?m{o&t`C`>$-T$DnCa>}U;l48MnE&6qLpg*UT>7~HJz7L} z#V|3XC8PkYrg%>F!&bsN+tUHktcyJ z!v>7GRo_R=8#&M4nKo`uvl4Z_dQb zKd2@J5bHgq+oa~>yiq*}&wK67Nji9iT1Ayp1=}o?*mHf9 zhLBSG3Awo7FQ>6+3!)MNe3lY_eaq1aco7jlr9~*2-(9wisnxxofKA z@}COYm^86_XKTk?Q97P*ry*avr8B}mqc;Zhc?)pssBY2;qa+IYI?vY80KdD=rvz1Y zG-9zaUfHDR@)zwye9s*A1a9}KU%Z{_Rw*?dwt4&Tf;bY_GH9?)2M3f&8)}4yvd`uh`Kn z31L^2w$WR??XAWRe1ibG6w*RBhuz#x+{noQQ*5ad&Wo2z(K(97-k-26ZJH56^H}?2 zkp+j6{fF_#A%+P)HrIGcI*R;LcgzZ=@38v<$66SgcJcL?Z5S>C!hC|E}1VnNf}p0?|}K^RcH~uf2u; z3b5$9WeYOt$3PhKe?~6?33n3XZ0B=$>k4}Sc|wwzs2l(5;a(}p*m3Gps4bqzT2-`o zV=?|$x(IjU^A<)1-7|=gGe0^!*GukW2w1m{9SXM=9<*z^Tb$G$mq3r&sU@1c9HV(! zXU#|G-et!R_xP?gS-@#SVA@3Agq_V)i+Z>r&{=Y0-Veup#$WFBea}R4Sn+p}+*0~J zU)m8;v@IXX;wRinXW1v!1-_o*G$P6KEyfSyjFcy*ShIg?lLN_@dTITmB%*M@0!O8w z?vA@cjjtkK&yoMzN+`UN3`ddyrcCyM9yBRWW3Q`<-z}4g?fP&_+-#A>;Q`A3tEo))ixr9!J+!?0s-krM{)1zc_=~_qbSL*Pur6n;v zukZW(^)#akIv0Y`ypzdcDv6JRIxYtjA`g2Gmu1v#faDA6#G%vj2W)fE6nWi%sF_fQ zxzB#i8by7gL^W2$M=np9zszc93MN7NYI7ckEQcR$>wKW~St-{HaxY)fRJpxg+emw4 z_ugJt_W`wgSuCx?wt#%|*Zpct!(-CTmU;J&irf|%J$Vj}y0#fukJl#!?CG|1 z_d!~A?j!3hX)E9;Um9anL^m>5BMh!M$NanjP)w1!xDQ0E%=a_z4qV5j(q24H)>!L-o(EVl*erV_rL+qOU@$(f*MHD zcy??h(mOd1-0Nds&3oTP#vFMJEay(`CYplm>!2(bow9-p1$R@5y5A&%QBveP?dbJ$ zUonJ7*x1jH&mpNfvY^_57}5u)IU1&tGm)W5huAI~qC~GR5h}Azz87_Vmd*q-TxET+ zXs)0<)&E{+7PosAVrfCQ8lVnTq^sXzF|^jn1&zuE*}*UHPr1B*2_ z*d`jC)jgwdJzKK9-+4q{)X*yzBwA$DY|hJKIwJ-Bk`aKx?2qu8+^ch!eU}csB%fN2 z3sZkEnuoUiBaPwRuws?_qm{n&UYRat^CJH$}%45!zEy z3JoIrW>M`ryi%SD+4hJBe)JAT8+>~rJ}O+1lITLO;BmN1C4k$N6#-s`<|_@>~0$tyj{UqYX%lj+~LRGwOaBT1C#uVXr$) z*z(Z0{jDnzlw52gb|iMXz@GBxXWA(!{U{_8IE&))V#k9W`AQ4@N_=N+y{LhPdMk*+ZSs1jLwr=860IOo;bD@(PAJanQb2=vd5Qx8ez!|&$3%=x*G31tZ2k!fL zZeyz8-ZFysihlt&tZuw$X46&gR6!D4f~$>@C~eXY#;d4>ZlFFrBrYDDda9upDwE)hH@X>>uqH$9viG8AeBTCJZ=O*=9<95iA^^fOoBE zSB>#3R5mib{fL{;TQ9RG^g-^H_omq<$@0%y4g9%$KaO+Hmr!K1z0C}x>>^DRe|2cs zQFb*X#YG6#=pIvRPN{|wy-H;(T3=HhabREAtwj_~*lBbHSA1mItQ1RfkQCV^=#0ZR z4~XPR>$F>m+z#d8Sk@E(O1{R3cXm+x^JyfvEo86Mikx-G`3Z8iMj?~Itae3=kNhe&zbjB`_(tyskd{IX(qEE1PTMQ zYTI8tn;cm}@l(fzrbUGziaGpXd{6fd>gJ4zQT~DqVYgUnl%>pavnV8*cm_Tb-fJW! z#^#msxxH&vMy1Lek@%O4{xUBP2FEwCIBXeb~r*Ik@=D`q%u+GY8V zP-Wb;!*OO!GrValalg|?f_3U>#YEFAP{&O1T~OY%T;)qgv*(WLd{pop&`}eiJ2)D_8UMx6_-WT+I~5yD zH7x8zFVvx=CH=>tSXrgB?J`yy?uif%amPq8*^KQu?1VokEBT_K2i$Eu-%`Drvmf4M zcU{b{*W+ufemvNM+O>DWD61zFZ=OmPevYE6?IQwW#PNcdCy;4Y)9i^Ze(y^QM; zU!td`?0W8AfO0dvl{0j>RI_&l4&{re1Z*9Sa_J{3*g1cXKk<5cQT z%l~UO3THhMVHi=E_H4cE@*HRb5>Mg-5Uqyw&VAJy_l935XzuCS*|jGBsK!Oj$>uP5 z#rErR=^cjq3e0@17|`nAzcY-t7wRkUk;j=Z2~9CJ3a@JgA>PHLT4=lxUEKO#r#++Q z(8SvCPH#zuS(nMQARg(8zi4 zM9#Uo6|(alvufX-2}?ki8Ed)I4w#$1lvL5(K&2&QUE6RxJrDb>Y5Z*kdXu}$(+7#q zKAcBV7!=>Fop<*0NMQaca@=oFHt|LY!8p_K2>;sxt9W*N1JkUCb+RBEw z7^T;t;#``pO9kLchHZ;SJiqFcvQkAFEtqBVbwpfa>7AX8u?zB`upDh^V-8*0s7COR z3`%-=yMNQZyGA4c-!ZZu0)_cpGwmimzoxeY{>_kOIBCL?cL}~>$s~fi^(nr6<(UN{ zl9X_$g|lfBg1LmXtd1FE-7Yw#JgC1bPHsx4_|uL_3+U(3_JyIB>qLN<87_9LlgLqViMo(BP`mFrpN2~KnhyHIJX=@B$Y^G>vw7Q>UHi2dp+K<^>DvlE zB9ynW^x(KjDjx0z1{sGH9HNDd#s5q{GW**cI>!XB0ZE6X) zhO{kGd(`!wZAmhMfF@nP&NC?26D-;3uZ{`U7$agF>CMm{E5pTjtBP)F&vE-+9<+R^ zE9WFlYF$uwY=RFahrCXozZjE?#dJ{gs+owwp>iuyTE|*GK=??-U<~q-hOJ+|4X$EI zGFEw}M*q+r5*j(#&3&wOT2Tq|dV!zWZVOzQb^5hX|7!j#Qsr!sI}X*psvj8KbVzu@>4WS7lod5 z!czfW=sb)im6H#(GQ+e?Mf2xa}QVTq_5CxR?4aC5){uDC);QU_7RwCV@a*Iq=5 zjMWWaISB#qQR7=7(VHb_DdvFFV==TJw-3WJdz<{4p0XqhsyU@8Y>|&>ZEEh-%h?b1 zTP81*cQBWs!dAUx!^N$z&$R&xh~E%x$adKDZ!L|p2`}VEDH|>=6`$LVI(ck)2RPbJ zTNGbiZnxjy8C0R(QE9Q~Uma@l3{skgL6wIu z0T+xEMmC3<^w^H;yUq&pqrl(+xtD?!;BmFFIbVMch`g#*3N^DEZ-5I9T9SZHD3=)T zj)zUP05}IHIe6(#wYr$7(Q4 zZNbEjuR-)01iAc~(3LM0$c@XBq-dn+%+si|4Eb%eAMuik>Ebxk2desiY(nQ5eB|~W zeUiD3Jjvu0v{$O7$ndQ3n{QW#UcKD56kc7vG(GD*Ol2PtX&ZPX zuU&?n&%Ui!t!L_~1uB$%@X%>Lp2I=+uxZ>~_r!VQ?eRpCj?q!eT4wk`e3~bi6>^3nXyt(l{!EaMxL&OLpj<~BR)8n`$ zH#qRtUbVj#y4M($i&G!r|Jw|+mzmQ`%|~CINoz$VD@<*UD25RgN^NA1F{5LpID?eOK zAIYPw3_P54$$Pew`4DFFO<>Mp^1?+z3Bn!O8*1&()V^T3pE(A9;FQ=Q>RyX+9Ba8+ za?-_BvFRx7M<^SAOUo^@&Yd@SQH5%G_qz&D-Egq34o4rJ7K=rkFWn0r*stP5hV2j@ zW7O6qg1#?({WSnja{m|!m4_V3!@Jt%54)qkK*WV^cbpGARdOJX?7I zpQ)s@{LE4hcL>UyLnuirL@i~_N6hk&-?kSUq!~gNtm&sTCH7eo=KdZ04ozwOPXR95 zKLxnN9IQ-#OJ@ItW~Y^kjLi-sM%$s93v%KLc>gzXFifGE8xp%EE0wZ&9TKK>@t7Qx zg0oKDXkEW#E99K3n@86H!m;(nmXh zYNg29LyG&E`u&01Q$B6epGD_Hw65z3!BWPb7ZypaHhvB$RU@~0vviX0+dYU~4xcL$ zJ-4LV#%!Lv=}G8LuxJ@IiXLFSvaUBhI!9LsNa_3R_*2nMZV6 zLOKB>(NIZ#WwhVipU#5Dvf-O(=d4Wk#G_6A`fNcl>G`nD$_FufSjnut-d6jfW&X{= zfin{ciWwVOl}0tS{mkt3eTfh;-Rt;j#PtP$L%yyaQ(03n_EbkF(`dCJQ@!WWpCtMN zPFm@eBY7%p-K&5+Y2@$`PNfbmiMdXFfYLVH3N${qsAmnUr)MVw4hwozT|I+Rts0CNjhuPsa*eC#3YS>Uu!ijQ=L?4moQwF$L9XNCp zY%5>ygf*R`B#E{hGwR@@UdHnk*zQ=gQpAn-QX&*AXTsZ*p zmr0*Y=v1z#8)6nSOh-hxU|O^?nk~)>C;Y=m(h70Fpa|ZrI}p)>{!_2wQSgyN|Yc8NY;tm17ugBRH>e zNrp?1yo7!sc%sc5pDWtUgx)Ck9DbI_#>0GGD@mP<4D+y7K6S`P%f>78u*NVJ6^zDh znoB&6Dx;9$AX#Hwkdr#mdh-g|$C3n{_*03U{q~q})93N2C^)KTPvBp6B+W zt8pAWf6QJ%S8a}V9~pc{ipUDzVh|WBQ~wh+?EfF3X6V1D`7dhzk5l7jU1snnYB>Ht z4b%U^tWo|wYf#$%Vb-Vw7##}lRR6=QA;o{bSY#2#hP?xE!|BB79BUivt$H{cn_J%) z?=hv~Tbt5;9vQFMY<0N2jB%dd-&?twZ(f<7J-+<7@lv_cqQGO4?lH^08&6@ga^h0D z-uIkpw5~SG40({Q~TC>-pS2J;5*r7wF20iwT2~B_eW*ol<*P@7&M@!7|}!Cj`*r zAIM$$MpV_ezUrYExd^aYUGP1STOrmlK88K&hi)tfezFy;3w0rCIT|JbvDveuWIphHP@H52Kk-tj z=Sqb_Xr_+S!95B~k#htC`*cA*XL1Q6oy>6_Da>j3v!RFKbxDngBAq02+Lug19;pE! zFnvuD_1i@FjwfT3P{hE=UtOwHZ=-T_Epb(l03>bKfR-pmaA1xYrn}dtu;fN|txnKm6jlvJ7wn{dv|9RwlLiLKn!5;*9(?v&gBD z&GmdDCXj!vYF0M5xRLo0Iq@cm?@wF+{+SC_PS$^2gT>3(Qe&hY^*M<87;=t72R6#0GYw zw+Q<2))>-T_&odhdL?n?kdiN9eLjrvAQiDDTks(4&BuIHv$Og#)xjEEtAgDr(6CO2 zQC+{=?EH&`F%^L0Q7Ot-YMa@`G>xi`J@O{p{h2loFQ5lF@2v`px4Wq|Ikv zs=GiLZ_V0L&rMb}0lWxzjRA}dD>3f(pFhB`crvakIoELYV!4C&q!!>-4k)|v?;zU} zZXyarV_kFRy_<7)90>~y^a~BliFhVVD-Fi_ohyEE;W9*OFYbTK)IcKm;IJ36qqUc^ zL$eo<@@XQz+4?v%>FAVZC`??_|pNT!v~ z!C=u;^wh#`gEHcvchS~{sFU=3-BZHICSskaF~ge9mP8Wq;nnH{>igOx-P@yF@2uAL zD>#HSe(;}ueE#{})WFu|-*>bA6$$@8JXVQFec?!!VPgSbwGu7r)x@Pa&?Mwxg5O^5 z@X*Uuc~NAtHpkko;2j=*I&n{{G&Z8&4K1hhIwyD3#h*T$W2~?5YrC3%<>t}STDIWf z<^QqRLDTq6?_rsIv+nM4Ybd>eM~9lb#)};1MWNBxkMHzt{Vg*=MhTv8S@&dDz7ZYi z+MO5pqJ}|-1A%)?qlDYR`@=c*13~s%3RgFcx2nW`7N&Q)cwOs4(Z)L$}x%6mp zDo&hVHjgye&-s84RUYJqW#B_qlYaSjoFFUsEuNK6WHz~@#&g}P`lV`SZTjAxi-69E zk+4Fwyarlrdi=f3k~aMfup4>v=2{~?(|Xx5Qdx62TUqy7`C2_4cY~>g)lXexb$2Fh zpIyTru{LM-gpXXfn+T4u!v^{pAdr(5!W#o35OzsL4qXkB13Lr&nL}xY#_;LsS9BqE z@)2qIf?~l8BAy%sQkFZV>f7B6#whG2t^J75V^D4A7%Krr0E=B}N~&k(I33QIG4SPc zz;*-}BXTcJ0Ummg!wGN|Z5zSDhzB8A0_21}qxJ5}&GhD~Wz@U%?F2y$zQArMCN7^Q znK1j)eIgdr)!3kA-v$^WB7LYqnL}o@{V`TxK^o*X+8NO9*IB3s_S4?ZFsSk-eYrBv;^@E{uPNehM zcHU}7cuEXpOzvNR*J%pjllTw@Q{GP5Mq&eFIxu``OKFiTZ1X z`LR&tQfksqmkY`3nDnu5b}M-xH{p)G&>I@}==fyoN z9gQZsI*OB}&CBUrW{fDmX29-2|Ed(~E41C2f8QTKU}gO0=O{ZT+rR!C{eK4t#qqC& zd`M2*+glxrz0-DHTqa0{x{RNxyuW%^w%&(^icK_wFIXiyA({ZhX1^n`6C@IxG@Mi! zED_Fp{C$$`y^G8;-lYd0UY)$rOP6s!$^-7=Y_4s)9`Q?T3$uD2)2uF@LASo=jb(mi zD4v}bg4~_9=%;qKgJy;woUqvO>5VXRR8 zaaDD5g7ksvrFTe%AbIjio=b4!6;-dIk=CbIZO^NoE0W^1`P;H!QforBrz=%_va6(q*Jjzv2LHiffwBC2v`C#q`w)%de&!nR%j zHyDA6v&H`Siqk;XTpwl9qwVu4-PF`p9Ay#L;uB@EwSst=28xcJwV;PJ<^9G%Na2N4 z)2*k&mvG>>x!4XH!KRC!LIh(#GoLu5R{<5I39d9RG ztHtVOp!3(&gzUVfeJtGFN|puUfaqmT@N35Pa&%}bSp%Yz+3qI1gZ4S@a&iExLbgyP%M+ZX3EboA&92k7!u>Yun{woyfD1YfzRx#z6rb^EJ% z3q*x#%IJfC;_;3SXhX#{W*>I5r{7PBj*_K!H22<2dB^nxn?28{HB_x4e|r~Aw8_1szQ@P)QYgx{Ajx?UlhH6vT;Fn91aQmR6vaE9Nv69%ZSx>~SY2RTmnx`(> zP|=YoajtY99GlKXpS_#w*k!f`e-8u;gcTGA+6VXJgAbT| zlR6x70aA>3GQ5xs6v*)miU1qN3N<4@4a7t|x)eTJP#xi01U<7v&L_}McM%W>k@Rk; zL9;2NA&eOcAP4#Ye@DX}TiHzTYakMl5y9KcumbBr4vl;|7hm~woS%TYJ#*W_mlX*| z(cW=?X4j#wW2T2V>&Yc@Nh>F}n@mC4sq#jFemyTnxt6%CYt8R*P5!if993#F+4lJ5 zpa~23t>lyErl0NRqRi>e++d%e3=UpRQd<^LP$x5A&?@y!hhm0%n?Tz=;HosL z;F;z80?yO$q>$7k8lDJTF60x$Wr79+lPR5_ zt|nAx9h}#(#--FCa120nI^zDC;d(5**-DP>W~hbnGTu4(;Hhy-ww)bG=eUZqUwE<3 zm8&9Z2?n2MeP)%O*@9kkBxY&*rNzaaD(`)6!|&lw95Da$Jm6sA{MYBfKYN@1eBMMs zdVTnL^~q)V9t4*^I@|QLd+pUlTTXf1YMj(#?{qw)#X&b!;@-gl;r#r%l8Z>1n+H!R z-7SNc7buLrFPH#@5w&jp_@&rPtOc-cqFYt@yL%Wn`J( z^MIpw!@eWkq+!6?VvO#|_*rSTUQU|Fe!?o(>}NT5S1(nHj$`$4$=Ag?CvVi=h8QfE z;gcd<`cDtq1gH9XSI;-BDZORuPJ(GQ$LX0oh_A&;k0{c*&nvewFWu)h3CpQ>PcCbC zC3-yS^cq&*8glR6+BmVi~Iv{ermg;pqE7htu3|m{j-^ z{S@$p)tX&4kwx!N z&?IV?X9!;tQro|WBs|~*&PCReV9<59d3TBcW^3_4a+D zpEcfdVS(kCFM7`&Y@Xi~ zgaMt+>%TjjRX}Ic@_*)R{x&@yTxSA-&ZcJ;Gtk+D%`TRH<}v;UXOr8_^RCQca}|1; zx8b+5xmb7bOi0xF>9?~vG}wt4p)<0@aCK86|0ibtd7%rK2QdC`FoW<<%!o(;Za1s4 ziMHuh2?cf!uWllEQ(B3tR#Pii+A{;AGuoGPc^?Wh3;CV3ZJm?r_n(Z?ua;LvRA)~w z`*dC!)>dNIi_^c(&h7etvPrvksXXm_&N4a`TStMs*MGRUe<eN!^@8fI8Uc3S0Ox*Fni4s0u(xrZPO(({_`K8UXoURQn@zy13p$0>`r951!6qAJ0XM3mD&A+he@X*4Y+j^!_cP#-Up~6%I@q4lB7e<-1*~ zb!tZlzX%_`x;oc27yV?Z z3NAv)cd+>wWzF0C>^|>e3_S7w3RPNL@S$jfM(xpnx!jUf1Wg)(^z3opG67w3k10KL zL1qT80IL%0C4mogLUakWa{JVY88VrUZ(3#VqO1uFRbK z!x#z02})Utg07Ne`&8OrF2>%kUl|BC$X&#$s&rh>-5-db2N(DqFZ<>r+dBreSG%d- zuaKW^8$t7vKR&{@I+Qs42^!Wv44VHV(5#jze{UBuXo)2b75WX$raLVCKSIOsAJD8g zxF1V#OL?ES{4GQqX76RNq|uW3D@1GDUqCwLTQ^ECA|%9jolO{&GNxE~AT`_shG=?3 z-`-8$$3~Q@O#TvFCcwRERb(--CzGURw3jaiOi`(@1N`bAKn3dRceud zE(ADesx*v|E3|DJv0^nm(VEx4H|{NLt_a1 zQ-QHm(X0>hc4|tvsHYfsyF#{92M3Ort)K`y+t|3OH!sT*Q|^bWkQhe}@71 z=mUJa6HQpa2<-<&YKY-u7vOFeGlGR*RzJggM=oQh0gA_$Pz1)5q|2XM0#Y6A!!7J> z*EkrXTMU)`l>eB(_Z-V9+aVzZZE&3#&Al9sERx`JVrT}i_E4B#@YgED**t|qzz7XA z9(nC|gf_)MrfmqNjtaF&ayQonTWSiE5i>F5lVuhGk|80I0RvEw^%DG@p&j8k?D#_^ zDiAz|J>uLd6rf)yI?TWxmIh8nCh>7_-2#!x#v^!IJ6byYOp}dQ;c3lkEb33l>n8R) zL%Uh!IR<8EL7NA|Ayux-z)2&0bYq0YCr5tm$_&iV6!_#!j&F=q`Q$#o@Zjv;7%65` z#6R(*t{o0z@X1jP+L`)!F<%tFvFsMeKI~ zcQ*qDPqm8iuJc-z4vX~d?l?88dj3TdBTu=RgLZRX$d~FDjvG@&g6AhAck6rMSy@1d z&3JWVf{jTk-H%DK1FT={ucgN;f6JZa>zoz{^tLdh89pvKCe&N5v~*JHYaPJ_7&$2+ zCda;1BHfc#yS&=rj_J>zw-C!L-_FZs1-AjADxv9KscFlmuyAmSy0K7Yvx7DBFXIyZ2Ngo}GBDS2)^ zW>|4;>SB)iJ!#Yx34qY3gHOVI=t4c~@-^(KoHRCV#NsN9sr(I%2jj#q7hk*cL7ahp zivLn)|Deuj+&pi8tFwnG-ui!2XSjb?X9I)Y2oYJMTSVO;#r}U{=8w7a|BgEQx3)$D z^}Iz@Oo}zBxIEgQPBiP%%Jb)Af1PM<&zfn*Nne|JA)TEyw6da$aIzvxrP(I#!npw*lCvsa$uZ&oku@v5sy z_-Vt?TApsv&z}vh20O71*6^-Mg=eVj&w6gLJw^FVp6kl%qR6X)+J6n2rie9X*SbUB z{`5&N#zCiA8~@)H*QpV|W47$BFr()&_3Zp_b(UyS%zij~tVisBrB`o|xV##*@P2b| zwsfl%tv2y;ILvrF;OCfVW&Awbz`EY>#Mu3JtCvycZu5%}&9|4>G1M&y}w$e*?Ky|htRE#lmM~ZG2fjm%3!~g^b z&<}VI=C==9#dYa%MIo6G>hk9uh5!zl=YX58JA@_In9^MUGPA#`;9`#L(8t^su&i0l zLAwo?1ArqmXB*6+jL0R!klOa+9+gu@1^_L^qsg-{XEn%+>KK=0D}d1>cuvU|U2zfx zIB3L$>zakYT?b|{QSh;n;>>r;g=FW-3307!Q_FJMWl27+Q64+w z3uWW6-83u20{a@C*2+5$`3aMedF7Nx3Lh?_vY&WLf3eGzHI2s~G`i00SZ3AzN~0in z<~dHCjF0ljay-2$K+mS&1)gBwvneWo$@9JIOzC9&1n?u^X$U^MIN^n-u;-8}kx%aU z)Xnrf`NHeiCH*o^C%gT6&FNm$ak9ew=(jp+?ieb*4^TPZ&APto1piB&r8|}-{0W*r z#m@g*>g@j!XeKFI9Xzp5O#ZUN{P#UL#=l0c&!uQxk2-p;v;WNoAh9W{lrPGvO}(%vgcMXT&YODmf|1B3@%nw$#P&X*95cW$R8e4dS3{2)K? zda|MQ|GwaqoVl)flpxfSW%M!XsvD@zAO}*3im?yhFU4f-w7`LlrjLCZFfP>;sUT82 zLfN*2OEh5!W0YS#xu)t=ceG0PtI-orXX=LegWtK+_}yG zk(vO0w0)Cc4{SF*@)XV-Qe|pijJ;m3GZ5IIZ%qIEu9nq0!54D=!wH!ntEq))@!@eU zt9rz9O_=Y^O7B(?!W+~8uXONF(EK5j=4AX|n>4FjC~ZI0s)Ip}yV>|j73mSc3yi=y zZ^30QFDs+^_FzMdg&UBc9-?b4)2(8KBj#oUO7t5pLg**rb zGW_^<-l*QVe|{TuCNrHp+&*KCU-=Z-Vr)pChCsi#Is_ngbE-Eq{oO2%9LboJ^I)*5_etV^40@J2&N zC^yS(_v_1;x3p(Q(3Q&Cy96B_Zx(WJBMhz(*7wAtU8%B^gZGYgQKom>0j-JI7ZNy` z%`jJ|7d}GdReIUgD>f`CDm8=V7KlqH=oTo%MjmKRmW69LbTw2pNf+qU97tllCN{`i7xNZ~JnzRMO(X0% z#xK48Vx@{-avKw<=#wy+sVAmN^hp9#+93OAgKFni#V@OK^k|?gK&E=>L7Lb=pk*h1 z2psKHA5E!B(V=gFx@42xrmV3br;+6dR%^`qxaCfyLCwMNS>nonDz7a9y~9mqvtm@s zw@EAN?Z~pIs4QN4ZOo((n$paTQYKXgVJM}traJhfhLId#LwOKy)1|1n0gpo}m1oNu z7p2)iXR!07f~jb&T5=EeK>nlHRjm4)Lb;zg3IhNd_^x;zpB_!S2rf;Bi1swj1f!g^ z3ZW!!lt_ z9N{OPd`rl{lKt`ssUO|-B#QTy6j|d zxt{95T;Qdeo$G$W=Jl4Cpc9bnL6ny%nCE#az{(S{YQfTp+$ zh_<~YHOD9IaV~i;z3A--{Hq^H_L@<4^w#4{+JlILTT1T-I1HsooAQaT7f$z`9Lw(= zV{Z>C_nnQ)?;FPNFI+GDL#OXKHShitZ^+~?{0YnNBu4MS%lA-nwhrOuWzqW;=$5mSEB;FL6T&}1{fDN&e_p7$ z-T9DhiuE%j_V^Hs8xwA(Ys6C?!O`pXvjGIbua7Dv42mNV<%1o|7mU3>QgmHHo)VWw z8SiHUBV3^Bh0Lds*N?A7JJ;z$eaE9O29?%$BN$!`nlWy4&97Y2HbS=#4c-h-c3C5; zcHWRp%=fQ60BxexC^hO8B1F21-K;MUXY^rDV&EH!SsTa-X3wf)WvdTYE=%9+R-&qY zs7p4ZjgFhB^!<3Zl;PgE{$)R2p;Y;GCVC;9F_oV&d07K5LyuEt`|fQ zWa3LKM36h>Q7s-HHXlS#B%dOvlJfZ;kQ}HaP-veMRHpfly*WYKxP&~Ci92xZh0=CE z%3~Dlf<7a{la%k!5t}e&6mER>3xVTo#FE9Ka&!M?T+dlng8MsVODEp_^UgCz zrho-Fr-F~&U@j6ec2Z14&3R3v9>Pr79^gqc*E&PQ8fby}v5s>!R;cKQ7LiQ*K`I~R z7=zI`!=n50q6{#l{av~_r?jQ+!7vuW4CkiP3{U6Ba!JAGzhaK)+DboV8IIVvGBh2t z4Z`<%sKTuw+$BP}Bi=}#%P`T%kTaDhB7_Y0sSt;&;wur%j9`x(;*d4rWGN`}r7s$O z>wOUG5eTxXf-#+d`z5u|M_jz)fk>n5;mu}J`nWQ`_4TO@-$iqfsY&Dfr#$e6(VU|F z4kLq6Ppz=U1YHF_OBDkw=POJAU}gj_Wl_7%&l}&eu)90agJO$pup@8u!eYVq8r&Vh zbT1^jK4I2A!okim>X^$K0G0jBG3g@V^cYlj>Rna)-ah>HV0_)Om?=LsOfqe_9NWbA@0LIFm=zGP@t{)vApKo=&tGUEJi3t$W{ZPj%2avvbAT zj@~=j|7&n=&>UO0|CMNLW@Put)+Jnz#OQ}+C|^f`pZWd8sy4<=G*nw5PdicG)NMtI zO5?@uRrT3U9f7Xlj|6+Nr1bH!5X1Myv~O+uPy1s-I&9CG>gF5Q-k=y~)u4!-Yr(PUa6M<>msBTR`E>*24wV;8n32vrrCf&qd~3 zr$0*-)f)0%V&Nm-6LSdz~+xb!_$SOThSnL43! zE9IJZFk$mr$9C2->O;s=IYtxC2@T>s?m_BWx`Aq%w1GMe4oZCJcLA&lKoM6#B2&U7 zi=-_JPkw<1d)44+&_#5BFT$!0>O=8So5h&oRsHwwZ7+L77p{;l8Ao28=4I&ODEWvOe1L9U5EPfYieC8EmmyN>C*d;IIg=&84^ z@wxDLOX4k&^1BWy93;)sSyI#$^aOx|O(7m@=%{yMasrkrigyZP3>TWhSVcu%( zbY?MCJLRqBw3IK00ru{hAonTqlBoABn$D)S$LkWtJGevWvBICA{!wkWW z!ryl7{$u|ESk$Hd+pgVzxVL$K-+#EQIrBB+@nZ3s^uj&&YO^+Nv3U4aM9Ar0E9`#$ z4>94*F#B$8ePTt?`gUmhy;XDlr&c+R<)YF-Xy^^r{i`p_uy?#eOVKS?D1hMg&ehG@ zYt{w7$$tyiAA#XI((M_agT%LVBlX+Sd*Q!3QQ9AWJ5jz7Bq_Qj)lVYl@*FDO+r9T) z^eDfZx;mIW8}p5%+cGNLDeVU*8VcgMYk>L?L(oDy;gd%RzMdmXx--lGS={ z<_opk?KTc9C=z789j*`8JT>B0r94bdFo#+Swv9B@2shGLbz1U$jj`=8kx(cHUsZlT z>sS84FFmy+_=LtM8T>n24>yK@rDp<7ACWgWMk}+m6Nmi(EkXJP-%`2>Aa1Y}p=8z< zXb+3Xdwb#*w#&w(ejTdtWV)!^qihAMjL?{u$T>0AZ}v>(Lq12gd9i zRCWlvAOX{A9QRzMtquOvzqUiN-<0(3ep^zOz*%H7t$HHr2c z%njkf`j~&Bc4w;VF2%w5;7{wK zlY2eWt52pj-^>_1jZF^@JHml=u7;M33cT!!3M<}BBw+Yq^F5PbxqbOSYcT>jtIcWt zSaCJp#$nSQk=v6;m}WTfkl5bw3@Wv2Pi z*Z1jg4H`}@{T{&(Vfq(!;5dcd;K2N=3Np7(i@3Nd$GppHkF(SL1L$z;q<-CD1$V== z+xUUan=ST>+}tC|IIzuiANiv9pj-Hqzh3xL77JK&w_-1E5YwHWd~f5|Vt@ZOfOq)2 znX=Oe(^A&ZAlz8>wbL2cYINw(|5>vpvM~RiNk+13n^BTld`;G7koYyP9)oZc18mKZ z$fvge!M6~(plb*Yp`c5?Oa8kS;-*ts_Eo9wo$01-;*|^L3u{y+sT}AiO7Jrek~+8! z#1{}*A!sB3BKFTO+=i8YX+j1vkuH#E&9K-{t|?^T_yh7ouq+ndHkQ6zq6l`NT+I=_ z3`W>^tRaBQ4?YUTtV4pC%+S0azZCSOX@t;pjmCuI&rLe)QT9O|eZXZS&C$S)NFG9d zMzjy>iuLjLI;9cp{31S-IHhL4Ip97B4ke5A=wdVK{TLSf;)|0=+?hbysVwbP3i(us zb}d!7mpn4WPze0;55l)iVY+m0iLrydp?9~Pq<5~CXyBZ!Z76XZJy@R*}|Dq*=+ zU>Fu_|4391{30i+Cn0i@xCt2iq{IsJ65`zw3-yn3CAb2V$_!Ekk))K7P1*B$2BQ5!;j&Cf5+>t%cD zem!4aw=e;evX%V8nQ|9pd{VK`1LI-k+qmt*rv~{CRCH+#d|9Tqx)(zHF~5G<0iM^D z9#+J@C#Jl^g$M6m3gQywJw@kU39q(?0{64^kDux`JKcv+m^G_JcXh!HOWt2h4*P%2 z;xaHX5VEs!9#C1l`IAXq+z zNMMp6m_EjkNQjE#1*woAg=q;ZYoJ4v&(vTAD@-6!*jxscg(4ym2`P$mXaatSSVQs5 z5&0@%JBrk@AE-cJq$E-%a_4+{8H0rbAcN zPd$}Ob55i#sP4Ayc<#Gas+*@p-PHDv0Vp!l+$x*aVMpfDkX`JeW|=0%2*+J@q@Hb<-J_YWa@#8n_IfE`;BNGK>$L@&y!5#%9ZMRgki94wghN=O~ezmFC z;bR?5x4;7O{11&CHTxA+87+5-vyC102McQSt#^sv$#wVbuT==;+^+ZX;;lhA39G0! z-bRJBvx7+5wr_oNigBXtSD!{Xj5aS~Mfsl}BXqgPyp*T;t?;I?{-ogk)LCKv*A<*6 zaKq%U5k%7#EP3WTz#fo79oAQz(A%J*RB@l?H!ErZ0?PTj=Y=4l`n?Q#$`Huo!SdL6 z^kHW9+-GFr&B;#W|^Su=UoW#(^r&Q6Zx zB(4vfviD6^$eE2g;r-!S_{H1t7@Mx)#kmc_vmN29447O4=W`E4SQY`d?05>R{xqj; zu4_aU>K==afzQv&TDiwsKSs*FpDkU`aG$O^|Ewrn3vtr0=2=~Muc!d*o-rzc%lj|2 z7dS3yrBCVCIx^6)fx%*j<_R>32z-i>59_6u2V_(NUCj!b@4b4e>1rt7Gy@Dazk=0YqNN^>=8DR5U6J7Fim39VyOyY~H8FxY$$YZuI>(iwj43SDHoUE4=Q@1J%= zQ0*g)khhB!)FvZ4vI1e3yuXlT_=Ya&9x74BiJ2G~e;;z}9UO@{nb`mBQV3!;0QXv&l zgPlIMfh9s41Ul!uo6bA^>P?Ni~yLti?X&Pk`U#P&2Z}5EFj~ z(?B=FgVUqTG$#~cnVau3zA#XW%!c0YfI7;s$jz7o-?FvW^ zWRhP4L{FD=HCY$P8NZpj@~$3ulFtkzx*cjA3*LUz$G8F>=q3?gNkPi|8FeKyJ~T@K zcFh=d&KNGYP&Q)xPt-UM=_qEzAgO3(VR&R&F@iiL0I`JDwLa__%6)=+xAw^Kd{R`HFm0LwHoPE+*1?A8IwxQi8^ z>55x~3k$9Y>j#~%L6!6g^Jyr@_v6z+6kj9+&wIFWb5|Yg*_9kSIWgg$%XWUq zjLx^$PI)%@HUvlYm(GsnJ zM11K71!hE`dL;88dr)IPV?V2>SZ3nofX@fQyXAyqgWX=i6ph}@;(}FR-l|r0?Up2< z!K{3acc3|NuxrWV3=P*>D%>jasa7J*5~Zy8{5doC8*Xv80ZGv;wpe@8O1q%Gi?js= z+!tq_aPt97HhS>rVNKddQ=NEQf>)1?r#)6;s0$Kmx8MW=fzW<%NyKN?NM;}C+G-(c z-;OKLC3}!`3M^1^su&^GB?Amtx>5**FCaRRjFc^EFwtG#&h|j^F~#8qNbn_3qzJF+!mzYMzz=hN=$SXWMf{}sf(CU;6 zeZsLwE3)E#(kYnL@|NrPR$I(ezkDDqqEuktONu#FqZI!E)R{FAH6vPv%*_rDl39=} zS}%yWStwZ4m>#SOp&8}6XBA=rWU!QUe%N?LKu1_;CL+BbYVA@7b}oasr5cYX3pyqe z+if(~ulEo!+}vM4@z*areufUHqqN4$sfb{YZV#WD~~xH8P4{`0h1 z0n(B+VzYH%`YPwzGpdKEv>}>te~bce&YA=$0#}t3*M7w;r(xfrI+1M?@!T54jckgS!s z_@cRqX`vvq6UtdH8I)*4*(6j$H9@SCw60VeB)>?cgO&%N;GB7A95XBeVPopZXHpF{!z z1Gc#mrRd6}lWFKGEgq;O4B6Yy`OgSNma!HZ7JgP})^vYAi##2^KjP_g zwK4h8U|hf%KfIvd=<{9|b*&eiBwXw^5jxH3(xSL!Ni0Q(`HAO{LT`#>#vJAs9D;(M zECxo;$6$#LwY;r_b?=gxcHAclEvJQp{x1&j6VZ zdtD#Lrs}@)K(S_5vLl>nSU#pm;Kzq@weUNH9e{D9(Z@~kLa&HtArO3?J+3O0r@pTK z%EH||DYgEC?6QTtXcvxpQA+qwNbVSN9Tz9_{9HM+=5PAu{gJPrnX`%sX0ci@-ks?SHBGRNrnv{qErK+KR@qO>j zy!rm$%zHC;X7BDjXV0F!d-u-l?>FaI)Zb9%i5@C@ZFt$glBz5cB;@c^$>O$#K0pBX zk(r_HW+~@kTQZ>4p}6P1O7(jo(YJp5kKon=b7j@QaGs5fIHSv`?rY(ka>76qom;~9 zqJ+y4MiLO)M_8ax-9m9-Iix=efD(N1EJL!}{C0vEjnl@B5)$m&r%4JRKAE(V41H2& z`-)}4dCu3Es}NgsRWG9^%v$(0nBNI$h0|S+$A5Tb>O8QhNe_}8JkcsxR{tu;UuTnj z{^G-vQ5rhsl`){=f~qai zgqpeSG7US$T2f8fSB*FA*$^;SJd>4rEV#5O05Y>@HeiY|7JBv6VwsY{`KWfK=0{E( zB;G;LZzw8oNJ`T2uY|NOf-Ib?=lMH#$B3gLex*FY+! zW=@xffDk74le|oeUKBNX)M)Leu56ssXt=f z{Fc@l82FArCa6uZ)ZV8VQAQiS~AB|_3)Q!mS)Ay;O=Gxo`+6a%a zjj*w^&}i!QwPFA*y|=Afjni}h4BX@V4BE=$iq3`bqyFjSX@Mu7d+drMaCM*kCAmI* z1-jh9R-`k0QOI%e4mbJOvYuI!uDlC_Z9*s8%?quWnthvM@4pz`yPu zaWz&>ghLNajJRq6>nR(hCMx8Mn11mw#Nf8qJ~P6c%6eUWCQ3m~i?cG%ru0-QGqoJb z_kw>a)0}j~)?x}rbz~oJrl@ws$#f(aEO?aj6eyi;4-prb#ERUNI`UbPb+;yFgt%?3 zmCRX}%X;yQS5ntkN%FC%9&J_QajN2>~_aDylf?=Gp8-knw!~Ih)tA_iRCij|8?wsvQ;2Kpg z8pC~}=T-qJ#wurlCtFhPrIEmi2MkV`#NWX{#z0CPlM7gnb%XBBA|=6mV+yeW6xEoJ z8v1Z_#0t8}e{U(0&D!~vL*5Gbbm!DD)}PX|g<@y*Y zVSnKpb9@Q4axHkLaf^MnVNaVx-TkXAn({`hs49Q-B$3Fc77nUHPDMkgCCB^D=*Rd1 z=+R@PH*(G)X1n;JprgUbmLE5A$~)~%_{15yWRW}7p-!({K>WJea2+a(q{{PN;M?uZ_A_I-gp z{o{dE{)UkJc5qlUW+`!)1-rTC2I*qEelFH!wZyXQxgl_=rV`(p>bJD5yrg=vG1P^c6EYw%Qo2kUXU5Y+LiQ#;s0l&?ko$9mpESb5al0_W=mFEulB zRcYFJqv|-MK&=#YU9wbo@yw3!reoJ!s;eBKO8Ub6qxFx*B-rezv#I}}MO^n&?Tp{u(O36rYMoIf&dM>UsE zznt*om6zRJWN%VtZ&H}4@D$M;6*be6a)8zMaC3Ux?6wz6Wwe+7y^u9nYqBJ8S^0*j zvp>8n_L0?bY_(1vDbjuflo^sE(X!X6+wO0CeMc=|cD1g+XsU5Q0ZKe z^`^&UKV+d+)gw=K2#?+oYv{0`)E&fh8L~p5v!@jt<%(9J7cn z3@-L5+)5X%P1clJn+g@;wmInSkrZC*G$WuCCb%>0?l;UG{CL~|!h@AF9$KVwj3zf& zwLLGJ7lJrYN+S+qAcNoX&l9)KtN3!}hi$p@w^X9e9+iGGuyp0o?qKc4 zQ}BEm1vZ|ksnPlmH!K(puBMwT@?&FIJ%#+ zKj6Ek)s(}d5*`!un<2P#b#?JG&)C%OQ>l|s+h)S^Z2ExRK$;qJuj3ErLTV9@tQF{{ zoUfQ$G`~8Tt6t5GQ&~FRJ{d6xl7h7AW>pKSKE*EX`R^lxs6eK(4j z|4Z9{z2Aw4o4X^5gdacSd!H!(+fMhE#;%^kk)8CJyV}OkkzdJ?R$Yl~o@zT+dbPI2 z6DarA=2~B~P z{MW?!jqmBmeJZg1vhq&#${xk1T~~BnD`r(Sd~bGjvz>lCDp3>(;C#Nl;1BwyoDv5lp`4ywfjGFev%1Bk1Nd@Mg0| zMa=dLQm=_kLm6?SG6g%fF6uXGMihTFVmkO5>kSkHKcBm%spB(hN~C=`@>e@$aZ0gE z8{N=0o0p?$6WceNrfoclFO+14-=A!AMC}SF!hE%U3|KoRAN-_))+)@Opbh8mrtsvh ztj>JJ`HULxCzgyD4;H0&vS-s%@IJ*4ZbFh)on@#^e?q>WM!TyQRho>gVB$yG-7FCZ1IcU z4fdJTu4~}ILh3jDOzV5)Dm&CJo=3QGDo*wZe|UgS>i6U&QshIXa)lC+^1+Sqli3E{s>uS-8AjaU z%Ou-F`Zy{P>MV~(kuMH8kiwLaNvfOUC;H6776O#;13NGzn2Qos+w)-!ZtZ!Tl613( z5e!wmYxqTkZ(mP#vs^9lU|aY|fnX6zf*3qV{j8=-*lR8kpR~PLlNV3q+?k%sM1B(* zu&aHp@BIzRN)#HXqIO4G6Ih9yt6EJ1Hd6rOuZ>{MvgkZ{;lk}hoF zA4at`;oZ(3`YgoJ*d^B@6dtN7mjj>^OfhCjtXx1+WXyc;XuqW8__e7b@)K2IiK1i0)#omAc?GHpc61(( zov*%S5si(gtEi#o*2h0>x>Wi;%`=G==qumRs~onx2XK}sqH2XdAYiqjvkOrI6iuS_ z%^uR1E{dV2e|kX}D&Vg5I&bf`Ju@nICUd-MSDI{`>g$lv_X!*iZcwu3#Tq{>Oju#9 z_xQr+ueNUv_Ey7!RlLf1)_I_;E^%8H)eFz0Itft9XK&sPbC=Q3B`=R8imQ|g#^ zT2C{d;@wOUrt=DMP4kH~MOJxU)87(k@LXF7loue{rQ`@32lQxVU^YFN(Vp>qCjcDz0=AX2NV zeej1L7G{5P#-ft#Yw`EPb+GKe6Cg~3{NW+)h#+3k065YTVIu_sK!5-+OpI1qJp}FP zA0#R!ZHYqrAjqXfh?^I}9ZkN|3_`e~$`DgOe(fwMKxjqnVL&?Ok(|;)`Kcj^Apmd(bDdlS6o$lYT1I$>%#4rio#DQ^Q zgEYjhK1K2;god3nDj~`}?EGjelru=6^Jh{^Z)9Mf#_v#cQvm z0o0HOfi+=34Q&WS1}X=IX~;m-<-zhWbxj>I?xVu{pGC-a{`pSGdBy*0?t{=L10N%^ zn*+N|AKxr!6Pqnj)m3GkRV_~2z6PDQr}82#M%{F@G&J~yk&H9~luS??Zq0{%4_Kkd z%Wi_Zv~aJ1*UY6py)&%1kIWfc8N^W0M~6ct?{V8u;as^6dXY9X-XuFFl)ym``)v2U zH>FaqnZYmDUAP;2qHQioiBIZ8OZ$#pyXwNXB$aTX>?N~H-R*s@^8rSH4}XZ7?Caxh z`5v%~cS7)`IL)N7GV}27vNja?q5+G`Ke}g*;+d#a#RspS@wd7V*{Hj-crYvs(b63+ zx_2oDuj5@5e=m((t=BB;S_oT?TjGrcU02q$UK+U} zLNn^ciWCnh(rec`Fg87%rnEZ>G><}0d@UW`Pp%oI+HqoK|AKo49r2qf` literal 0 HcmV?d00001 diff --git a/docs/src/runtime/zk-docs/zero_proof.pdf b/docs/src/runtime/zk-docs/zero_proof.pdf index 1415a5d8a9e9f66143455f7e3d710b793079fe7b..8227b131f1f2042ccde5b5e62f99e1c304076ae2 100644 GIT binary patch delta 49703 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 component contains the data that a zero-knowledge proof - is certifying. For example, context component for a `VerifyRangeProofU64` - instruction data is the Pedersen commitment that holds an unsigned 64-bit - number. The context component for a `VerifyPubkeyValidity` instruction data is - the ElGamal public key that is properly formed. + is certifying. For example, context component for a + `VerifyBatchedRangeProofU64` instruction data is the Pedersen commitment that + holds an unsigned 64-bit number. The context component for a + `VerifyPubkeyValidity` instruction data is the ElGamal public key that is + properly formed. - The proof component contains the actual mathematical pieces that certify different properties of the context data. @@ -90,7 +88,8 @@ to fit inside a single transaction. ## Proof Instructions -The ZK Token proof program supports the following list of zero-knowledge proofs. +The ZK ElGamal Proof program supports the following list of zero-knowledge +proofs. #### Proofs on ElGamal encryption @@ -99,14 +98,14 @@ The ZK Token proof program supports the following list of zero-knowledge proofs. - The ElGamal public-key validity proof instruction certifies that an ElGamal public-key is a properly formed public key. - Mathematical description and proof of security: - [[Notes]](https://github.com/solana-labs/solana/blob/master/docs/src/runtime/zk-docs/pubkey_proof.pdf) + [[Notes]](https://github.com/anza-xyz/agave/blob/master/docs/src/runtime/zk-docs/pubkey_proof.pdf) -- `VerifyZeroBalance`: +- `VerifyZeroCiphertext`: - - The zero-balance proof certifies that an ElGamal ciphertext encrypts the + - The zero-ciphertext proof certifies that an ElGamal ciphertext encrypts the number zero. - Mathematical description and proof of security: - [[Notes]](https://github.com/solana-labs/solana/blob/master/docs/src/runtime/zk-docs/zero_proof.pdf) + [[Notes]](https://github.com/anza-xyz/agave/blob/master/docs/src/runtime/zk-docs/zero_proof.pdf) #### Equality proofs @@ -115,11 +114,20 @@ The ZK Token proof program supports the following list of zero-knowledge proofs. - The ciphertext-commitment equality proof certifies that an ElGamal ciphertext and a Pedersen commitment encode the same message. - Mathematical description and proof of security: - [[Notes]](https://github.com/solana-labs/solana/blob/master/docs/src/runtime/zk-docs/ciphertext_commitment_equality.pdf) + [[Notes]](https://github.com/anza-xyz/agave/blob/master/docs/src/runtime/zk-docs/ciphertext_commitment_equality.pdf) - `VerifyCiphertextCiphertextEquality`: - The ciphertext-ciphertext equality proof certifies that two ElGamal ciphertexts encrypt the same message. - Mathematical description and proof of security: - [[Notes]](https://github.com/solana-labs/solana/blob/master/docs/src/runtime/zk-docs/ciphertext_ciphertext_equality.pdf) + [[Notes]](https://github.com/anza-xyz/agave/blob/master/docs/src/runtime/zk-docs/ciphertext_ciphertext_equality.pdf) + +#### Ciphertext Validity proofs + +- `VerifyGroupedCiphertextValidity`: + + - The grouped ciphertext validity proof certifies that a grouped ElGamal + cipehrtext is well-formed + - Mathematical description and proof of security: + [[Notes]](https://github.com/anza-xyz/agave/blob/master/docs/src/runtime/zk-docs/ciphertext_validity.pdf) diff --git a/dos/Cargo.toml b/dos/Cargo.toml index a59dce7b337239..535653e3386e6c 100644 --- a/dos/Cargo.toml +++ b/dos/Cargo.toml @@ -19,6 +19,7 @@ rand = { workspace = true } serde = { workspace = true } solana-bench-tps = { workspace = true } solana-client = { workspace = true } +solana-connection-cache = { workspace = true } solana-core = { workspace = true } solana-faucet = { workspace = true } solana-gossip = { workspace = true } diff --git a/dos/src/main.rs b/dos/src/main.rs index 033de4bf4b49da..0b299718467134 100644 --- a/dos/src/main.rs +++ b/dos/src/main.rs @@ -46,10 +46,8 @@ use { log::*, rand::{thread_rng, Rng}, solana_bench_tps::bench::generate_and_fund_keypairs, - solana_client::{ - connection_cache::ConnectionCache, tpu_client::TpuClientWrapper, - tpu_connection::TpuConnection, - }, + solana_client::{connection_cache::ConnectionCache, tpu_client::TpuClientWrapper}, + solana_connection_cache::client_connection::ClientConnection as TpuConnection, solana_core::repair::serve_repair::{RepairProtocol, RepairRequestHeader, ServeRepair}, solana_dos::cli::*, solana_gossip::{ diff --git a/geyser-plugin-manager/src/accounts_update_notifier.rs b/geyser-plugin-manager/src/accounts_update_notifier.rs index b9a7ed964d8a5b..60df441a7e3cef 100644 --- a/geyser-plugin-manager/src/accounts_update_notifier.rs +++ b/geyser-plugin-manager/src/accounts_update_notifier.rs @@ -33,11 +33,9 @@ impl AccountsUpdateNotifierInterface for AccountsUpdateNotifierImpl { pubkey: &Pubkey, write_version: u64, ) { - if let Some(account_info) = - self.accountinfo_from_shared_account_data(account, txn, pubkey, write_version) - { - self.notify_plugins_of_account_update(account_info, slot, false); - } + let account_info = + self.accountinfo_from_shared_account_data(account, txn, pubkey, write_version); + self.notify_plugins_of_account_update(account_info, slot, false); } fn notify_account_restore_from_snapshot(&self, slot: Slot, account: &StoredAccountMeta) { @@ -54,9 +52,8 @@ impl AccountsUpdateNotifierInterface for AccountsUpdateNotifierImpl { 100000 ); - if let Some(account_info) = account { - self.notify_plugins_of_account_update(account_info, slot, true); - } + self.notify_plugins_of_account_update(account, slot, true); + measure_all.stop(); inc_new_counter_debug!( @@ -110,8 +107,8 @@ impl AccountsUpdateNotifierImpl { txn: &'a Option<&'a SanitizedTransaction>, pubkey: &'a Pubkey, write_version: u64, - ) -> Option> { - Some(ReplicaAccountInfoV3 { + ) -> ReplicaAccountInfoV3<'a> { + ReplicaAccountInfoV3 { pubkey: pubkey.as_ref(), lamports: account.lamports(), owner: account.owner().as_ref(), @@ -120,13 +117,13 @@ impl AccountsUpdateNotifierImpl { data: account.data(), write_version, txn: *txn, - }) + } } fn accountinfo_from_stored_account_meta<'a>( &self, stored_account_meta: &'a StoredAccountMeta, - ) -> Option> { + ) -> ReplicaAccountInfoV3<'a> { // We do not need to rely on the specific write_version read from the append vec. // So, overwrite the write_version with something that works. // There is already only entry per pubkey. @@ -134,7 +131,7 @@ impl AccountsUpdateNotifierImpl { // so it doesn't matter what value it gets here. // Passing 0 for everyone's write_version is sufficiently correct. let write_version = 0; - Some(ReplicaAccountInfoV3 { + ReplicaAccountInfoV3 { pubkey: stored_account_meta.pubkey().as_ref(), lamports: stored_account_meta.lamports(), owner: stored_account_meta.owner().as_ref(), @@ -143,7 +140,7 @@ impl AccountsUpdateNotifierImpl { data: stored_account_meta.data(), write_version, txn: None, - }) + } } fn notify_plugins_of_account_update( diff --git a/gossip/src/cluster_info.rs b/gossip/src/cluster_info.rs index 8b98b5ff3580ba..a9a14b5557239a 100644 --- a/gossip/src/cluster_info.rs +++ b/gossip/src/cluster_info.rs @@ -13,12 +13,6 @@ //! //! Bank needs to provide an interface for us to query the stake weight -#[deprecated( - since = "1.10.6", - note = "Please use `solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE}` instead" -)] -#[allow(deprecated)] -pub use solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE}; use { crate::{ cluster_info_metrics::{ @@ -55,7 +49,7 @@ use { solana_measure::measure::Measure, solana_net_utils::{ bind_common, bind_common_in_range, bind_in_range, bind_two_in_range_with_offset, - find_available_port_in_range, multi_bind_in_range, PortRange, + find_available_port_in_range, multi_bind_in_range, PortRange, VALIDATOR_PORT_RANGE, }, solana_perf::{ data_budget::DataBudget, @@ -1260,6 +1254,7 @@ impl ClusterInfo { other_payload, None:: Option>, // Leader schedule DUPLICATE_SHRED_MAX_PAYLOAD_SIZE, + self.my_shred_version(), )?; Ok(()) } @@ -3639,6 +3634,7 @@ mod tests { Some(leader_schedule), timestamp(), DUPLICATE_SHRED_MAX_PAYLOAD_SIZE, + version, ) .unwrap() .collect(); diff --git a/gossip/src/crds_gossip.rs b/gossip/src/crds_gossip.rs index 3300f3ec4d1d79..788e9f9af87a02 100644 --- a/gossip/src/crds_gossip.rs +++ b/gossip/src/crds_gossip.rs @@ -90,6 +90,7 @@ impl CrdsGossip { leader_schedule: Option, // Maximum serialized size of each DuplicateShred chunk payload. max_payload_size: usize, + shred_version: u16, ) -> Result<(), duplicate_shred::Error> where F: FnOnce(Slot) -> Option, @@ -114,6 +115,7 @@ impl CrdsGossip { leader_schedule, timestamp(), max_payload_size, + shred_version, )?; // Find the index of oldest duplicate shred. let mut num_dup_shreds = 0; diff --git a/gossip/src/duplicate_shred.rs b/gossip/src/duplicate_shred.rs index 4c270f61421aed..adfd89595a03db 100644 --- a/gossip/src/duplicate_shred.rs +++ b/gossip/src/duplicate_shred.rs @@ -69,6 +69,8 @@ pub enum Error { InvalidErasureMetaConflict, #[error("invalid last index conflict")] InvalidLastIndexConflict, + #[error("invalid shred version: {0}")] + InvalidShredVersion(u16), #[error("invalid signature")] InvalidSignature, #[error("invalid size limit")] @@ -93,6 +95,7 @@ pub enum Error { /// Check that `shred1` and `shred2` indicate a valid duplicate proof /// - Must be for the same slot +/// - Must match the expected shred version /// - Must both sigverify for the correct leader /// - Must have a merkle root conflict, otherwise `shred1` and `shred2` must have the same `shred_type` /// - If `shred1` and `shred2` share the same index they must be not equal @@ -101,7 +104,12 @@ pub enum Error { /// LAST_SHRED_IN_SLOT, however the other shred must have a higher index. /// - If `shred1` and `shred2` do not share the same index and are coding shreds /// verify that they have conflicting erasure metas -fn check_shreds(leader_schedule: Option, shred1: &Shred, shred2: &Shred) -> Result<(), Error> +fn check_shreds( + leader_schedule: Option, + shred1: &Shred, + shred2: &Shred, + shred_version: u16, +) -> Result<(), Error> where F: FnOnce(Slot) -> Option, { @@ -109,6 +117,13 @@ where return Err(Error::SlotMismatch); } + if shred1.version() != shred_version { + return Err(Error::InvalidShredVersion(shred1.version())); + } + if shred2.version() != shred_version { + return Err(Error::InvalidShredVersion(shred2.version())); + } + if let Some(leader_schedule) = leader_schedule { let slot_leader = leader_schedule(shred1.slot()).ok_or(Error::UnknownSlotLeader(shred1.slot()))?; @@ -168,6 +183,7 @@ pub(crate) fn from_shred( leader_schedule: Option, wallclock: u64, max_size: usize, // Maximum serialized size of each DuplicateShred. + shred_version: u16, ) -> Result, Error> where F: FnOnce(Slot) -> Option, @@ -176,7 +192,7 @@ where return Err(Error::InvalidDuplicateShreds); } let other_shred = Shred::new_from_serialized_shred(other_payload)?; - check_shreds(leader_schedule, &shred, &other_shred)?; + check_shreds(leader_schedule, &shred, &other_shred, shred_version)?; let slot = shred.slot(); let proof = DuplicateSlotProof { shred1: shred.into_payload(), @@ -229,6 +245,7 @@ fn check_chunk(slot: Slot, num_chunks: u8) -> impl Fn(&DuplicateShred) -> Result pub(crate) fn into_shreds( slot_leader: &Pubkey, chunks: impl IntoIterator, + shred_version: u16, ) -> Result<(Shred, Shred), Error> { let mut chunks = chunks.into_iter(); let DuplicateShred { @@ -264,10 +281,16 @@ pub(crate) fn into_shreds( } let shred1 = Shred::new_from_serialized_shred(proof.shred1)?; let shred2 = Shred::new_from_serialized_shred(proof.shred2)?; + if shred1.slot() != slot || shred2.slot() != slot { Err(Error::SlotMismatch) } else { - check_shreds(Some(|_| Some(slot_leader).copied()), &shred1, &shred2)?; + check_shreds( + Some(|_| Some(slot_leader).copied()), + &shred1, + &shred2, + shred_version, + )?; Ok((shred1, shred2)) } } @@ -490,11 +513,12 @@ pub(crate) mod tests { Some(leader_schedule), rng.gen(), // wallclock 512, // max_size + version, ) .unwrap() .collect(); assert!(chunks.len() > 4); - let (shred3, shred4) = into_shreds(&leader.pubkey(), chunks).unwrap(); + let (shred3, shred4) = into_shreds(&leader.pubkey(), chunks, version).unwrap(); assert_eq!(shred1, shred3); assert_eq!(shred2, shred4); } @@ -545,6 +569,7 @@ pub(crate) mod tests { Some(leader_schedule), rng.gen(), // wallclock 512, // max_size + version, ) .err() .unwrap(), @@ -563,7 +588,9 @@ pub(crate) mod tests { assert!(chunks.len() > 4); assert_matches!( - into_shreds(&leader.pubkey(), chunks).err().unwrap(), + into_shreds(&leader.pubkey(), chunks, version) + .err() + .unwrap(), Error::InvalidDuplicateSlotProof ); } @@ -632,11 +659,12 @@ pub(crate) mod tests { Some(leader_schedule), rng.gen(), // wallclock 512, // max_size + version, ) .unwrap() .collect(); assert!(chunks.len() > 4); - let (shred3, shred4) = into_shreds(&leader.pubkey(), chunks).unwrap(); + let (shred3, shred4) = into_shreds(&leader.pubkey(), chunks, version).unwrap(); assert_eq!(shred1, &shred3); assert_eq!(shred2, &shred4); } @@ -740,6 +768,7 @@ pub(crate) mod tests { Some(leader_schedule), rng.gen(), // wallclock 512, // max_size + version, ) .err() .unwrap(), @@ -758,7 +787,9 @@ pub(crate) mod tests { assert!(chunks.len() > 4); assert_matches!( - into_shreds(&leader.pubkey(), chunks).err().unwrap(), + into_shreds(&leader.pubkey(), chunks, version) + .err() + .unwrap(), Error::InvalidLastIndexConflict ); } @@ -817,11 +848,12 @@ pub(crate) mod tests { Some(leader_schedule), rng.gen(), // wallclock 512, // max_size + version, ) .unwrap() .collect(); assert!(chunks.len() > 4); - let (shred3, shred4) = into_shreds(&leader.pubkey(), chunks).unwrap(); + let (shred3, shred4) = into_shreds(&leader.pubkey(), chunks, version).unwrap(); assert_eq!(shred1, shred3); assert_eq!(shred2, shred4); } @@ -898,6 +930,7 @@ pub(crate) mod tests { Some(leader_schedule), rng.gen(), // wallclock 512, // max_size + version, ) .err() .unwrap(), @@ -916,7 +949,9 @@ pub(crate) mod tests { assert!(chunks.len() > 4); assert_matches!( - into_shreds(&leader.pubkey(), chunks).err().unwrap(), + into_shreds(&leader.pubkey(), chunks, version) + .err() + .unwrap(), Error::InvalidErasureMetaConflict ); } @@ -989,11 +1024,12 @@ pub(crate) mod tests { Some(leader_schedule), rng.gen(), // wallclock 512, // max_size + version, ) .unwrap() .collect(); assert!(chunks.len() > 4); - let (shred3, shred4) = into_shreds(&leader.pubkey(), chunks).unwrap(); + let (shred3, shred4) = into_shreds(&leader.pubkey(), chunks, version).unwrap(); assert_eq!(shred1, shred3); assert_eq!(shred2, shred4); } @@ -1080,6 +1116,7 @@ pub(crate) mod tests { Some(leader_schedule), rng.gen(), // wallclock 512, // max_size + version, ) .err() .unwrap(), @@ -1098,9 +1135,124 @@ pub(crate) mod tests { assert!(chunks.len() > 4); assert_matches!( - into_shreds(&leader.pubkey(), chunks).err().unwrap(), + into_shreds(&leader.pubkey(), chunks, version) + .err() + .unwrap(), Error::ShredTypeMismatch ); } } + + #[test] + fn test_shred_version() { + let mut rng = rand::thread_rng(); + let leader = Arc::new(Keypair::new()); + let (slot, parent_slot, reference_tick, version) = (53084024, 53084023, 0, 0); + let shredder = Shredder::new(slot, parent_slot, reference_tick, version).unwrap(); + let next_shred_index = rng.gen_range(0..31_000); + let leader_schedule = |s| { + if s == slot { + Some(leader.pubkey()) + } else { + None + } + }; + + let (data_shreds, coding_shreds) = new_rand_shreds( + &mut rng, + next_shred_index, + next_shred_index, + 10, + true, + &shredder, + &leader, + true, + ); + + // Wrong shred version 1 + let shredder = Shredder::new(slot, parent_slot, reference_tick, version + 1).unwrap(); + let (wrong_data_shreds_1, wrong_coding_shreds_1) = new_rand_shreds( + &mut rng, + next_shred_index, + next_shred_index, + 10, + true, + &shredder, + &leader, + true, + ); + + // Wrong shred version 2 + let shredder = Shredder::new(slot, parent_slot, reference_tick, version + 2).unwrap(); + let (wrong_data_shreds_2, wrong_coding_shreds_2) = new_rand_shreds( + &mut rng, + next_shred_index, + next_shred_index, + 10, + true, + &shredder, + &leader, + true, + ); + + let test_cases = vec![ + // One correct shred version, one wrong + (coding_shreds[0].clone(), wrong_coding_shreds_1[0].clone()), + (coding_shreds[0].clone(), wrong_data_shreds_1[0].clone()), + (data_shreds[0].clone(), wrong_coding_shreds_1[0].clone()), + (data_shreds[0].clone(), wrong_data_shreds_1[0].clone()), + // Both wrong shred version + ( + wrong_coding_shreds_2[0].clone(), + wrong_coding_shreds_1[0].clone(), + ), + ( + wrong_coding_shreds_2[0].clone(), + wrong_data_shreds_1[0].clone(), + ), + ( + wrong_data_shreds_2[0].clone(), + wrong_coding_shreds_1[0].clone(), + ), + ( + wrong_data_shreds_2[0].clone(), + wrong_data_shreds_1[0].clone(), + ), + ]; + + for (shred1, shred2) in test_cases.into_iter() { + assert_matches!( + from_shred( + shred1.clone(), + Pubkey::new_unique(), // self_pubkey + shred2.payload().clone(), + Some(leader_schedule), + rng.gen(), // wallclock + 512, // max_size + version, + ) + .err() + .unwrap(), + Error::InvalidShredVersion(_) + ); + + let chunks: Vec<_> = from_shred_bypass_checks( + shred1.clone(), + Pubkey::new_unique(), // self_pubkey + shred2.clone(), + rng.gen(), // wallclock + 512, // max_size + ) + .unwrap() + .collect(); + assert!(chunks.len() > 4); + + assert_matches!( + into_shreds(&leader.pubkey(), chunks, version) + .err() + .unwrap(), + Error::InvalidShredVersion(_) + ); + } + } } diff --git a/gossip/src/duplicate_shred_handler.rs b/gossip/src/duplicate_shred_handler.rs index 883d0a7da00504..edf62aaf4276fc 100644 --- a/gossip/src/duplicate_shred_handler.rs +++ b/gossip/src/duplicate_shred_handler.rs @@ -48,6 +48,7 @@ pub struct DuplicateShredHandler { cached_slots_in_epoch: u64, // Used to notify duplicate consensus state machine duplicate_slots_sender: Sender, + shred_version: u16, } impl DuplicateShredHandlerTrait for DuplicateShredHandler { @@ -68,6 +69,7 @@ impl DuplicateShredHandler { leader_schedule_cache: Arc, bank_forks: Arc>, duplicate_slots_sender: Sender, + shred_version: u16, ) -> Self { Self { buffer: HashMap::<(Slot, Pubkey), BufferEntry>::default(), @@ -80,6 +82,7 @@ impl DuplicateShredHandler { leader_schedule_cache, bank_forks, duplicate_slots_sender, + shred_version, } } @@ -130,7 +133,8 @@ impl DuplicateShredHandler { .leader_schedule_cache .slot_leader_at(slot, /*bank:*/ None) .ok_or(Error::UnknownSlotLeader(slot))?; - let (shred1, shred2) = duplicate_shred::into_shreds(&pubkey, chunks)?; + let (shred1, shred2) = + duplicate_shred::into_shreds(&pubkey, chunks, self.shred_version)?; if !self.blockstore.has_duplicate_shreds_in_slot(slot) { self.blockstore.store_duplicate_slot( slot, @@ -255,16 +259,17 @@ mod tests { slot: u64, expected_error: Option, chunk_size: usize, + shred_version: u16, ) -> Result, Error> { let my_keypair = match expected_error { Some(Error::InvalidSignature) => Arc::new(Keypair::new()), _ => keypair, }; let mut rng = rand::thread_rng(); - let shredder = Shredder::new(slot, slot - 1, 0, 0).unwrap(); + let shredder = Shredder::new(slot, slot - 1, 0, shred_version).unwrap(); let next_shred_index = 353; let shred1 = new_rand_shred(&mut rng, next_shred_index, &shredder, &my_keypair); - let shredder1 = Shredder::new(slot + 1, slot, 0, 0).unwrap(); + let shredder1 = Shredder::new(slot + 1, slot, 0, shred_version).unwrap(); let shred2 = match expected_error { Some(Error::SlotMismatch) => { new_rand_shred(&mut rng, next_shred_index, &shredder1, &my_keypair) @@ -283,6 +288,7 @@ mod tests { None:: Option>, timestamp(), // wallclock chunk_size, // max_size + shred_version, )?; Ok(chunks) } @@ -295,6 +301,7 @@ mod tests { let blockstore = Arc::new(Blockstore::open(ledger_path.path()).unwrap()); let my_keypair = Arc::new(Keypair::new()); let my_pubkey = my_keypair.pubkey(); + let shred_version = 0; let genesis_config_info = create_genesis_config_with_leader(10_000, &my_pubkey, 10_000); let GenesisConfigInfo { genesis_config, .. } = genesis_config_info; let mut bank = Bank::new_for_tests(&genesis_config); @@ -322,6 +329,7 @@ mod tests { leader_schedule_cache, bank_forks_arc, sender, + shred_version, ); let chunks = create_duplicate_proof( my_keypair.clone(), @@ -329,6 +337,7 @@ mod tests { start_slot, None, DUPLICATE_SHRED_MAX_PAYLOAD_SIZE, + shred_version, ) .unwrap(); let chunks1 = create_duplicate_proof( @@ -337,6 +346,7 @@ mod tests { start_slot + 1, None, DUPLICATE_SHRED_MAX_PAYLOAD_SIZE, + shred_version, ) .unwrap(); assert!(!blockstore.has_duplicate_shreds_in_slot(start_slot)); @@ -365,6 +375,7 @@ mod tests { start_slot + 2, Some(error), DUPLICATE_SHRED_MAX_PAYLOAD_SIZE, + shred_version, ) { Err(_) => (), Ok(chunks) => { @@ -386,6 +397,7 @@ mod tests { let blockstore = Arc::new(Blockstore::open(ledger_path.path()).unwrap()); let my_keypair = Arc::new(Keypair::new()); let my_pubkey = my_keypair.pubkey(); + let shred_version = 0; let genesis_config_info = create_genesis_config_with_leader(10_000, &my_pubkey, 10_000); let GenesisConfigInfo { genesis_config, .. } = genesis_config_info; let mut bank = Bank::new_for_tests(&genesis_config); @@ -410,6 +422,7 @@ mod tests { leader_schedule_cache, bank_forks_arc, sender, + shred_version, ); // The feature will only be activated at Epoch 1. let start_slot: Slot = slots_in_epoch + 1; @@ -421,6 +434,7 @@ mod tests { start_slot, None, DUPLICATE_SHRED_MAX_PAYLOAD_SIZE / 2, + shred_version, ) .unwrap(); for chunk in chunks { @@ -438,6 +452,7 @@ mod tests { future_slot, None, DUPLICATE_SHRED_MAX_PAYLOAD_SIZE, + shred_version, ) .unwrap(); for chunk in chunks { @@ -454,6 +469,7 @@ mod tests { start_slot, None, DUPLICATE_SHRED_MAX_PAYLOAD_SIZE, + shred_version, ) .unwrap(); // handle chunk 0 of the first proof. @@ -474,6 +490,7 @@ mod tests { let blockstore = Arc::new(Blockstore::open(ledger_path.path()).unwrap()); let my_keypair = Arc::new(Keypair::new()); let my_pubkey = my_keypair.pubkey(); + let shred_version = 0; let genesis_config_info = create_genesis_config_with_leader(10_000, &my_pubkey, 10_000); let GenesisConfigInfo { genesis_config, .. } = genesis_config_info; let mut bank = Bank::new_for_tests(&genesis_config); @@ -492,6 +509,7 @@ mod tests { leader_schedule_cache, bank_forks_arc, sender, + shred_version, ); let chunks = create_duplicate_proof( my_keypair.clone(), @@ -499,6 +517,7 @@ mod tests { 1, None, DUPLICATE_SHRED_MAX_PAYLOAD_SIZE, + shred_version, ) .unwrap(); assert!(!blockstore.has_duplicate_shreds_in_slot(1)); diff --git a/gossip/src/main.rs b/gossip/src/main.rs index 2e98516583778d..7c8b4c51490c27 100644 --- a/gossip/src/main.rs +++ b/gossip/src/main.rs @@ -29,6 +29,23 @@ fn parse_matches() -> ArgMatches<'static> { .default_value("0") .help("Filter gossip nodes by this shred version"); + let gossip_port_arg = clap::Arg::with_name("gossip_port") + .long("gossip-port") + .value_name("PORT") + .takes_value(true) + .validator(is_port) + .help("Gossip port number for the node"); + + let gossip_host_arg = clap::Arg::with_name("gossip_host") + .long("gossip-host") + .value_name("HOST") + .takes_value(true) + .validator(solana_net_utils::is_host) + .help( + "Gossip DNS name or IP address for the node to advertise in gossip \ + [default: ask --entrypoint, or 127.0.0.1 when --entrypoint is not provided]", + ); + App::new(crate_name!()) .about(crate_description!()) .version(solana_version::version!()) @@ -75,6 +92,8 @@ fn parse_matches() -> ArgMatches<'static> { .help("Timeout in seconds"), ) .arg(&shred_version_arg) + .arg(&gossip_port_arg) + .arg(&gossip_host_arg) .setting(AppSettings::DisableVersion), ) .subcommand( @@ -90,23 +109,6 @@ fn parse_matches() -> ArgMatches<'static> { .validator(solana_net_utils::is_host_port) .help("Rendezvous with the cluster at this entrypoint"), ) - .arg( - clap::Arg::with_name("gossip_port") - .long("gossip-port") - .value_name("PORT") - .takes_value(true) - .validator(is_port) - .help("Gossip port number for the node"), - ) - .arg( - clap::Arg::with_name("gossip_host") - .long("gossip-host") - .value_name("HOST") - .takes_value(true) - .validator(solana_net_utils::is_host) - .help("Gossip DNS name or IP address for the node to advertise in gossip \ - [default: ask --entrypoint, or 127.0.0.1 when --entrypoint is not provided]"), - ) .arg( Arg::with_name("identity") .short("i") @@ -144,6 +146,8 @@ fn parse_matches() -> ArgMatches<'static> { .help("Public key of a specific node to wait for"), ) .arg(&shred_version_arg) + .arg(&gossip_port_arg) + .arg(&gossip_host_arg) .arg( Arg::with_name("timeout") .long("timeout") @@ -226,21 +230,9 @@ fn process_spy(matches: &ArgMatches, socket_addr_space: SocketAddrSpace) -> std: let pubkeys = pubkeys_of(matches, "node_pubkey"); let shred_version = value_t_or_exit!(matches, "shred_version", u16); let identity_keypair = keypair_of(matches, "identity"); - let entrypoint_addr = parse_entrypoint(matches); + let gossip_addr = get_gossip_address(matches, entrypoint_addr); - let gossip_host = parse_gossip_host(matches, entrypoint_addr); - - let gossip_addr = SocketAddr::new( - gossip_host, - value_t!(matches, "gossip_port", u16).unwrap_or_else(|_| { - solana_net_utils::find_available_port_in_range( - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - (0, 1), - ) - .expect("unable to find an available gossip port") - }), - ); let discover_timeout = Duration::from_secs(timeout.unwrap_or(u64::MAX)); let (_all_peers, validators) = discover( identity_keypair, @@ -280,9 +272,11 @@ fn process_rpc_url( ) -> std::io::Result<()> { let any = matches.is_present("any"); let all = matches.is_present("all"); - let entrypoint_addr = parse_entrypoint(matches); let timeout = value_t_or_exit!(matches, "timeout", u64); let shred_version = value_t_or_exit!(matches, "shred_version", u16); + let entrypoint_addr = parse_entrypoint(matches); + let gossip_addr = get_gossip_address(matches, entrypoint_addr); + let (_all_peers, validators) = discover( None, // keypair entrypoint_addr.as_ref(), @@ -290,7 +284,7 @@ fn process_rpc_url( Duration::from_secs(timeout), None, // find_nodes_by_pubkey entrypoint_addr.as_ref(), // find_node_by_gossip_addr - None, // my_gossip_addr + Some(&gossip_addr), // my_gossip_addr shred_version, socket_addr_space, )?; @@ -323,6 +317,20 @@ fn process_rpc_url( Ok(()) } +fn get_gossip_address(matches: &ArgMatches, entrypoint_addr: Option) -> SocketAddr { + let gossip_host = parse_gossip_host(matches, entrypoint_addr); + SocketAddr::new( + gossip_host, + value_t!(matches, "gossip_port", u16).unwrap_or_else(|_| { + solana_net_utils::find_available_port_in_range( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + (0, 1), + ) + .expect("unable to find an available gossip port") + }), + ) +} + fn main() -> Result<(), Box> { solana_logger::setup_with_default_filter(); diff --git a/ledger-tool/Cargo.toml b/ledger-tool/Cargo.toml index 351aa44462cf1e..d9e55d9771b36c 100644 --- a/ledger-tool/Cargo.toml +++ b/ledger-tool/Cargo.toml @@ -48,6 +48,7 @@ solana-stake-program = { workspace = true } solana-storage-bigtable = { workspace = true } solana-streamer = { workspace = true } solana-transaction-status = { workspace = true } +solana-type-overrides = { workspace = true } solana-unified-scheduler-pool = { workspace = true } solana-version = { workspace = true } solana-vote-program = { workspace = true } diff --git a/ledger-tool/src/args.rs b/ledger-tool/src/args.rs index ce0c8a5d7d3f36..94d0baa4f7b4e5 100644 --- a/ledger-tool/src/args.rs +++ b/ledger-tool/src/args.rs @@ -1,13 +1,17 @@ use { crate::LEDGER_TOOL_DIRECTORY, - clap::{value_t, value_t_or_exit, values_t, values_t_or_exit, ArgMatches}, + clap::{value_t, value_t_or_exit, values_t, values_t_or_exit, Arg, ArgMatches}, solana_accounts_db::{ accounts_db::{AccountsDb, AccountsDbConfig}, accounts_index::{AccountsIndexConfig, IndexLimitMb}, partitioned_rewards::TestPartitionedEpochRewards, utils::create_and_canonicalize_directories, }, - solana_clap_utils::input_parsers::pubkeys_of, + solana_clap_utils::{ + hidden_unless_forced, + input_parsers::pubkeys_of, + input_validators::{is_parsable, is_pow2}, + }, solana_ledger::{ blockstore_processor::ProcessOptions, use_snapshot_archives_at_startup::{self, UseSnapshotArchivesAtStartup}, @@ -21,6 +25,141 @@ use { }, }; +/// Returns the arguments that configure AccountsDb +pub fn accounts_db_args<'a, 'b>() -> Box<[Arg<'a, 'b>]> { + vec![ + Arg::with_name("account_paths") + .long("accounts") + .value_name("PATHS") + .takes_value(true) + .help( + "Persistent accounts location. May be specified multiple times. \ + [default: /accounts]", + ), + Arg::with_name("accounts_index_path") + .long("accounts-index-path") + .value_name("PATH") + .takes_value(true) + .multiple(true) + .help( + "Persistent accounts-index location. May be specified multiple times. \ + [default: /accounts_index]", + ), + Arg::with_name("accounts_hash_cache_path") + .long("accounts-hash-cache-path") + .value_name("PATH") + .takes_value(true) + .help( + "Use PATH as accounts hash cache location [default: /accounts_hash_cache]", + ), + Arg::with_name("accounts_index_bins") + .long("accounts-index-bins") + .value_name("BINS") + .validator(is_pow2) + .takes_value(true) + .help("Number of bins to divide the accounts index into"), + Arg::with_name("accounts_index_memory_limit_mb") + .long("accounts-index-memory-limit-mb") + .value_name("MEGABYTES") + .validator(is_parsable::) + .takes_value(true) + .help( + "How much memory the accounts index can consume. If this is exceeded, some \ + account index entries will be stored on disk.", + ), + Arg::with_name("disable_accounts_disk_index") + .long("disable-accounts-disk-index") + .help( + "Disable the disk-based accounts index. It is enabled by default. The entire \ + accounts index will be kept in memory.", + ) + .conflicts_with("accounts_index_memory_limit_mb"), + Arg::with_name("accounts_db_skip_shrink") + .long("accounts-db-skip-shrink") + .help( + "Enables faster starting of ledger-tool by skipping shrink. This option is for \ + use during testing.", + ), + Arg::with_name("accounts_db_verify_refcounts") + .long("accounts-db-verify-refcounts") + .help( + "Debug option to scan all AppendVecs and verify account index refcounts prior to \ + clean", + ) + .hidden(hidden_unless_forced()), + Arg::with_name("accounts_db_test_skip_rewrites") + .long("accounts-db-test-skip-rewrites") + .help( + "Debug option to skip rewrites for rent-exempt accounts but still add them in \ + bank delta hash calculation", + ) + .hidden(hidden_unless_forced()), + Arg::with_name("accounts_db_skip_initial_hash_calculation") + .long("accounts-db-skip-initial-hash-calculation") + .help("Do not verify accounts hash at startup.") + .hidden(hidden_unless_forced()), + Arg::with_name("accounts_db_ancient_append_vecs") + .long("accounts-db-ancient-append-vecs") + .value_name("SLOT-OFFSET") + .validator(is_parsable::) + .takes_value(true) + .help( + "AppendVecs that are older than (slots_per_epoch - SLOT-OFFSET) are squashed \ + together.", + ) + .hidden(hidden_unless_forced()), + ] + .into_boxed_slice() +} + +// For our current version of CLAP, the value passed to Arg::default_value() +// must be a &str. But, we can't convert an integer to a &str at compile time. +// So, declare this constant and enforce equality with the following unit test +// test_max_genesis_archive_unpacked_size_constant +const MAX_GENESIS_ARCHIVE_UNPACKED_SIZE_STR: &str = "10485760"; + +/// Returns the arguments that configure loading genesis +pub fn load_genesis_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name("max_genesis_archive_unpacked_size") + .long("max-genesis-archive-unpacked-size") + .value_name("NUMBER") + .takes_value(true) + .default_value(MAX_GENESIS_ARCHIVE_UNPACKED_SIZE_STR) + .help("maximum total uncompressed size of unpacked genesis archive") +} + +/// Returns the arguments that configure snapshot loading +pub fn snapshot_args<'a, 'b>() -> Box<[Arg<'a, 'b>]> { + vec![ + Arg::with_name("no_snapshot") + .long("no-snapshot") + .takes_value(false) + .help("Do not start from a local snapshot if present"), + Arg::with_name("snapshots") + .long("snapshots") + .alias("snapshot-archive-path") + .alias("full-snapshot-archive-path") + .value_name("DIR") + .takes_value(true) + .global(true) + .help("Use DIR for snapshot location [default: --ledger value]"), + Arg::with_name("incremental_snapshot_archive_path") + .long("incremental-snapshot-archive-path") + .value_name("DIR") + .takes_value(true) + .global(true) + .help("Use DIR for separate incremental snapshot location"), + Arg::with_name(use_snapshot_archives_at_startup::cli::NAME) + .long(use_snapshot_archives_at_startup::cli::LONG_ARG) + .takes_value(true) + .possible_values(use_snapshot_archives_at_startup::cli::POSSIBLE_VALUES) + .default_value(use_snapshot_archives_at_startup::cli::default_value_for_ledger_tool()) + .help(use_snapshot_archives_at_startup::cli::HELP) + .long_help(use_snapshot_archives_at_startup::cli::LONG_HELP), + ] + .into_boxed_slice() +} + /// Parse a `ProcessOptions` from subcommand arguments. This function attempts /// to parse all flags related to `ProcessOptions`; however, subcommands that /// use this function may not support all flags. @@ -155,3 +294,18 @@ pub fn hardforks_of(matches: &ArgMatches<'_>, name: &str) -> Option> { None } } + +#[cfg(test)] +mod tests { + use {super::*, solana_accounts_db::hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE}; + + #[test] + fn test_max_genesis_archive_unpacked_size_constant() { + assert_eq!( + MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, + MAX_GENESIS_ARCHIVE_UNPACKED_SIZE_STR + .parse::() + .unwrap() + ); + } +} diff --git a/ledger-tool/src/bigtable.rs b/ledger-tool/src/bigtable.rs index 17f7ba598fa473..a79645e4282e08 100644 --- a/ledger-tool/src/bigtable.rs +++ b/ledger-tool/src/bigtable.rs @@ -1,18 +1,21 @@ //! The `bigtable` subcommand use { crate::{ + args::{load_genesis_arg, snapshot_args}, ledger_path::canonicalize_ledger_path, + load_and_process_ledger_or_exit, open_genesis_config_by, output::{ encode_confirmed_block, CliBlockWithEntries, CliEntries, EncodedConfirmedBlockWithEntries, }, + parse_process_options, LoadAndProcessLedgerOutput, }, clap::{ value_t, value_t_or_exit, values_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand, }, crossbeam_channel::unbounded, futures::stream::FuturesUnordered, - log::{debug, error, info}, + log::{debug, error, info, warn}, serde_json::json, solana_clap_utils::{ input_parsers::pubkey_of, @@ -22,11 +25,17 @@ use { display::println_transaction, CliBlock, CliTransaction, CliTransactionConfirmation, OutputFormat, }, + solana_entry::entry::{create_ticks, Entry}, solana_ledger::{ - bigtable_upload::ConfirmedBlockUploadConfig, blockstore::Blockstore, + bigtable_upload::ConfirmedBlockUploadConfig, + blockstore::Blockstore, blockstore_options::AccessType, + shred::{ProcessShredsStats, ReedSolomonCache, Shredder}, + }, + solana_sdk::{ + clock::Slot, hash::Hash, pubkey::Pubkey, shred_version::compute_shred_version, + signature::Signature, signer::keypair::keypair_from_seed, }, - solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature}, solana_storage_bigtable::CredentialType, solana_transaction_status::{ConfirmedBlock, UiTransactionEncoding, VersionedConfirmedBlock}, std::{ @@ -164,6 +173,170 @@ async fn entries( Ok(()) } +struct ShredConfig { + shred_version: u16, + num_hashes_per_tick: u64, + num_ticks_per_slot: u64, + allow_mock_poh: bool, +} + +async fn shreds( + blockstore: Arc, + starting_slot: Slot, + ending_slot: Slot, + shred_config: ShredConfig, + config: solana_storage_bigtable::LedgerStorageConfig, +) -> Result<(), Box> { + let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config) + .await + .map_err(|err| format!("Failed to connect to storage: {err:?}"))?; + + // Make the range inclusive of both starting and ending slot + let limit = ending_slot.saturating_sub(starting_slot).saturating_add(1) as usize; + let mut slots = bigtable.get_confirmed_blocks(starting_slot, limit).await?; + slots.retain(|&slot| slot <= ending_slot); + + // Create a "dummy" keypair to sign the shreds that will later be created. + // + // The validator shred ingestion path sigverifies shreds from the network + // using the known leader for any given slot. It is unlikely that a user of + // this tool will have access to these leader identity keypairs. However, + // shred sigverify occurs prior to Blockstore::insert_shreds(). Thus, the + // shreds being signed with the "dummy" keyapir can still be inserted and + // later read/replayed/etc + let keypair = keypair_from_seed(&[0; 64])?; + let ShredConfig { + shred_version, + num_hashes_per_tick, + num_ticks_per_slot, + allow_mock_poh, + } = shred_config; + + for slot in slots.iter() { + let block = bigtable.get_confirmed_block(*slot).await?; + let entry_summaries = match bigtable.get_entries(*slot).await { + Ok(summaries) => Some(summaries), + Err(err) => { + let err_msg = format!("Failed to get PoH entries for {slot}: {err}"); + + if allow_mock_poh { + warn!("{err_msg}. Will create mock PoH entries instead."); + } else { + return Err(format!( + "{err_msg}. Try passing --allow-mock-poh to allow \ + creation of shreds with mocked PoH entries" + ))?; + } + None + } + }; + + let entries = match entry_summaries { + Some(entry_summaries) => entry_summaries + .enumerate() + .map(|(i, entry_summary)| { + let num_hashes = entry_summary.num_hashes; + let hash = entry_summary.hash; + let starting_transaction_index = entry_summary.starting_transaction_index; + let num_transactions = entry_summary.num_transactions as usize; + + let Some(transactions) = block.transactions.get( + starting_transaction_index..starting_transaction_index + num_transactions, + ) else { + let num_block_transactions = block.transactions.len(); + return Err(format!( + "Entry summary {i} for slot {slot} with starting_transaction_index \ + {starting_transaction_index} and num_transactions {num_transactions} \ + is in conflict with the block, which has {num_block_transactions} \ + transactions" + )); + }; + let transactions = transactions + .iter() + .map(|tx_with_meta| tx_with_meta.get_transaction()) + .collect(); + + Ok(Entry { + num_hashes, + hash, + transactions, + }) + }) + .collect::, std::string::String>>()?, + None => { + let num_total_ticks = ((slot - block.parent_slot) * num_ticks_per_slot) as usize; + let num_total_entries = num_total_ticks + block.transactions.len(); + let mut entries = Vec::with_capacity(num_total_entries); + + // Create virtual tick entries for any skipped slots + // + // These ticks are necessary so that the tick height is + // advanced to the proper value when this block is processed. + // + // Additionally, a blockhash will still be inserted into the + // recent blockhashes sysvar for skipped slots. So, these + // virtual ticks will have the proper PoH + let num_skipped_slots = slot - block.parent_slot - 1; + if num_skipped_slots > 0 { + let num_virtual_ticks = num_skipped_slots * num_ticks_per_slot; + let parent_blockhash = Hash::from_str(&block.previous_blockhash)?; + let virtual_ticks_entries = + create_ticks(num_virtual_ticks, num_hashes_per_tick, parent_blockhash); + entries.extend(virtual_ticks_entries.into_iter()); + } + + // Create transaction entries + // + // Keep it simple and just do one transaction per Entry + let transaction_entries = block.transactions.iter().map(|tx_with_meta| Entry { + num_hashes: 0, + hash: Hash::default(), + transactions: vec![tx_with_meta.get_transaction()], + }); + entries.extend(transaction_entries.into_iter()); + + // Create the tick entries for this slot + // + // We do not know the intermediate hashes, so just use default + // hash for all ticks. The exception is the final tick; the + // final tick determines the blockhash so set it the known + // blockhash from the bigtable block + let blockhash = Hash::from_str(&block.blockhash)?; + let tick_entries = (0..num_ticks_per_slot).map(|idx| { + let hash = if idx == num_ticks_per_slot - 1 { + blockhash + } else { + Hash::default() + }; + Entry { + num_hashes: 0, + hash, + transactions: vec![], + } + }); + entries.extend(tick_entries.into_iter()); + + entries + } + }; + + let shredder = Shredder::new(*slot, block.parent_slot, 0, shred_version)?; + let (data_shreds, _coding_shreds) = shredder.entries_to_shreds( + &keypair, + &entries, + true, // last_in_slot + None, // chained_merkle_root + 0, // next_shred_index + 0, // next_code_index + false, // merkle_variant + &ReedSolomonCache::default(), + &mut ProcessShredsStats::default(), + ); + blockstore.insert_shreds(data_shreds, None, false)?; + } + Ok(()) +} + async fn blocks( starting_slot: Slot, limit: usize, @@ -848,6 +1021,45 @@ impl BigTableSubCommand for App<'_, '_> { .required(true), ), ) + .subcommand( + SubCommand::with_name("shreds") + .about( + "Get confirmed blocks from BigTable, reassemble the transactions \ + and entries, shred the block and then insert the shredded blocks into \ + the local Blockstore", + ) + .arg(load_genesis_arg()) + .args(&snapshot_args()) + .arg( + Arg::with_name("starting_slot") + .long("starting-slot") + .validator(is_slot) + .value_name("SLOT") + .takes_value(true) + .required(true) + .help("Start shred creation at this slot (inclusive)"), + ) + .arg( + Arg::with_name("ending_slot") + .long("ending-slot") + .validator(is_slot) + .value_name("SLOT") + .takes_value(true) + .required(true) + .help("Stop shred creation at this slot (inclusive)"), + ) + .arg( + Arg::with_name("allow_mock_poh") + .long("allow-mock-poh") + .takes_value(false) + .help( + "For slots where PoH entries are unavailable, allow the \ + generation of mock PoH entries. The mock PoH entries enable \ + the shredded block(s) to be replayable if PoH verification is \ + disabled.", + ), + ), + ) .subcommand( SubCommand::with_name("confirm") .about("Confirm transaction by signature") @@ -1142,6 +1354,87 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) { }; runtime.block_on(entries(slot, output_format, config)) } + ("shreds", Some(arg_matches)) => { + let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot); + let ending_slot = value_t_or_exit!(arg_matches, "ending_slot", Slot); + if starting_slot > ending_slot { + eprintln!( + "The specified --starting-slot {starting_slot} must be less than or equal to \ + the specified --ending-slot {ending_slot}." + ); + exit(1); + } + let allow_mock_poh = arg_matches.is_present("allow_mock_poh"); + + let ledger_path = canonicalize_ledger_path(ledger_path); + let process_options = parse_process_options(&ledger_path, arg_matches); + let genesis_config = open_genesis_config_by(&ledger_path, arg_matches); + let blockstore = Arc::new(crate::open_blockstore( + &ledger_path, + arg_matches, + AccessType::Primary, + )); + let LoadAndProcessLedgerOutput { bank_forks, .. } = load_and_process_ledger_or_exit( + arg_matches, + &genesis_config, + blockstore.clone(), + process_options, + None, + ); + + let bank = bank_forks.read().unwrap().working_bank(); + // If mock PoH is allowed, ensure that the requested slots are in + // the same epoch as the working bank. This will ensure the values + // extracted from the Bank are accurate for the slot range + if allow_mock_poh { + let working_bank_epoch = bank.epoch(); + let epoch_schedule = bank.epoch_schedule(); + let starting_epoch = epoch_schedule.get_epoch(starting_slot); + let ending_epoch = epoch_schedule.get_epoch(ending_slot); + if starting_epoch != ending_epoch { + eprintln!( + "The specified --starting-slot and --ending-slot must be in the\ + same epoch. --starting-slot {starting_slot} is in epoch {starting_epoch},\ + but --ending-slot {ending_slot} is in epoch {ending_epoch}." + ); + exit(1); + } + if starting_epoch != working_bank_epoch { + eprintln!( + "The range of slots between --starting-slot and --ending-slot are in a \ + different epoch than the working bank. The specified range is in epoch \ + {starting_epoch}, but the working bank is in {working_bank_epoch}." + ); + exit(1); + } + } + + let shred_version = + compute_shred_version(&genesis_config.hash(), Some(&bank.hard_forks())); + let num_hashes_per_tick = bank.hashes_per_tick().unwrap_or(0); + let num_ticks_per_slot = bank.ticks_per_slot(); + let shred_config = ShredConfig { + shred_version, + num_hashes_per_tick, + num_ticks_per_slot, + allow_mock_poh, + }; + + let config = solana_storage_bigtable::LedgerStorageConfig { + read_only: true, + instance_name, + app_profile_id, + ..solana_storage_bigtable::LedgerStorageConfig::default() + }; + + runtime.block_on(shreds( + blockstore, + starting_slot, + ending_slot, + shred_config, + config, + )) + } ("blocks", Some(arg_matches)) => { let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot); let limit = value_t_or_exit!(arg_matches, "limit", usize); diff --git a/ledger-tool/src/ledger_utils.rs b/ledger-tool/src/ledger_utils.rs index 44b6667f0516f5..98a647e21f2851 100644 --- a/ledger-tool/src/ledger_utils.rs +++ b/ledger-tool/src/ledger_utils.rs @@ -54,6 +54,19 @@ use { thiserror::Error, }; +pub struct LoadAndProcessLedgerOutput { + pub bank_forks: Arc>, + pub starting_snapshot_hashes: Option, + // Typically, we would want to join all threads before returning. However, + // AccountsBackgroundService (ABS) performs several long running operations + // that don't respond to the exit flag. Blocking on these operations could + // significantly delay getting results that do not need ABS to finish. So, + // skip joining ABS and instead let the caller decide whether to block or + // not. It is safe to let ABS continue in the background, and ABS will stop + // if/when it finally checks the exit flag + pub accounts_background_service: AccountsBackgroundService, +} + const PROCESS_SLOTS_HELP_STRING: &str = "The starting slot is either the latest found snapshot slot, or genesis (slot 0) if the \ --no-snapshot flag was specified or if no snapshots were found. \ @@ -98,17 +111,13 @@ pub fn load_and_process_ledger_or_exit( genesis_config: &GenesisConfig, blockstore: Arc, process_options: ProcessOptions, - snapshot_archive_path: Option, - incremental_snapshot_archive_path: Option, transaction_status_sender: Option, -) -> (Arc>, Option) { +) -> LoadAndProcessLedgerOutput { load_and_process_ledger( arg_matches, genesis_config, blockstore, process_options, - snapshot_archive_path, - incremental_snapshot_archive_path, transaction_status_sender, ) .unwrap_or_else(|err| { @@ -122,10 +131,8 @@ pub fn load_and_process_ledger( genesis_config: &GenesisConfig, blockstore: Arc, process_options: ProcessOptions, - snapshot_archive_path: Option, - incremental_snapshot_archive_path: Option, transaction_status_sender: Option, -) -> Result<(Arc>, Option), LoadAndProcessLedgerError> { +) -> Result { let bank_snapshots_dir = if blockstore.is_primary_access() { blockstore.ledger_path().join("snapshot") } else { @@ -139,10 +146,15 @@ pub fn load_and_process_ledger( let snapshot_config = if arg_matches.is_present("no_snapshot") { None } else { - let full_snapshot_archives_dir = - snapshot_archive_path.unwrap_or_else(|| blockstore.ledger_path().to_path_buf()); + let full_snapshot_archives_dir = value_t!(arg_matches, "snapshots", String) + .ok() + .map(PathBuf::from) + .unwrap_or_else(|| blockstore.ledger_path().to_path_buf()); let incremental_snapshot_archives_dir = - incremental_snapshot_archive_path.unwrap_or_else(|| full_snapshot_archives_dir.clone()); + value_t!(arg_matches, "incremental_snapshot_archive_path", String) + .ok() + .map(PathBuf::from) + .unwrap_or_else(|| full_snapshot_archives_dir.clone()); if let Some(full_snapshot_slot) = snapshot_utils::get_highest_full_snapshot_archive_slot(&full_snapshot_archives_dir) { @@ -403,11 +415,14 @@ pub fn load_and_process_ledger( None, // Maybe support this later, though &accounts_background_request_sender, ) - .map(|_| (bank_forks, starting_snapshot_hashes)) + .map(|_| LoadAndProcessLedgerOutput { + bank_forks, + starting_snapshot_hashes, + accounts_background_service, + }) .map_err(LoadAndProcessLedgerError::ProcessBlockstoreFromRoot); exit.store(true, Ordering::Relaxed); - accounts_background_service.join().unwrap(); accounts_hash_verifier.join().unwrap(); if let Some(service) = transaction_status_service { service.join().unwrap(); diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index fee913c82b593d..8339c0a14d07ff 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -8,6 +8,7 @@ use { ledger_utils::*, output::{ output_account, AccountsOutputConfig, AccountsOutputMode, AccountsOutputStreamer, + SlotBankHash, }, program::*, }, @@ -19,15 +20,12 @@ use { log::*, serde_derive::Serialize, solana_account_decoder::UiAccountEncoding, - solana_accounts_db::{ - accounts_db::CalcAccountsHashDataSource, accounts_index::ScanConfig, - hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, - }, + solana_accounts_db::{accounts_db::CalcAccountsHashDataSource, accounts_index::ScanConfig}, solana_clap_utils::{ hidden_unless_forced, input_parsers::{cluster_type_of, pubkey_of, pubkeys_of}, input_validators::{ - is_parsable, is_pow2, is_pubkey, is_pubkey_or_keypair, is_slot, is_valid_percentage, + is_parsable, is_pubkey, is_pubkey_or_keypair, is_slot, is_valid_percentage, is_within_range, }, }, @@ -43,7 +41,6 @@ use { blockstore_processor::{ ProcessSlotCallback, TransactionStatusMessage, TransactionStatusSender, }, - use_snapshot_archives_at_startup, }, solana_measure::{measure, measure::Measure}, solana_runtime::{ @@ -562,76 +559,10 @@ fn main() { solana_logger::setup_with_default_filter(); - let no_snapshot_arg = Arg::with_name("no_snapshot") - .long("no-snapshot") - .takes_value(false) - .help("Do not start from a local snapshot if present"); - let accounts_index_bins = Arg::with_name("accounts_index_bins") - .long("accounts-index-bins") - .value_name("BINS") - .validator(is_pow2) - .takes_value(true) - .help("Number of bins to divide the accounts index into"); - let accounts_index_limit = Arg::with_name("accounts_index_memory_limit_mb") - .long("accounts-index-memory-limit-mb") - .value_name("MEGABYTES") - .validator(is_parsable::) - .takes_value(true) - .help( - "How much memory the accounts index can consume. If this is exceeded, some account \ - index entries will be stored on disk.", - ); - let disable_disk_index = Arg::with_name("disable_accounts_disk_index") - .long("disable-accounts-disk-index") - .help( - "Disable the disk-based accounts index. It is enabled by default. The entire accounts \ - index will be kept in memory.", - ) - .conflicts_with("accounts_index_memory_limit_mb"); - let accountsdb_skip_shrink = Arg::with_name("accounts_db_skip_shrink") - .long("accounts-db-skip-shrink") - .help( - "Enables faster starting of ledger-tool by skipping shrink. This option is for use \ - during testing.", - ); - let accountsdb_verify_refcounts = Arg::with_name("accounts_db_verify_refcounts") - .long("accounts-db-verify-refcounts") - .help( - "Debug option to scan all AppendVecs and verify account index refcounts prior to clean", - ) - .hidden(hidden_unless_forced()); - let accounts_db_test_skip_rewrites_but_include_in_bank_hash = - Arg::with_name("accounts_db_test_skip_rewrites") - .long("accounts-db-test-skip-rewrites") - .help( - "Debug option to skip rewrites for rent-exempt accounts but still add them in \ - bank delta hash calculation", - ) - .hidden(hidden_unless_forced()); - let account_paths_arg = Arg::with_name("account_paths") - .long("accounts") - .value_name("PATHS") - .takes_value(true) - .help( - "Persistent accounts location. \ - May be specified multiple times. \ - [default: /accounts]", - ); - let accounts_hash_cache_path_arg = Arg::with_name("accounts_hash_cache_path") - .long("accounts-hash-cache-path") - .value_name("PATH") - .takes_value(true) - .help("Use PATH as accounts hash cache location [default: /accounts_hash_cache]"); - let accounts_index_path_arg = Arg::with_name("accounts_index_path") - .long("accounts-index-path") - .value_name("PATH") - .takes_value(true) - .multiple(true) - .help( - "Persistent accounts-index location. \ - May be specified multiple times. \ - [default: /accounts_index]", - ); + let load_genesis_config_arg = load_genesis_arg(); + let accounts_db_config_args = accounts_db_args(); + let snapshot_config_args = snapshot_args(); + let accounts_db_test_hash_calculation_arg = Arg::with_name("accounts_db_test_hash_calculation") .long("accounts-db-test-hash-calculation") .help("Enable hash calculation test"); @@ -644,20 +575,6 @@ fn main() { let os_memory_stats_reporting_arg = Arg::with_name("os_memory_stats_reporting") .long("os-memory-stats-reporting") .help("Enable reporting of OS memory statistics."); - let accounts_db_skip_initial_hash_calc_arg = - Arg::with_name("accounts_db_skip_initial_hash_calculation") - .long("accounts-db-skip-initial-hash-calculation") - .help("Do not verify accounts hash at startup.") - .hidden(hidden_unless_forced()); - let ancient_append_vecs = Arg::with_name("accounts_db_ancient_append_vecs") - .long("accounts-db-ancient-append-vecs") - .value_name("SLOT-OFFSET") - .validator(is_parsable::) - .takes_value(true) - .help( - "AppendVecs that are older than (slots_per_epoch - SLOT-OFFSET) are squashed together.", - ) - .hidden(hidden_unless_forced()); let halt_at_slot_store_hash_raw_data = Arg::with_name("halt_at_slot_store_hash_raw_data") .long("halt-at-slot-store-hash-raw-data") .help( @@ -690,13 +607,6 @@ fn main() { .long("allow-dead-slots") .takes_value(false) .help("Output dead slots as well"); - let default_genesis_archive_unpacked_size = MAX_GENESIS_ARCHIVE_UNPACKED_SIZE.to_string(); - let max_genesis_archive_unpacked_size_arg = Arg::with_name("max_genesis_archive_unpacked_size") - .long("max-genesis-archive-unpacked-size") - .value_name("NUMBER") - .takes_value(true) - .default_value(&default_genesis_archive_unpacked_size) - .help("maximum total uncompressed size of unpacked genesis archive"); let hashes_per_tick = Arg::with_name("hashes_per_tick") .long("hashes-per-tick") .value_name("NUM_HASHES|\"sleep\"") @@ -719,14 +629,6 @@ fn main() { .multiple(true) .takes_value(true) .help("Log when transactions are processed that reference the given key(s)."); - let use_snapshot_archives_at_startup = - Arg::with_name(use_snapshot_archives_at_startup::cli::NAME) - .long(use_snapshot_archives_at_startup::cli::LONG_ARG) - .takes_value(true) - .possible_values(use_snapshot_archives_at_startup::cli::POSSIBLE_VALUES) - .default_value(use_snapshot_archives_at_startup::cli::default_value_for_ledger_tool()) - .help(use_snapshot_archives_at_startup::cli::HELP) - .long_help(use_snapshot_archives_at_startup::cli::LONG_HELP); let geyser_plugin_args = Arg::with_name("geyser_plugin_config") .long("geyser-plugin-config") @@ -811,24 +713,6 @@ fn main() { run fine with a reduced file descriptor limit while others will not", ), ) - .arg( - Arg::with_name("snapshots") - .long("snapshots") - .alias("snapshot-archive-path") - .alias("full-snapshot-archive-path") - .value_name("DIR") - .takes_value(true) - .global(true) - .help("Use DIR for snapshot location [default: --ledger value]"), - ) - .arg( - Arg::with_name("incremental_snapshot_archive_path") - .long("incremental-snapshot-archive-path") - .value_name("DIR") - .takes_value(true) - .global(true) - .help("Use DIR for separate incremental snapshot location"), - ) .arg( Arg::with_name("block_verification_method") .long("block-verification-method") @@ -836,7 +720,6 @@ fn main() { .takes_value(true) .possible_values(BlockVerificationMethod::cli_names()) .global(true) - .hidden(hidden_unless_forced()) .help(BlockVerificationMethod::cli_message()), ) .arg( @@ -846,7 +729,6 @@ fn main() { .takes_value(true) .validator(|s| is_within_range(s, 1..)) .global(true) - .hidden(hidden_unless_forced()) .help(DefaultSchedulerPool::cli_message()), ) .arg( @@ -879,7 +761,7 @@ fn main() { .subcommand( SubCommand::with_name("genesis") .about("Prints the ledger's genesis config") - .arg(&max_genesis_archive_unpacked_size_arg) + .arg(&load_genesis_config_arg) .arg( Arg::with_name("accounts") .long("accounts") @@ -898,12 +780,12 @@ fn main() { .subcommand( SubCommand::with_name("genesis-hash") .about("Prints the ledger's genesis hash") - .arg(&max_genesis_archive_unpacked_size_arg), + .arg(&load_genesis_config_arg) ) .subcommand( SubCommand::with_name("modify-genesis") .about("Modifies genesis parameters") - .arg(&max_genesis_archive_unpacked_size_arg) + .arg(&load_genesis_config_arg) .arg(&hashes_per_tick) .arg( Arg::with_name("cluster_type") @@ -923,57 +805,36 @@ fn main() { .subcommand( SubCommand::with_name("shred-version") .about("Prints the ledger's shred hash") + .arg(&load_genesis_config_arg) + .args(&accounts_db_config_args) + .args(&snapshot_config_args) .arg(&hard_forks_arg) - .arg(&max_genesis_archive_unpacked_size_arg) - .arg(&accounts_index_bins) - .arg(&accounts_index_limit) - .arg(&disable_disk_index) - .arg(&accountsdb_verify_refcounts) - .arg(&accounts_db_skip_initial_hash_calc_arg) - .arg(&accounts_db_test_skip_rewrites_but_include_in_bank_hash) - .arg(&use_snapshot_archives_at_startup), ) .subcommand( SubCommand::with_name("bank-hash") .about("Prints the hash of the working bank after reading the ledger") - .arg(&max_genesis_archive_unpacked_size_arg) + .arg(&load_genesis_config_arg) + .args(&accounts_db_config_args) + .args(&snapshot_config_args) .arg(&halt_at_slot_arg) - .arg(&accounts_index_bins) - .arg(&accounts_index_limit) - .arg(&disable_disk_index) - .arg(&accountsdb_verify_refcounts) - .arg(&accounts_db_skip_initial_hash_calc_arg) - .arg(&accounts_db_test_skip_rewrites_but_include_in_bank_hash) - .arg(&use_snapshot_archives_at_startup), ) .subcommand( SubCommand::with_name("verify") .about("Verify the ledger") - .arg(&no_snapshot_arg) - .arg(&account_paths_arg) - .arg(&accounts_hash_cache_path_arg) - .arg(&accounts_index_path_arg) + .arg(&load_genesis_config_arg) + .args(&accounts_db_config_args) + .args(&snapshot_config_args) .arg(&halt_at_slot_arg) .arg(&limit_load_slot_count_from_snapshot_arg) - .arg(&accounts_index_bins) - .arg(&accounts_index_limit) - .arg(&disable_disk_index) - .arg(&accountsdb_skip_shrink) - .arg(&accountsdb_verify_refcounts) - .arg(&accounts_db_test_skip_rewrites_but_include_in_bank_hash) .arg(&verify_index_arg) - .arg(&accounts_db_skip_initial_hash_calc_arg) - .arg(&ancient_append_vecs) .arg(&halt_at_slot_store_hash_raw_data) .arg(&hard_forks_arg) .arg(&accounts_db_test_hash_calculation_arg) .arg(&os_memory_stats_reporting_arg) .arg(&allow_dead_slots_arg) - .arg(&max_genesis_archive_unpacked_size_arg) .arg(&debug_key_arg) .arg(&geyser_plugin_args) .arg(&log_messages_bytes_limit_arg) - .arg(&use_snapshot_archives_at_startup) .arg( Arg::with_name("skip_poh_verify") .long("skip-poh-verify") @@ -1096,19 +957,11 @@ fn main() { .subcommand( SubCommand::with_name("graph") .about("Create a Graphviz rendering of the ledger") - .arg(&no_snapshot_arg) - .arg(&account_paths_arg) - .arg(&accounts_hash_cache_path_arg) - .arg(&accounts_index_bins) - .arg(&accounts_index_limit) - .arg(&disable_disk_index) - .arg(&accountsdb_verify_refcounts) - .arg(&accounts_db_test_skip_rewrites_but_include_in_bank_hash) - .arg(&accounts_db_skip_initial_hash_calc_arg) + .arg(&load_genesis_config_arg) + .args(&accounts_db_config_args) + .args(&snapshot_config_args) .arg(&halt_at_slot_arg) .arg(&hard_forks_arg) - .arg(&max_genesis_archive_unpacked_size_arg) - .arg(&use_snapshot_archives_at_startup) .arg( Arg::with_name("include_all_votes") .long("include-all-votes") @@ -1137,23 +990,13 @@ fn main() { .subcommand( SubCommand::with_name("create-snapshot") .about("Create a new ledger snapshot") - .arg(&no_snapshot_arg) - .arg(&account_paths_arg) - .arg(&accounts_hash_cache_path_arg) - .arg(&accounts_index_bins) - .arg(&accounts_index_limit) - .arg(&disable_disk_index) - .arg(&accountsdb_verify_refcounts) - .arg(&accounts_db_test_skip_rewrites_but_include_in_bank_hash) - .arg(&accounts_db_skip_initial_hash_calc_arg) - .arg(&accountsdb_skip_shrink) - .arg(&ancient_append_vecs) + .arg(&load_genesis_config_arg) + .args(&accounts_db_config_args) + .args(&snapshot_config_args) .arg(&hard_forks_arg) - .arg(&max_genesis_archive_unpacked_size_arg) .arg(&snapshot_version_arg) .arg(&geyser_plugin_args) .arg(&log_messages_bytes_limit_arg) - .arg(&use_snapshot_archives_at_startup) .arg( Arg::with_name("snapshot_slot") .index(1) @@ -1353,22 +1196,14 @@ fn main() { .subcommand( SubCommand::with_name("accounts") .about("Print account stats and contents after processing the ledger") - .arg(&no_snapshot_arg) - .arg(&account_paths_arg) - .arg(&accounts_hash_cache_path_arg) - .arg(&accounts_index_bins) - .arg(&accounts_index_limit) - .arg(&disable_disk_index) - .arg(&accountsdb_verify_refcounts) - .arg(&accounts_db_test_skip_rewrites_but_include_in_bank_hash) - .arg(&accounts_db_skip_initial_hash_calc_arg) + .arg(&load_genesis_config_arg) + .args(&accounts_db_config_args) + .args(&snapshot_config_args) .arg(&halt_at_slot_arg) .arg(&hard_forks_arg) .arg(&geyser_plugin_args) .arg(&log_messages_bytes_limit_arg) .arg(&accounts_data_encoding_arg) - .arg(&use_snapshot_archives_at_startup) - .arg(&max_genesis_archive_unpacked_size_arg) .arg( Arg::with_name("include_sysvars") .long("include-sysvars") @@ -1415,21 +1250,13 @@ fn main() { .subcommand( SubCommand::with_name("capitalization") .about("Print capitalization (aka, total supply) while checksumming it") - .arg(&no_snapshot_arg) - .arg(&account_paths_arg) - .arg(&accounts_hash_cache_path_arg) - .arg(&accounts_index_bins) - .arg(&accounts_index_limit) - .arg(&disable_disk_index) - .arg(&accountsdb_verify_refcounts) - .arg(&accounts_db_test_skip_rewrites_but_include_in_bank_hash) - .arg(&accounts_db_skip_initial_hash_calc_arg) + .arg(&load_genesis_config_arg) + .args(&accounts_db_config_args) + .args(&snapshot_config_args) .arg(&halt_at_slot_arg) .arg(&hard_forks_arg) - .arg(&max_genesis_archive_unpacked_size_arg) .arg(&geyser_plugin_args) .arg(&log_messages_bytes_limit_arg) - .arg(&use_snapshot_archives_at_startup) .arg( Arg::with_name("warp_epoch") .required(false) @@ -1500,14 +1327,6 @@ fn main() { info!("{} {}", crate_name!(), solana_version::version!()); let ledger_path = PathBuf::from(value_t_or_exit!(matches, "ledger_path", String)); - let snapshot_archive_path = value_t!(matches, "snapshots", String) - .ok() - .map(PathBuf::from); - let incremental_snapshot_archive_path = - value_t!(matches, "incremental_snapshot_archive_path", String) - .ok() - .map(PathBuf::from); - let verbose_level = matches.occurrences_of("verbose"); // Name the rayon global thread pool @@ -1610,15 +1429,14 @@ fn main() { arg_matches, get_access_type(&process_options), ); - let (bank_forks, _) = load_and_process_ledger_or_exit( - arg_matches, - &genesis_config, - Arc::new(blockstore), - process_options, - snapshot_archive_path, - incremental_snapshot_archive_path, - None, - ); + let LoadAndProcessLedgerOutput { bank_forks, .. } = + load_and_process_ledger_or_exit( + arg_matches, + &genesis_config, + Arc::new(blockstore), + process_options, + None, + ); println!( "{}", @@ -1791,9 +1609,12 @@ fn main() { process_options.slot_callback = slot_callback; + let output_format = + OutputFormat::from_matches(arg_matches, "output_format", false); let print_accounts_stats = arg_matches.is_present("print_accounts_stats"); let print_bank_hash = arg_matches.is_present("print_bank_hash"); let write_bank_file = arg_matches.is_present("write_bank_file"); + let genesis_config = open_genesis_config_by(&ledger_path, arg_matches); info!("genesis hash: {}", genesis_config.hash()); @@ -1802,26 +1623,25 @@ fn main() { arg_matches, get_access_type(&process_options), ); - let (bank_forks, _) = load_and_process_ledger_or_exit( - arg_matches, - &genesis_config, - Arc::new(blockstore), - process_options, - snapshot_archive_path, - incremental_snapshot_archive_path, - transaction_status_sender, - ); + let LoadAndProcessLedgerOutput { bank_forks, .. } = + load_and_process_ledger_or_exit( + arg_matches, + &genesis_config, + Arc::new(blockstore), + process_options, + transaction_status_sender, + ); let working_bank = bank_forks.read().unwrap().working_bank(); if print_accounts_stats { working_bank.print_accounts_stats(); } if print_bank_hash { - println!( - "Bank hash for slot {}: {}", - working_bank.slot(), - working_bank.hash() - ); + let slot_bank_hash = SlotBankHash { + slot: working_bank.slot(), + hash: working_bank.hash().to_string(), + }; + println!("{}", output_format.formatted_string(&slot_bank_hash)); } if write_bank_file { bank_hash_details::write_bank_hash_details_file(&working_bank) @@ -1870,15 +1690,14 @@ fn main() { arg_matches, get_access_type(&process_options), ); - let (bank_forks, _) = load_and_process_ledger_or_exit( - arg_matches, - &genesis_config, - Arc::new(blockstore), - process_options, - snapshot_archive_path, - incremental_snapshot_archive_path, - None, - ); + let LoadAndProcessLedgerOutput { bank_forks, .. } = + load_and_process_ledger_or_exit( + arg_matches, + &genesis_config, + Arc::new(blockstore), + process_options, + None, + ); let dot = graph_forks(&bank_forks.read().unwrap(), &graph_config); let extension = Path::new(&output_file).extension(); @@ -1901,6 +1720,13 @@ fn main() { let is_minimized = arg_matches.is_present("minimized"); let output_directory = value_t!(arg_matches, "output_directory", PathBuf) .unwrap_or_else(|_| { + let snapshot_archive_path = value_t!(matches, "snapshots", String) + .ok() + .map(PathBuf::from); + let incremental_snapshot_archive_path = + value_t!(matches, "incremental_snapshot_archive_path", String) + .ok() + .map(PathBuf::from); match ( is_incremental, &snapshot_archive_path, @@ -2034,15 +1860,23 @@ fn main() { output_directory.display() ); - let (bank_forks, starting_snapshot_hashes) = load_and_process_ledger_or_exit( + let LoadAndProcessLedgerOutput { + bank_forks, + starting_snapshot_hashes, + accounts_background_service, + } = load_and_process_ledger_or_exit( arg_matches, &genesis_config, blockstore.clone(), process_options, - snapshot_archive_path, - incremental_snapshot_archive_path, None, ); + // Snapshot creation will implicitly perform AccountsDb + // flush and clean operations. These operations cannot be + // run concurrently, so ensure ABS is stopped to avoid that + // possibility. + accounts_background_service.join().unwrap(); + let mut bank = bank_forks .read() .unwrap() @@ -2091,7 +1925,7 @@ fn main() { if remove_stake_accounts { for (address, mut account) in bank - .get_program_accounts(&stake::program::id(), &ScanConfig::default()) + .get_program_accounts(&stake::program::id(), &ScanConfig::new(false)) .unwrap() .into_iter() { @@ -2141,7 +1975,7 @@ fn main() { if !vote_accounts_to_destake.is_empty() { for (address, mut account) in bank - .get_program_accounts(&stake::program::id(), &ScanConfig::default()) + .get_program_accounts(&stake::program::id(), &ScanConfig::new(false)) .unwrap() .into_iter() { @@ -2181,7 +2015,7 @@ fn main() { for (address, mut account) in bank .get_program_accounts( &solana_vote_program::id(), - &ScanConfig::default(), + &ScanConfig::new(false), ) .unwrap() .into_iter() @@ -2424,15 +2258,14 @@ fn main() { arg_matches, get_access_type(&process_options), ); - let (bank_forks, _) = load_and_process_ledger_or_exit( - arg_matches, - &genesis_config, - Arc::new(blockstore), - process_options, - snapshot_archive_path, - incremental_snapshot_archive_path, - None, - ); + let LoadAndProcessLedgerOutput { bank_forks, .. } = + load_and_process_ledger_or_exit( + arg_matches, + &genesis_config, + Arc::new(blockstore), + process_options, + None, + ); let bank = bank_forks.read().unwrap().working_bank(); let include_sysvars = arg_matches.is_present("include_sysvars"); @@ -2477,15 +2310,14 @@ fn main() { arg_matches, get_access_type(&process_options), ); - let (bank_forks, _) = load_and_process_ledger_or_exit( - arg_matches, - &genesis_config, - Arc::new(blockstore), - process_options, - snapshot_archive_path, - incremental_snapshot_archive_path, - None, - ); + let LoadAndProcessLedgerOutput { bank_forks, .. } = + load_and_process_ledger_or_exit( + arg_matches, + &genesis_config, + Arc::new(blockstore), + process_options, + None, + ); let bank_forks = bank_forks.read().unwrap(); let slot = bank_forks.working_bank().slot(); let bank = bank_forks.get(slot).unwrap_or_else(|| { diff --git a/ledger-tool/src/output.rs b/ledger-tool/src/output.rs index 3de08cec989806..fd0b64eb58c84a 100644 --- a/ledger-tool/src/output.rs +++ b/ledger-tool/src/output.rs @@ -101,6 +101,22 @@ impl Display for SlotBounds<'_> { } } +#[derive(Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct SlotBankHash { + pub slot: Slot, + pub hash: String, +} + +impl VerboseDisplay for SlotBankHash {} +impl QuietDisplay for SlotBankHash {} + +impl Display for SlotBankHash { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + writeln!(f, "Bank hash for slot {}: {}", self.slot, self.hash) + } +} + fn writeln_entry(f: &mut dyn fmt::Write, i: usize, entry: &CliEntry, prefix: &str) -> fmt::Result { writeln!( f, @@ -656,7 +672,7 @@ impl AccountsScanner { match &self.config.mode { AccountsOutputMode::All => { - self.bank.scan_all_accounts(scan_func).unwrap(); + self.bank.scan_all_accounts(scan_func, true).unwrap(); } AccountsOutputMode::Individual(pubkeys) => pubkeys.iter().for_each(|pubkey| { if let Some((account, slot)) = self @@ -676,7 +692,7 @@ impl AccountsScanner { }), AccountsOutputMode::Program(program_pubkey) => self .bank - .get_program_accounts(program_pubkey, &ScanConfig::default()) + .get_program_accounts(program_pubkey, &ScanConfig::new(false)) .unwrap() .iter() .filter(|(_, account)| self.should_process_account(account)) diff --git a/ledger-tool/src/program.rs b/ledger-tool/src/program.rs index 15750290fbed2a..463d017b17dbed 100644 --- a/ledger-tool/src/program.rs +++ b/ledger-tool/src/program.rs @@ -1,6 +1,6 @@ use { crate::{args::*, canonicalize_ledger_path, ledger_utils::*}, - clap::{value_t, App, AppSettings, Arg, ArgMatches, SubCommand}, + clap::{App, AppSettings, Arg, ArgMatches, SubCommand}, log::*, serde_derive::{Deserialize, Serialize}, serde_json::Result, @@ -9,7 +9,7 @@ use { syscalls::create_program_runtime_environment_v1, }, solana_cli_output::{OutputFormat, QuietDisplay, VerboseDisplay}, - solana_ledger::{blockstore_options::AccessType, use_snapshot_archives_at_startup}, + solana_ledger::blockstore_options::AccessType, solana_program_runtime::{ invoke_context::InvokeContext, loaded_programs::{ @@ -36,7 +36,7 @@ use { fmt::{self, Debug, Formatter}, fs::File, io::{Read, Seek, Write}, - path::{Path, PathBuf}, + path::Path, process::exit, sync::Arc, time::{Duration, Instant}, @@ -75,24 +75,15 @@ fn load_accounts(path: &Path) -> Result { fn load_blockstore(ledger_path: &Path, arg_matches: &ArgMatches<'_>) -> Arc { let process_options = parse_process_options(ledger_path, arg_matches); - let snapshot_archive_path = value_t!(arg_matches, "snapshots", String) - .ok() - .map(PathBuf::from); - let incremental_snapshot_archive_path = - value_t!(arg_matches, "incremental_snapshot_archive_path", String) - .ok() - .map(PathBuf::from); let genesis_config = open_genesis_config_by(ledger_path, arg_matches); info!("genesis hash: {}", genesis_config.hash()); let blockstore = open_blockstore(ledger_path, arg_matches, AccessType::Secondary); - let (bank_forks, ..) = load_and_process_ledger_or_exit( + let LoadAndProcessLedgerOutput { bank_forks, .. } = load_and_process_ledger_or_exit( arg_matches, &genesis_config, Arc::new(blockstore), process_options, - snapshot_archive_path, - incremental_snapshot_archive_path, None, ); let bank = bank_forks.read().unwrap().working_bank(); @@ -112,22 +103,9 @@ impl ProgramSubCommand for App<'_, '_> { ) .required(true) .index(1); - let max_genesis_arg = Arg::with_name("max_genesis_archive_unpacked_size") - .long("max-genesis-archive-unpacked-size") - .value_name("NUMBER") - .takes_value(true) - .default_value("10485760") - .help("maximum total uncompressed size of unpacked genesis archive"); - let use_snapshot_archives_at_startup = - Arg::with_name(use_snapshot_archives_at_startup::cli::NAME) - .long(use_snapshot_archives_at_startup::cli::LONG_ARG) - .takes_value(true) - .possible_values(use_snapshot_archives_at_startup::cli::POSSIBLE_VALUES) - .default_value( - use_snapshot_archives_at_startup::cli::default_value_for_ledger_tool(), - ) - .help(use_snapshot_archives_at_startup::cli::HELP) - .long_help(use_snapshot_archives_at_startup::cli::LONG_HELP); + + let load_genesis_config_arg = load_genesis_arg(); + let snapshot_config_args = snapshot_args(); self.subcommand( SubCommand::with_name("program") @@ -179,8 +157,8 @@ and the following fields are required .takes_value(true) .default_value("0"), ) - .arg(&max_genesis_arg) - .arg(&use_snapshot_archives_at_startup) + .arg(&load_genesis_config_arg) + .args(&snapshot_config_args) .arg( Arg::with_name("memory") .help("Heap memory for the program to run on") @@ -579,7 +557,7 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { account_lengths, &mut invoke_context, ); - let mut vm = vm.unwrap(); + let (mut vm, _, _) = vm.unwrap(); let start_time = Instant::now(); if matches.value_of("mode").unwrap() == "debugger" { vm.debug_port = Some(matches.value_of("port").unwrap().parse::().unwrap()); diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 450f7b4f005f3c..0342a323905876 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -57,8 +57,9 @@ use { solana_storage_proto::{StoredExtendedRewards, StoredTransactionStatusMeta}, solana_transaction_status::{ ConfirmedTransactionStatusWithSignature, ConfirmedTransactionWithStatusMeta, Rewards, - TransactionStatusMeta, TransactionWithStatusMeta, VersionedConfirmedBlock, - VersionedConfirmedBlockWithEntries, VersionedTransactionWithStatusMeta, + RewardsAndNumPartitions, TransactionStatusMeta, TransactionWithStatusMeta, + VersionedConfirmedBlock, VersionedConfirmedBlockWithEntries, + VersionedTransactionWithStatusMeta, }, std::{ borrow::Cow, @@ -2678,7 +2679,7 @@ impl Blockstore { Hash::default() }; - let rewards = self + let (rewards, num_partitions) = self .rewards_cf .get_protobuf_or_bincode::(slot)? .unwrap_or_default() @@ -2699,6 +2700,7 @@ impl Blockstore { transactions: self .map_transactions_to_statuses(slot, slot_transaction_iterator)?, rewards, + num_partitions, block_time, block_height, }; @@ -3371,7 +3373,7 @@ impl Blockstore { .map(|result| result.map(|option| option.into())) } - pub fn write_rewards(&self, index: Slot, rewards: Rewards) -> Result<()> { + pub fn write_rewards(&self, index: Slot, rewards: RewardsAndNumPartitions) -> Result<()> { let rewards = rewards.into(); self.rewards_cf.put_protobuf(index, &rewards) } @@ -8302,6 +8304,7 @@ pub mod tests { blockhash: blockhash.to_string(), previous_blockhash: Hash::default().to_string(), rewards: vec![], + num_partitions: None, block_time: None, block_height: None, }; @@ -8316,6 +8319,7 @@ pub mod tests { blockhash: blockhash.to_string(), previous_blockhash: blockhash.to_string(), rewards: vec![], + num_partitions: None, block_time: None, block_height: None, }; @@ -8333,6 +8337,7 @@ pub mod tests { blockhash: blockhash.to_string(), previous_blockhash: blockhash.to_string(), rewards: vec![], + num_partitions: None, block_time: None, block_height: None, }; diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index 30abc9bf6eaea6..0e1c247b1af1e9 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -62,7 +62,8 @@ use { solana_svm::{ transaction_processor::ExecutionRecordingConfig, transaction_results::{ - TransactionExecutionDetails, TransactionExecutionResult, TransactionResults, + TransactionExecutionDetails, TransactionExecutionResult, + TransactionLoadedAccountsStats, TransactionResults, }, }, solana_transaction_status::token_balances::TransactionTokenBalancesSet, @@ -80,6 +81,7 @@ use { time::{Duration, Instant}, }, thiserror::Error, + ExecuteTimingType::{NumExecuteBatches, TotalBatchesLen}, }; pub struct TransactionBatchWithIndexes<'a, 'b> { @@ -180,11 +182,33 @@ pub fn execute_batch( let TransactionResults { fee_collection_results, + loaded_accounts_stats, execution_results, rent_debits, .. } = tx_results; + let (check_block_cost_limits_result, check_block_cost_limits_time): (Result<()>, Measure) = + measure!(if bank + .feature_set + .is_active(&feature_set::apply_cost_tracker_during_replay::id()) + { + check_block_cost_limits( + bank, + &loaded_accounts_stats, + &execution_results, + batch.sanitized_transactions(), + ) + } else { + Ok(()) + }); + + timings.saturating_add_in_place( + ExecuteTimingType::CheckBlockLimitsUs, + check_block_cost_limits_time.as_us(), + ); + check_block_cost_limits_result?; + let executed_transactions = execution_results .iter() .zip(batch.sanitized_transactions()) @@ -219,6 +243,49 @@ pub fn execute_batch( first_err.map(|(result, _)| result).unwrap_or(Ok(())) } +// collect transactions actual execution costs, subject to block limits; +// block will be marked as dead if exceeds cost limits, details will be +// reported to metric `replay-stage-mark_dead_slot` +fn check_block_cost_limits( + bank: &Bank, + loaded_accounts_stats: &[Result], + execution_results: &[TransactionExecutionResult], + sanitized_transactions: &[SanitizedTransaction], +) -> Result<()> { + assert_eq!(loaded_accounts_stats.len(), execution_results.len()); + + let tx_costs_with_actual_execution_units: Vec<_> = execution_results + .iter() + .zip(loaded_accounts_stats) + .zip(sanitized_transactions) + .filter_map(|((execution_result, loaded_accounts_stats), tx)| { + if let Some(details) = execution_result.details() { + let tx_cost = CostModel::calculate_cost_for_executed_transaction( + tx, + details.executed_units, + loaded_accounts_stats + .as_ref() + .map_or(0, |stats| stats.loaded_accounts_data_size), + &bank.feature_set, + ); + Some(tx_cost) + } else { + None + } + }) + .collect(); + + { + let mut cost_tracker = bank.write_cost_tracker().unwrap(); + for tx_cost in &tx_costs_with_actual_execution_units { + cost_tracker + .try_add(tx_cost) + .map_err(TransactionError::from)?; + } + } + Ok(()) +} + #[derive(Default)] pub struct ExecuteBatchesInternalMetrics { execution_timings_per_thread: HashMap, @@ -455,22 +522,10 @@ fn rebatch_and_execute_batches( let cost = tx_cost.sum(); minimal_tx_cost = std::cmp::min(minimal_tx_cost, cost); total_cost = total_cost.saturating_add(cost); - tx_cost + cost }) .collect::>(); - if bank - .feature_set - .is_active(&feature_set::apply_cost_tracker_during_replay::id()) - { - let mut cost_tracker = bank.write_cost_tracker().unwrap(); - for tx_cost in &tx_costs { - cost_tracker - .try_add(tx_cost) - .map_err(TransactionError::from)?; - } - } - let target_batch_count = get_thread_count() as u64; let mut tx_batches: Vec = vec![]; @@ -478,26 +533,23 @@ fn rebatch_and_execute_batches( let target_batch_cost = total_cost / target_batch_count; let mut batch_cost: u64 = 0; let mut slice_start = 0; - tx_costs - .into_iter() - .enumerate() - .for_each(|(index, tx_cost)| { - let next_index = index + 1; - batch_cost = batch_cost.saturating_add(tx_cost.sum()); - if batch_cost >= target_batch_cost || next_index == sanitized_txs.len() { - let tx_batch = rebatch_transactions( - &lock_results, - bank, - &sanitized_txs, - slice_start, - index, - &transaction_indexes, - ); - slice_start = next_index; - tx_batches.push(tx_batch); - batch_cost = 0; - } - }); + tx_costs.into_iter().enumerate().for_each(|(index, cost)| { + let next_index = index + 1; + batch_cost = batch_cost.saturating_add(cost); + if batch_cost >= target_batch_cost || next_index == sanitized_txs.len() { + let tx_batch = rebatch_transactions( + &lock_results, + bank, + &sanitized_txs, + slice_start, + index, + &transaction_indexes, + ); + slice_start = next_index; + tx_batches.push(tx_batch); + batch_cost = 0; + } + }); &tx_batches[..] } else { batches @@ -513,7 +565,8 @@ fn rebatch_and_execute_batches( prioritization_fee_cache, )?; - timing.accumulate(execute_batches_internal_metrics); + // Pass false because this code-path is never touched by unified scheduler. + timing.accumulate(execute_batches_internal_metrics, false); Ok(()) } @@ -1079,11 +1132,15 @@ pub struct ConfirmationTiming { /// and replay. As replay can run in parallel with the verification, this value can not be /// recovered from the `replay_elapsed` and or `{poh,transaction}_verify_elapsed`. This /// includes failed cases, when `confirm_slot_entries` exist with an error. In microseconds. + /// When unified scheduler is enabled, replay excludes the transaction execution, only + /// accounting for task creation and submission to the scheduler. pub confirmation_elapsed: u64, /// Wall clock time used by the entry replay code. Does not include the PoH or the transaction /// signature/precompiles verification, but can overlap with the PoH and signature verification. /// In microseconds. + /// When unified scheduler is enabled, replay excludes the transaction execution, only + /// accounting for task creation and submission to the scheduler. pub replay_elapsed: u64, /// Wall clock times, used for the PoH verification of entries. In microseconds. @@ -1129,42 +1186,59 @@ pub struct BatchExecutionTiming { /// Wall clock time used by the transaction execution part of pipeline. /// [`ConfirmationTiming::replay_elapsed`] includes this time. In microseconds. - pub wall_clock_us: u64, + wall_clock_us: u64, /// Time used to execute transactions, via `execute_batch()`, in the thread that consumed the - /// most time. - pub slowest_thread: ThreadExecuteTimings, + /// most time (in terms of total_thread_us) among rayon threads. Note that the slowest thread + /// is determined each time a given group of batches is newly processed. So, this is a coarse + /// approximation of wall-time single-threaded linearized metrics, discarding all metrics other + /// than the arbitrary set of batches mixed with various transactions, which replayed slowest + /// as a whole for each rayon processing session, also after blockstore_processor's rebatching. + /// + /// When unified scheduler is enabled, this field isn't maintained, because it's not batched at + /// all. + slowest_thread: ThreadExecuteTimings, } impl BatchExecutionTiming { - pub fn accumulate(&mut self, new_batch: ExecuteBatchesInternalMetrics) { + pub fn accumulate( + &mut self, + new_batch: ExecuteBatchesInternalMetrics, + is_unified_scheduler_enabled: bool, + ) { let Self { totals, wall_clock_us, slowest_thread, } = self; - saturating_add_assign!(*wall_clock_us, new_batch.execute_batches_us); + // These metric fields aren't applicable for the unified scheduler + if !is_unified_scheduler_enabled { + saturating_add_assign!(*wall_clock_us, new_batch.execute_batches_us); - use ExecuteTimingType::{NumExecuteBatches, TotalBatchesLen}; - totals.saturating_add_in_place(TotalBatchesLen, new_batch.total_batches_len); - totals.saturating_add_in_place(NumExecuteBatches, 1); + totals.saturating_add_in_place(TotalBatchesLen, new_batch.total_batches_len); + totals.saturating_add_in_place(NumExecuteBatches, 1); + } for thread_times in new_batch.execution_timings_per_thread.values() { totals.accumulate(&thread_times.execute_timings); } - let slowest = new_batch - .execution_timings_per_thread - .values() - .max_by_key(|thread_times| thread_times.total_thread_us); - - if let Some(slowest) = slowest { - slowest_thread.accumulate(slowest); - slowest_thread - .execute_timings - .saturating_add_in_place(NumExecuteBatches, 1); - }; + // This whole metric (replay-slot-end-to-end-stats) isn't applicable for the unified + // scheduler. + if !is_unified_scheduler_enabled { + let slowest = new_batch + .execution_timings_per_thread + .values() + .max_by_key(|thread_times| thread_times.total_thread_us); + + if let Some(slowest) = slowest { + slowest_thread.accumulate(slowest); + slowest_thread + .execute_timings + .saturating_add_in_place(NumExecuteBatches, 1); + }; + } } } @@ -1185,7 +1259,8 @@ impl ThreadExecuteTimings { ("total_transactions_executed", self.total_transactions_executed as i64, i64), // Everything inside the `eager!` block will be eagerly expanded before // evaluation of the rest of the surrounding macro. - eager!{report_execute_timings!(self.execute_timings)} + // Pass false because this code-path is never touched by unified scheduler. + eager!{report_execute_timings!(self.execute_timings, false)} ); }; } @@ -1222,7 +1297,24 @@ impl ReplaySlotStats { num_entries: usize, num_shreds: u64, bank_complete_time_us: u64, + is_unified_scheduler_enabled: bool, ) { + let confirmation_elapsed = if is_unified_scheduler_enabled { + "confirmation_without_replay_us" + } else { + "confirmation_time_us" + }; + let replay_elapsed = if is_unified_scheduler_enabled { + "task_submission_us" + } else { + "replay_time" + }; + let execute_batches_us = if is_unified_scheduler_enabled { + None + } else { + Some(self.batch_execute.wall_clock_us as i64) + }; + lazy! { datapoint_info!( "replay-slot-stats", @@ -1243,9 +1335,9 @@ impl ReplaySlotStats { self.transaction_verify_elapsed as i64, i64 ), - ("confirmation_time_us", self.confirmation_elapsed as i64, i64), - ("replay_time", self.replay_elapsed as i64, i64), - ("execute_batches_us", self.batch_execute.wall_clock_us as i64, i64), + (confirmation_elapsed, self.confirmation_elapsed as i64, i64), + (replay_elapsed, self.replay_elapsed as i64, i64), + ("execute_batches_us", execute_batches_us, Option), ( "replay_total_elapsed", self.started.elapsed().as_micros() as i64, @@ -1257,11 +1349,17 @@ impl ReplaySlotStats { ("total_shreds", num_shreds as i64, i64), // Everything inside the `eager!` block will be eagerly expanded before // evaluation of the rest of the surrounding macro. - eager!{report_execute_timings!(self.batch_execute.totals)} + eager!{report_execute_timings!(self.batch_execute.totals, is_unified_scheduler_enabled)} ); }; - self.batch_execute.slowest_thread.report_stats(slot); + // Skip reporting replay-slot-end-to-end-stats entirely if unified scheduler is enabled, + // because the whole metrics itself is only meaningful for rayon-based worker threads. + // + // See slowest_thread doc comment for details. + if !is_unified_scheduler_enabled { + self.batch_execute.slowest_thread.report_stats(slot); + } let mut per_pubkey_timings: Vec<_> = self .batch_execute @@ -2151,6 +2249,7 @@ pub mod tests { }, assert_matches::assert_matches, rand::{thread_rng, Rng}, + solana_cost_model::transaction_cost::TransactionCost, solana_entry::entry::{create_ticks, next_entry, next_entry_mut}, solana_program_runtime::declare_process_instruction, solana_runtime::{ @@ -4975,4 +5074,69 @@ pub mod tests { } } } + + #[test] + fn test_check_block_cost_limit() { + let dummy_leader_pubkey = solana_sdk::pubkey::new_rand(); + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config_with_leader(500, &dummy_leader_pubkey, 100); + let bank = Bank::new_for_tests(&genesis_config); + + let tx = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &Pubkey::new_unique(), + 1, + genesis_config.hash(), + )); + let mut tx_cost = CostModel::calculate_cost(&tx, &bank.feature_set); + let actual_execution_cu = 1; + let actual_loaded_accounts_data_size = 64 * 1024; + let TransactionCost::Transaction(ref mut usage_cost_details) = tx_cost else { + unreachable!("test tx is non-vote tx"); + }; + usage_cost_details.programs_execution_cost = actual_execution_cu; + usage_cost_details.loaded_accounts_data_size_cost = + CostModel::calculate_loaded_accounts_data_size_cost( + actual_loaded_accounts_data_size, + &bank.feature_set, + ); + // set block-limit to be able to just have one transaction + let block_limit = tx_cost.sum(); + + bank.write_cost_tracker() + .unwrap() + .set_limits(u64::MAX, block_limit, u64::MAX); + let txs = vec![tx.clone(), tx]; + let results = vec![ + TransactionExecutionResult::Executed { + details: TransactionExecutionDetails { + status: Ok(()), + log_messages: None, + inner_instructions: None, + fee_details: solana_sdk::fee::FeeDetails::default(), + return_data: None, + executed_units: actual_execution_cu, + accounts_data_len_delta: 0, + }, + programs_modified_by_tx: HashMap::new(), + }, + TransactionExecutionResult::NotExecuted(TransactionError::AccountNotFound), + ]; + let loaded_accounts_stats = vec![ + Ok(TransactionLoadedAccountsStats { + loaded_accounts_data_size: actual_loaded_accounts_data_size, + loaded_accounts_count: 2 + }); + 2 + ]; + + assert!(check_block_cost_limits(&bank, &loaded_accounts_stats, &results, &txs).is_ok()); + assert_eq!( + Err(TransactionError::WouldExceedMaxBlockCostLimit), + check_block_cost_limits(&bank, &loaded_accounts_stats, &results, &txs) + ); + } } diff --git a/local-cluster/Cargo.toml b/local-cluster/Cargo.toml index 0d72a867c60ac1..3e4cbc0e366531 100644 --- a/local-cluster/Cargo.toml +++ b/local-cluster/Cargo.toml @@ -48,6 +48,7 @@ serial_test = { workspace = true } solana-core = { workspace = true, features = ["dev-context-only-utils"] } solana-download-utils = { workspace = true } solana-ledger = { workspace = true, features = ["dev-context-only-utils"] } +solana-local-cluster = { path = ".", features = ["dev-context-only-utils"] } solana-runtime = { workspace = true, features = ["dev-context-only-utils"] } [package.metadata.docs.rs] diff --git a/local-cluster/src/cluster_tests.rs b/local-cluster/src/cluster_tests.rs index b31c874b9e6db3..9b80c15824f494 100644 --- a/local-cluster/src/cluster_tests.rs +++ b/local-cluster/src/cluster_tests.rs @@ -4,14 +4,11 @@ /// discover the rest of the network. use log::*; use { - crate::cluster::QuicTpuClient, + crate::{cluster::QuicTpuClient, local_cluster::LocalCluster}, rand::{thread_rng, Rng}, rayon::{prelude::*, ThreadPool}, solana_client::connection_cache::{ConnectionCache, Protocol}, - solana_core::consensus::{ - tower_storage::{FileTowerStorage, SavedTower, SavedTowerVersions, TowerStorage}, - VOTE_THRESHOLD_DEPTH, - }, + solana_core::consensus::VOTE_THRESHOLD_DEPTH, solana_entry::entry::{self, Entry, EntrySlice}, solana_gossip::{ cluster_info::{self, ClusterInfo}, @@ -44,7 +41,7 @@ use { std::{ collections::{HashMap, HashSet, VecDeque}, net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}, - path::{Path, PathBuf}, + path::Path, sync::{ atomic::{AtomicBool, Ordering}, Arc, RwLock, @@ -53,6 +50,13 @@ use { time::{Duration, Instant}, }, }; +#[cfg(feature = "dev-context-only-utils")] +use { + solana_core::consensus::tower_storage::{ + FileTowerStorage, SavedTower, SavedTowerVersions, TowerStorage, + }, + std::path::PathBuf, +}; pub fn get_client_facing_addr( protocol: Protocol, @@ -101,12 +105,17 @@ pub fn spend_and_verify_all_nodes( .rpc_client() .get_latest_blockhash_with_commitment(CommitmentConfig::confirmed()) .unwrap(); - let transaction = + let mut transaction = system_transaction::transfer(funding_keypair, &random_keypair.pubkey(), 1, blockhash); let confs = VOTE_THRESHOLD_DEPTH + 1; - client - .send_transaction_to_upcoming_leaders(&transaction) - .unwrap(); + LocalCluster::send_transaction_with_retries( + &client, + &[funding_keypair], + &mut transaction, + 10, + confs, + ) + .unwrap(); for validator in &cluster_nodes { if ignore_nodes.contains(validator.pubkey()) { continue; @@ -160,16 +169,21 @@ pub fn send_many_transactions( .unwrap(); let transfer_amount = thread_rng().gen_range(1..max_tokens_per_transfer); - let transaction = system_transaction::transfer( + let mut transaction = system_transaction::transfer( funding_keypair, &random_keypair.pubkey(), transfer_amount, blockhash, ); - client - .send_transaction_to_upcoming_leaders(&transaction) - .unwrap(); + LocalCluster::send_transaction_with_retries( + &client, + &[funding_keypair], + &mut transaction, + 5, + 0, + ) + .unwrap(); expected_balances.insert(random_keypair.pubkey(), transfer_amount); } @@ -292,7 +306,7 @@ pub fn kill_entry_and_spend_and_verify_rest( .rpc_client() .get_latest_blockhash_with_commitment(CommitmentConfig::processed()) .unwrap(); - let transaction = system_transaction::transfer( + let mut transaction = system_transaction::transfer( funding_keypair, &random_keypair.pubkey(), 1, @@ -300,16 +314,29 @@ pub fn kill_entry_and_spend_and_verify_rest( ); let confs = VOTE_THRESHOLD_DEPTH + 1; - if let Err(e) = client.send_transaction_to_upcoming_leaders(&transaction) { - result = Err(e); - continue; - } + let sig = { + let sig = LocalCluster::send_transaction_with_retries( + &client, + &[funding_keypair], + &mut transaction, + 5, + confs, + ); + match sig { + Err(e) => { + result = Err(e); + continue; + } + + Ok(sig) => sig, + } + }; info!("poll_all_nodes_for_signature()"); match poll_all_nodes_for_signature( entry_point_info, &cluster_nodes, connection_cache, - &transaction.signatures[0], + &sig, confs, ) { Err(e) => { @@ -325,6 +352,7 @@ pub fn kill_entry_and_spend_and_verify_rest( } } +#[cfg(feature = "dev-context-only-utils")] pub fn apply_votes_to_tower(node_keypair: &Keypair, votes: Vec<(Slot, Hash)>, tower_path: PathBuf) { let tower_storage = FileTowerStorage::new(tower_path); let mut tower = tower_storage.load(&node_keypair.pubkey()).unwrap(); diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 64fefb1114f99e..f6bef8bff3c63d 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -28,7 +28,7 @@ use { }, solana_sdk::{ account::{Account, AccountSharedData}, - clock::{Slot, DEFAULT_DEV_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT}, + clock::{Slot, DEFAULT_DEV_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE}, commitment_config::CommitmentConfig, epoch_schedule::EpochSchedule, feature_set, @@ -36,13 +36,15 @@ use { message::Message, poh_config::PohConfig, pubkey::Pubkey, - signature::{Keypair, Signer}, + signature::{Keypair, Signature, Signer}, + signers::Signers, stake::{ instruction as stake_instruction, state::{Authorized, Lockup}, }, system_transaction, transaction::Transaction, + transport::TransportError, }, solana_stake_program::stake_state, solana_streamer::{socket::SocketAddrSpace, streamer::StakedNodes}, @@ -61,6 +63,7 @@ use { net::{IpAddr, Ipv4Addr, UdpSocket}, path::{Path, PathBuf}, sync::{Arc, RwLock}, + time::Instant, }, }; @@ -187,44 +190,43 @@ impl LocalCluster { pub fn new(config: &mut ClusterConfig, socket_addr_space: SocketAddrSpace) -> Self { assert_eq!(config.validator_configs.len(), config.node_stakes.len()); - let connection_cache = match config.tpu_use_quic { - true => { - let client_keypair = Keypair::new(); - let stake = DEFAULT_NODE_STAKE; + let connection_cache = if config.tpu_use_quic { + let client_keypair = Keypair::new(); + let stake = DEFAULT_NODE_STAKE; - for validator_config in config.validator_configs.iter_mut() { - let mut overrides = HashMap::new(); - overrides.insert(client_keypair.pubkey(), stake); - validator_config.staked_nodes_overrides = Arc::new(RwLock::new(overrides)); - } - - assert!( - config.tpu_use_quic, - "no support for staked override forwarding without quic" - ); - - let total_stake = config.node_stakes.iter().sum::(); - let stakes = HashMap::from([ - (client_keypair.pubkey(), stake), - (Pubkey::new_unique(), total_stake.saturating_sub(stake)), - ]); - let staked_nodes = Arc::new(RwLock::new(StakedNodes::new( - Arc::new(stakes), - HashMap::::default(), // overrides - ))); - - Arc::new(ConnectionCache::new_with_client_options( - "connection_cache_local_cluster_quic_staked", - config.tpu_connection_pool_size, - None, - Some((&client_keypair, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))), - Some((&staked_nodes, &client_keypair.pubkey())), - )) + for validator_config in config.validator_configs.iter_mut() { + let mut overrides = HashMap::new(); + overrides.insert(client_keypair.pubkey(), stake); + validator_config.staked_nodes_overrides = Arc::new(RwLock::new(overrides)); } - false => Arc::new(ConnectionCache::with_udp( + + assert!( + config.tpu_use_quic, + "no support for staked override forwarding without quic" + ); + + let total_stake = config.node_stakes.iter().sum::(); + let stakes = HashMap::from([ + (client_keypair.pubkey(), stake), + (Pubkey::new_unique(), total_stake.saturating_sub(stake)), + ]); + let staked_nodes = Arc::new(RwLock::new(StakedNodes::new( + Arc::new(stakes), + HashMap::::default(), // overrides + ))); + + Arc::new(ConnectionCache::new_with_client_options( + "connection_cache_local_cluster_quic_staked", + config.tpu_connection_pool_size, + None, + Some((&client_keypair, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))), + Some((&staked_nodes, &client_keypair.pubkey())), + )) + } else { + Arc::new(ConnectionCache::with_udp( "connection_cache_local_cluster_udp", config.tpu_connection_pool_size, - )), + )) }; let mut validator_keys = { @@ -665,6 +667,53 @@ impl LocalCluster { info!("{} done waiting for roots", test_name); } + /// Attempt to send and confirm tx "attempts" times + /// Wait for signature confirmation before returning + /// Return the transaction signature + pub fn send_transaction_with_retries( + client: &QuicTpuClient, + keypairs: &T, + transaction: &mut Transaction, + attempts: usize, + pending_confirmations: usize, + ) -> std::result::Result { + for attempt in 0..attempts { + let now = Instant::now(); + let mut num_confirmed = 0; + let mut wait_time = MAX_PROCESSING_AGE; + + while now.elapsed().as_secs() < wait_time as u64 { + if num_confirmed == 0 { + client.send_transaction_to_upcoming_leaders(transaction)?; + } + + if let Ok(confirmed_blocks) = client.rpc_client().poll_for_signature_confirmation( + &transaction.signatures[0], + pending_confirmations, + ) { + num_confirmed = confirmed_blocks; + if confirmed_blocks >= pending_confirmations { + return Ok(transaction.signatures[0]); + } + // Since network has seen the transaction, wait longer to receive + // all pending confirmations. Resending the transaction could result into + // extra transaction fees + wait_time = wait_time.max( + MAX_PROCESSING_AGE * pending_confirmations.saturating_sub(num_confirmed), + ); + } + } + info!("{attempt} tries failed transfer"); + let blockhash = client.rpc_client().get_latest_blockhash()?; + transaction.sign(keypairs, blockhash); + } + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "failed to confirm transaction".to_string(), + ) + .into()) + } + fn transfer_with_client( client: &QuicTpuClient, source_keypair: &Keypair, @@ -676,7 +725,7 @@ impl LocalCluster { .rpc_client() .get_latest_blockhash_with_commitment(CommitmentConfig::processed()) .unwrap(); - let tx = system_transaction::transfer(source_keypair, dest_pubkey, lamports, blockhash); + let mut tx = system_transaction::transfer(source_keypair, dest_pubkey, lamports, blockhash); info!( "executing transfer of {} from {} to {}", lamports, @@ -684,8 +733,7 @@ impl LocalCluster { *dest_pubkey ); - client - .send_transaction_to_upcoming_leaders(&tx) + LocalCluster::send_transaction_with_retries(client, &[source_keypair], &mut tx, 10, 0) .expect("client transfer should succeed"); client .rpc_client() @@ -749,7 +797,7 @@ impl LocalCluster { }, ); let message = Message::new(&instructions, Some(&from_account.pubkey())); - let transaction = Transaction::new( + let mut transaction = Transaction::new( &[from_account.as_ref(), vote_account], message, client @@ -758,9 +806,14 @@ impl LocalCluster { .unwrap() .0, ); - client - .send_transaction_to_upcoming_leaders(&transaction) - .expect("fund vote"); + LocalCluster::send_transaction_with_retries( + client, + &[from_account], + &mut transaction, + 10, + 0, + ) + .expect("should fund vote"); client .rpc_client() .wait_for_balance_with_commitment( @@ -779,7 +832,7 @@ impl LocalCluster { amount, ); let message = Message::new(&instructions, Some(&from_account.pubkey())); - let transaction = Transaction::new( + let mut transaction = Transaction::new( &[from_account.as_ref(), &stake_account_keypair], message, client @@ -789,9 +842,14 @@ impl LocalCluster { .0, ); - client - .send_transaction_to_upcoming_leaders(&transaction) - .expect("delegate stake"); + LocalCluster::send_transaction_with_retries( + client, + &[from_account.as_ref(), &stake_account_keypair], + &mut transaction, + 5, + 0, + ) + .expect("should delegate stake"); client .rpc_client() .wait_for_balance_with_commitment( diff --git a/local-cluster/tests/local_cluster.rs b/local-cluster/tests/local_cluster.rs index f0613a0d0e5469..8826ca0a17ed8c 100644 --- a/local-cluster/tests/local_cluster.rs +++ b/local-cluster/tests/local_cluster.rs @@ -222,7 +222,7 @@ fn test_local_cluster_signature_subscribe() { .get_latest_blockhash_with_commitment(CommitmentConfig::processed()) .unwrap(); - let transaction = system_transaction::transfer( + let mut transaction = system_transaction::transfer( &cluster.funding_keypair, &solana_sdk::pubkey::new_rand(), 10, @@ -239,9 +239,14 @@ fn test_local_cluster_signature_subscribe() { ) .unwrap(); - tx_client - .send_transaction_to_upcoming_leaders(&transaction) - .unwrap(); + LocalCluster::send_transaction_with_retries( + &tx_client, + &[&cluster.funding_keypair], + &mut transaction, + 5, + 0, + ) + .unwrap(); let mut got_received_notification = false; loop { @@ -1788,12 +1793,9 @@ fn test_validator_saves_tower() { // Wait for the first new root let last_replayed_root = loop { - #[allow(deprecated)] - // This test depends on knowing the immediate root, without any delay from the commitment - // service, so the deprecated CommitmentConfig::root() is retained if let Ok(root) = validator_client .rpc_client() - .get_slot_with_commitment(CommitmentConfig::root()) + .get_slot_with_commitment(CommitmentConfig::finalized()) { trace!("current root: {}", root); if root > 0 { @@ -1820,12 +1822,9 @@ fn test_validator_saves_tower() { // Wait for a new root, demonstrating the validator was able to make progress from the older `tower1` let new_root = loop { - #[allow(deprecated)] - // This test depends on knowing the immediate root, without any delay from the commitment - // service, so the deprecated CommitmentConfig::root() is retained if let Ok(root) = validator_client .rpc_client() - .get_slot_with_commitment(CommitmentConfig::root()) + .get_slot_with_commitment(CommitmentConfig::finalized()) { trace!( "current root: {}, last_replayed_root: {}", @@ -1856,12 +1855,9 @@ fn test_validator_saves_tower() { // Wait for another new root let new_root = loop { - #[allow(deprecated)] - // This test depends on knowing the immediate root, without any delay from the commitment - // service, so the deprecated CommitmentConfig::root() is retained if let Ok(root) = validator_client .rpc_client() - .get_slot_with_commitment(CommitmentConfig::root()) + .get_slot_with_commitment(CommitmentConfig::finalized()) { trace!("current root: {}, last tower root: {}", root, tower3_root); if root > tower3_root { @@ -2669,6 +2665,7 @@ fn test_oc_bad_signatures() { // 3) Start up a spy to listen for and push votes to leader TPU let client = cluster.build_tpu_quic_client().unwrap(); + let cluster_funding_keypair = cluster.funding_keypair.insecure_clone(); let voter_thread_sleep_ms: usize = 100; let num_votes_simulated = Arc::new(AtomicUsize::new(0)); let gossip_voter = cluster_tests::start_gossip_voter( @@ -2703,7 +2700,7 @@ fn test_oc_bad_signatures() { let vote_slots: Vec = vec![vote_slot]; let bad_authorized_signer_keypair = Keypair::new(); - let vote_tx = vote_transaction::new_vote_transaction( + let mut vote_tx = vote_transaction::new_vote_transaction( vote_slots, vote_hash, leader_vote_tx.message.recent_blockhash, @@ -2713,9 +2710,14 @@ fn test_oc_bad_signatures() { &bad_authorized_signer_keypair, None, ); - client - .send_transaction_to_upcoming_leaders(&vote_tx) - .unwrap(); + LocalCluster::send_transaction_with_retries( + &client, + &[&cluster_funding_keypair], + &mut vote_tx, + 5, + 0, + ) + .unwrap(); num_votes_simulated.fetch_add(1, Ordering::Relaxed); } diff --git a/net/gce.sh b/net/gce.sh index 058dcdcf0e7590..2c48cb7132f4a8 100755 --- a/net/gce.sh +++ b/net/gce.sh @@ -805,6 +805,7 @@ $( install-certbot.sh \ install-earlyoom.sh \ install-iftop.sh \ + install-jq.sh \ install-libssl-compatability.sh \ install-rsync.sh \ install-perf.sh \ diff --git a/net/remote/remote-node.sh b/net/remote/remote-node.sh index 71378019730f05..882f7891702cde 100755 --- a/net/remote/remote-node.sh +++ b/net/remote/remote-node.sh @@ -263,7 +263,7 @@ EOF agave-ledger-tool -l config/bootstrap-validator shred-version --max-genesis-archive-unpacked-size 1073741824 | tee config/shred-version if [[ -n "$maybeWaitForSupermajority" ]]; then - bankHash=$(agave-ledger-tool -l config/bootstrap-validator bank-hash --halt-at-slot 0) + bankHash=$(agave-ledger-tool -l config/bootstrap-validator verify --halt-at-slot 0 --print-bank-hash --output json | jq -r ".hash") shredVersion="$(cat "$SOLANA_CONFIG_DIR"/shred-version)" extraNodeArgs="$extraNodeArgs --expected-bank-hash $bankHash --expected-shred-version $shredVersion" echo "$bankHash" > config/bank-hash diff --git a/net/scripts/install-jq.sh b/net/scripts/install-jq.sh new file mode 100755 index 00000000000000..9d7c837c8dd9f1 --- /dev/null +++ b/net/scripts/install-jq.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# +# jq setup +# +set -ex + +[[ $(uname) = Linux ]] || exit 1 +[[ $USER = root ]] || exit 1 + +apt-get --assume-yes install jq diff --git a/program-runtime/Cargo.toml b/program-runtime/Cargo.toml index d84114e90213f3..8c8a1010051105 100644 --- a/program-runtime/Cargo.toml +++ b/program-runtime/Cargo.toml @@ -28,6 +28,7 @@ solana-frozen-abi-macro = { workspace = true, optional = true } solana-measure = { workspace = true } # solana-metrics = { workspace = true } solana-sdk = { workspace = true } +solana-type-overrides = { workspace = true } solana-vote = { workspace = true } solana_rbpf = { workspace = true } thiserror = { workspace = true } @@ -55,3 +56,4 @@ frozen-abi = [ "solana-compute-budget/frozen-abi", "solana-sdk/frozen-abi", ] +shuttle-test = ["solana-type-overrides/shuttle-test"] diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 293214a870f13a..05404aa51c2c98 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -36,13 +36,13 @@ use { IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext, }, }, + solana_type_overrides::sync::{atomic::Ordering, Arc}, solana_vote::vote_account::VoteAccountsHashMap, std::{ alloc::Layout, cell::RefCell, fmt::{self, Debug}, rc::Rc, - sync::{atomic::Ordering, Arc}, }, }; @@ -193,7 +193,7 @@ pub struct InvokeContext<'a> { /// Information about the currently executing transaction. pub transaction_context: &'a mut TransactionContext, /// The local program cache for the transaction batch. - pub program_cache_for_tx_batch: &'a ProgramCacheForTxBatch, + pub program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch, /// Runtime configurations used to provision the invocation environment. pub environment_config: EnvironmentConfig<'a>, /// The compute budget for the current invocation. @@ -202,7 +202,6 @@ pub struct InvokeContext<'a> { /// the designated compute budget during program execution. compute_meter: RefCell, log_collector: Option>>, - pub programs_modified_by_tx: &'a mut ProgramCacheForTxBatch, /// Latest measurement not yet accumulated in [ExecuteDetailsTimings::execute_us] pub execute_time: Option, pub timings: ExecuteDetailsTimings, @@ -214,11 +213,10 @@ impl<'a> InvokeContext<'a> { #[allow(clippy::too_many_arguments)] pub fn new( transaction_context: &'a mut TransactionContext, - program_cache_for_tx_batch: &'a ProgramCacheForTxBatch, + program_cache_for_tx_batch: &'a mut ProgramCacheForTxBatch, environment_config: EnvironmentConfig<'a>, log_collector: Option>>, compute_budget: ComputeBudget, - programs_modified_by_tx: &'a mut ProgramCacheForTxBatch, ) -> Self { Self { transaction_context, @@ -227,7 +225,6 @@ impl<'a> InvokeContext<'a> { log_collector, compute_budget, compute_meter: RefCell::new(compute_budget.compute_unit_limit), - programs_modified_by_tx, execute_time: None, timings: ExecuteDetailsTimings::default(), syscall_context: Vec::new(), @@ -235,14 +232,6 @@ impl<'a> InvokeContext<'a> { } } - pub fn find_program_in_cache(&self, pubkey: &Pubkey) -> Option> { - // First lookup the cache of the programs modified by the current transaction. If not found, lookup - // the cache of the cache of the programs that are loaded for the transaction batch. - self.programs_modified_by_tx - .find(pubkey) - .or_else(|| self.program_cache_for_tx_batch.find(pubkey)) - } - pub fn get_environments_for_slot( &self, effective_slot: Slot, @@ -692,7 +681,7 @@ macro_rules! with_mock_invoke_context { account::ReadableAccount, feature_set::FeatureSet, hash::Hash, sysvar::rent::Rent, transaction_context::TransactionContext, }, - std::sync::Arc, + solana_type_overrides::sync::Arc, $crate::{ invoke_context::{EnvironmentConfig, InvokeContext}, loaded_programs::ProgramCacheForTxBatch, @@ -704,7 +693,7 @@ macro_rules! with_mock_invoke_context { let mut $transaction_context = TransactionContext::new( $transaction_accounts, Rent::default(), - compute_budget.max_invoke_stack_height, + compute_budget.max_instruction_stack_depth, compute_budget.max_instruction_trace_length, ); let mut sysvar_cache = SysvarCache::default(); @@ -733,15 +722,13 @@ macro_rules! with_mock_invoke_context { 0, &sysvar_cache, ); - let program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); let mut $invoke_context = InvokeContext::new( &mut $transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, environment_config, Some(LogCollector::new_ref()), compute_budget, - &mut programs_modified_by_tx, ); }; } @@ -802,7 +789,7 @@ pub fn mock_process_instruction>, + loading_entries: Mutex>, }, } @@ -663,6 +666,8 @@ pub struct ProgramCacheForTxBatch { /// Pubkey is the address of a program. /// ProgramCacheEntry is the corresponding program entry valid for the slot in which a transaction is being executed. entries: HashMap>, + /// Program entries modified during the transaction batch. + modified_entries: HashMap>, slot: Slot, pub environments: ProgramRuntimeEnvironments, /// Anticipated replacement for `environments` at the next epoch. @@ -689,6 +694,7 @@ impl ProgramCacheForTxBatch { ) -> Self { Self { entries: HashMap::new(), + modified_entries: HashMap::new(), slot, environments, upcoming_environments, @@ -706,6 +712,7 @@ impl ProgramCacheForTxBatch { ) -> Self { Self { entries: HashMap::new(), + modified_entries: HashMap::new(), slot, environments: cache.get_environments_for_epoch(epoch), upcoming_environments: cache.get_upcoming_environments_for_epoch(epoch), @@ -716,14 +723,6 @@ impl ProgramCacheForTxBatch { } } - pub fn entries(&self) -> &HashMap> { - &self.entries - } - - pub fn take_entries(&mut self) -> HashMap> { - std::mem::take(&mut self.entries) - } - /// Returns the current environments depending on the given epoch pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments { if epoch != self.latest_root_epoch { @@ -747,21 +746,39 @@ impl ProgramCacheForTxBatch { (self.entries.insert(key, entry.clone()).is_some(), entry) } + /// Store an entry in `modified_entries` for a program modified during the + /// transaction batch. + pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc) { + self.modified_entries.insert(key, entry); + } + + /// Drain the program cache's modified entries, returning the owned + /// collection. + pub fn drain_modified_entries(&mut self) -> HashMap> { + std::mem::take(&mut self.modified_entries) + } + pub fn find(&self, key: &Pubkey) -> Option> { - self.entries.get(key).map(|entry| { - if entry.is_implicit_delay_visibility_tombstone(self.slot) { - // Found a program entry on the current fork, but it's not effective - // yet. It indicates that the program has delayed visibility. Return - // the tombstone to reflect that. - Arc::new(ProgramCacheEntry::new_tombstone( - entry.deployment_slot, - entry.account_owner, - ProgramCacheEntryType::DelayVisibility, - )) - } else { - entry.clone() - } - }) + // First lookup the cache of the programs modified by the current + // transaction. If not found, lookup the cache of the cache of the + // programs that are loaded for the transaction batch. + self.modified_entries + .get(key) + .or(self.entries.get(key)) + .map(|entry| { + if entry.is_implicit_delay_visibility_tombstone(self.slot) { + // Found a program entry on the current fork, but it's not effective + // yet. It indicates that the program has delayed visibility. Return + // the tombstone to reflect that. + Arc::new(ProgramCacheEntry::new_tombstone( + entry.deployment_slot, + entry.account_owner, + ProgramCacheEntryType::DelayVisibility, + )) + } else { + entry.clone() + } + }) } pub fn slot(&self) -> Slot { @@ -1108,7 +1125,7 @@ impl ProgramCache { if let Entry::Vacant(entry) = entry { entry.insert(( loaded_programs_for_tx_batch.slot, - std::thread::current().id(), + thread::current().id(), )); cooperative_loading_task = Some((*key, *usage_count)); } @@ -1142,7 +1159,7 @@ impl ProgramCache { loading_entries, .. } => { let loading_thread = loading_entries.get_mut().unwrap().remove(&key); - debug_assert_eq!(loading_thread, Some((slot, std::thread::current().id()))); + debug_assert_eq!(loading_thread, Some((slot, thread::current().id()))); // Check that it will be visible to our own fork once inserted if loaded_program.deployment_slot > self.latest_root_slot && !matches!( diff --git a/program-runtime/src/mem_pool.rs b/program-runtime/src/mem_pool.rs new file mode 100644 index 00000000000000..398de5bfa64c57 --- /dev/null +++ b/program-runtime/src/mem_pool.rs @@ -0,0 +1,146 @@ +use { + solana_compute_budget::{ + compute_budget::{MAX_CALL_DEPTH, MAX_INSTRUCTION_STACK_DEPTH, STACK_FRAME_SIZE}, + compute_budget_processor::{MAX_HEAP_FRAME_BYTES, MIN_HEAP_FRAME_BYTES}, + }, + solana_rbpf::{aligned_memory::AlignedMemory, ebpf::HOST_ALIGN}, + std::array, +}; + +trait Reset { + fn reset(&mut self); +} + +struct Pool { + items: [Option; SIZE], + next_empty: usize, +} + +impl Pool { + fn new(items: [T; SIZE]) -> Self { + Self { + items: items.map(|i| Some(i)), + next_empty: SIZE, + } + } + + fn len(&self) -> usize { + SIZE + } + + fn get(&mut self) -> Option { + if self.next_empty == 0 { + return None; + } + self.next_empty = self.next_empty.saturating_sub(1); + self.items + .get_mut(self.next_empty) + .and_then(|item| item.take()) + } + + fn put(&mut self, mut value: T) -> bool { + self.items + .get_mut(self.next_empty) + .map(|item| { + value.reset(); + item.replace(value); + self.next_empty = self.next_empty.saturating_add(1); + true + }) + .unwrap_or(false) + } +} + +impl Reset for AlignedMemory<{ HOST_ALIGN }> { + fn reset(&mut self) { + self.as_slice_mut().fill(0) + } +} + +pub struct VmMemoryPool { + stack: Pool, MAX_INSTRUCTION_STACK_DEPTH>, + heap: Pool, MAX_INSTRUCTION_STACK_DEPTH>, +} + +impl VmMemoryPool { + pub fn new() -> Self { + Self { + stack: Pool::new(array::from_fn(|_| { + AlignedMemory::zero_filled(STACK_FRAME_SIZE * MAX_CALL_DEPTH) + })), + heap: Pool::new(array::from_fn(|_| { + AlignedMemory::zero_filled(MAX_HEAP_FRAME_BYTES as usize) + })), + } + } + + pub fn stack_len(&self) -> usize { + self.stack.len() + } + + pub fn heap_len(&self) -> usize { + self.heap.len() + } + + pub fn get_stack(&mut self, size: usize) -> AlignedMemory<{ HOST_ALIGN }> { + debug_assert!(size == STACK_FRAME_SIZE * MAX_CALL_DEPTH); + self.stack + .get() + .unwrap_or_else(|| AlignedMemory::zero_filled(size)) + } + + pub fn put_stack(&mut self, stack: AlignedMemory<{ HOST_ALIGN }>) -> bool { + self.stack.put(stack) + } + + pub fn get_heap(&mut self, heap_size: u32) -> AlignedMemory<{ HOST_ALIGN }> { + debug_assert!((MIN_HEAP_FRAME_BYTES..=MAX_HEAP_FRAME_BYTES).contains(&heap_size)); + self.heap + .get() + .unwrap_or_else(|| AlignedMemory::zero_filled(MAX_HEAP_FRAME_BYTES as usize)) + } + + pub fn put_heap(&mut self, heap: AlignedMemory<{ HOST_ALIGN }>) -> bool { + let heap_size = heap.len(); + debug_assert!( + heap_size >= MIN_HEAP_FRAME_BYTES as usize + && heap_size <= MAX_HEAP_FRAME_BYTES as usize + ); + self.heap.put(heap) + } +} + +impl Default for VmMemoryPool { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[derive(Debug, Eq, PartialEq)] + struct Item(u8, u8); + impl Reset for Item { + fn reset(&mut self) { + self.1 = 0; + } + } + + #[test] + fn test_pool() { + let mut pool = Pool::::new([Item(0, 1), Item(1, 1)]); + assert_eq!(pool.get(), Some(Item(1, 1))); + assert_eq!(pool.get(), Some(Item(0, 1))); + assert_eq!(pool.get(), None); + pool.put(Item(1, 1)); + assert_eq!(pool.get(), Some(Item(1, 0))); + pool.put(Item(2, 2)); + pool.put(Item(3, 3)); + assert!(!pool.put(Item(4, 4))); + assert_eq!(pool.get(), Some(Item(3, 0))); + assert_eq!(pool.get(), Some(Item(2, 0))); + assert_eq!(pool.get(), None); + } +} diff --git a/program-runtime/src/sysvar_cache.rs b/program-runtime/src/sysvar_cache.rs index 313faec6e11b02..1a270484410531 100644 --- a/program-runtime/src/sysvar_cache.rs +++ b/program-runtime/src/sysvar_cache.rs @@ -13,7 +13,7 @@ use { }, transaction_context::{IndexOfAccount, InstructionContext, TransactionContext}, }, - std::sync::Arc, + solana_type_overrides::sync::Arc, }; #[cfg(all(RUSTC_WITH_SPECIALIZATION, feature = "frozen-abi"))] diff --git a/program-runtime/src/timings.rs b/program-runtime/src/timings.rs index f1966ba00151e0..9ffc4702178676 100644 --- a/program-runtime/src/timings.rs +++ b/program-runtime/src/timings.rs @@ -53,6 +53,7 @@ pub enum ExecuteTimingType { TotalBatchesLen, UpdateTransactionStatuses, ProgramCacheUs, + CheckBlockLimitsUs, } pub struct Metrics([u64; ExecuteTimingType::CARDINALITY]); @@ -88,7 +89,7 @@ impl core::fmt::Debug for Metrics { eager_macro_rules! { $eager_1 #[macro_export] macro_rules! report_execute_timings { - ($self: expr) => { + ($self: expr, $is_unified_scheduler_enabled: expr) => { ( "validate_transactions_us", *$self @@ -149,19 +150,25 @@ eager_macro_rules! { $eager_1 ), ( "total_batches_len", - *$self - - .metrics - .index(ExecuteTimingType::TotalBatchesLen), - i64 + (if $is_unified_scheduler_enabled { + None + } else { + Some(*$self + .metrics + .index(ExecuteTimingType::TotalBatchesLen)) + }), + Option ), ( "num_execute_batches", - *$self - - .metrics - .index(ExecuteTimingType::NumExecuteBatches), - i64 + (if $is_unified_scheduler_enabled { + None + } else { + Some(*$self + .metrics + .index(ExecuteTimingType::NumExecuteBatches)) + }), + Option ), ( "update_transaction_statuses", @@ -171,6 +178,11 @@ eager_macro_rules! { $eager_1 .index(ExecuteTimingType::UpdateTransactionStatuses), i64 ), + ( + "check_block_limits_us", + *$self.metrics.index(ExecuteTimingType::CheckBlockLimitsUs), + i64 + ), ( "execute_details_serialize_us", $self.details.serialize_us, @@ -235,13 +247,6 @@ eager_macro_rules! { $eager_1 .feature_set_clone_us, i64 ), - ( - "execute_accessories_compute_budget_process_transaction_us", - $self - .execute_accessories - .compute_budget_process_transaction_us, - i64 - ), ( "execute_accessories_get_executors_us", $self.execute_accessories.get_executors_us, @@ -342,7 +347,6 @@ impl ExecuteProcessInstructionTimings { #[derive(Default, Debug)] pub struct ExecuteAccessoryTimings { pub feature_set_clone_us: u64, - pub compute_budget_process_transaction_us: u64, pub get_executors_us: u64, pub process_message_us: u64, pub update_executors_us: u64, @@ -352,10 +356,6 @@ pub struct ExecuteAccessoryTimings { impl ExecuteAccessoryTimings { pub fn accumulate(&mut self, other: &ExecuteAccessoryTimings) { saturating_add_assign!(self.feature_set_clone_us, other.feature_set_clone_us); - saturating_add_assign!( - self.compute_budget_process_transaction_us, - other.compute_budget_process_transaction_us - ); saturating_add_assign!(self.get_executors_us, other.get_executors_us); saturating_add_assign!(self.process_message_us, other.process_message_us); saturating_add_assign!(self.update_executors_us, other.update_executors_us); diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index 37453f986b9519..dff52faea8cb51 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -34,7 +34,7 @@ use { clock::{Epoch, Slot}, entrypoint::{deserialize, ProgramResult, SUCCESS}, feature_set::FEATURE_NAMES, - fee_calculator::{FeeCalculator, FeeRateGovernor, DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE}, + fee_calculator::{FeeRateGovernor, DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE}, genesis_config::{ClusterType, GenesisConfig}, hash::Hash, instruction::{Instruction, InstructionError}, @@ -545,13 +545,6 @@ impl ProgramTest { self.transaction_account_lock_limit = Some(transaction_account_lock_limit); } - /// Override the SBF compute budget - #[allow(deprecated)] - #[deprecated(since = "1.8.0", note = "please use `set_compute_max_units` instead")] - pub fn set_bpf_compute_max_units(&mut self, bpf_compute_max_units: u64) { - self.set_compute_max_units(bpf_compute_max_units); - } - /// Add an account to the test environment's genesis config. pub fn add_genesis_account(&mut self, address: Pubkey, account: Account) { self.genesis_accounts @@ -972,47 +965,12 @@ impl ProgramTest { #[async_trait] pub trait ProgramTestBanksClientExt { - /// Get a new blockhash, similar in spirit to RpcClient::get_new_blockhash() - /// - /// This probably should eventually be moved into BanksClient proper in some form - #[deprecated( - since = "1.9.0", - note = "Please use `get_new_latest_blockhash `instead" - )] - async fn get_new_blockhash(&mut self, blockhash: &Hash) -> io::Result<(Hash, FeeCalculator)>; /// Get a new latest blockhash, similar in spirit to RpcClient::get_latest_blockhash() async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result; } #[async_trait] impl ProgramTestBanksClientExt for BanksClient { - async fn get_new_blockhash(&mut self, blockhash: &Hash) -> io::Result<(Hash, FeeCalculator)> { - let mut num_retries = 0; - let start = Instant::now(); - while start.elapsed().as_secs() < 5 { - #[allow(deprecated)] - if let Ok((fee_calculator, new_blockhash, _slot)) = self.get_fees().await { - if new_blockhash != *blockhash { - return Ok((new_blockhash, fee_calculator)); - } - } - debug!("Got same blockhash ({:?}), will retry...", blockhash); - - tokio::time::sleep(Duration::from_millis(200)).await; - num_retries += 1; - } - - Err(io::Error::new( - io::ErrorKind::Other, - format!( - "Unable to get new blockhash after {}ms (retried {} times), stuck at {}", - start.elapsed().as_millis(), - num_retries, - blockhash - ), - )) - } - async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result { let mut num_retries = 0; let start = Instant::now(); diff --git a/programs/address-lookup-table/src/lib.rs b/programs/address-lookup-table/src/lib.rs index 737c35e4c4b2f4..e146dd184b5385 100644 --- a/programs/address-lookup-table/src/lib.rs +++ b/programs/address-lookup-table/src/lib.rs @@ -3,13 +3,3 @@ #[cfg(not(target_os = "solana"))] pub mod processor; - -#[deprecated( - since = "1.17.0", - note = "Please use `solana_program::address_lookup_table` instead" -)] -pub use solana_program::address_lookup_table::{ - error, instruction, - program::{check_id, id, ID}, - state, -}; diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index aac0f10e7cd57e..148c0c92333995 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -16,11 +16,12 @@ libsecp256k1 = { workspace = true } log = { workspace = true } scopeguard = { workspace = true } solana-compute-budget = { workspace = true } +solana-curve25519 = { workspace = true } solana-measure = { workspace = true } solana-poseidon = { workspace = true } solana-program-runtime = { workspace = true } solana-sdk = { workspace = true } -solana-zk-token-sdk = { workspace = true } +solana-type-overrides = { workspace = true } solana_rbpf = { workspace = true } thiserror = { workspace = true } @@ -38,3 +39,6 @@ name = "solana_bpf_loader_program" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] + +[features] +shuttle-test = ["solana-type-overrides/shuttle-test", "solana-program-runtime/shuttle-test"] diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 8f14544ffcf0b3..2c2b16245c88ce 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -5,6 +5,7 @@ pub mod serialization; pub mod syscalls; use { + solana_compute_budget::compute_budget::MAX_INSTRUCTION_STACK_DEPTH, solana_measure::measure::Measure, solana_program_runtime::{ ic_logger_msg, ic_msg, @@ -14,13 +15,13 @@ use { DELAY_VISIBILITY_SLOT_OFFSET, }, log_collector::LogCollector, + mem_pool::VmMemoryPool, stable_log, sysvar_cache::get_sysvar_with_account_check, }, solana_rbpf::{ - aligned_memory::AlignedMemory, declare_builtin_function, - ebpf::{self, HOST_ALIGN, MM_HEAP_START}, + ebpf::{self, MM_HEAP_START}, elf::Executable, error::{EbpfError, ProgramResult}, memory_region::{AccessType, MemoryCowCallback, MemoryMapping, MemoryRegion}, @@ -46,12 +47,8 @@ use { system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, transaction_context::{IndexOfAccount, InstructionContext, TransactionContext}, }, - std::{ - cell::RefCell, - mem, - rc::Rc, - sync::{atomic::Ordering, Arc}, - }, + solana_type_overrides::sync::{atomic::Ordering, Arc}, + std::{cell::RefCell, mem, rc::Rc}, syscalls::{create_program_runtime_environment_v1, morph_into_deployment_environment_v1}, }; @@ -59,6 +56,10 @@ pub const DEFAULT_LOADER_COMPUTE_UNITS: u64 = 570; pub const DEPRECATED_LOADER_COMPUTE_UNITS: u64 = 1_140; pub const UPGRADEABLE_LOADER_COMPUTE_UNITS: u64 = 2_370; +thread_local! { + pub static MEMORY_POOL: RefCell = RefCell::new(VmMemoryPool::new()); +} + #[allow(clippy::too_many_arguments)] pub fn load_program_from_bytes( log_collector: Option>>, @@ -152,7 +153,7 @@ macro_rules! deploy_program { environments.program_runtime_v1.clone(), true, )?; - if let Some(old_entry) = $invoke_context.find_program_in_cache(&$program_id) { + if let Some(old_entry) = $invoke_context.program_cache_for_tx_batch.find(&$program_id) { executor.tx_usage_counter.store( old_entry.tx_usage_counter.load(Ordering::Relaxed), Ordering::Relaxed @@ -165,7 +166,7 @@ macro_rules! deploy_program { $drop load_program_metrics.program_id = $program_id.to_string(); load_program_metrics.submit_datapoint(&mut $invoke_context.timings); - $invoke_context.programs_modified_by_tx.replenish($program_id, Arc::new(executor)); + $invoke_context.program_cache_for_tx_batch.store_modified_entry($program_id, Arc::new(executor)); }}; } @@ -244,8 +245,8 @@ pub fn create_vm<'a, 'b>( regions: Vec, accounts_metadata: Vec, invoke_context: &'a mut InvokeContext<'b>, - stack: &mut AlignedMemory, - heap: &mut AlignedMemory, + stack: &mut [u8], + heap: &mut [u8], ) -> Result>, Box> { let stack_size = stack.len(); let heap_size = heap.len(); @@ -299,24 +300,23 @@ macro_rules! create_vm { heap_size, invoke_context.get_compute_budget().heap_cost, )); - let mut allocations = None; let $vm = heap_cost_result.and_then(|_| { - let mut stack = solana_rbpf::aligned_memory::AlignedMemory::< - { solana_rbpf::ebpf::HOST_ALIGN }, - >::zero_filled(stack_size); - let mut heap = solana_rbpf::aligned_memory::AlignedMemory::< - { solana_rbpf::ebpf::HOST_ALIGN }, - >::zero_filled(usize::try_from(heap_size).unwrap()); + let (mut stack, mut heap) = $crate::MEMORY_POOL + .with_borrow_mut(|pool| (pool.get_stack(stack_size), pool.get_heap(heap_size))); let vm = $crate::create_vm( $program, $regions, $accounts_metadata, $invoke_context, - &mut stack, - &mut heap, + stack + .as_slice_mut() + .get_mut(..stack_size) + .expect("invalid stack size"), + heap.as_slice_mut() + .get_mut(..heap_size as usize) + .expect("invalid heap size"), ); - allocations = Some((stack, heap)); - vm + vm.map(|vm| (vm, stack, heap)) }); }; } @@ -324,7 +324,7 @@ macro_rules! create_vm { #[macro_export] macro_rules! mock_create_vm { ($vm:ident, $additional_regions:expr, $accounts_metadata:expr, $invoke_context:expr $(,)?) => { - let loader = std::sync::Arc::new(BuiltinProgram::new_mock()); + let loader = solana_type_overrides::sync::Arc::new(BuiltinProgram::new_mock()); let function_registry = solana_rbpf::program::FunctionRegistry::default(); let executable = solana_rbpf::elf::Executable::::from_text_bytes( &[0x95, 0, 0, 0, 0, 0, 0, 0], @@ -343,13 +343,14 @@ macro_rules! mock_create_vm { $accounts_metadata, $invoke_context, ); + let $vm = $vm.map(|(vm, _, _)| vm); }; } fn create_memory_mapping<'a, 'b, C: ContextObject>( executable: &'a Executable, - stack: &'b mut AlignedMemory<{ HOST_ALIGN }>, - heap: &'b mut AlignedMemory<{ HOST_ALIGN }>, + stack: &'b mut [u8], + heap: &'b mut [u8], additional_regions: Vec, cow_cb: Option, ) -> Result, Box> { @@ -358,7 +359,7 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>( let regions: Vec = vec![ executable.get_ro_region(), MemoryRegion::new_writable_gapped( - stack.as_slice_mut(), + stack, ebpf::MM_STACK_START, if !sbpf_version.dynamic_stack_frames() && config.enable_stack_frame_gaps { config.stack_frame_size as u64 @@ -366,7 +367,7 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>( 0 }, ), - MemoryRegion::new_writable(heap.as_slice_mut(), MM_HEAP_START), + MemoryRegion::new_writable(heap, MM_HEAP_START), ] .into_iter() .chain(additional_regions) @@ -437,7 +438,8 @@ pub fn process_instruction_inner( let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time"); let executor = invoke_context - .find_program_in_cache(program_account.get_key()) + .program_cache_for_tx_batch + .find(program_account.get_key()) .ok_or_else(|| { ic_logger_msg!(log_collector, "Program is not cached"); InstructionError::InvalidAccountData @@ -1109,14 +1111,16 @@ fn process_loader_upgradeable_instruction( &log_collector, )?; let clock = invoke_context.get_sysvar_cache().get_clock()?; - invoke_context.programs_modified_by_tx.replenish( - program_key, - Arc::new(ProgramCacheEntry::new_tombstone( - clock.slot, - ProgramCacheEntryOwner::LoaderV3, - ProgramCacheEntryType::Closed, - )), - ); + invoke_context + .program_cache_for_tx_batch + .store_modified_entry( + program_key, + Arc::new(ProgramCacheEntry::new_tombstone( + clock.slot, + ProgramCacheEntryOwner::LoaderV3, + ProgramCacheEntryType::Closed, + )), + ); } _ => { ic_logger_msg!(log_collector, "Invalid Program account"); @@ -1388,7 +1392,7 @@ fn execute<'a, 'b: 'a>( let execution_result = { let compute_meter_prev = invoke_context.get_remaining(); create_vm!(vm, executable, regions, accounts_metadata, invoke_context); - let mut vm = match vm { + let (mut vm, stack, heap) = match vm { Ok(info) => info, Err(e) => { ic_logger_msg!(log_collector, "Failed to create SBF VM: {}", e); @@ -1399,6 +1403,12 @@ fn execute<'a, 'b: 'a>( vm.context_object_pointer.execute_time = Some(Measure::start("execute")); let (compute_units_consumed, result) = vm.execute_program(executable, !use_jit); + MEMORY_POOL.with_borrow_mut(|memory_pool| { + memory_pool.put_stack(stack); + memory_pool.put_heap(heap); + debug_assert!(memory_pool.stack_len() <= MAX_INSTRUCTION_STACK_DEPTH); + debug_assert!(memory_pool.heap_len() <= MAX_INSTRUCTION_STACK_DEPTH); + }); drop(vm); if let Some(execute_time) = invoke_context.execute_time.as_mut() { execute_time.stop(); @@ -1543,11 +1553,11 @@ pub mod test_utils { false, ) { invoke_context - .programs_modified_by_tx + .program_cache_for_tx_batch .set_slot_for_tests(DELAY_VISIBILITY_SLOT_OFFSET); invoke_context - .programs_modified_by_tx - .replenish(*pubkey, Arc::new(loaded_program)); + .program_cache_for_tx_batch + .store_modified_entry(*pubkey, Arc::new(loaded_program)); } } } @@ -3763,7 +3773,7 @@ mod tests { latest_access_slot: AtomicU64::new(0), }; invoke_context - .programs_modified_by_tx + .program_cache_for_tx_batch .replenish(program_id, Arc::new(program)); assert_matches!( @@ -3772,7 +3782,7 @@ mod tests { ); let updated_program = invoke_context - .programs_modified_by_tx + .program_cache_for_tx_batch .find(&program_id) .expect("Didn't find upgraded program in the cache"); @@ -3807,7 +3817,7 @@ mod tests { latest_access_slot: AtomicU64::new(0), }; invoke_context - .programs_modified_by_tx + .program_cache_for_tx_batch .replenish(program_id, Arc::new(program)); let program_id2 = Pubkey::new_unique(); @@ -3817,7 +3827,7 @@ mod tests { ); let program2 = invoke_context - .programs_modified_by_tx + .program_cache_for_tx_batch .find(&program_id2) .expect("Didn't find upgraded program in the cache"); diff --git a/programs/bpf_loader/src/syscalls/cpi.rs b/programs/bpf_loader/src/syscalls/cpi.rs index 71d5736f895e70..94046f5f741560 100644 --- a/programs/bpf_loader/src/syscalls/cpi.rs +++ b/programs/bpf_loader/src/syscalls/cpi.rs @@ -925,7 +925,7 @@ where // account (caller_account). We need to update the corresponding // BorrowedAccount (callee_account) so the callee can see the // changes. - update_callee_account( + let update_caller = update_callee_account( invoke_context, memory_mapping, is_loader_deprecated, @@ -934,7 +934,7 @@ where direct_mapping, )?; - let caller_account = if instruction_account.is_writable { + let caller_account = if instruction_account.is_writable || update_caller { Some(caller_account) } else { None @@ -1173,6 +1173,9 @@ fn cpi_common( // // This method updates callee_account so the CPI callee can see the caller's // changes. +// +// When true is returned, the caller account must be updated after CPI. This +// is only set for direct mapping when the pointer may have changed. fn update_callee_account( invoke_context: &InvokeContext, memory_mapping: &MemoryMapping, @@ -1180,7 +1183,9 @@ fn update_callee_account( caller_account: &CallerAccount, mut callee_account: BorrowedAccount<'_>, direct_mapping: bool, -) -> Result<(), Error> { +) -> Result { + let mut must_update_caller = false; + if callee_account.get_lamports() != *caller_account.lamports { callee_account.set_lamports(*caller_account.lamports)?; } @@ -1198,7 +1203,11 @@ fn update_callee_account( if is_loader_deprecated && realloc_bytes_used > 0 { return Err(InstructionError::InvalidRealloc.into()); } - callee_account.set_data_length(post_len)?; + if prev_len != post_len { + callee_account.set_data_length(post_len)?; + // pointer to data may have changed, so caller must be updated + must_update_caller = true; + } if realloc_bytes_used > 0 { let serialized_data = translate_slice::( memory_mapping, @@ -1239,7 +1248,7 @@ fn update_callee_account( callee_account.set_owner(caller_account.owner.as_ref())?; } - Ok(()) + Ok(must_update_caller) } fn update_caller_account_perms( diff --git a/programs/bpf_loader/src/syscalls/logging.rs b/programs/bpf_loader/src/syscalls/logging.rs index 1555053f87d7ae..522145b1d71408 100644 --- a/programs/bpf_loader/src/syscalls/logging.rs +++ b/programs/bpf_loader/src/syscalls/logging.rs @@ -80,7 +80,7 @@ declare_builtin_function!( ); declare_builtin_function!( - /// Log 5 64-bit values + /// Log a [`Pubkey`] as a base58 string SyscallLogPubkey, fn rust( invoke_context: &mut InvokeContext, diff --git a/programs/bpf_loader/src/syscalls/mod.rs b/programs/bpf_loader/src/syscalls/mod.rs index ddf655f48d4225..e982728e398e50 100644 --- a/programs/bpf_loader/src/syscalls/mod.rs +++ b/programs/bpf_loader/src/syscalls/mod.rs @@ -58,12 +58,12 @@ use { sysvar::{Sysvar, SysvarId}, transaction_context::{IndexOfAccount, InstructionAccount}, }, + solana_type_overrides::sync::Arc, std::{ alloc::Layout, mem::{align_of, size_of}, slice::from_raw_parts_mut, str::{from_utf8, Utf8Error}, - sync::Arc, }, thiserror::Error as ThisError, }; @@ -902,7 +902,7 @@ declare_builtin_function!( _arg5: u64, memory_mapping: &mut MemoryMapping, ) -> Result { - use solana_zk_token_sdk::curve25519::{curve_syscall_traits::*, edwards, ristretto}; + use solana_curve25519::{curve_syscall_traits::*, edwards, ristretto}; match curve_id { CURVE25519_EDWARDS => { let cost = invoke_context @@ -968,9 +968,7 @@ declare_builtin_function!( result_point_addr: u64, memory_mapping: &mut MemoryMapping, ) -> Result { - use solana_zk_token_sdk::curve25519::{ - curve_syscall_traits::*, edwards, ristretto, scalar, - }; + use solana_curve25519::{curve_syscall_traits::*, edwards, ristretto, scalar}; match curve_id { CURVE25519_EDWARDS => match group_op { ADD => { @@ -1196,9 +1194,7 @@ declare_builtin_function!( result_point_addr: u64, memory_mapping: &mut MemoryMapping, ) -> Result { - use solana_zk_token_sdk::curve25519::{ - curve_syscall_traits::*, edwards, ristretto, scalar, - }; + use solana_curve25519::{curve_syscall_traits::*, edwards, ristretto, scalar}; if points_len > 512 { return Err(Box::new(SyscallError::InvalidLength)); @@ -2766,7 +2762,7 @@ mod tests { #[test] fn test_syscall_edwards_curve_point_validation() { - use solana_zk_token_sdk::curve25519::curve_syscall_traits::CURVE25519_EDWARDS; + use solana_curve25519::curve_syscall_traits::CURVE25519_EDWARDS; let config = Config::default(); prepare_mockup!(invoke_context, program_id, bpf_loader::id()); @@ -2839,7 +2835,7 @@ mod tests { #[test] fn test_syscall_ristretto_curve_point_validation() { - use solana_zk_token_sdk::curve25519::curve_syscall_traits::CURVE25519_RISTRETTO; + use solana_curve25519::curve_syscall_traits::CURVE25519_RISTRETTO; let config = Config::default(); prepare_mockup!(invoke_context, program_id, bpf_loader::id()); @@ -2912,9 +2908,7 @@ mod tests { #[test] fn test_syscall_edwards_curve_group_ops() { - use solana_zk_token_sdk::curve25519::curve_syscall_traits::{ - ADD, CURVE25519_EDWARDS, MUL, SUB, - }; + use solana_curve25519::curve_syscall_traits::{ADD, CURVE25519_EDWARDS, MUL, SUB}; let config = Config::default(); prepare_mockup!(invoke_context, program_id, bpf_loader::id()); @@ -3069,9 +3063,7 @@ mod tests { #[test] fn test_syscall_ristretto_curve_group_ops() { - use solana_zk_token_sdk::curve25519::curve_syscall_traits::{ - ADD, CURVE25519_RISTRETTO, MUL, SUB, - }; + use solana_curve25519::curve_syscall_traits::{ADD, CURVE25519_RISTRETTO, MUL, SUB}; let config = Config::default(); prepare_mockup!(invoke_context, program_id, bpf_loader::id()); @@ -3228,9 +3220,7 @@ mod tests { #[test] fn test_syscall_multiscalar_multiplication() { - use solana_zk_token_sdk::curve25519::curve_syscall_traits::{ - CURVE25519_EDWARDS, CURVE25519_RISTRETTO, - }; + use solana_curve25519::curve_syscall_traits::{CURVE25519_EDWARDS, CURVE25519_RISTRETTO}; let config = Config::default(); prepare_mockup!(invoke_context, program_id, bpf_loader::id()); @@ -3336,9 +3326,7 @@ mod tests { #[test] fn test_syscall_multiscalar_multiplication_maximum_length_exceeded() { - use solana_zk_token_sdk::curve25519::curve_syscall_traits::{ - CURVE25519_EDWARDS, CURVE25519_RISTRETTO, - }; + use solana_curve25519::curve_syscall_traits::{CURVE25519_EDWARDS, CURVE25519_RISTRETTO}; let config = Config::default(); prepare_mockup!(invoke_context, program_id, bpf_loader::id()); diff --git a/programs/loader-v4/Cargo.toml b/programs/loader-v4/Cargo.toml index da7d5c10c72a51..79c078f29785b5 100644 --- a/programs/loader-v4/Cargo.toml +++ b/programs/loader-v4/Cargo.toml @@ -14,6 +14,7 @@ solana-compute-budget = { workspace = true } solana-measure = { workspace = true } solana-program-runtime = { workspace = true } solana-sdk = { workspace = true } +solana-type-overrides = { workspace = true } solana_rbpf = { workspace = true } [dev-dependencies] @@ -25,3 +26,6 @@ name = "solana_loader_v4_program" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] + +[features] +shuttle-test = ["solana-type-overrides/shuttle-test", "solana-program-runtime/shuttle-test"] diff --git a/programs/loader-v4/src/lib.rs b/programs/loader-v4/src/lib.rs index b564e84c43a9d4..161f72a478cc07 100644 --- a/programs/loader-v4/src/lib.rs +++ b/programs/loader-v4/src/lib.rs @@ -30,11 +30,8 @@ use { saturating_add_assign, transaction_context::{BorrowedAccount, InstructionContext}, }, - std::{ - cell::RefCell, - rc::Rc, - sync::{atomic::Ordering, Arc}, - }, + solana_type_overrides::sync::{atomic::Ordering, Arc}, + std::{cell::RefCell, rc::Rc}, }; pub const DEFAULT_COMPUTE_UNITS: u64 = 2_000; @@ -449,7 +446,10 @@ pub fn process_instruction_deploy( state.slot = current_slot; state.status = LoaderV4Status::Deployed; - if let Some(old_entry) = invoke_context.find_program_in_cache(program.get_key()) { + if let Some(old_entry) = invoke_context + .program_cache_for_tx_batch + .find(program.get_key()) + { executor.tx_usage_counter.store( old_entry.tx_usage_counter.load(Ordering::Relaxed), Ordering::Relaxed, @@ -460,8 +460,8 @@ pub fn process_instruction_deploy( ); } invoke_context - .programs_modified_by_tx - .replenish(*program.get_key(), Arc::new(executor)); + .program_cache_for_tx_batch + .store_modified_entry(*program.get_key(), Arc::new(executor)); Ok(()) } @@ -592,7 +592,8 @@ pub fn process_instruction_inner( } let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time"); let loaded_program = invoke_context - .find_program_in_cache(program.get_key()) + .program_cache_for_tx_batch + .find(program.get_key()) .ok_or_else(|| { ic_logger_msg!(log_collector, "Program is not cached"); InstructionError::InvalidAccountData @@ -661,7 +662,7 @@ mod tests { if let Ok(loaded_program) = ProgramCacheEntry::new( &loader_v4::id(), invoke_context - .programs_modified_by_tx + .program_cache_for_tx_batch .environments .program_runtime_v2 .clone(), @@ -671,10 +672,12 @@ mod tests { account.data().len(), &mut load_program_metrics, ) { - invoke_context.programs_modified_by_tx.set_slot_for_tests(0); invoke_context - .programs_modified_by_tx - .replenish(*pubkey, Arc::new(loaded_program)); + .program_cache_for_tx_batch + .set_slot_for_tests(0); + invoke_context + .program_cache_for_tx_batch + .store_modified_entry(*pubkey, Arc::new(loaded_program)); } } } @@ -708,7 +711,7 @@ mod tests { Entrypoint::vm, |invoke_context| { invoke_context - .programs_modified_by_tx + .program_cache_for_tx_batch .environments .program_runtime_v2 = Arc::new(create_program_runtime_environment_v2( &ComputeBudget::default(), diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 27023f608791db..fe8f9472463d97 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "agave-geyser-plugin-interface" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "solana-sdk", @@ -75,7 +75,7 @@ dependencies = [ [[package]] name = "agave-validator" -version = "2.0.0" +version = "2.0.2" dependencies = [ "agave-geyser-plugin-interface", "chrono", @@ -417,7 +417,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure 0.12.6", + "synstructure", ] [[package]] @@ -847,22 +847,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.4.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] @@ -2210,124 +2210,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2347,14 +2229,12 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -2844,12 +2724,6 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - [[package]] name = "lock_api" version = "0.4.10" @@ -4074,7 +3948,7 @@ dependencies = [ "tokio-rustls", "tokio-util 0.7.1", "tower-service", - "url 2.5.1", + "url 2.5.2", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4646,7 +4520,7 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "2.0.0" +version = "2.0.2" dependencies = [ "Inflector", "base64 0.22.1", @@ -4669,12 +4543,13 @@ dependencies = [ [[package]] name = "solana-accounts-db" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "blake3", "bv", "bytemuck", + "bytemuck_derive", "bzip2", "crossbeam-channel", "dashmap", @@ -4711,7 +4586,7 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "bytemuck", @@ -4727,7 +4602,7 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "borsh 1.5.1", "futures 0.3.30", @@ -4742,7 +4617,7 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "2.0.0" +version = "2.0.2" dependencies = [ "serde", "serde_derive", @@ -4752,7 +4627,7 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "crossbeam-channel", @@ -4770,7 +4645,7 @@ dependencies = [ [[package]] name = "solana-bloom" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bv", "fnv", @@ -4785,7 +4660,7 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "byteorder 1.5.0", @@ -4793,21 +4668,23 @@ dependencies = [ "log", "scopeguard", "solana-compute-budget", + "solana-curve25519", "solana-measure", "solana-poseidon", "solana-program-runtime", "solana-sdk", - "solana-zk-token-sdk", + "solana-type-overrides", "solana_rbpf", "thiserror", ] [[package]] name = "solana-bucket-map" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bv", "bytemuck", + "bytemuck_derive", "log", "memmap2", "modular-bitfield", @@ -4820,7 +4697,7 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "chrono", "clap 2.33.3", @@ -4830,12 +4707,12 @@ dependencies = [ "thiserror", "tiny-bip39", "uriparse", - "url 2.5.1", + "url 2.5.2", ] [[package]] name = "solana-cli-config" -version = "2.0.0" +version = "2.0.2" dependencies = [ "dirs-next", "lazy_static", @@ -4844,12 +4721,12 @@ dependencies = [ "serde_yaml", "solana-clap-utils", "solana-sdk", - "url 2.5.1", + "url 2.5.2", ] [[package]] name = "solana-cli-output" -version = "2.0.0" +version = "2.0.2" dependencies = [ "Inflector", "base64 0.22.1", @@ -4874,7 +4751,7 @@ dependencies = [ [[package]] name = "solana-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "bincode", @@ -4905,7 +4782,7 @@ dependencies = [ [[package]] name = "solana-compute-budget" -version = "2.0.0" +version = "2.0.2" dependencies = [ "rustc_version", "solana-sdk", @@ -4913,7 +4790,7 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -4921,7 +4798,7 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "chrono", @@ -4933,7 +4810,7 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "bincode", @@ -4952,7 +4829,7 @@ dependencies = [ [[package]] name = "solana-core" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ahash 0.8.10", "base64 0.22.1", @@ -4987,6 +4864,7 @@ dependencies = [ "solana-bloom", "solana-client", "solana-compute-budget", + "solana-connection-cache", "solana-cost-model", "solana-entry", "solana-geyser-plugin-manager", @@ -5027,7 +4905,7 @@ dependencies = [ [[package]] name = "solana-cost-model" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ahash 0.8.10", "lazy_static", @@ -5046,9 +4924,20 @@ dependencies = [ "solana-vote-program", ] +[[package]] +name = "solana-curve25519" +version = "2.0.2" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "solana-program", + "thiserror", +] + [[package]] name = "solana-download-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "console", "indicatif", @@ -5060,7 +4949,7 @@ dependencies = [ [[package]] name = "solana-entry" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "crossbeam-channel", @@ -5080,7 +4969,7 @@ dependencies = [ [[package]] name = "solana-faucet" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "byteorder 1.5.0", @@ -5102,7 +4991,7 @@ dependencies = [ [[package]] name = "solana-genesis-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "solana-accounts-db", @@ -5113,7 +5002,7 @@ dependencies = [ [[package]] name = "solana-geyser-plugin-manager" -version = "2.0.0" +version = "2.0.2" dependencies = [ "agave-geyser-plugin-interface", "bs58", @@ -5138,7 +5027,7 @@ dependencies = [ [[package]] name = "solana-gossip" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -5184,7 +5073,7 @@ dependencies = [ [[package]] name = "solana-inline-spl" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bytemuck", "rustc_version", @@ -5193,7 +5082,7 @@ dependencies = [ [[package]] name = "solana-ledger" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "bincode", @@ -5259,19 +5148,20 @@ dependencies = [ [[package]] name = "solana-loader-v4-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "solana-compute-budget", "solana-measure", "solana-program-runtime", "solana-sdk", + "solana-type-overrides", "solana_rbpf", ] [[package]] name = "solana-logger" -version = "2.0.0" +version = "2.0.2" dependencies = [ "env_logger", "lazy_static", @@ -5280,7 +5170,7 @@ dependencies = [ [[package]] name = "solana-measure" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "solana-sdk", @@ -5288,7 +5178,7 @@ dependencies = [ [[package]] name = "solana-merkle-tree" -version = "2.0.0" +version = "2.0.2" dependencies = [ "fast-math", "solana-program", @@ -5296,7 +5186,7 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "2.0.0" +version = "2.0.2" dependencies = [ "crossbeam-channel", "gethostname", @@ -5309,7 +5199,7 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "clap 3.2.25", @@ -5325,7 +5215,7 @@ dependencies = [ "solana-version", "static_assertions", "tokio", - "url 2.5.1", + "url 2.5.2", ] [[package]] @@ -5336,7 +5226,7 @@ checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" [[package]] name = "solana-perf" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ahash 0.8.10", "bincode", @@ -5361,7 +5251,7 @@ dependencies = [ [[package]] name = "solana-poh" -version = "2.0.0" +version = "2.0.2" dependencies = [ "core_affinity", "crossbeam-channel", @@ -5377,7 +5267,7 @@ dependencies = [ [[package]] name = "solana-poseidon" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ark-bn254", "light-poseidon", @@ -5386,7 +5276,7 @@ dependencies = [ [[package]] name = "solana-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "ark-bn254", "ark-ec", @@ -5401,12 +5291,11 @@ dependencies = [ "bs58", "bv", "bytemuck", - "cc", + "bytemuck_derive", "console_error_panic_hook", "console_log", "curve25519-dalek", "getrandom 0.2.10", - "itertools 0.12.1", "js-sys", "lazy_static", "libsecp256k1 0.6.0", @@ -5431,7 +5320,7 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "2.0.0" +version = "2.0.2" dependencies = [ "base64 0.22.1", "bincode", @@ -5450,6 +5339,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-sdk", + "solana-type-overrides", "solana-vote", "solana_rbpf", "thiserror", @@ -5457,7 +5347,7 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "async-trait", @@ -5487,7 +5377,7 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "crossbeam-channel", "futures-util", @@ -5505,12 +5395,12 @@ dependencies = [ "tokio-stream", "tokio-tungstenite", "tungstenite", - "url 2.5.1", + "url 2.5.2", ] [[package]] name = "solana-quic-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-mutex", "async-trait", @@ -5534,7 +5424,7 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "2.0.0" +version = "2.0.2" dependencies = [ "lazy_static", "num_cpus", @@ -5542,7 +5432,7 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "2.0.0" +version = "2.0.2" dependencies = [ "console", "dialoguer", @@ -5559,7 +5449,7 @@ dependencies = [ [[package]] name = "solana-rpc" -version = "2.0.0" +version = "2.0.2" dependencies = [ "base64 0.22.1", "bincode", @@ -5616,7 +5506,7 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "base64 0.22.1", @@ -5641,7 +5531,7 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "base64 0.22.1", @@ -5663,7 +5553,7 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "2.0.0" +version = "2.0.2" dependencies = [ "clap 2.33.3", "solana-clap-utils", @@ -5674,7 +5564,7 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "2.0.0" +version = "2.0.2" dependencies = [ "aquamarine", "arrayref", @@ -5736,6 +5626,8 @@ dependencies = [ "solana-version", "solana-vote", "solana-vote-program", + "solana-zk-elgamal-proof-program", + "solana-zk-sdk", "solana-zk-token-proof-program", "solana-zk-token-sdk", "static_assertions", @@ -5750,7 +5642,7 @@ dependencies = [ [[package]] name = "solana-sbf-programs" -version = "2.0.0" +version = "2.0.2" dependencies = [ "agave-validator", "bincode", @@ -5770,26 +5662,20 @@ dependencies = [ "solana-measure", "solana-program", "solana-program-runtime", - "solana-program-test", "solana-runtime", "solana-sbf-rust-invoke-dep", - "solana-sbf-rust-mem", "solana-sbf-rust-realloc-dep", "solana-sbf-rust-realloc-invoke-dep", - "solana-sbf-rust-remaining-compute-units", - "solana-sbf-rust-sanity", - "solana-sbf-rust-simulation", - "solana-sbf-rust-sysvar", "solana-sdk", "solana-svm", "solana-transaction-status", + "solana-type-overrides", "solana_rbpf", - "walkdir", ] [[package]] name = "solana-sbf-rust-128bit" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", "solana-sbf-rust-128bit-dep", @@ -5797,21 +5683,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-128bit-dep" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-alloc" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-alt-bn128" -version = "2.0.0" +version = "2.0.2" dependencies = [ "array-bytes", "solana-program", @@ -5819,7 +5705,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-alt-bn128-compression" -version = "2.0.0" +version = "2.0.2" dependencies = [ "array-bytes", "solana-program", @@ -5827,7 +5713,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-big-mod-exp" -version = "2.0.0" +version = "2.0.2" dependencies = [ "array-bytes", "serde", @@ -5838,36 +5724,37 @@ dependencies = [ [[package]] name = "solana-sbf-rust-call-depth" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-caller-access" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-curve25519" -version = "2.0.0" +version = "2.0.2" dependencies = [ + "solana-curve25519", "solana-program", "solana-zk-token-sdk", ] [[package]] name = "solana-sbf-rust-custom-heap" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-dep-crate" -version = "2.0.0" +version = "2.0.2" dependencies = [ "byteorder 1.5.0", "solana-program", @@ -5875,21 +5762,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-deprecated-loader" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-dup-accounts" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-error-handling" -version = "2.0.0" +version = "2.0.2" dependencies = [ "num-derive", "num-traits", @@ -5899,42 +5786,42 @@ dependencies = [ [[package]] name = "solana-sbf-rust-external-spend" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-finalize" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-get-minimum-delegation" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-inner_instruction_alignment_check" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-instruction-introspection" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-invoke" -version = "2.0.0" +version = "2.0.2" dependencies = [ "rustversion", "solana-program", @@ -5945,32 +5832,32 @@ dependencies = [ [[package]] name = "solana-sbf-rust-invoke-and-error" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-invoke-and-ok" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-invoke-and-return" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-invoke-dep" -version = "2.0.0" +version = "2.0.2" [[package]] name = "solana-sbf-rust-invoked" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", "solana-sbf-rust-invoked-dep", @@ -5978,28 +5865,28 @@ dependencies = [ [[package]] name = "solana-sbf-rust-invoked-dep" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-iter" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-log-data" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-many-args" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", "solana-sbf-rust-many-args-dep", @@ -6007,14 +5894,14 @@ dependencies = [ [[package]] name = "solana-sbf-rust-many-args-dep" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-mem" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", "solana-sbf-rust-mem-dep", @@ -6022,14 +5909,14 @@ dependencies = [ [[package]] name = "solana-sbf-rust-mem-dep" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-membuiltins" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", "solana-sbf-rust-mem-dep", @@ -6037,21 +5924,21 @@ dependencies = [ [[package]] name = "solana-sbf-rust-noop" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-panic" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-param-passing" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", "solana-sbf-rust-param-passing-dep", @@ -6059,14 +5946,14 @@ dependencies = [ [[package]] name = "solana-sbf-rust-param-passing-dep" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-poseidon" -version = "2.0.0" +version = "2.0.2" dependencies = [ "array-bytes", "solana-poseidon", @@ -6075,7 +5962,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-rand" -version = "2.0.0" +version = "2.0.2" dependencies = [ "getrandom 0.2.10", "rand 0.8.5", @@ -6084,7 +5971,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-realloc" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", "solana-sbf-rust-realloc-dep", @@ -6092,14 +5979,14 @@ dependencies = [ [[package]] name = "solana-sbf-rust-realloc-dep" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-realloc-invoke" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", "solana-sbf-rust-realloc-dep", @@ -6108,39 +5995,39 @@ dependencies = [ [[package]] name = "solana-sbf-rust-realloc-invoke-dep" -version = "2.0.0" +version = "2.0.2" [[package]] name = "solana-sbf-rust-remaining-compute-units" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-ro-account_modify" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-ro-modify" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-sanity" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-secp256k1-recover" -version = "2.0.0" +version = "2.0.2" dependencies = [ "libsecp256k1 0.7.0", "solana-program", @@ -6148,7 +6035,7 @@ dependencies = [ [[package]] name = "solana-sbf-rust-sha" -version = "2.0.0" +version = "2.0.2" dependencies = [ "blake3", "solana-program", @@ -6156,69 +6043,70 @@ dependencies = [ [[package]] name = "solana-sbf-rust-sibling-inner-instructions" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-sibling-instructions" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-simulation" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-spoof1" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-spoof1-system" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-sysvar" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-upgradeable" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sbf-rust-upgraded" -version = "2.0.0" +version = "2.0.2" dependencies = [ "solana-program", ] [[package]] name = "solana-sdk" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "bitflags 2.5.0", "borsh 1.5.1", "bs58", "bytemuck", + "bytemuck_derive", "byteorder 1.5.0", "chrono", "derivation-path", @@ -6259,7 +6147,7 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bs58", "proc-macro2", @@ -6276,11 +6164,12 @@ checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" [[package]] name = "solana-send-transaction-service" -version = "2.0.0" +version = "2.0.2" dependencies = [ "crossbeam-channel", "log", "solana-client", + "solana-connection-cache", "solana-measure", "solana-metrics", "solana-runtime", @@ -6290,7 +6179,7 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "log", @@ -6298,12 +6187,13 @@ dependencies = [ "solana-config-program", "solana-program-runtime", "solana-sdk", + "solana-type-overrides", "solana-vote-program", ] [[package]] name = "solana-storage-bigtable" -version = "2.0.0" +version = "2.0.2" dependencies = [ "backoff", "bincode", @@ -6335,7 +6225,7 @@ dependencies = [ [[package]] name = "solana-storage-proto" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "bs58", @@ -6350,7 +6240,7 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-channel", "bytes", @@ -6382,7 +6272,7 @@ dependencies = [ [[package]] name = "solana-svm" -version = "2.0.0" +version = "2.0.2" dependencies = [ "itertools 0.12.1", "log", @@ -6400,12 +6290,13 @@ dependencies = [ "solana-program-runtime", "solana-sdk", "solana-system-program", + "solana-type-overrides", "solana-vote", ] [[package]] name = "solana-system-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "log", @@ -6413,11 +6304,12 @@ dependencies = [ "serde_derive", "solana-program-runtime", "solana-sdk", + "solana-type-overrides", ] [[package]] name = "solana-test-validator" -version = "2.0.0" +version = "2.0.2" dependencies = [ "base64 0.22.1", "bincode", @@ -6447,7 +6339,7 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "log", @@ -6460,7 +6352,7 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "bincode", @@ -6482,7 +6374,7 @@ dependencies = [ [[package]] name = "solana-transaction-metrics-tracker" -version = "2.0.0" +version = "2.0.2" dependencies = [ "Inflector", "base64 0.22.1", @@ -6496,7 +6388,7 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "2.0.0" +version = "2.0.2" dependencies = [ "Inflector", "base64 0.22.1", @@ -6521,7 +6413,7 @@ dependencies = [ [[package]] name = "solana-turbine" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "bytes", @@ -6553,9 +6445,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "solana-type-overrides" +version = "2.0.2" +dependencies = [ + "lazy_static", + "rand 0.8.5", +] + [[package]] name = "solana-udp-client" -version = "2.0.0" +version = "2.0.2" dependencies = [ "async-trait", "solana-connection-cache", @@ -6568,7 +6468,7 @@ dependencies = [ [[package]] name = "solana-unified-scheduler-logic" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "solana-sdk", @@ -6577,7 +6477,7 @@ dependencies = [ [[package]] name = "solana-unified-scheduler-pool" -version = "2.0.0" +version = "2.0.2" dependencies = [ "assert_matches", "crossbeam-channel", @@ -6596,7 +6496,7 @@ dependencies = [ [[package]] name = "solana-version" -version = "2.0.0" +version = "2.0.2" dependencies = [ "log", "rustc_version", @@ -6608,7 +6508,7 @@ dependencies = [ [[package]] name = "solana-vote" -version = "2.0.0" +version = "2.0.2" dependencies = [ "itertools 0.12.1", "log", @@ -6621,7 +6521,7 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bincode", "log", @@ -6639,7 +6539,7 @@ dependencies = [ [[package]] name = "solana-wen-restart" -version = "2.0.0" +version = "2.0.2" dependencies = [ "anyhow", "log", @@ -6660,9 +6560,48 @@ dependencies = [ "solana-vote-program", ] +[[package]] +name = "solana-zk-elgamal-proof-program" +version = "2.0.2" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-program-runtime", + "solana-sdk", + "solana-zk-sdk", +] + +[[package]] +name = "solana-zk-sdk" +version = "2.0.2" +dependencies = [ + "aes-gcm-siv", + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "itertools 0.12.1", + "lazy_static", + "merlin", + "num-derive", + "num-traits", + "rand 0.7.3", + "serde", + "serde_derive", + "serde_json", + "sha3 0.9.1", + "solana-program", + "solana-sdk", + "subtle", + "thiserror", + "zeroize", +] + [[package]] name = "solana-zk-token-proof-program" -version = "2.0.0" +version = "2.0.2" dependencies = [ "bytemuck", "num-derive", @@ -6674,15 +6613,15 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "2.0.0" +version = "2.0.2" dependencies = [ "aes-gcm-siv", "base64 0.22.1", "bincode", "bytemuck", + "bytemuck_derive", "byteorder 1.5.0", "curve25519-dalek", - "getrandom 0.1.14", "itertools 0.12.1", "lazy_static", "merlin", @@ -6693,6 +6632,7 @@ dependencies = [ "serde_derive", "serde_json", "sha3 0.9.1", + "solana-curve25519", "solana-program", "solana-sdk", "subtle", @@ -6733,9 +6673,9 @@ checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" [[package]] name = "spl-associated-token-account" -version = "3.0.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e688554bac5838217ffd1fab7845c573ff106b6336bf7d290db7c98d5a8efd" +checksum = "68034596cf4804880d265f834af1ff2f821ad5293e41fa0f8f59086c181fc38e" dependencies = [ "assert_matches", "borsh 1.5.1", @@ -6749,9 +6689,9 @@ dependencies = [ [[package]] name = "spl-discriminator" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d1814406e98b08c5cd02c1126f83fd407ad084adce0b05fda5730677822eac" +checksum = "a38ea8b6dedb7065887f12d62ed62c1743aa70749e8558f963609793f6fb12bc" dependencies = [ "bytemuck", "solana-program", @@ -6784,21 +6724,22 @@ dependencies = [ [[package]] name = "spl-memo" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e9bae02de3405079a057fe244c867a08f92d48327d231fc60da831f94caf0a" +checksum = "a0dba2f2bb6419523405d21c301a32c9f9568354d4742552e7972af801f4bdb3" dependencies = [ "solana-program", ] [[package]] name = "spl-pod" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ce669f48cf2eca1ec518916d8725596bfb655beb1c74374cf71dc6cb773c9" +checksum = "e6166a591d93af33afd75bbd8573c5fd95fb1213f1bf254f0508c89fdb5ee156" dependencies = [ "borsh 1.5.1", "bytemuck", + "bytemuck_derive", "solana-program", "solana-zk-token-sdk", "spl-program-error", @@ -6806,9 +6747,9 @@ dependencies = [ [[package]] name = "spl-program-error" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49065093ea91f57b9b2bd81493ff705e2ad4e64507a07dbc02b085778e02770e" +checksum = "d7b28bed65356558133751cc32b48a7a5ddfc59ac4e941314630bbed1ac10532" dependencies = [ "num-derive", "num-traits", @@ -6831,9 +6772,9 @@ dependencies = [ [[package]] name = "spl-tlv-account-resolution" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cace91ba08984a41556efe49cbf2edca4db2f577b649da7827d3621161784bf8" +checksum = "37a75a5f0fcc58126693ed78a17042e9dc53f07e357d6be91789f7d62aff61a4" dependencies = [ "bytemuck", "solana-program", @@ -6845,9 +6786,9 @@ dependencies = [ [[package]] name = "spl-token" -version = "4.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ae123223633a389f95d1da9d49c2d0a50d499e7060b9624626a69e536ad2a4" +checksum = "70a0f06ac7f23dc0984931b1fe309468f14ea58e32660439c1cef19456f5d0e3" dependencies = [ "arrayref", "bytemuck", @@ -6860,9 +6801,9 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "3.0.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5412f99ae7ee6e0afde00defaa354e6228e47e30c0e3adf553e2e01e6abb584" +checksum = "d9c10f3483e48679619c76598d4e4aebb955bc49b0a5cc63323afbf44135c9bf" dependencies = [ "arrayref", "bytemuck", @@ -6884,9 +6825,9 @@ dependencies = [ [[package]] name = "spl-token-group-interface" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d419b5cfa3ee8e0f2386fd7e02a33b3ec8a7db4a9c7064a2ea24849dc4a273b6" +checksum = "df8752b85a5ecc1d9f3a43bce3dd9a6a053673aacf5deb513d1cbb88d3534ffd" dependencies = [ "bytemuck", "solana-program", @@ -6897,9 +6838,9 @@ dependencies = [ [[package]] name = "spl-token-metadata-interface" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30179c47e93625680dabb620c6e7931bd12d62af390f447bc7beb4a3a9b5feee" +checksum = "c6c2318ddff97e006ed9b1291ebec0750a78547f870f62a69c56fe3b46a5d8fc" dependencies = [ "borsh 1.5.1", "solana-program", @@ -6911,9 +6852,9 @@ dependencies = [ [[package]] name = "spl-transfer-hook-interface" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a98359769cd988f7b35c02558daa56d496a7e3bd8626e61f90a7c757eedb9b" +checksum = "a110f33d941275d9f868b96daaa993f1e73b6806cc8836e43075b4d3ad8338a7" dependencies = [ "arrayref", "bytemuck", @@ -6927,9 +6868,9 @@ dependencies = [ [[package]] name = "spl-type-length-value" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ce13429dbd41d2cee8a73931c05fda0b0c8ca156a8b0c19445642550bb61a" +checksum = "bdcd73ec187bc409464c60759232e309f83b52a18a9c5610bf281c9c6432918c" dependencies = [ "bytemuck", "solana-program", @@ -6938,12 +6879,6 @@ dependencies = [ "spl-program-error", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -7059,17 +6994,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", -] - [[package]] name = "sys-info" version = "0.9.1" @@ -7312,16 +7236,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -7658,7 +7572,7 @@ dependencies = [ "rustls", "sha1", "thiserror", - "url 2.5.1", + "url 2.5.2", "utf-8", "webpki-roots 0.24.0", ] @@ -7777,12 +7691,12 @@ dependencies = [ [[package]] name = "url" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna 1.0.0", + "idna 0.5.0", "percent-encoding 2.3.1", ] @@ -7792,18 +7706,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "valuable" version = "0.1.0" @@ -8169,18 +8071,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "x509-parser" version = "0.14.0" @@ -8210,30 +8100,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", - "synstructure 0.13.1", -] - [[package]] name = "zerocopy" version = "0.7.31" @@ -8254,27 +8120,6 @@ dependencies = [ "syn 2.0.58", ] -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", - "synstructure 0.13.1", -] - [[package]] name = "zeroize" version = "1.3.0" @@ -8295,28 +8140,6 @@ dependencies = [ "syn 2.0.58", ] -[[package]] -name = "zerovec" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", -] - [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 6e8f54a427a986..a2880b5b1c6d9c 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "2.0.0" +version = "2.0.2" description = "Solana SBF test program written in Rust" authors = ["Anza Maintainers "] repository = "https://github.com/anza-xyz/agave" @@ -26,37 +26,33 @@ rustversion = "1.0.14" serde = "1.0.112" # must match the serde_derive version, see https://github.com/serde-rs/serde/issues/2584#issuecomment-1685252251 serde_derive = "1.0.112" # must match the serde version, see https://github.com/serde-rs/serde/issues/2584#issuecomment-1685252251 serde_json = "1.0.56" -solana-account-decoder = { path = "../../account-decoder", version = "=2.0.0" } -solana-accounts-db = { path = "../../accounts-db", version = "=2.0.0" } -solana-bpf-loader-program = { path = "../bpf_loader", version = "=2.0.0" } -solana-cli-output = { path = "../../cli-output", version = "=2.0.0" } -solana-compute-budget = { path = "../../compute-budget", version = "=2.0.0" } -solana-ledger = { path = "../../ledger", version = "=2.0.0" } -solana-logger = { path = "../../logger", version = "=2.0.0" } -solana-measure = { path = "../../measure", version = "=2.0.0" } -solana-poseidon = { path = "../../poseidon/", version = "=2.0.0" } -solana-program = { path = "../../sdk/program", version = "=2.0.0" } -solana-program-runtime = { path = "../../program-runtime", version = "=2.0.0" } -solana-program-test = { path = "../../program-test", version = "=2.0.0" } -solana-runtime = { path = "../../runtime", version = "=2.0.0" } -solana-sbf-rust-128bit-dep = { path = "rust/128bit_dep", version = "=2.0.0" } -solana-sbf-rust-invoke-dep = { path = "rust/invoke_dep", version = "=2.0.0" } -solana-sbf-rust-invoked-dep = { path = "rust/invoked_dep", version = "=2.0.0" } -solana-sbf-rust-many-args-dep = { path = "rust/many_args_dep", version = "=2.0.0" } -solana-sbf-rust-mem = { path = "rust/mem", version = "=2.0.0" } -solana-sbf-rust-mem-dep = { path = "rust/mem_dep", version = "=2.0.0" } -solana-sbf-rust-param-passing-dep = { path = "rust/param_passing_dep", version = "=2.0.0" } -solana-sbf-rust-realloc-dep = { path = "rust/realloc_dep", version = "=2.0.0" } -solana-sbf-rust-realloc-invoke-dep = { path = "rust/realloc_invoke_dep", version = "=2.0.0" } -solana-sbf-rust-remaining-compute-units = { path = "rust/remaining_compute_units", version = "=2.0.0" } -solana-sbf-rust-sanity = { path = "rust/sanity", version = "=2.0.0" } -solana-sbf-rust-simulation = { path = "rust/simulation", version = "=2.0.0" } -solana-sbf-rust-sysvar = { path = "rust/sysvar", version = "=2.0.0" } -solana-sdk = { path = "../../sdk", version = "=2.0.0" } -solana-svm = { path = "../../svm", version = "=2.0.0" } -solana-transaction-status = { path = "../../transaction-status", version = "=2.0.0" } -agave-validator = { path = "../../validator", version = "=2.0.0" } -solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=2.0.0" } +solana-account-decoder = { path = "../../account-decoder", version = "=2.0.2" } +solana-accounts-db = { path = "../../accounts-db", version = "=2.0.2" } +solana-bpf-loader-program = { path = "../bpf_loader", version = "=2.0.2" } +solana-cli-output = { path = "../../cli-output", version = "=2.0.2" } +solana-compute-budget = { path = "../../compute-budget", version = "=2.0.2" } +solana-curve25519 = { path = "../../curves/curve25519", version = "=2.0.2" } +solana-ledger = { path = "../../ledger", version = "=2.0.2" } +solana-logger = { path = "../../logger", version = "=2.0.2" } +solana-measure = { path = "../../measure", version = "=2.0.2" } +solana-poseidon = { path = "../../poseidon/", version = "=2.0.2" } +solana-program = { path = "../../sdk/program", version = "=2.0.2" } +solana-program-runtime = { path = "../../program-runtime", version = "=2.0.2" } +solana-runtime = { path = "../../runtime", version = "=2.0.2" } +solana-sbf-rust-128bit-dep = { path = "rust/128bit_dep", version = "=2.0.2" } +solana-sbf-rust-invoke-dep = { path = "rust/invoke_dep", version = "=2.0.2" } +solana-sbf-rust-invoked-dep = { path = "rust/invoked_dep", version = "=2.0.2" } +solana-sbf-rust-many-args-dep = { path = "rust/many_args_dep", version = "=2.0.2" } +solana-sbf-rust-mem-dep = { path = "rust/mem_dep", version = "=2.0.2" } +solana-sbf-rust-param-passing-dep = { path = "rust/param_passing_dep", version = "=2.0.2" } +solana-sbf-rust-realloc-dep = { path = "rust/realloc_dep", version = "=2.0.2" } +solana-sbf-rust-realloc-invoke-dep = { path = "rust/realloc_invoke_dep", version = "=2.0.2" } +solana-sdk = { path = "../../sdk", version = "=2.0.2" } +solana-svm = { path = "../../svm", version = "=2.0.2" } +solana-transaction-status = { path = "../../transaction-status", version = "=2.0.2" } +solana-type-overrides = { path = "../../type-overrides", version = "=2.0.2" } +agave-validator = { path = "../../validator", version = "=2.0.2" } +solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=2.0.2" } solana_rbpf = "=0.8.1" thiserror = "1.0" @@ -73,14 +69,16 @@ homepage = { workspace = true } license = { workspace = true } edition = { workspace = true } +[profile.release] +# The test programs are build in release mode +# Minimize their file size so that they fit into the account size limit +strip = true + [features] sbf_c = [] sbf_rust = [] dummy-for-ci-check = ["sbf_c", "sbf_rust"] -[build-dependencies] -walkdir = "2" - [dev-dependencies] agave-validator = { workspace = true } bincode = { workspace = true } @@ -100,19 +98,14 @@ solana-logger = { workspace = true } solana-measure = { workspace = true } solana-program = { workspace = true } solana-program-runtime = { workspace = true } -solana-program-test = { workspace = true } solana-runtime = { workspace = true, features = ["dev-context-only-utils"] } solana-sbf-rust-invoke-dep = { workspace = true } -solana-sbf-rust-mem = { workspace = true } solana-sbf-rust-realloc-dep = { workspace = true } solana-sbf-rust-realloc-invoke-dep = { workspace = true } -solana-sbf-rust-remaining-compute-units = { workspace = true } -solana-sbf-rust-sanity = { workspace = true } -solana-sbf-rust-simulation = { workspace = true } -solana-sbf-rust-sysvar = { workspace = true } solana-sdk = { workspace = true, features = ["dev-context-only-utils"] } solana-svm = { workspace = true } solana-transaction-status = { workspace = true } +solana-type-overrides = { workspace = true } solana_rbpf = { workspace = true } [[bench]] @@ -216,5 +209,6 @@ members = [ # # There is a similar override in `../../Cargo.toml`. Please keep both comments # and the overrides in sync. +solana-curve25519 = { path = "../../curves/curve25519" } solana-program = { path = "../../sdk/program" } solana-zk-token-sdk = { path = "../../zk-token-sdk" } diff --git a/programs/sbf/Makefile b/programs/sbf/Makefile new file mode 100755 index 00000000000000..3f213d352a2995 --- /dev/null +++ b/programs/sbf/Makefile @@ -0,0 +1,13 @@ +SBF_SDK_PATH := ../../sdk/sbf +SRC_DIR := c/src +OUT_DIR := target/sbf-solana-solana/release + +test: rust all + SBF_OUT_DIR=$(OUT_DIR) cargo test --features="sbf_rust,sbf_c" $(TEST_ARGS) + +rust: + cargo +solana build --release --target sbf-solana-solana --workspace + +.PHONY: rust + +include $(SBF_SDK_PATH)/c/sbf.mk diff --git a/programs/sbf/benches/bpf_loader.rs b/programs/sbf/benches/bpf_loader.rs index 489c5224f24ee1..ab5e950faab874 100644 --- a/programs/sbf/benches/bpf_loader.rs +++ b/programs/sbf/benches/bpf_loader.rs @@ -139,7 +139,7 @@ fn bench_program_alu(bencher: &mut Bencher) { vec![], &mut invoke_context, ); - let mut vm = vm.unwrap(); + let (mut vm, _, _) = vm.unwrap(); println!("Interpreted:"); vm.context_object_pointer @@ -314,7 +314,7 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) { account_lengths, &mut invoke_context, ); - let mut vm = vm.unwrap(); + let (mut vm, _, _) = vm.unwrap(); let mut measure = Measure::start("tune"); let (instructions, _result) = vm.execute_program(&executable, true); diff --git a/programs/sbf/build.rs b/programs/sbf/build.rs deleted file mode 100644 index 97f2423162aee7..00000000000000 --- a/programs/sbf/build.rs +++ /dev/null @@ -1,130 +0,0 @@ -extern crate walkdir; - -use { - std::{env, path::Path, process::Command}, - walkdir::WalkDir, -}; - -fn rerun_if_changed(files: &[&str], directories: &[&str], excludes: &[&str]) { - let mut all_files: Vec<_> = files.iter().map(|f| f.to_string()).collect(); - - for directory in directories { - let files_in_directory: Vec<_> = WalkDir::new(directory) - .into_iter() - .map(|entry| entry.unwrap()) - .filter(|entry| { - if !entry.file_type().is_file() { - return false; - } - for exclude in excludes.iter() { - if entry.path().to_str().unwrap().contains(exclude) { - return false; - } - } - true - }) - .map(|f| f.path().to_str().unwrap().to_owned()) - .collect(); - all_files.extend_from_slice(&files_in_directory[..]); - } - - for file in all_files { - if !Path::new(&file).is_file() { - panic!("{file} is not a file"); - } - println!("cargo:rerun-if-changed={file}"); - } -} - -fn main() { - if env::var("CARGO_FEATURE_DUMMY_FOR_CI_CHECK").is_ok() { - println!("cargo:warning=(not a warning) Compiling with host toolchain for CI..."); - return; - } - - let build_profile = env::var("PROFILE").expect("`PROFILE` envvar to be set"); - let install_dir = format!("target/{build_profile}/sbf"); - let sbf_c = env::var("CARGO_FEATURE_SBF_C").is_ok(); - if sbf_c { - let install_dir = format!("OUT_DIR=../{install_dir}"); - println!("cargo:warning=(not a warning) Building C-based on-chain programs"); - assert!(Command::new("make") - .current_dir("c") - .arg("programs") - .arg(&install_dir) - .status() - .expect("Failed to build C-based SBF programs") - .success()); - - rerun_if_changed(&["c/makefile"], &["c/src", "../../sdk"], &["/target/"]); - } - - let sbf_rust = env::var("CARGO_FEATURE_SBF_RUST").is_ok(); - if sbf_rust { - let rust_programs = [ - "128bit", - "alloc", - "alt_bn128", - "alt_bn128_compression", - "big_mod_exp", - "call_depth", - "caller_access", - "curve25519", - "custom_heap", - "dep_crate", - "deprecated_loader", - "dup_accounts", - "error_handling", - "log_data", - "external_spend", - "finalize", - "get_minimum_delegation", - "inner_instruction_alignment_check", - "instruction_introspection", - "invoke", - "invoke_and_error", - "invoke_and_ok", - "invoke_and_return", - "invoked", - "iter", - "many_args", - "mem", - "membuiltins", - "noop", - "panic", - "param_passing", - "poseidon", - "rand", - "realloc", - "realloc_invoke", - "remaining_compute_units", - "ro_modify", - "ro_account_modify", - "sanity", - "secp256k1_recover", - "sha", - "sibling_inner_instructions", - "sibling_instructions", - "simulation", - "spoof1", - "spoof1_system", - "upgradeable", - "upgraded", - ]; - for program in rust_programs.iter() { - println!("cargo:warning=(not a warning) Building Rust-based on-chain programs: solana_sbf_rust_{program}"); - assert!(Command::new("../../cargo-build-sbf") - .args([ - "--manifest-path", - &format!("rust/{program}/Cargo.toml"), - "--sbf-out-dir", - &install_dir - ]) - .status() - .expect("Error calling cargo-build-sbf from build.rs") - .success()); - } - - rerun_if_changed(&[], &["rust", "../../sdk", &install_dir], &["/target/"]); - } -} diff --git a/programs/sbf/c/makefile b/programs/sbf/c/makefile deleted file mode 100644 index 77b774c2a6bf82..00000000000000 --- a/programs/sbf/c/makefile +++ /dev/null @@ -1,2 +0,0 @@ -SBF_SDK := ../../../sdk/sbf/c -include $(SBF_SDK)/sbf.mk diff --git a/programs/sbf/rust/curve25519/Cargo.toml b/programs/sbf/rust/curve25519/Cargo.toml index c75477788e0dca..ad555810ff203e 100644 --- a/programs/sbf/rust/curve25519/Cargo.toml +++ b/programs/sbf/rust/curve25519/Cargo.toml @@ -9,6 +9,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +solana-curve25519 = { workspace = true } solana-program = { workspace = true } solana-zk-token-sdk = { workspace = true } diff --git a/programs/sbf/rust/curve25519/src/lib.rs b/programs/sbf/rust/curve25519/src/lib.rs index a8096d65b34710..42718278c2c685 100644 --- a/programs/sbf/rust/curve25519/src/lib.rs +++ b/programs/sbf/rust/curve25519/src/lib.rs @@ -2,8 +2,8 @@ extern crate solana_program; use { + solana_curve25519::{edwards, ristretto, scalar}, solana_program::{custom_heap_default, custom_panic_default, msg}, - solana_zk_token_sdk::curve25519::{edwards, ristretto, scalar}, }; #[no_mangle] diff --git a/programs/sbf/rust/invoke/src/lib.rs b/programs/sbf/rust/invoke/src/lib.rs index 72d23245ce7150..1f1eb97abf8281 100644 --- a/programs/sbf/rust/invoke/src/lib.rs +++ b/programs/sbf/rust/invoke/src/lib.rs @@ -1350,6 +1350,100 @@ fn process_instruction<'a>( let byte_index = usize::from_le_bytes(instruction_data[2..10].try_into().unwrap()); target_account.data.borrow_mut()[byte_index] = instruction_data[10]; } + TEST_CALLEE_ACCOUNT_UPDATES => { + msg!("TEST_CALLEE_ACCOUNT_UPDATES"); + + if instruction_data.len() < 2 + 2 * std::mem::size_of::() { + return Ok(()); + } + + let writable = instruction_data[1] != 0; + let resize = usize::from_le_bytes(instruction_data[2..10].try_into().unwrap()); + let write_offset = usize::from_le_bytes(instruction_data[10..18].try_into().unwrap()); + let invoke_struction = &instruction_data[18..]; + + let account = &accounts[ARGUMENT_INDEX]; + + if resize != 0 { + account.realloc(resize, false).unwrap(); + } + + if !invoke_struction.is_empty() { + // Invoke another program. With direct mapping, before CPI the callee will update the accounts (incl resizing) + // so the pointer may change. + let invoked_program_id = accounts[INVOKED_PROGRAM_INDEX].key; + + invoke( + &create_instruction( + *invoked_program_id, + &[ + (accounts[MINT_INDEX].key, false, false), + (accounts[ARGUMENT_INDEX].key, writable, false), + (invoked_program_id, false, false), + ], + invoke_struction.to_vec(), + ), + accounts, + ) + .unwrap(); + } + + if write_offset != 0 { + // Ensure we still have access to the correct account + account.data.borrow_mut()[write_offset] ^= 0xe5; + } + } + TEST_STACK_HEAP_ZEROED => { + msg!("TEST_STACK_HEAP_ZEROED"); + const MM_STACK_START: u64 = 0x200000000; + const MM_HEAP_START: u64 = 0x300000000; + const ZEROS: [u8; 256 * 1024] = [0; 256 * 1024]; + const STACK_FRAME_SIZE: usize = 4096; + const MAX_CALL_DEPTH: usize = 64; + + // Check that the heap is always zeroed. + // + // At this point the code up to here will have allocated some values on the heap. The + // bump allocator writes the current heap pointer to the start of the memory region. We + // read it to find the slice of unallocated memory and check that it's zeroed. We then + // fill this memory with a sentinel value, and in the next nested invocation check that + // it's been zeroed as expected. + let heap_len = usize::from_le_bytes(instruction_data[1..9].try_into().unwrap()); + let heap = unsafe { slice::from_raw_parts_mut(MM_HEAP_START as *mut u8, heap_len) }; + let pos = usize::from_le_bytes(heap[0..8].try_into().unwrap()) + .saturating_sub(MM_HEAP_START as usize); + assert!(heap[8..pos] == ZEROS[8..pos], "heap not zeroed"); + heap[8..pos].fill(42); + + // Check that the stack is zeroed too. + // + // We don't know in which frame we are now, so we skip a few (10) frames at the start + // which might have been used by the current call stack. We check that the memory for + // the 10..MAX_CALL_DEPTH frames is zeroed. Then we write a sentinel value, and in the + // next nested invocation check that it's been zeroed. + let stack = + unsafe { slice::from_raw_parts_mut(MM_STACK_START as *mut u8, 0x100000000) }; + for i in 10..MAX_CALL_DEPTH { + let stack = &mut stack[i * STACK_FRAME_SIZE..][..STACK_FRAME_SIZE]; + assert!(stack == &ZEROS[..STACK_FRAME_SIZE], "stack not zeroed"); + stack.fill(42); + } + + // Recurse to check that the stack and heap are zeroed. + // + // We recurse until we go over max CPI depth and error out. Stack and heap allocations + // are reused across CPI, by going over max depth we ensure that it's impossible to get + // non-zeroed regions through execution. + invoke( + &create_instruction( + *program_id, + &[(program_id, false, false)], + instruction_data.to_vec(), + ), + accounts, + ) + .unwrap(); + } _ => panic!("unexpected program data"), } diff --git a/programs/sbf/rust/invoke_dep/src/lib.rs b/programs/sbf/rust/invoke_dep/src/lib.rs index b335fb52f5b6b1..066e900b7f9d2e 100644 --- a/programs/sbf/rust/invoke_dep/src/lib.rs +++ b/programs/sbf/rust/invoke_dep/src/lib.rs @@ -39,6 +39,8 @@ pub const TEST_CPI_INVALID_LAMPORTS_POINTER: u8 = 36; pub const TEST_CPI_INVALID_DATA_POINTER: u8 = 37; pub const TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION: u8 = 38; pub const TEST_WRITE_ACCOUNT: u8 = 39; +pub const TEST_CALLEE_ACCOUNT_UPDATES: u8 = 40; +pub const TEST_STACK_HEAP_ZEROED: u8 = 41; pub const MINT_INDEX: usize = 0; pub const ARGUMENT_INDEX: usize = 1; diff --git a/programs/sbf/rust/mem/Cargo.toml b/programs/sbf/rust/mem/Cargo.toml index ab05428bcd0a26..9b99a551c7c8ec 100644 --- a/programs/sbf/rust/mem/Cargo.toml +++ b/programs/sbf/rust/mem/Cargo.toml @@ -13,4 +13,4 @@ solana-program = { workspace = true } solana-sbf-rust-mem-dep = { workspace = true } [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["cdylib"] diff --git a/programs/sbf/rust/remaining_compute_units/Cargo.toml b/programs/sbf/rust/remaining_compute_units/Cargo.toml index 403177a8df61d6..c35ed06152b234 100644 --- a/programs/sbf/rust/remaining_compute_units/Cargo.toml +++ b/programs/sbf/rust/remaining_compute_units/Cargo.toml @@ -12,4 +12,4 @@ edition = { workspace = true } solana-program = { workspace = true } [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["cdylib"] diff --git a/programs/sbf/rust/sanity/Cargo.toml b/programs/sbf/rust/sanity/Cargo.toml index 435acceddfef36..f01dd7501e6906 100644 --- a/programs/sbf/rust/sanity/Cargo.toml +++ b/programs/sbf/rust/sanity/Cargo.toml @@ -12,4 +12,4 @@ edition = { workspace = true } solana-program = { workspace = true } [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["cdylib"] diff --git a/programs/sbf/rust/simulation/Cargo.toml b/programs/sbf/rust/simulation/Cargo.toml index 3114e2a1a75c8b..3fd65622c876a4 100644 --- a/programs/sbf/rust/simulation/Cargo.toml +++ b/programs/sbf/rust/simulation/Cargo.toml @@ -12,4 +12,4 @@ edition = { workspace = true } solana-program = { workspace = true } [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["cdylib"] diff --git a/programs/sbf/rust/sysvar/Cargo.toml b/programs/sbf/rust/sysvar/Cargo.toml index 1144ebde960cae..bb9683fa5a63dd 100644 --- a/programs/sbf/rust/sysvar/Cargo.toml +++ b/programs/sbf/rust/sysvar/Cargo.toml @@ -12,4 +12,4 @@ edition = { workspace = true } solana-program = { workspace = true } [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["cdylib"] diff --git a/programs/sbf/rust/sysvar/src/lib.rs b/programs/sbf/rust/sysvar/src/lib.rs index d460b5ca635da2..88b7a4aa404b4e 100644 --- a/programs/sbf/rust/sysvar/src/lib.rs +++ b/programs/sbf/rust/sysvar/src/lib.rs @@ -2,8 +2,6 @@ extern crate solana_program; #[allow(deprecated)] -use solana_program::sysvar::fees::Fees; -#[allow(deprecated)] use solana_program::sysvar::recent_blockhashes::RecentBlockhashes; use solana_program::{ account_info::AccountInfo, @@ -31,7 +29,7 @@ pub fn process_instruction( sysvar::clock::id().log(); let clock = Clock::from_account_info(&accounts[2]).unwrap(); assert_ne!(clock, Clock::default()); - let got_clock = Clock::get()?; + let got_clock = Clock::get().unwrap(); assert_eq!(clock, got_clock); } @@ -41,7 +39,7 @@ pub fn process_instruction( sysvar::epoch_schedule::id().log(); let epoch_schedule = EpochSchedule::from_account_info(&accounts[3]).unwrap(); assert_eq!(epoch_schedule, EpochSchedule::default()); - let got_epoch_schedule = EpochSchedule::get()?; + let got_epoch_schedule = EpochSchedule::get().unwrap(); assert_eq!(epoch_schedule, got_epoch_schedule); } @@ -49,8 +47,9 @@ pub fn process_instruction( msg!("Instructions identifier:"); sysvar::instructions::id().log(); assert_eq!(*accounts[4].owner, sysvar::id()); - let index = instructions::load_current_index_checked(&accounts[4])?; - let instruction = instructions::load_instruction_at_checked(index as usize, &accounts[4])?; + let index = instructions::load_current_index_checked(&accounts[4]).unwrap(); + let instruction = + instructions::load_instruction_at_checked(index as usize, &accounts[4]).unwrap(); assert_eq!(0, index); assert_eq!( instruction, @@ -69,7 +68,6 @@ pub fn process_instruction( AccountMeta::new_readonly(*accounts[8].key, false), AccountMeta::new_readonly(*accounts[9].key, false), AccountMeta::new_readonly(*accounts[10].key, false), - AccountMeta::new_readonly(*accounts[11].key, false), ], ) ); @@ -88,8 +86,7 @@ pub fn process_instruction( msg!("Rent identifier:"); sysvar::rent::id().log(); let rent = Rent::from_account_info(&accounts[6]).unwrap(); - assert_eq!(rent, Rent::default()); - let got_rent = Rent::get()?; + let got_rent = Rent::get().unwrap(); assert_eq!(rent, got_rent); } @@ -114,22 +111,12 @@ pub fn process_instruction( sysvar::stake_history::id().log(); let _ = StakeHistory::from_account_info(&accounts[9]).unwrap(); - // Fees - #[allow(deprecated)] - if instruction_data[0] == 1 { - msg!("Fee identifier:"); - sysvar::fees::id().log(); - let fees = Fees::from_account_info(&accounts[10]).unwrap(); - let got_fees = Fees::get()?; - assert_eq!(fees, got_fees); - } - // Epoch Rewards { msg!("EpochRewards identifier:"); sysvar::epoch_rewards::id().log(); - let epoch_rewards = EpochRewards::from_account_info(&accounts[11]).unwrap(); - let got_epoch_rewards = EpochRewards::get()?; + let epoch_rewards = EpochRewards::from_account_info(&accounts[10]).unwrap(); + let got_epoch_rewards = EpochRewards::get().unwrap(); assert_eq!(epoch_rewards, got_epoch_rewards); } diff --git a/programs/sbf/tests/mem.rs b/programs/sbf/tests/mem.rs deleted file mode 100644 index 530ad158a6b3e7..00000000000000 --- a/programs/sbf/tests/mem.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![cfg(feature = "test-bpf")] - -use { - solana_program_test::*, - solana_sbf_rust_mem::process_instruction, - solana_sdk::{ - instruction::Instruction, pubkey::Pubkey, signature::Signer, transaction::Transaction, - }, -}; - -#[tokio::test] -async fn test_mem() { - let program_id = Pubkey::new_unique(); - let program_test = ProgramTest::new( - "solana_sbf_rust_mem", - program_id, - processor!(process_instruction), - ); - let (mut banks_client, payer, recent_blockhash) = program_test.start().await; - - let mut transaction = Transaction::new_with_payer( - &[Instruction::new_with_bincode(program_id, &(), vec![])], - Some(&payer.pubkey()), - ); - transaction.sign(&[&payer], recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); -} diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index 62f12bcef1d823..b26c2e74e230ff 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -18,10 +18,15 @@ use { compute_budget_processor::process_compute_budget_instructions, }, solana_ledger::token_balances::collect_token_balances, - solana_program_runtime::timings::ExecuteTimings, + solana_program_runtime::{invoke_context::mock_process_instruction, timings::ExecuteTimings}, solana_rbpf::vm::ContextObject, solana_runtime::{ - bank::TransactionBalancesSet, + bank::{Bank, TransactionBalancesSet}, + bank_client::BankClient, + genesis_utils::{ + bootstrap_validator_stake_lamports, create_genesis_config, + create_genesis_config_with_leader_ex, GenesisConfigInfo, + }, loader_utils::{ create_program, load_program_from_file, load_upgradeable_buffer, load_upgradeable_program, load_upgradeable_program_and_advance_slot, @@ -32,60 +37,44 @@ use { solana_sbf_rust_realloc_dep::*, solana_sbf_rust_realloc_invoke_dep::*, solana_sdk::{ - account::{ReadableAccount, WritableAccount}, + account::{AccountSharedData, ReadableAccount, WritableAccount}, account_utils::StateMut, - bpf_loader_upgradeable, - clock::MAX_PROCESSING_AGE, + bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, + client::SyncClient, + clock::{UnixTimestamp, MAX_PROCESSING_AGE}, compute_budget::ComputeBudgetInstruction, entrypoint::MAX_PERMITTED_DATA_INCREASE, feature_set::{self, FeatureSet}, fee::FeeStructure, - message::{v0::LoadedAddresses, SanitizedMessage}, - signature::keypair_from_seed, + fee_calculator::FeeRateGovernor, + genesis_config::ClusterType, + hash::Hash, + instruction::{AccountMeta, Instruction, InstructionError}, + message::{v0::LoadedAddresses, Message, SanitizedMessage}, + pubkey::Pubkey, + rent::Rent, + reserved_account_keys::ReservedAccountKeys, + signature::{keypair_from_seed, Keypair, Signer}, stake, system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, + system_program, sysvar::{self, clock}, - transaction::VersionedTransaction, + transaction::{SanitizedTransaction, Transaction, TransactionError, VersionedTransaction}, }, - solana_svm::transaction_processor::ExecutionRecordingConfig, - solana_svm::transaction_results::{ - InnerInstruction, TransactionExecutionDetails, TransactionExecutionResult, - TransactionResults, + solana_svm::{ + transaction_processor::ExecutionRecordingConfig, + transaction_results::{ + InnerInstruction, TransactionExecutionDetails, TransactionExecutionResult, + TransactionResults, + }, }, solana_transaction_status::{ map_inner_instructions, ConfirmedTransactionWithStatusMeta, TransactionStatusMeta, TransactionWithStatusMeta, VersionedTransactionWithStatusMeta, }, - std::collections::HashMap, -}; -use { - solana_program_runtime::invoke_context::mock_process_instruction, - solana_runtime::{ - bank::Bank, - bank_client::BankClient, - genesis_utils::{ - bootstrap_validator_stake_lamports, create_genesis_config, - create_genesis_config_with_leader_ex, GenesisConfigInfo, - }, - }, - solana_sdk::{ - account::AccountSharedData, - bpf_loader, bpf_loader_deprecated, - client::SyncClient, - clock::UnixTimestamp, - fee_calculator::FeeRateGovernor, - genesis_config::ClusterType, - hash::Hash, - instruction::{AccountMeta, Instruction, InstructionError}, - message::Message, - pubkey::Pubkey, - rent::Rent, - reserved_account_keys::ReservedAccountKeys, - signature::{Keypair, Signer}, - system_program, - transaction::{SanitizedTransaction, Transaction, TransactionError}, + std::{ + assert_eq, cell::RefCell, collections::HashMap, str::FromStr, sync::Arc, time::Duration, }, - std::{cell::RefCell, str::FromStr, sync::Arc, time::Duration}, }; #[cfg(feature = "sbf_rust")] @@ -227,6 +216,9 @@ fn execute_transactions( .collect() } +#[cfg(feature = "sbf_rust")] +const LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST: u32 = 64 * 1024 * 1024; + #[test] #[cfg(any(feature = "sbf_c", feature = "sbf_rust"))] fn test_program_sbf_sanity() { @@ -270,12 +262,14 @@ fn test_program_sbf_sanity() { ("solana_sbf_rust_external_spend", false), ("solana_sbf_rust_iter", true), ("solana_sbf_rust_many_args", true), + ("solana_sbf_rust_mem", true), ("solana_sbf_rust_membuiltins", true), ("solana_sbf_rust_noop", true), ("solana_sbf_rust_panic", false), ("solana_sbf_rust_param_passing", true), ("solana_sbf_rust_poseidon", true), ("solana_sbf_rust_rand", true), + ("solana_sbf_rust_remaining_compute_units", true), ("solana_sbf_rust_sanity", true), ("solana_sbf_rust_secp256k1_recover", true), ("solana_sbf_rust_sha", true), @@ -2764,7 +2758,18 @@ fn test_program_sbf_realloc() { instruction.accounts[0].is_writable = false; assert_eq!( bank_client - .send_and_confirm_message(signer, Message::new(&[instruction], Some(&mint_pubkey),),) + .send_and_confirm_message( + signer, + Message::new( + &[ + instruction, + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], + Some(&mint_pubkey), + ), + ) .unwrap_err() .unwrap(), TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) @@ -2776,7 +2781,12 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc(&program_id, &pubkey, usize::MAX, &mut bump)], + &[ + realloc(&program_id, &pubkey, usize::MAX, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ), ) @@ -2790,7 +2800,12 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc(&program_id, &pubkey, 0, &mut bump)], + &[ + realloc(&program_id, &pubkey, 0, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -2803,12 +2818,17 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc_extend_and_undo( - &program_id, - &pubkey, - MAX_PERMITTED_DATA_INCREASE, - &mut bump, - )], + &[ + realloc_extend_and_undo( + &program_id, + &pubkey, + MAX_PERMITTED_DATA_INCREASE, + &mut bump, + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -2822,12 +2842,17 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc_extend_and_undo( - &program_id, - &pubkey, - MAX_PERMITTED_DATA_INCREASE + 1, - &mut bump, - )], + &[ + realloc_extend_and_undo( + &program_id, + &pubkey, + MAX_PERMITTED_DATA_INCREASE + 1, + &mut bump, + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ), ) @@ -2842,12 +2867,17 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc( - &program_id, - &pubkey, - MAX_PERMITTED_DATA_INCREASE + 1, - &mut bump - )], + &[ + realloc( + &program_id, + &pubkey, + MAX_PERMITTED_DATA_INCREASE + 1, + &mut bump + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ), ) @@ -2863,13 +2893,18 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc_extend_and_fill( - &program_id, - &pubkey, - MAX_PERMITTED_DATA_INCREASE, - 1, - &mut bump, - )], + &[ + realloc_extend_and_fill( + &program_id, + &pubkey, + MAX_PERMITTED_DATA_INCREASE, + 1, + &mut bump, + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -2887,12 +2922,17 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc_extend( - &program_id, - &pubkey, - MAX_PERMITTED_DATA_INCREASE, - &mut bump - )], + &[ + realloc_extend( + &program_id, + &pubkey, + MAX_PERMITTED_DATA_INCREASE, + &mut bump + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -2906,7 +2946,12 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc(&program_id, &pubkey, 6, &mut bump)], + &[ + realloc(&program_id, &pubkey, 6, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -2920,11 +2965,12 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[extend_and_write_u64( - &program_id, - &pubkey, - 0x1122334455667788, - )], + &[ + extend_and_write_u64(&program_id, &pubkey, 0x1122334455667788), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -2938,7 +2984,12 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc(&program_id, &pubkey, 0, &mut bump)], + &[ + realloc(&program_id, &pubkey, 0, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -2951,11 +3002,16 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - program_id, - &[REALLOC_AND_ASSIGN], - vec![AccountMeta::new(pubkey, false)], - )], + &[ + Instruction::new_with_bytes( + program_id, + &[REALLOC_AND_ASSIGN], + vec![AccountMeta::new(pubkey, false)], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -2971,7 +3027,12 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc(&program_id, &pubkey, 0, &mut bump)], + &[ + realloc(&program_id, &pubkey, 0, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ), ) @@ -2986,14 +3047,19 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( - &[Instruction::new_with_bytes( - program_id, - &[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], - vec![ - AccountMeta::new(pubkey, true), - AccountMeta::new(solana_sdk::system_program::id(), false), - ], - )], + &[ + Instruction::new_with_bytes( + program_id, + &[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new(solana_sdk::system_program::id(), false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3007,14 +3073,19 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( - &[Instruction::new_with_bytes( - program_id, - &[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], - vec![ - AccountMeta::new(pubkey, true), - AccountMeta::new(solana_sdk::system_program::id(), false), - ], - )], + &[ + Instruction::new_with_bytes( + program_id, + &[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new(solana_sdk::system_program::id(), false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3029,7 +3100,12 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( signer, Message::new( - &[realloc(&program_id, &pubkey, 0, &mut bump)], + &[ + realloc(&program_id, &pubkey, 0, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3042,11 +3118,16 @@ fn test_program_sbf_realloc() { .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( - &[Instruction::new_with_bytes( - program_id, - &[ZERO_INIT], - vec![AccountMeta::new(pubkey, true)], - )], + &[ + Instruction::new_with_bytes( + program_id, + &[ZERO_INIT], + vec![AccountMeta::new(pubkey, true)], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3104,14 +3185,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_ZERO_RO], - vec![ - AccountMeta::new_readonly(pubkey, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_ZERO_RO], + vec![ + AccountMeta::new_readonly(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3127,7 +3213,12 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], + &[ + realloc(&realloc_program_id, &pubkey, 0, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3143,14 +3234,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_MAX_PLUS_ONE], - vec![ - AccountMeta::new(pubkey, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_MAX_PLUS_ONE], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3165,14 +3261,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_MAX_TWICE], - vec![ - AccountMeta::new(pubkey, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_MAX_TWICE], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3186,7 +3287,12 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], + &[ + realloc(&realloc_program_id, &pubkey, 0, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3201,14 +3307,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_AND_ASSIGN], - vec![ - AccountMeta::new(pubkey, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_AND_ASSIGN], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3224,7 +3335,12 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], + &[ + realloc(&realloc_program_id, &pubkey, 0, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3239,15 +3355,20 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], - vec![ - AccountMeta::new(pubkey, true), - AccountMeta::new_readonly(realloc_program_id, false), - AccountMeta::new_readonly(solana_sdk::system_program::id(), false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM], + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new_readonly(realloc_program_id, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3261,15 +3382,20 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( &[&mint_keypair, &keypair], Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], - vec![ - AccountMeta::new(pubkey, true), - AccountMeta::new_readonly(realloc_program_id, false), - AccountMeta::new_readonly(solana_sdk::system_program::id(), false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC], + vec![ + AccountMeta::new(pubkey, true), + AccountMeta::new_readonly(realloc_program_id, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3284,7 +3410,12 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[realloc(&realloc_program_id, &pubkey, 0, &mut bump)], + &[ + realloc(&realloc_program_id, &pubkey, 0, &mut bump), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3299,14 +3430,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_INVOKE_CHECK], - vec![ - AccountMeta::new(invoke_pubkey, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_INVOKE_CHECK], + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3333,16 +3469,21 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( &[&mint_keypair, &new_keypair], Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &instruction_data, - vec![ - AccountMeta::new(mint_pubkey, true), - AccountMeta::new(new_pubkey, true), - AccountMeta::new(solana_sdk::system_program::id(), false), - AccountMeta::new_readonly(realloc_invoke_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &instruction_data, + vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(new_pubkey, true), + AccountMeta::new(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3365,15 +3506,20 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &instruction_data, - vec![ - AccountMeta::new(invoke_pubkey, false), - AccountMeta::new_readonly(realloc_invoke_program_id, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &instruction_data, + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3395,14 +3541,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_MAX_INVOKE_MAX], - vec![ - AccountMeta::new(invoke_pubkey, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_MAX_INVOKE_MAX], + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3429,14 +3580,19 @@ fn test_program_sbf_realloc_invoke() { let result = bank_client.send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &instruction_data, - vec![ - AccountMeta::new(invoke_pubkey, false), - AccountMeta::new_readonly(realloc_invoke_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &instruction_data, + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ); @@ -3464,15 +3620,20 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_INVOKE_MAX_TWICE], - vec![ - AccountMeta::new(invoke_pubkey, false), - AccountMeta::new_readonly(realloc_invoke_program_id, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_INVOKE_MAX_TWICE], + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3500,14 +3661,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8], - vec![ - AccountMeta::new(pubkey, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -3525,14 +3691,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1], - vec![ - AccountMeta::new(pubkey, false), - AccountMeta::new_readonly(realloc_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1], + vec![ + AccountMeta::new(pubkey, false), + AccountMeta::new_readonly(realloc_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST + ), + ], Some(&mint_pubkey), ) ) @@ -3553,14 +3724,19 @@ fn test_program_sbf_realloc_invoke() { .send_and_confirm_message( signer, Message::new( - &[Instruction::new_with_bytes( - realloc_invoke_program_id, - &instruction_data, - vec![ - AccountMeta::new(invoke_pubkey, false), - AccountMeta::new_readonly(realloc_invoke_program_id, false), - ], - )], + &[ + Instruction::new_with_bytes( + realloc_invoke_program_id, + &instruction_data, + vec![ + AccountMeta::new(invoke_pubkey, false), + AccountMeta::new_readonly(realloc_invoke_program_id, false), + ], + ), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + LOADED_ACCOUNTS_DATA_SIZE_LIMIT_FOR_TEST, + ), + ], Some(&mint_pubkey), ), ) @@ -4463,3 +4639,282 @@ fn test_deny_executable_write() { ); } } + +#[test] +fn test_update_callee_account() { + // Test that fn update_callee_account() works and we are updating the callee account on CPI. + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100_123_456_789); + + for direct_mapping in [false, true] { + let mut bank = Bank::new_for_tests(&genesis_config); + let feature_set = Arc::make_mut(&mut bank.feature_set); + // by default test banks have all features enabled, so we only need to + // disable when needed + if !direct_mapping { + feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id()); + } + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + + let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_invoke", + ); + + let account_keypair = Keypair::new(); + + let mint_pubkey = mint_keypair.pubkey(); + + let account_metas = vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(account_keypair.pubkey(), false), + AccountMeta::new_readonly(invoke_program_id, false), + ]; + + // I. do CPI with account in read only (separate code path with direct mapping) + let mut account = AccountSharedData::new(42, 10240, &invoke_program_id); + let data: Vec = (0..10240).map(|n| n as u8).collect(); + account.set_data(data); + + bank.store_account(&account_keypair.pubkey(), &account); + + let mut instruction_data = vec![TEST_CALLEE_ACCOUNT_UPDATES, 0]; + instruction_data.extend_from_slice(20480usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(16384usize.to_le_bytes().as_ref()); + // instruction data for inner CPI (2x) + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 0]); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 0]); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + + let instruction = Instruction::new_with_bytes( + invoke_program_id, + &instruction_data, + account_metas.clone(), + ); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + + let data = bank_client + .get_account_data(&account_keypair.pubkey()) + .unwrap() + .unwrap(); + + assert_eq!(data.len(), 20480); + + data.iter().enumerate().for_each(|(i, v)| { + let expected = match i { + ..=10240 => i as u8, + 16384 => 0xe5, + _ => 0, + }; + + assert_eq!(*v, expected, "offset:{i} {v:#x} != {expected:#x}"); + }); + + // II. do CPI with account with resize to smaller and write + let mut account = AccountSharedData::new(42, 10240, &invoke_program_id); + let data: Vec = (0..10240).map(|n| n as u8).collect(); + account.set_data(data); + bank.store_account(&account_keypair.pubkey(), &account); + + let mut instruction_data = vec![TEST_CALLEE_ACCOUNT_UPDATES, 1]; + instruction_data.extend_from_slice(20480usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(16384usize.to_le_bytes().as_ref()); + // instruction data for inner CPI + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 0]); + instruction_data.extend_from_slice(19480usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(8129usize.to_le_bytes().as_ref()); + + let instruction = Instruction::new_with_bytes( + invoke_program_id, + &instruction_data, + account_metas.clone(), + ); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + + let data = bank_client + .get_account_data(&account_keypair.pubkey()) + .unwrap() + .unwrap(); + + assert_eq!(data.len(), 19480); + + data.iter().enumerate().for_each(|(i, v)| { + let expected = match i { + 8129 => (i as u8) ^ 0xe5, + ..=10240 => i as u8, + 16384 => 0xe5, + _ => 0, + }; + + assert_eq!(*v, expected, "offset:{i} {v:#x} != {expected:#x}"); + }); + + // III. do CPI with account with resize to larger and write + let mut account = AccountSharedData::new(42, 10240, &invoke_program_id); + let data: Vec = (0..10240).map(|n| n as u8).collect(); + account.set_data(data); + bank.store_account(&account_keypair.pubkey(), &account); + + let mut instruction_data = vec![TEST_CALLEE_ACCOUNT_UPDATES, 1]; + instruction_data.extend_from_slice(16384usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(16384usize.to_le_bytes().as_ref()); + // instruction data for inner CPI + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 0]); + instruction_data.extend_from_slice(20480usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(16385usize.to_le_bytes().as_ref()); + + let instruction = Instruction::new_with_bytes( + invoke_program_id, + &instruction_data, + account_metas.clone(), + ); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + + let data = bank_client + .get_account_data(&account_keypair.pubkey()) + .unwrap() + .unwrap(); + + assert_eq!(data.len(), 20480); + + data.iter().enumerate().for_each(|(i, v)| { + let expected = match i { + ..=10240 => i as u8, + 16384 | 16385 => 0xe5, + _ => 0, + }; + + assert_eq!(*v, expected, "offset:{i} {v:#x} != {expected:#x}"); + }); + + // IV. do CPI with account with resize to larger and write + let mut account = AccountSharedData::new(42, 10240, &invoke_program_id); + let data: Vec = (0..10240).map(|n| n as u8).collect(); + account.set_data(data); + bank.store_account(&account_keypair.pubkey(), &account); + + let mut instruction_data = vec![TEST_CALLEE_ACCOUNT_UPDATES, 1]; + instruction_data.extend_from_slice(16384usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(16384usize.to_le_bytes().as_ref()); + // instruction data for inner CPI (2x) + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 1]); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 1]); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(0usize.to_le_bytes().as_ref()); + // instruction data for inner CPI + instruction_data.extend_from_slice(&[TEST_CALLEE_ACCOUNT_UPDATES, 0]); + instruction_data.extend_from_slice(20480usize.to_le_bytes().as_ref()); + instruction_data.extend_from_slice(16385usize.to_le_bytes().as_ref()); + + let instruction = Instruction::new_with_bytes( + invoke_program_id, + &instruction_data, + account_metas.clone(), + ); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert!(result.is_ok()); + + let data = bank_client + .get_account_data(&account_keypair.pubkey()) + .unwrap() + .unwrap(); + + assert_eq!(data.len(), 20480); + + data.iter().enumerate().for_each(|(i, v)| { + let expected = match i { + ..=10240 => i as u8, + 16384 | 16385 => 0xe5, + _ => 0, + }; + + assert_eq!(*v, expected, "offset:{i} {v:#x} != {expected:#x}"); + }); + } +} + +#[test] +fn test_stack_heap_zeroed() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100_123_456_789); + + let bank = Bank::new_for_tests(&genesis_config); + + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let mut bank_client = BankClient::new_shared(bank); + let authority_keypair = Keypair::new(); + + let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_invoke", + ); + + let account_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + let account_metas = vec![ + AccountMeta::new(mint_pubkey, true), + AccountMeta::new(account_keypair.pubkey(), false), + AccountMeta::new_readonly(invoke_program_id, false), + ]; + + // Check multiple heap sizes. It's generally a good idea, and also it's needed to ensure that + // pooled heap and stack values are reused - and therefore zeroed - across executions. + for heap_len in [32usize * 1024, 64 * 1024, 128 * 1024, 256 * 1024] { + // TEST_STACK_HEAP_ZEROED will recursively check that stack and heap are zeroed until it + // reaches max CPI invoke depth. We make it fail at max depth so we're sure that there's no + // legit way to access non-zeroed stack and heap regions. + let mut instruction_data = vec![TEST_STACK_HEAP_ZEROED]; + instruction_data.extend_from_slice(&heap_len.to_le_bytes()); + + let instruction = Instruction::new_with_bytes( + invoke_program_id, + &instruction_data, + account_metas.clone(), + ); + + let message = Message::new( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), + ComputeBudgetInstruction::request_heap_frame(heap_len as u32), + instruction, + ], + Some(&mint_pubkey), + ); + let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash()); + let (result, _, logs) = process_transaction_and_record_inner(&bank, tx); + assert!(result.is_err(), "{result:?}"); + assert!( + logs.iter() + .any(|log| log.contains("Cross-program invocation call depth too deep")), + "{logs:?}" + ); + } +} diff --git a/programs/sbf/tests/remaining_compute_units.rs b/programs/sbf/tests/remaining_compute_units.rs deleted file mode 100644 index 30da15b2953a53..00000000000000 --- a/programs/sbf/tests/remaining_compute_units.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![cfg(feature = "test-bpf")] - -use { - solana_program_test::*, - solana_sbf_rust_remaining_compute_units::process_instruction, - solana_sdk::{ - instruction::Instruction, pubkey::Pubkey, signature::Signer, transaction::Transaction, - }, -}; - -#[tokio::test] -async fn test_remaining_compute_units() { - let program_id = Pubkey::new_unique(); - let program_test = ProgramTest::new( - "solana_sbf_rust_remaining_compute_units", - program_id, - processor!(process_instruction), - ); - let (mut banks_client, payer, recent_blockhash) = program_test.start().await; - - let mut transaction = Transaction::new_with_payer( - &[Instruction::new_with_bincode(program_id, &(), vec![])], - Some(&payer.pubkey()), - ); - transaction.sign(&[&payer], recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); -} diff --git a/programs/sbf/tests/sanity.rs b/programs/sbf/tests/sanity.rs deleted file mode 100644 index 6a561bcae1c395..00000000000000 --- a/programs/sbf/tests/sanity.rs +++ /dev/null @@ -1,37 +0,0 @@ -#![cfg(feature = "test-bpf")] - -use { - solana_program_test::*, - solana_sbf_rust_sanity::process_instruction, - solana_sdk::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - signature::{Keypair, Signer}, - transaction::Transaction, - }, -}; - -#[tokio::test] -async fn test_sanity() { - let program_id = Pubkey::new_unique(); - let program_test = ProgramTest::new( - "solana_sbf_rust_sanity", - program_id, - processor!(process_instruction), - ); - let (mut banks_client, payer_keypair, recent_blockhash) = program_test.start().await; - - let mut transaction = Transaction::new_with_payer( - &[Instruction::new_with_bincode( - program_id, - &(), - vec![ - AccountMeta::new(payer_keypair.pubkey(), true), - AccountMeta::new(Keypair::new().pubkey(), false), - ], - )], - Some(&payer_keypair.pubkey()), - ); - transaction.sign(&[&payer_keypair], recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); -} diff --git a/programs/sbf/tests/simulation.rs b/programs/sbf/tests/simulation.rs index f27cf52eeb25f9..6b799c05690c0b 100644 --- a/programs/sbf/tests/simulation.rs +++ b/programs/sbf/tests/simulation.rs @@ -1,44 +1,86 @@ -#![cfg(feature = "test-bpf")] - use { - solana_program_test::{processor, tokio, ProgramTest}, - solana_sbf_rust_simulation::process_instruction, + agave_validator::test_validator::*, + solana_runtime::{ + bank::Bank, + bank_client::BankClient, + genesis_utils::{create_genesis_config, GenesisConfigInfo}, + loader_utils::load_upgradeable_program_and_advance_slot, + }, solana_sdk::{ instruction::{AccountMeta, Instruction}, + message::Message, pubkey::Pubkey, - signature::Signer, - sysvar, - transaction::Transaction, + signature::{Keypair, Signer}, + sysvar::{clock, slot_history}, + transaction::{SanitizedTransaction, Transaction}, }, }; -#[tokio::test] -async fn no_panic_banks_client() { - let program_id = Pubkey::new_unique(); - let program_test = ProgramTest::new( +#[test] +#[cfg(feature = "sbf_rust")] +fn test_no_panic_banks_client() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + let (bank, program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, "solana_sbf_rust_simulation", + ); + bank.freeze(); + + let instruction = Instruction::new_with_bincode( program_id, - processor!(process_instruction), + &[0u8; 0], + vec![ + AccountMeta::new_readonly(slot_history::id(), false), + AccountMeta::new_readonly(clock::id(), false), + ], ); + let blockhash = bank.last_blockhash(); + let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); + let transaction = Transaction::new(&[&mint_keypair], message, blockhash); + let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction); + let result = bank.simulate_transaction(&sanitized_tx, false); + assert!(result.result.is_ok()); +} + +#[test] +#[cfg(feature = "sbf_rust")] +fn test_no_panic_rpc_client() { + solana_logger::setup(); + + let program_id = Pubkey::new_unique(); + let (test_validator, payer) = TestValidatorGenesis::default() + .add_program("solana_sbf_rust_simulation", program_id) + .start(); + let rpc_client = test_validator.get_rpc_client(); + let blockhash = rpc_client.get_latest_blockhash().unwrap(); - let mut context = program_test.start_with_context().await; let transaction = Transaction::new_signed_with_payer( &[Instruction { program_id, accounts: vec![ - AccountMeta::new_readonly(sysvar::slot_history::id(), false), - AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(slot_history::id(), false), + AccountMeta::new_readonly(clock::id(), false), ], data: vec![], }], - Some(&context.payer.pubkey()), - &[&context.payer], - context.last_blockhash, + Some(&payer.pubkey()), + &[&payer], + blockhash, ); - context - .banks_client - .process_transaction_with_preflight(transaction) - .await + rpc_client + .send_and_confirm_transaction(&transaction) .unwrap(); } diff --git a/programs/sbf/tests/simulation_validator.rs b/programs/sbf/tests/simulation_validator.rs deleted file mode 100644 index 17de51e665e3ec..00000000000000 --- a/programs/sbf/tests/simulation_validator.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![cfg(feature = "test-bpf")] - -use { - agave_validator::test_validator::*, - solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - sysvar, - }, - solana_sdk::{signature::Signer, transaction::Transaction}, -}; - -#[test] -fn no_panic_rpc_client() { - solana_logger::setup_with_default("solana_program_runtime=debug"); - let program_id = Pubkey::new_unique(); - - let (test_validator, payer) = TestValidatorGenesis::default() - .add_program("solana_sbf_rust_simulation", program_id) - .start(); - let rpc_client = test_validator.get_rpc_client(); - let blockhash = rpc_client.get_latest_blockhash().unwrap(); - - let transaction = Transaction::new_signed_with_payer( - &[Instruction { - program_id, - accounts: vec![ - AccountMeta::new_readonly(sysvar::slot_history::id(), false), - AccountMeta::new_readonly(sysvar::clock::id(), false), - ], - data: vec![], - }], - Some(&payer.pubkey()), - &[&payer], - blockhash, - ); - - rpc_client - .send_and_confirm_transaction(&transaction) - .unwrap(); -} diff --git a/programs/sbf/tests/sysvar.rs b/programs/sbf/tests/sysvar.rs index ffa2f625b6d03d..4e9dcc566dde63 100644 --- a/programs/sbf/tests/sysvar.rs +++ b/programs/sbf/tests/sysvar.rs @@ -1,31 +1,36 @@ -#![cfg(feature = "test-bpf")] - use { - solana_program_test::*, - solana_sbf_rust_sysvar::process_instruction, + solana_runtime::{ + bank::Bank, + bank_client::BankClient, + genesis_utils::{create_genesis_config, GenesisConfigInfo}, + loader_utils::load_upgradeable_program_and_advance_slot, + }, solana_sdk::{ feature_set::disable_fees_sysvar, instruction::{AccountMeta, Instruction}, + message::Message, pubkey::Pubkey, - signature::Signer, + signature::{Keypair, Signer}, sysvar::{ - clock, epoch_rewards, epoch_schedule, fees, instructions, recent_blockhashes, rent, + clock, epoch_rewards, epoch_schedule, instructions, recent_blockhashes, rent, slot_hashes, slot_history, stake_history, }, - transaction::Transaction, + transaction::{SanitizedTransaction, Transaction}, }, }; -#[tokio::test] -async fn test_sysvars() { - let program_id = Pubkey::new_unique(); - - let mut program_test = ProgramTest::new( - "solana_sbf_rust_sysvar", - program_id, - processor!(process_instruction), - ); +#[test] +#[cfg(feature = "sbf_rust")] +fn test_sysvar_syscalls() { + solana_logger::setup(); + let GenesisConfigInfo { + mut genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + genesis_config.accounts.remove(&disable_fees_sysvar::id()); + let bank = Bank::new_for_tests(&genesis_config); let epoch_rewards = epoch_rewards::EpochRewards { distribution_starting_block_height: 42, total_rewards: 100, @@ -33,50 +38,25 @@ async fn test_sysvars() { active: true, ..epoch_rewards::EpochRewards::default() }; - program_test.add_sysvar_account(epoch_rewards::id(), &epoch_rewards); - let (mut banks_client, payer, recent_blockhash) = program_test.start().await; - - let mut transaction = Transaction::new_with_payer( - &[Instruction::new_with_bincode( - program_id, - &[0u8], - vec![ - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(Pubkey::new_unique(), false), - AccountMeta::new_readonly(clock::id(), false), - AccountMeta::new_readonly(epoch_schedule::id(), false), - AccountMeta::new_readonly(instructions::id(), false), - #[allow(deprecated)] - AccountMeta::new_readonly(recent_blockhashes::id(), false), - AccountMeta::new_readonly(rent::id(), false), - AccountMeta::new_readonly(slot_hashes::id(), false), - AccountMeta::new_readonly(slot_history::id(), false), - AccountMeta::new_readonly(stake_history::id(), false), - #[allow(deprecated)] - AccountMeta::new_readonly(fees::id(), false), - AccountMeta::new_readonly(epoch_rewards::id(), false), - ], - )], - Some(&payer.pubkey()), - ); - transaction.sign(&[&payer], recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); - - let mut program_test = ProgramTest::new( + bank.set_sysvar_for_tests(&epoch_rewards); + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let mut bank_client = BankClient::new_shared(bank); + let authority_keypair = Keypair::new(); + let (bank, program_id) = load_upgradeable_program_and_advance_slot( + &mut bank_client, + bank_forks.as_ref(), + &mint_keypair, + &authority_keypair, "solana_sbf_rust_sysvar", - program_id, - processor!(process_instruction), ); - program_test.deactivate_feature(disable_fees_sysvar::id()); - program_test.add_sysvar_account(epoch_rewards::id(), &epoch_rewards); - let (mut banks_client, payer, recent_blockhash) = program_test.start().await; + bank.freeze(); - let mut transaction = Transaction::new_with_payer( - &[Instruction::new_with_bincode( + for instruction_data in &[0u8, 1u8] { + let instruction = Instruction::new_with_bincode( program_id, - &[1u8], + &[instruction_data], vec![ - AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(mint_keypair.pubkey(), true), AccountMeta::new(Pubkey::new_unique(), false), AccountMeta::new_readonly(clock::id(), false), AccountMeta::new_readonly(epoch_schedule::id(), false), @@ -87,13 +67,14 @@ async fn test_sysvars() { AccountMeta::new_readonly(slot_hashes::id(), false), AccountMeta::new_readonly(slot_history::id(), false), AccountMeta::new_readonly(stake_history::id(), false), - #[allow(deprecated)] - AccountMeta::new_readonly(fees::id(), false), AccountMeta::new_readonly(epoch_rewards::id(), false), ], - )], - Some(&payer.pubkey()), - ); - transaction.sign(&[&payer], recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); + ); + let blockhash = bank.last_blockhash(); + let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); + let transaction = Transaction::new(&[&mint_keypair], message, blockhash); + let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction); + let result = bank.simulate_transaction(&sanitized_tx, false); + assert!(result.result.is_ok()); + } } diff --git a/programs/stake/Cargo.toml b/programs/stake/Cargo.toml index 1ccd2e857c7521..b55c904b4b17a6 100644 --- a/programs/stake/Cargo.toml +++ b/programs/stake/Cargo.toml @@ -15,6 +15,7 @@ log = { workspace = true } solana-config-program = { workspace = true } solana-program-runtime = { workspace = true } solana-sdk = { workspace = true } +solana-type-overrides = { workspace = true } solana-vote-program = { workspace = true } [dev-dependencies] diff --git a/programs/system/Cargo.toml b/programs/system/Cargo.toml index a3366d5c8f3f4f..1e5643587c8f88 100644 --- a/programs/system/Cargo.toml +++ b/programs/system/Cargo.toml @@ -16,6 +16,7 @@ serde = { workspace = true } serde_derive = { workspace = true } solana-program-runtime = { workspace = true } solana-sdk = { workspace = true } +solana-type-overrides = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/programs/system/src/system_processor.rs b/programs/system/src/system_processor.rs index d455fb84ba5c12..db14cc9c5ebba7 100644 --- a/programs/system/src/system_processor.rs +++ b/programs/system/src/system_processor.rs @@ -542,7 +542,10 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| mod tests { #[allow(deprecated)] use solana_sdk::{ - account::{self, Account, AccountSharedData, ReadableAccount}, + account::{ + self, create_account_shared_data_with_fields, to_account, Account, AccountSharedData, + ReadableAccount, DUMMY_INHERITABLE_ACCOUNT_FIELDS, + }, fee_calculator::FeeCalculator, hash::{hash, Hash}, instruction::{AccountMeta, Instruction, InstructionError}, @@ -552,8 +555,12 @@ mod tests { Data as NonceData, DurableNonce, State as NonceState, Versions as NonceVersions, }, }, - nonce_account, recent_blockhashes_account, system_instruction, system_program, - sysvar::{self, recent_blockhashes::IterItem, rent::Rent}, + nonce_account, system_instruction, system_program, + sysvar::{ + self, + recent_blockhashes::{IntoIterSorted, IterItem, RecentBlockhashes, MAX_ENTRIES}, + rent::Rent, + }, }; use { super::*, @@ -562,6 +569,7 @@ mod tests { solana_program_runtime::{ invoke_context::mock_process_instruction, with_mock_invoke_context, }, + std::collections::BinaryHeap, }; impl From for Address { @@ -595,11 +603,30 @@ mod tests { fn create_default_account() -> AccountSharedData { AccountSharedData::new(0, 0, &Pubkey::new_unique()) } + #[allow(deprecated)] + fn create_recent_blockhashes_account_for_test<'a, I>( + recent_blockhash_iter: I, + ) -> AccountSharedData + where + I: IntoIterator>, + { + let mut account = create_account_shared_data_with_fields::( + &RecentBlockhashes::default(), + DUMMY_INHERITABLE_ACCOUNT_FIELDS, + ); + let sorted = BinaryHeap::from_iter(recent_blockhash_iter); + let sorted_iter = IntoIterSorted::new(sorted); + let recent_blockhash_iter = sorted_iter.take(MAX_ENTRIES); + let recent_blockhashes: RecentBlockhashes = recent_blockhash_iter.collect(); + to_account(&recent_blockhashes, &mut account); + account + } fn create_default_recent_blockhashes_account() -> AccountSharedData { #[allow(deprecated)] - recent_blockhashes_account::create_account_with_data_for_test( - vec![IterItem(0u64, &Hash::default(), 0); sysvar::recent_blockhashes::MAX_ENTRIES], - ) + create_recent_blockhashes_account_for_test(vec![ + IterItem(0u64, &Hash::default(), 0); + sysvar::recent_blockhashes::MAX_ENTRIES + ]) } fn create_default_rent_account() -> AccountSharedData { account::create_account_shared_data_for_test(&Rent::free()) @@ -1551,10 +1578,10 @@ mod tests { ); let blockhash = hash(&serialize(&0).unwrap()); #[allow(deprecated)] - let new_recent_blockhashes_account = - solana_sdk::recent_blockhashes_account::create_account_with_data_for_test( - vec![IterItem(0u64, &blockhash, 0); sysvar::recent_blockhashes::MAX_ENTRIES], - ); + let new_recent_blockhashes_account = create_recent_blockhashes_account_for_test(vec![ + IterItem(0u64, &blockhash, 0); + sysvar::recent_blockhashes::MAX_ENTRIES + ]); mock_process_instruction( &system_program::id(), Vec::new(), @@ -1837,8 +1864,7 @@ mod tests { #[allow(deprecated)] let blockhash_id = sysvar::recent_blockhashes::id(); #[allow(deprecated)] - let new_recent_blockhashes_account = - solana_sdk::recent_blockhashes_account::create_account_with_data_for_test(vec![]); + let new_recent_blockhashes_account = create_recent_blockhashes_account_for_test(vec![]); process_instruction( &serialize(&SystemInstruction::InitializeNonceAccount(nonce_address)).unwrap(), vec![ @@ -1900,8 +1926,7 @@ mod tests { Ok(()), ); #[allow(deprecated)] - let new_recent_blockhashes_account = - solana_sdk::recent_blockhashes_account::create_account_with_data_for_test(vec![]); + let new_recent_blockhashes_account = create_recent_blockhashes_account_for_test(vec![]); mock_process_instruction( &system_program::id(), Vec::new(), diff --git a/programs/zk-elgamal-proof/Cargo.toml b/programs/zk-elgamal-proof/Cargo.toml index c6d795adeb467b..059f5481e91460 100644 --- a/programs/zk-elgamal-proof/Cargo.toml +++ b/programs/zk-elgamal-proof/Cargo.toml @@ -9,7 +9,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] -bytemuck = { workspace = true, features = ["derive"] } +bytemuck = { workspace = true } num-derive = { workspace = true } num-traits = { workspace = true } solana-program-runtime = { workspace = true } diff --git a/programs/zk-token-proof-tests/Cargo.toml b/programs/zk-token-proof-tests/Cargo.toml index 1a20cae3730767..a00c98b20e4d2b 100644 --- a/programs/zk-token-proof-tests/Cargo.toml +++ b/programs/zk-token-proof-tests/Cargo.toml @@ -8,7 +8,7 @@ license = { workspace = true } edition = { workspace = true } [dev-dependencies] -bytemuck = { workspace = true, features = ["derive"] } +bytemuck = { workspace = true } curve25519-dalek = { workspace = true } solana-compute-budget = { workspace = true } solana-program-test = { workspace = true } diff --git a/programs/zk-token-proof/Cargo.toml b/programs/zk-token-proof/Cargo.toml index ec577487a9f5c6..29f53ec069209f 100644 --- a/programs/zk-token-proof/Cargo.toml +++ b/programs/zk-token-proof/Cargo.toml @@ -9,7 +9,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] -bytemuck = { workspace = true, features = ["derive"] } +bytemuck = { workspace = true } num-derive = { workspace = true } num-traits = { workspace = true } solana-program-runtime = { workspace = true } diff --git a/pubsub-client/src/nonblocking/pubsub_client.rs b/pubsub-client/src/nonblocking/pubsub_client.rs index b79e91f681b97f..44663b3372cb2c 100644 --- a/pubsub-client/src/nonblocking/pubsub_client.rs +++ b/pubsub-client/src/nonblocking/pubsub_client.rs @@ -183,10 +183,9 @@ use { RpcTransactionLogsFilter, }, error_object::RpcErrorObject, - filter::maybe_map_filters, response::{ Response as RpcResponse, RpcBlockUpdate, RpcKeyedAccount, RpcLogsResponse, - RpcSignatureResult, RpcVersionInfo, RpcVote, SlotInfo, SlotUpdate, + RpcSignatureResult, RpcVote, SlotInfo, SlotUpdate, }, }, solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature}, @@ -194,7 +193,7 @@ use { thiserror::Error, tokio::{ net::TcpStream, - sync::{mpsc, oneshot, RwLock}, + sync::{mpsc, oneshot}, task::JoinHandle, time::{sleep, Duration}, }, @@ -265,9 +264,8 @@ type RequestMsg = ( #[derive(Debug)] pub struct PubsubClient { subscribe_sender: mpsc::UnboundedSender, - request_sender: mpsc::UnboundedSender, + _request_sender: mpsc::UnboundedSender, shutdown_sender: oneshot::Sender<()>, - node_version: RwLock>, ws: JoinHandle, } @@ -279,14 +277,14 @@ impl PubsubClient { .map_err(PubsubClientError::ConnectionError)?; let (subscribe_sender, subscribe_receiver) = mpsc::unbounded_channel(); - let (request_sender, request_receiver) = mpsc::unbounded_channel(); + let (_request_sender, request_receiver) = mpsc::unbounded_channel(); let (shutdown_sender, shutdown_receiver) = oneshot::channel(); + #[allow(clippy::used_underscore_binding)] Ok(Self { subscribe_sender, - request_sender, + _request_sender, shutdown_sender, - node_version: RwLock::new(None), ws: tokio::spawn(PubsubClient::run_ws( ws, subscribe_receiver, @@ -301,43 +299,11 @@ impl PubsubClient { self.ws.await.unwrap() // WS future should not be cancelled or panicked } - pub async fn set_node_version(&self, version: semver::Version) -> Result<(), ()> { - let mut w_node_version = self.node_version.write().await; - *w_node_version = Some(version); + #[deprecated(since = "2.0.2", note = "PubsubClient::node_version is no longer used")] + pub async fn set_node_version(&self, _version: semver::Version) -> Result<(), ()> { Ok(()) } - async fn get_node_version(&self) -> PubsubClientResult { - let r_node_version = self.node_version.read().await; - if let Some(version) = &*r_node_version { - Ok(version.clone()) - } else { - drop(r_node_version); - let mut w_node_version = self.node_version.write().await; - let node_version = self.get_version().await?; - *w_node_version = Some(node_version.clone()); - Ok(node_version) - } - } - - async fn get_version(&self) -> PubsubClientResult { - let (response_sender, response_receiver) = oneshot::channel(); - self.request_sender - .send(("getVersion".to_string(), Value::Null, response_sender)) - .map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))?; - let result = response_receiver - .await - .map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))??; - let node_version: RpcVersionInfo = serde_json::from_value(result)?; - let node_version = semver::Version::parse(&node_version.solana_core).map_err(|e| { - PubsubClientError::RequestFailed { - reason: format!("failed to parse cluster version: {e}"), - message: "getVersion".to_string(), - } - })?; - Ok(node_version) - } - async fn subscribe<'a, T>(&self, operation: &str, params: Value) -> SubscribeResult<'a, T> where T: DeserializeOwned + Send + 'a, @@ -426,22 +392,8 @@ impl PubsubClient { pub async fn program_subscribe( &self, pubkey: &Pubkey, - mut config: Option, + config: Option, ) -> SubscribeResult<'_, RpcResponse> { - if let Some(ref mut config) = config { - if let Some(ref mut filters) = config.filters { - let node_version = self.get_node_version().await.ok(); - // If node does not support the pubsub `getVersion` method, assume version is old - // and filters should be mapped (node_version.is_none()). - maybe_map_filters(node_version, filters).map_err(|e| { - PubsubClientError::RequestFailed { - reason: e, - message: "maybe_map_filters".to_string(), - } - })?; - } - } - let params = json!([pubkey.to_string(), config]); self.subscribe("program", params).await } diff --git a/pubsub-client/src/pubsub_client.rs b/pubsub-client/src/pubsub_client.rs index 70769619db1f4d..5247bdb8b9e263 100644 --- a/pubsub-client/src/pubsub_client.rs +++ b/pubsub-client/src/pubsub_client.rs @@ -103,7 +103,6 @@ use { RpcProgramAccountsConfig, RpcSignatureSubscribeConfig, RpcTransactionLogsConfig, RpcTransactionLogsFilter, }, - filter, response::{ Response as RpcResponse, RpcBlockUpdate, RpcKeyedAccount, RpcLogsResponse, RpcSignatureResult, RpcVote, SlotInfo, SlotUpdate, @@ -207,35 +206,6 @@ where .map_err(|err| err.into()) } - fn get_version( - writable_socket: &Arc>>>, - ) -> Result { - writable_socket.write().unwrap().send(Message::Text( - json!({ - "jsonrpc":"2.0","id":1,"method":"getVersion", - }) - .to_string(), - ))?; - let message = writable_socket.write().unwrap().read()?; - let message_text = &message.into_text()?; - - if let Ok(json_msg) = serde_json::from_str::>(message_text) { - if let Some(Object(version_map)) = json_msg.get("result") { - if let Some(node_version) = version_map.get("solana-core") { - if let Some(node_version) = node_version.as_str() { - if let Ok(parsed) = semver::Version::parse(node_version) { - return Ok(parsed); - } - } - } - } - } - - Err(PubsubClientError::UnexpectedGetVersionResponse(format!( - "msg={message_text}" - ))) - } - fn read_message( writable_socket: &Arc>>>, ) -> Result, PubsubClientError> { @@ -523,7 +493,7 @@ impl PubsubClient { pub fn program_subscribe( url: &str, pubkey: &Pubkey, - mut config: Option, + config: Option, ) -> Result { let url = Url::parse(url)?; let socket = connect_with_retry(url)?; @@ -534,16 +504,6 @@ impl PubsubClient { let exit = Arc::new(AtomicBool::new(false)); let exit_clone = exit.clone(); - if let Some(ref mut config) = config { - if let Some(ref mut filters) = config.filters { - let node_version = PubsubProgramClientSubscription::get_version(&socket_clone).ok(); - // If node does not support the pubsub `getVersion` method, assume version is old - // and filters should be mapped (node_version.is_none()). - filter::maybe_map_filters(node_version, filters) - .map_err(PubsubClientError::RequestError)?; - } - } - let body = json!({ "jsonrpc":"2.0", "id":1, diff --git a/rpc-client-api/Cargo.toml b/rpc-client-api/Cargo.toml index c8d1eaad8b7959..22a883244c709e 100644 --- a/rpc-client-api/Cargo.toml +++ b/rpc-client-api/Cargo.toml @@ -28,6 +28,7 @@ solana-version = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +const_format = { workspace = true } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/rpc-client-api/src/config.rs b/rpc-client-api/src/config.rs index 9bf1819b32d0a2..db13ea1280d829 100644 --- a/rpc-client-api/src/config.rs +++ b/rpc-client-api/src/config.rs @@ -119,6 +119,7 @@ pub struct RpcLargestAccountsConfig { #[serde(flatten)] pub commitment: Option, pub filter: Option, + pub sort_results: Option, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/rpc-client-api/src/custom_error.rs b/rpc-client-api/src/custom_error.rs index b6175a9230bdcc..62857b1ee55c16 100644 --- a/rpc-client-api/src/custom_error.rs +++ b/rpc-client-api/src/custom_error.rs @@ -24,6 +24,7 @@ pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: i64 = -32013 pub const JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: i64 = -32014; pub const JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: i64 = -32015; pub const JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED: i64 = -32016; +pub const JSON_RPC_SERVER_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE: i64 = -32017; #[derive(Error, Debug)] pub enum RpcCustomError { @@ -65,6 +66,12 @@ pub enum RpcCustomError { UnsupportedTransactionVersion(u8), #[error("MinContextSlotNotReached")] MinContextSlotNotReached { context_slot: Slot }, + #[error("EpochRewardsPeriodActive")] + EpochRewardsPeriodActive { + slot: Slot, + current_block_height: u64, + rewards_complete_block_height: u64, + }, } #[derive(Debug, Serialize, Deserialize)] @@ -79,6 +86,13 @@ pub struct MinContextSlotNotReachedErrorData { pub context_slot: Slot, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EpochRewardsPeriodActiveErrorData { + pub current_block_height: u64, + pub rewards_complete_block_height: u64, +} + impl From for RpcCustomError { fn from(err: EncodeError) -> Self { match err { @@ -206,6 +220,14 @@ impl From for Error { context_slot, })), }, + RpcCustomError::EpochRewardsPeriodActive { slot, current_block_height, rewards_complete_block_height } => Self { + code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE), + message: format!("Epoch rewards period still active at slot {slot}"), + data: Some(serde_json::json!(EpochRewardsPeriodActiveErrorData { + current_block_height, + rewards_complete_block_height, + })), + }, } } } diff --git a/rpc-client-api/src/deprecated_config.rs b/rpc-client-api/src/deprecated_config.rs deleted file mode 100644 index ab562aa0e8c05b..00000000000000 --- a/rpc-client-api/src/deprecated_config.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![allow(deprecated)] -use { - crate::config::{ - EncodingConfig, RpcBlockConfig, RpcEncodingConfigWrapper, RpcTransactionConfig, - }, - solana_sdk::{clock::Slot, commitment_config::CommitmentConfig}, - solana_transaction_status::{TransactionDetails, UiTransactionEncoding}, -}; - -#[deprecated( - since = "1.7.0", - note = "Please use RpcSignaturesForAddressConfig instead" -)] -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RpcGetConfirmedSignaturesForAddress2Config { - pub before: Option, // Signature as base-58 string - pub until: Option, // Signature as base-58 string - pub limit: Option, - #[serde(flatten)] - pub commitment: Option, -} - -#[deprecated(since = "1.7.0", note = "Please use RpcBlockConfig instead")] -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RpcConfirmedBlockConfig { - pub encoding: Option, - pub transaction_details: Option, - pub rewards: Option, - #[serde(flatten)] - pub commitment: Option, -} - -impl EncodingConfig for RpcConfirmedBlockConfig { - fn new_with_encoding(encoding: &Option) -> Self { - Self { - encoding: *encoding, - ..Self::default() - } - } -} - -impl RpcConfirmedBlockConfig { - pub fn rewards_only() -> Self { - Self { - transaction_details: Some(TransactionDetails::None), - ..Self::default() - } - } - - pub fn rewards_with_commitment(commitment: Option) -> Self { - Self { - transaction_details: Some(TransactionDetails::None), - commitment, - ..Self::default() - } - } -} - -impl From for RpcEncodingConfigWrapper { - fn from(config: RpcConfirmedBlockConfig) -> Self { - RpcEncodingConfigWrapper::Current(Some(config)) - } -} - -impl From for RpcBlockConfig { - fn from(config: RpcConfirmedBlockConfig) -> Self { - Self { - encoding: config.encoding, - transaction_details: config.transaction_details, - rewards: config.rewards, - commitment: config.commitment, - max_supported_transaction_version: None, - } - } -} - -#[deprecated(since = "1.7.0", note = "Please use RpcTransactionConfig instead")] -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RpcConfirmedTransactionConfig { - pub encoding: Option, - #[serde(flatten)] - pub commitment: Option, -} - -impl EncodingConfig for RpcConfirmedTransactionConfig { - fn new_with_encoding(encoding: &Option) -> Self { - Self { - encoding: *encoding, - ..Self::default() - } - } -} - -impl From for RpcTransactionConfig { - fn from(config: RpcConfirmedTransactionConfig) -> Self { - Self { - encoding: config.encoding, - commitment: config.commitment, - max_supported_transaction_version: None, - } - } -} - -#[deprecated(since = "1.7.0", note = "Please use RpcBlocksConfigWrapper instead")] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum RpcConfirmedBlocksConfigWrapper { - EndSlotOnly(Option), - CommitmentOnly(Option), -} - -impl RpcConfirmedBlocksConfigWrapper { - pub fn unzip(&self) -> (Option, Option) { - match &self { - RpcConfirmedBlocksConfigWrapper::EndSlotOnly(end_slot) => (*end_slot, None), - RpcConfirmedBlocksConfigWrapper::CommitmentOnly(commitment) => (None, *commitment), - } - } -} diff --git a/rpc-client-api/src/filter.rs b/rpc-client-api/src/filter.rs index 368d7ce7d7f855..bef8d1d16e8e67 100644 --- a/rpc-client-api/src/filter.rs +++ b/rpc-client-api/src/filter.rs @@ -1,6 +1,6 @@ -#![allow(deprecated)] use { - crate::version_req::VersionReq, + base64::{prelude::BASE64_STANDARD, Engine}, + serde::Deserialize, solana_inline_spl::{token::GenericTokenAccount, token_2022::Account}, solana_sdk::account::{AccountSharedData, ReadableAccount}, std::borrow::Cow, @@ -24,62 +24,46 @@ impl RpcFilterType { match self { RpcFilterType::DataSize(_) => Ok(()), RpcFilterType::Memcmp(compare) => { - let encoding = compare.encoding.as_ref().unwrap_or(&MemcmpEncoding::Binary); - match encoding { - MemcmpEncoding::Binary => { - use MemcmpEncodedBytes::*; - match &compare.bytes { - // DEPRECATED - Binary(bytes) => { - if bytes.len() > MAX_DATA_BASE58_SIZE { - return Err(RpcFilterError::Base58DataTooLarge); - } - let bytes = bs58::decode(&bytes) - .into_vec() - .map_err(RpcFilterError::DecodeError)?; - if bytes.len() > MAX_DATA_SIZE { - Err(RpcFilterError::Base58DataTooLarge) - } else { - Ok(()) - } - } - Base58(bytes) => { - if bytes.len() > MAX_DATA_BASE58_SIZE { - return Err(RpcFilterError::DataTooLarge); - } - let bytes = bs58::decode(&bytes).into_vec()?; - if bytes.len() > MAX_DATA_SIZE { - Err(RpcFilterError::DataTooLarge) - } else { - Ok(()) - } - } - Base64(bytes) => { - if bytes.len() > MAX_DATA_BASE64_SIZE { - return Err(RpcFilterError::DataTooLarge); - } - let bytes = base64::decode(bytes)?; - if bytes.len() > MAX_DATA_SIZE { - Err(RpcFilterError::DataTooLarge) - } else { - Ok(()) - } - } - Bytes(bytes) => { - if bytes.len() > MAX_DATA_SIZE { - return Err(RpcFilterError::DataTooLarge); - } - Ok(()) - } + use MemcmpEncodedBytes::*; + match &compare.bytes { + Base58(bytes) => { + if bytes.len() > MAX_DATA_BASE58_SIZE { + return Err(RpcFilterError::DataTooLarge); + } + let bytes = bs58::decode(&bytes).into_vec()?; + if bytes.len() > MAX_DATA_SIZE { + Err(RpcFilterError::DataTooLarge) + } else { + Ok(()) + } + } + Base64(bytes) => { + if bytes.len() > MAX_DATA_BASE64_SIZE { + return Err(RpcFilterError::DataTooLarge); + } + let bytes = BASE64_STANDARD.decode(bytes)?; + if bytes.len() > MAX_DATA_SIZE { + Err(RpcFilterError::DataTooLarge) + } else { + Ok(()) } } + Bytes(bytes) => { + if bytes.len() > MAX_DATA_SIZE { + return Err(RpcFilterError::DataTooLarge); + } + Ok(()) + } } } RpcFilterType::TokenAccountState => Ok(()), } } - #[deprecated = "Use solana_rpc::filter::filter_allows instead"] + #[deprecated( + since = "2.0.0", + note = "Use solana_rpc::filter::filter_allows instead" + )] pub fn allows(&self, account: &AccountSharedData) -> bool { match self { RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, @@ -93,65 +77,68 @@ impl RpcFilterType { pub enum RpcFilterError { #[error("encoded binary data should be less than 129 bytes")] DataTooLarge, - #[deprecated( - since = "1.8.1", - note = "Error for MemcmpEncodedBytes::Binary which is deprecated" - )] - #[error("encoded binary (base 58) data should be less than 129 bytes")] - Base58DataTooLarge, - #[deprecated( - since = "1.8.1", - note = "Error for MemcmpEncodedBytes::Binary which is deprecated" - )] - #[error("bs58 decode error")] - DecodeError(bs58::decode::Error), #[error("base58 decode error")] Base58DecodeError(#[from] bs58::decode::Error), #[error("base64 decode error")] Base64DecodeError(#[from] base64::DecodeError), } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum MemcmpEncoding { - Binary, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", untagged)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] +#[serde(rename_all = "camelCase", tag = "encoding", content = "bytes")] pub enum MemcmpEncodedBytes { - #[deprecated( - since = "1.8.1", - note = "Please use MemcmpEncodedBytes::Base58 instead" - )] - Binary(String), Base58(String), Base64(String), Bytes(Vec), } +impl<'de> Deserialize<'de> for MemcmpEncodedBytes { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum DataType { + Encoded(String), + Raw(Vec), + } + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + enum RpcMemcmpEncoding { + Base58, + Base64, + Bytes, + } + + #[derive(Deserialize)] + struct RpcMemcmpInner { + bytes: DataType, + encoding: Option, + } + + let data = RpcMemcmpInner::deserialize(deserializer)?; + + let memcmp_encoded_bytes = match data.bytes { + DataType::Encoded(bytes) => match data.encoding.unwrap_or(RpcMemcmpEncoding::Base58) { + RpcMemcmpEncoding::Base58 => MemcmpEncodedBytes::Base58(bytes), + RpcMemcmpEncoding::Base64 => MemcmpEncodedBytes::Base64(bytes), + _ => unreachable!(), + }, + DataType::Raw(bytes) => MemcmpEncodedBytes::Bytes(bytes), + }; + + Ok(memcmp_encoded_bytes) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(into = "RpcMemcmp", from = "RpcMemcmp")] pub struct Memcmp { /// Data offset to begin match - #[deprecated( - since = "1.15.0", - note = "Field will be made private in future. Please use a constructor method instead." - )] - pub offset: usize, - /// Bytes, encoded with specified encoding, or default Binary - #[deprecated( - since = "1.15.0", - note = "Field will be made private in future. Please use a constructor method instead." - )] - pub bytes: MemcmpEncodedBytes, - /// Optional encoding specification - #[deprecated( - since = "1.11.2", - note = "Field has no server-side effect. Specify encoding with `MemcmpEncodedBytes` variant instead. \ - Field will be made private in future. Please use a constructor method instead." - )] - pub encoding: Option, + offset: usize, + /// Bytes, encoded with specified encoding + #[serde(flatten)] + bytes: MemcmpEncodedBytes, } impl Memcmp { @@ -159,7 +146,6 @@ impl Memcmp { Self { offset, bytes: encoded_bytes, - encoding: None, } } @@ -167,7 +153,6 @@ impl Memcmp { Self { offset, bytes: MemcmpEncodedBytes::Bytes(bytes), - encoding: None, } } @@ -175,15 +160,18 @@ impl Memcmp { Self { offset, bytes: MemcmpEncodedBytes::Base58(bs58::encode(bytes).into_string()), - encoding: None, } } + pub fn offset(&self) -> usize { + self.offset + } + pub fn bytes(&self) -> Option>> { use MemcmpEncodedBytes::*; match &self.bytes { - Binary(bytes) | Base58(bytes) => bs58::decode(bytes).into_vec().ok().map(Cow::Owned), - Base64(bytes) => base64::decode(bytes).ok().map(Cow::Owned), + Base58(bytes) => bs58::decode(bytes).into_vec().ok().map(Cow::Owned), + Base64(bytes) => BASE64_STANDARD.decode(bytes).ok().map(Cow::Owned), Bytes(bytes) => Some(Cow::Borrowed(bytes)), } } @@ -191,13 +179,13 @@ impl Memcmp { pub fn convert_to_raw_bytes(&mut self) -> Result<(), RpcFilterError> { use MemcmpEncodedBytes::*; match &self.bytes { - Binary(bytes) | Base58(bytes) => { + Base58(bytes) => { let bytes = bs58::decode(bytes).into_vec()?; self.bytes = Bytes(bytes); Ok(()) } Base64(bytes) => { - let bytes = base64::decode(bytes)?; + let bytes = BASE64_STANDARD.decode(bytes)?; self.bytes = Bytes(bytes); Ok(()) } @@ -219,120 +207,34 @@ impl Memcmp { None => false, } } -} - -// Internal struct to hold Memcmp filter data as either encoded String or raw Bytes -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(untagged)] -enum DataType { - Encoded(String), - Raw(Vec), -} - -// Internal struct used to specify explicit Base58 and Base64 encoding -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -enum RpcMemcmpEncoding { - Base58, - Base64, - // This variant exists only to preserve backward compatibility with generic `Memcmp` serde - #[serde(other)] - Binary, -} - -// Internal struct to enable Memcmp filters with explicit Base58 and Base64 encoding. The From -// implementations emulate `#[serde(tag = "encoding", content = "bytes")]` for -// `MemcmpEncodedBytes`. On the next major version, all these internal elements should be removed -// and replaced with adjacent tagging of `MemcmpEncodedBytes`. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -struct RpcMemcmp { - offset: usize, - bytes: DataType, - encoding: Option, -} - -impl From for RpcMemcmp { - fn from(memcmp: Memcmp) -> RpcMemcmp { - let (bytes, encoding) = match memcmp.bytes { - MemcmpEncodedBytes::Binary(string) => { - (DataType::Encoded(string), Some(RpcMemcmpEncoding::Binary)) - } - MemcmpEncodedBytes::Base58(string) => { - (DataType::Encoded(string), Some(RpcMemcmpEncoding::Base58)) - } - MemcmpEncodedBytes::Base64(string) => { - (DataType::Encoded(string), Some(RpcMemcmpEncoding::Base64)) - } - MemcmpEncodedBytes::Bytes(vector) => (DataType::Raw(vector), None), - }; - RpcMemcmp { - offset: memcmp.offset, - bytes, - encoding, - } - } -} - -impl From for Memcmp { - fn from(memcmp: RpcMemcmp) -> Memcmp { - let encoding = memcmp.encoding.unwrap_or(RpcMemcmpEncoding::Binary); - let bytes = match (encoding, memcmp.bytes) { - (RpcMemcmpEncoding::Binary, DataType::Encoded(string)) - | (RpcMemcmpEncoding::Base58, DataType::Encoded(string)) => { - MemcmpEncodedBytes::Base58(string) - } - (RpcMemcmpEncoding::Binary, DataType::Raw(vector)) => MemcmpEncodedBytes::Bytes(vector), - (RpcMemcmpEncoding::Base64, DataType::Encoded(string)) => { - MemcmpEncodedBytes::Base64(string) - } - _ => unreachable!(), - }; - Memcmp { - offset: memcmp.offset, - bytes, - encoding: None, - } - } -} -pub fn maybe_map_filters( - node_version: Option, - filters: &mut [RpcFilterType], -) -> Result<(), String> { - let version_reqs = VersionReq::from_strs(&["<1.11.2", "~1.13"])?; - let needs_mapping = node_version - .map(|version| version_reqs.matches_any(&version)) - .unwrap_or(true); - if needs_mapping { - for filter in filters.iter_mut() { - if let RpcFilterType::Memcmp(memcmp) = filter { - match &memcmp.bytes { - MemcmpEncodedBytes::Base58(string) => { - memcmp.bytes = MemcmpEncodedBytes::Binary(string.clone()); - } - MemcmpEncodedBytes::Base64(_) => { - return Err("RPC node on old version does not support base64 \ - encoding for memcmp filters" - .to_string()); - } - _ => {} - } - } + /// Returns reference to bytes if variant is MemcmpEncodedBytes::Bytes; + /// otherwise returns None. Used exclusively by solana-rpc to check + /// SPL-token filters. + pub fn raw_bytes_as_ref(&self) -> Option<&[u8]> { + use MemcmpEncodedBytes::*; + if let Bytes(bytes) = &self.bytes { + Some(bytes) + } else { + None } } - Ok(()) } #[cfg(test)] mod tests { - use super::*; + use { + super::*, + const_format::formatcp, + serde_json::{json, Value}, + }; #[test] fn test_worst_case_encoded_tx_goldens() { let ff_data = vec![0xffu8; MAX_DATA_SIZE]; let data58 = bs58::encode(&ff_data).into_string(); assert_eq!(data58.len(), MAX_DATA_BASE58_SIZE); - let data64 = base64::encode(&ff_data); + let data64 = BASE64_STANDARD.encode(&ff_data); assert_eq!(data64.len(), MAX_DATA_BASE64_SIZE); } @@ -344,7 +246,6 @@ mod tests { assert!(Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![1, 2, 3, 4, 5]).into_string()), - encoding: None, } .bytes_match(&data)); @@ -352,7 +253,6 @@ mod tests { assert!(Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![1, 2]).into_string()), - encoding: None, } .bytes_match(&data)); @@ -360,7 +260,6 @@ mod tests { assert!(Memcmp { offset: 2, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![3, 4]).into_string()), - encoding: None, } .bytes_match(&data)); @@ -368,7 +267,6 @@ mod tests { assert!(!Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![2]).into_string()), - encoding: None, } .bytes_match(&data)); @@ -376,7 +274,6 @@ mod tests { assert!(!Memcmp { offset: 2, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![3, 4, 5, 6]).into_string()), - encoding: None, } .bytes_match(&data)); @@ -384,7 +281,6 @@ mod tests { assert!(!Memcmp { offset: 6, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![5]).into_string()), - encoding: None, } .bytes_match(&data)); @@ -392,7 +288,6 @@ mod tests { assert!(!Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58("III".to_string()), - encoding: None, } .bytes_match(&data)); } @@ -407,7 +302,6 @@ mod tests { RpcFilterType::Memcmp(Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(base58_bytes.to_string()), - encoding: None, }) .verify(), Ok(()) @@ -422,10 +316,118 @@ mod tests { RpcFilterType::Memcmp(Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(base58_bytes.to_string()), - encoding: None, }) .verify(), Err(RpcFilterError::DataTooLarge) ); } + + const BASE58_STR: &str = "Bpf4ERpEvSFmCSTNh1PzTWTkALrKXvMXEdthxHuwCQcf"; + const BASE64_STR: &str = "oMoycDvJzrjQpCfukbO4VW/FLGLfnbqBEc9KUEVgj2g="; + const BYTES: [u8; 4] = [0, 1, 2, 3]; + const OFFSET: usize = 42; + const DEFAULT_ENCODING_FILTER: &str = + formatcp!(r#"{{"bytes":"{BASE58_STR}","offset":{OFFSET}}}"#); + const BINARY_FILTER: &str = + formatcp!(r#"{{"bytes":"{BASE58_STR}","offset":{OFFSET},"encoding":"binary"}}"#); + const BASE58_FILTER: &str = + formatcp!(r#"{{"bytes":"{BASE58_STR}","offset":{OFFSET},"encoding":"base58"}}"#); + const BASE64_FILTER: &str = + formatcp!(r#"{{"bytes":"{BASE64_STR}","offset":{OFFSET},"encoding":"base64"}}"#); + const BYTES_FILTER: &str = + formatcp!(r#"{{"bytes":[0, 1, 2, 3],"offset":{OFFSET},"encoding":null}}"#); + const BYTES_FILTER_WITH_ENCODING: &str = + formatcp!(r#"{{"bytes":[0, 1, 2, 3],"offset":{OFFSET},"encoding":"bytes"}}"#); + + #[test] + fn test_filter_deserialize() { + // Base58 is the default encoding + let default: Memcmp = serde_json::from_str(DEFAULT_ENCODING_FILTER).unwrap(); + assert_eq!( + default, + Memcmp { + offset: OFFSET, + bytes: MemcmpEncodedBytes::Base58(BASE58_STR.to_string()), + } + ); + + // Binary input is no longer supported + let binary = serde_json::from_str::(BINARY_FILTER); + assert!(binary.is_err()); + + // Base58 input + let base58_filter: Memcmp = serde_json::from_str(BASE58_FILTER).unwrap(); + assert_eq!( + base58_filter, + Memcmp { + offset: OFFSET, + bytes: MemcmpEncodedBytes::Base58(BASE58_STR.to_string()), + } + ); + + // Base64 input + let base64_filter: Memcmp = serde_json::from_str(BASE64_FILTER).unwrap(); + assert_eq!( + base64_filter, + Memcmp { + offset: OFFSET, + bytes: MemcmpEncodedBytes::Base64(BASE64_STR.to_string()), + } + ); + + // Raw bytes input + let bytes_filter: Memcmp = serde_json::from_str(BYTES_FILTER).unwrap(); + assert_eq!( + bytes_filter, + Memcmp { + offset: OFFSET, + bytes: MemcmpEncodedBytes::Bytes(BYTES.to_vec()), + } + ); + + let bytes_filter: Memcmp = serde_json::from_str(BYTES_FILTER_WITH_ENCODING).unwrap(); + assert_eq!( + bytes_filter, + Memcmp { + offset: OFFSET, + bytes: MemcmpEncodedBytes::Bytes(BYTES.to_vec()), + } + ); + } + + #[test] + fn test_filter_serialize() { + // Base58 + let base58 = Memcmp { + offset: OFFSET, + bytes: MemcmpEncodedBytes::Base58(BASE58_STR.to_string()), + }; + let serialized_json = json!(base58); + assert_eq!( + serialized_json, + serde_json::from_str::(BASE58_FILTER).unwrap() + ); + + // Base64 + let base64 = Memcmp { + offset: OFFSET, + bytes: MemcmpEncodedBytes::Base64(BASE64_STR.to_string()), + }; + let serialized_json = json!(base64); + assert_eq!( + serialized_json, + serde_json::from_str::(BASE64_FILTER).unwrap() + ); + + // Bytes + let bytes = Memcmp { + offset: OFFSET, + bytes: MemcmpEncodedBytes::Bytes(BYTES.to_vec()), + }; + let serialized_json = json!(bytes); + assert_eq!( + serialized_json, + serde_json::from_str::(BYTES_FILTER_WITH_ENCODING).unwrap() + ); + } } diff --git a/rpc-client-api/src/lib.rs b/rpc-client-api/src/lib.rs index 6386a433f719b7..b2484637766ce7 100644 --- a/rpc-client-api/src/lib.rs +++ b/rpc-client-api/src/lib.rs @@ -3,12 +3,10 @@ pub mod client_error; pub mod config; pub mod custom_error; -pub mod deprecated_config; pub mod error_object; pub mod filter; pub mod request; pub mod response; -pub mod version_req; #[macro_use] extern crate serde_derive; diff --git a/rpc-client-api/src/request.rs b/rpc-client-api/src/request.rs index f0bbe0af3e78c6..fe032a858deb47 100644 --- a/rpc-client-api/src/request.rs +++ b/rpc-client-api/src/request.rs @@ -8,9 +8,7 @@ use { #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum RpcRequest { - Custom { - method: &'static str, - }, + Custom { method: &'static str }, DeregisterNode, GetAccountInfo, GetBalance, @@ -21,43 +19,9 @@ pub enum RpcRequest { GetBlocksWithLimit, GetBlockTime, GetClusterNodes, - #[deprecated(since = "1.7.0", note = "Please use RpcRequest::GetBlock instead")] - GetConfirmedBlock, - #[deprecated(since = "1.7.0", note = "Please use RpcRequest::GetBlocks instead")] - GetConfirmedBlocks, - #[deprecated( - since = "1.7.0", - note = "Please use RpcRequest::GetBlocksWithLimit instead" - )] - GetConfirmedBlocksWithLimit, - #[deprecated( - since = "1.7.0", - note = "Please use RpcRequest::GetSignaturesForAddress instead" - )] - GetConfirmedSignaturesForAddress2, - #[deprecated( - since = "1.7.0", - note = "Please use RpcRequest::GetTransaction instead" - )] - GetConfirmedTransaction, GetEpochInfo, GetEpochSchedule, - #[deprecated( - since = "1.9.0", - note = "Please use RpcRequest::GetFeeForMessage instead" - )] - GetFeeCalculatorForBlockhash, GetFeeForMessage, - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - GetFeeRateGovernor, - #[deprecated( - since = "1.9.0", - note = "Please use RpcRequest::GetFeeForMessage instead" - )] - GetFees, GetFirstAvailableBlock, GetGenesisHash, GetHealth, @@ -73,19 +37,9 @@ pub enum RpcRequest { GetMinimumBalanceForRentExemption, GetMultipleAccounts, GetProgramAccounts, - #[deprecated( - since = "1.9.0", - note = "Please use RpcRequest::GetLatestBlockhash instead" - )] - GetRecentBlockhash, GetRecentPerformanceSamples, GetRecentPrioritizationFees, GetHighestSnapshotSlot, - #[deprecated( - since = "1.9.0", - note = "Please use RpcRequest::GetHighestSnapshotSlot instead" - )] - GetSnapshotSlot, GetSignaturesForAddress, GetSignatureStatuses, GetSlot, @@ -94,7 +48,6 @@ pub enum RpcRequest { GetStorageTurn, GetStorageTurnRate, GetSlotsPerSegment, - GetStakeActivation, GetStakeMinimumDelegation, GetStoragePubkeysForSlot, GetSupply, @@ -131,17 +84,9 @@ impl fmt::Display for RpcRequest { RpcRequest::GetBlocksWithLimit => "getBlocksWithLimit", RpcRequest::GetBlockTime => "getBlockTime", RpcRequest::GetClusterNodes => "getClusterNodes", - RpcRequest::GetConfirmedBlock => "getConfirmedBlock", - RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks", - RpcRequest::GetConfirmedBlocksWithLimit => "getConfirmedBlocksWithLimit", - RpcRequest::GetConfirmedSignaturesForAddress2 => "getConfirmedSignaturesForAddress2", - RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction", RpcRequest::GetEpochInfo => "getEpochInfo", RpcRequest::GetEpochSchedule => "getEpochSchedule", - RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash", RpcRequest::GetFeeForMessage => "getFeeForMessage", - RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor", - RpcRequest::GetFees => "getFees", RpcRequest::GetFirstAvailableBlock => "getFirstAvailableBlock", RpcRequest::GetGenesisHash => "getGenesisHash", RpcRequest::GetHealth => "getHealth", @@ -157,17 +102,14 @@ impl fmt::Display for RpcRequest { RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption", RpcRequest::GetMultipleAccounts => "getMultipleAccounts", RpcRequest::GetProgramAccounts => "getProgramAccounts", - RpcRequest::GetRecentBlockhash => "getRecentBlockhash", RpcRequest::GetRecentPerformanceSamples => "getRecentPerformanceSamples", RpcRequest::GetRecentPrioritizationFees => "getRecentPrioritizationFees", RpcRequest::GetHighestSnapshotSlot => "getHighestSnapshotSlot", - RpcRequest::GetSnapshotSlot => "getSnapshotSlot", RpcRequest::GetSignaturesForAddress => "getSignaturesForAddress", RpcRequest::GetSignatureStatuses => "getSignatureStatuses", RpcRequest::GetSlot => "getSlot", RpcRequest::GetSlotLeader => "getSlotLeader", RpcRequest::GetSlotLeaders => "getSlotLeaders", - RpcRequest::GetStakeActivation => "getStakeActivation", RpcRequest::GetStakeMinimumDelegation => "getStakeMinimumDelegation", RpcRequest::GetStorageTurn => "getStorageTurn", RpcRequest::GetStorageTurnRate => "getStorageTurnRate", @@ -303,20 +245,9 @@ mod tests { let request = test_request.build_request_json(1, Value::Null); assert_eq!(request["method"], "getEpochInfo"); - #[allow(deprecated)] - let test_request = RpcRequest::GetRecentBlockhash; - let request = test_request.build_request_json(1, Value::Null); - assert_eq!(request["method"], "getRecentBlockhash"); - - #[allow(deprecated)] - let test_request = RpcRequest::GetFeeCalculatorForBlockhash; - let request = test_request.build_request_json(1, json!([addr])); - assert_eq!(request["method"], "getFeeCalculatorForBlockhash"); - - #[allow(deprecated)] - let test_request = RpcRequest::GetFeeRateGovernor; + let test_request = RpcRequest::GetLatestBlockhash; let request = test_request.build_request_json(1, Value::Null); - assert_eq!(request["method"], "getFeeRateGovernor"); + assert_eq!(request["method"], "getLatestBlockhash"); let test_request = RpcRequest::GetSlot; let request = test_request.build_request_json(1, Value::Null); @@ -347,8 +278,7 @@ mod tests { let addr = json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"); // Test request with CommitmentConfig and no params - #[allow(deprecated)] - let test_request = RpcRequest::GetRecentBlockhash; + let test_request = RpcRequest::GetLatestBlockhash; let request = test_request.build_request_json(1, json!([commitment_config])); assert_eq!(request["params"], json!([commitment_config.clone()])); diff --git a/rpc-client-api/src/response.rs b/rpc-client-api/src/response.rs index f9d3085e83c2d9..fcb330103057e4 100644 --- a/rpc-client-api/src/response.rs +++ b/rpc-client-api/src/response.rs @@ -5,7 +5,6 @@ use { solana_sdk::{ clock::{Epoch, Slot, UnixTimestamp}, fee_calculator::{FeeCalculator, FeeRateGovernor}, - hash::Hash, inflation::Inflation, transaction::{Result, TransactionError}, }, @@ -119,31 +118,6 @@ pub struct RpcBlockhash { pub last_valid_block_height: u64, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct RpcFees { - pub blockhash: String, - pub fee_calculator: FeeCalculator, - pub last_valid_slot: Slot, - pub last_valid_block_height: u64, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct DeprecatedRpcFees { - pub blockhash: String, - pub fee_calculator: FeeCalculator, - pub last_valid_slot: Slot, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Fees { - pub blockhash: Hash, - pub fee_calculator: FeeCalculator, - pub last_valid_block_height: u64, -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct RpcFeeCalculator { @@ -294,10 +268,20 @@ pub struct RpcContactInfo { pub pubkey: String, /// Gossip port pub gossip: Option, + /// Tvu UDP port + pub tvu: Option, /// Tpu UDP port pub tpu: Option, /// Tpu QUIC port pub tpu_quic: Option, + /// Tpu UDP forwards port + pub tpu_forwards: Option, + /// Tpu QUIC forwards port + pub tpu_forwards_quic: Option, + /// Tpu UDP vote port + pub tpu_vote: Option, + /// Server repair UDP port + pub serve_repair: Option, /// JSON RPC port pub rpc: Option, /// WebSocket PubSub port @@ -459,14 +443,6 @@ pub enum StakeActivationState { Inactive, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct RpcStakeActivation { - pub state: StakeActivationState, - pub active: u64, - pub inactive: u64, -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct RpcTokenAccountBalance { diff --git a/rpc-client-api/src/version_req.rs b/rpc-client-api/src/version_req.rs deleted file mode 100644 index 8c8d57e35c2610..00000000000000 --- a/rpc-client-api/src/version_req.rs +++ /dev/null @@ -1,20 +0,0 @@ -pub(crate) struct VersionReq(Vec); - -impl VersionReq { - pub(crate) fn from_strs(versions: &[T]) -> Result - where - T: AsRef + std::fmt::Debug, - { - let mut version_reqs = vec![]; - for version in versions { - let version_req = semver::VersionReq::parse(version.as_ref()) - .map_err(|err| format!("Could not parse version {version:?}: {err:?}"))?; - version_reqs.push(version_req); - } - Ok(Self(version_reqs)) - } - - pub(crate) fn matches_any(&self, version: &semver::Version) -> bool { - self.0.iter().any(|r| r.matches(version)) - } -} diff --git a/rpc-client-nonce-utils/src/blockhash_query.rs b/rpc-client-nonce-utils/src/blockhash_query.rs index 20b3e0572ff9f6..7a6c1c1f8441b2 100644 --- a/rpc-client-nonce-utils/src/blockhash_query.rs +++ b/rpc-client-nonce-utils/src/blockhash_query.rs @@ -6,10 +6,7 @@ use { offline::*, }, solana_rpc_client::rpc_client::RpcClient, - solana_sdk::{ - commitment_config::CommitmentConfig, fee_calculator::FeeCalculator, hash::Hash, - pubkey::Pubkey, - }, + solana_sdk::{commitment_config::CommitmentConfig, hash::Hash, pubkey::Pubkey}, }; #[derive(Debug, PartialEq, Eq)] @@ -19,56 +16,6 @@ pub enum Source { } impl Source { - #[deprecated(since = "1.9.0", note = "Please use `get_blockhash` instead")] - pub fn get_blockhash_and_fee_calculator( - &self, - rpc_client: &RpcClient, - commitment: CommitmentConfig, - ) -> Result<(Hash, FeeCalculator), Box> { - match self { - Self::Cluster => { - #[allow(deprecated)] - let res = rpc_client - .get_recent_blockhash_with_commitment(commitment)? - .value; - Ok((res.0, res.1)) - } - Self::NonceAccount(ref pubkey) => { - let data = crate::get_account_with_commitment(rpc_client, pubkey, commitment) - .and_then(|ref a| crate::data_from_account(a))?; - Ok((data.blockhash(), data.fee_calculator)) - } - } - } - - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - pub fn get_fee_calculator( - &self, - rpc_client: &RpcClient, - blockhash: &Hash, - commitment: CommitmentConfig, - ) -> Result, Box> { - match self { - Self::Cluster => { - #[allow(deprecated)] - let res = rpc_client - .get_fee_calculator_for_blockhash_with_commitment(blockhash, commitment)? - .value; - Ok(res) - } - Self::NonceAccount(ref pubkey) => { - let res = crate::get_account_with_commitment(rpc_client, pubkey, commitment)?; - let res = crate::data_from_account(&res)?; - Ok(Some(res) - .filter(|d| d.blockhash() == *blockhash) - .map(|d| d.fee_calculator)) - } - } - } - pub fn get_blockhash( &self, rpc_client: &RpcClient, @@ -131,29 +78,6 @@ impl BlockhashQuery { BlockhashQuery::new(blockhash, sign_only, nonce_account) } - #[deprecated(since = "1.9.0", note = "Please use `get_blockhash` instead")] - pub fn get_blockhash_and_fee_calculator( - &self, - rpc_client: &RpcClient, - commitment: CommitmentConfig, - ) -> Result<(Hash, FeeCalculator), Box> { - match self { - BlockhashQuery::None(hash) => Ok((*hash, FeeCalculator::default())), - BlockhashQuery::FeeCalculator(source, hash) => { - #[allow(deprecated)] - let fee_calculator = source - .get_fee_calculator(rpc_client, hash, commitment)? - .ok_or(format!("Hash has expired {hash:?}"))?; - Ok((*hash, fee_calculator)) - } - BlockhashQuery::All(source) => - { - #[allow(deprecated)] - source.get_blockhash_and_fee_calculator(rpc_client, commitment) - } - } - } - pub fn get_blockhash( &self, rpc_client: &RpcClient, @@ -188,10 +112,11 @@ mod tests { solana_account_decoder::{UiAccount, UiAccountEncoding}, solana_rpc_client_api::{ request::RpcRequest, - response::{Response, RpcFeeCalculator, RpcFees, RpcResponseContext}, + response::{Response, RpcBlockhash, RpcResponseContext}, }, solana_sdk::{ account::Account, + fee_calculator::FeeCalculator, hash::hash, nonce::{self, state::DurableNonce}, system_program, @@ -350,65 +275,65 @@ mod tests { #[test] #[allow(deprecated)] - fn test_blockhash_query_get_blockhash_fee_calc() { + fn test_blockhash_query_get_blockhash() { let test_blockhash = hash(&[0u8]); let rpc_blockhash = hash(&[1u8]); - let rpc_fee_calc = FeeCalculator::new(42); - let get_recent_blockhash_response = json!(Response { + let get_latest_blockhash_response = json!(Response { context: RpcResponseContext { slot: 1, api_version: None }, - value: json!(RpcFees { + value: json!(RpcBlockhash { blockhash: rpc_blockhash.to_string(), - fee_calculator: rpc_fee_calc, - last_valid_slot: 42, last_valid_block_height: 42, }), }); - let get_fee_calculator_for_blockhash_response = json!(Response { + let is_blockhash_valid_response = json!(Response { context: RpcResponseContext { slot: 1, api_version: None }, - value: json!(RpcFeeCalculator { - fee_calculator: rpc_fee_calc - }), + value: true, }); let mut mocks = HashMap::new(); - mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response.clone()); + mocks.insert( + RpcRequest::GetLatestBlockhash, + get_latest_blockhash_response.clone(), + ); let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); assert_eq!( BlockhashQuery::default() - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) + .get_blockhash(&rpc_client, CommitmentConfig::default()) .unwrap(), - (rpc_blockhash, rpc_fee_calc), + rpc_blockhash, ); let mut mocks = HashMap::new(); - mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response.clone()); mocks.insert( - RpcRequest::GetFeeCalculatorForBlockhash, - get_fee_calculator_for_blockhash_response, + RpcRequest::IsBlockhashValid, + is_blockhash_valid_response.clone(), ); let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); assert_eq!( BlockhashQuery::FeeCalculator(Source::Cluster, test_blockhash) - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) + .get_blockhash(&rpc_client, CommitmentConfig::default()) .unwrap(), - (test_blockhash, rpc_fee_calc), + test_blockhash, ); let mut mocks = HashMap::new(); - mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response); + mocks.insert( + RpcRequest::GetLatestBlockhash, + get_latest_blockhash_response, + ); let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); assert_eq!( BlockhashQuery::None(test_blockhash) - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) + .get_blockhash(&rpc_client, CommitmentConfig::default()) .unwrap(), - (test_blockhash, FeeCalculator::default()), + test_blockhash, ); let rpc_client = RpcClient::new_mock("fails".to_string()); assert!(BlockhashQuery::default() - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) + .get_blockhash(&rpc_client, CommitmentConfig::default()) .is_err()); let durable_nonce = DurableNonce::from_blockhash(&Hash::new(&[2u8; 32])); @@ -447,40 +372,32 @@ mod tests { let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); assert_eq!( BlockhashQuery::All(Source::NonceAccount(nonce_pubkey)) - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) + .get_blockhash(&rpc_client, CommitmentConfig::default()) .unwrap(), - (nonce_blockhash, nonce_fee_calc), + nonce_blockhash, ); let mut mocks = HashMap::new(); mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone()); let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); assert_eq!( BlockhashQuery::FeeCalculator(Source::NonceAccount(nonce_pubkey), nonce_blockhash) - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) + .get_blockhash(&rpc_client, CommitmentConfig::default()) .unwrap(), - (nonce_blockhash, nonce_fee_calc), - ); - let mut mocks = HashMap::new(); - mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone()); - let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); - assert!( - BlockhashQuery::FeeCalculator(Source::NonceAccount(nonce_pubkey), test_blockhash) - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) - .is_err() + nonce_blockhash, ); let mut mocks = HashMap::new(); mocks.insert(RpcRequest::GetAccountInfo, get_account_response); let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); assert_eq!( BlockhashQuery::None(nonce_blockhash) - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) + .get_blockhash(&rpc_client, CommitmentConfig::default()) .unwrap(), - (nonce_blockhash, FeeCalculator::default()), + nonce_blockhash, ); let rpc_client = RpcClient::new_mock("fails".to_string()); assert!(BlockhashQuery::All(Source::NonceAccount(nonce_pubkey)) - .get_blockhash_and_fee_calculator(&rpc_client, CommitmentConfig::default()) + .get_blockhash(&rpc_client, CommitmentConfig::default()) .is_err()); } } diff --git a/rpc-client/src/mock_sender.rs b/rpc-client/src/mock_sender.rs index ec093461c96909..9730a6ff24a983 100644 --- a/rpc-client/src/mock_sender.rs +++ b/rpc-client/src/mock_sender.rs @@ -12,18 +12,17 @@ use { request::RpcRequest, response::{ Response, RpcAccountBalance, RpcBlockProduction, RpcBlockProductionRange, RpcBlockhash, - RpcConfirmedTransactionStatusWithSignature, RpcContactInfo, RpcFees, RpcIdentity, + RpcConfirmedTransactionStatusWithSignature, RpcContactInfo, RpcIdentity, RpcInflationGovernor, RpcInflationRate, RpcInflationReward, RpcKeyedAccount, RpcPerfSample, RpcPrioritizationFee, RpcResponseContext, RpcSimulateTransactionResult, - RpcSnapshotSlotInfo, RpcStakeActivation, RpcSupply, RpcVersionInfo, RpcVoteAccountInfo, - RpcVoteAccountStatus, StakeActivationState, + RpcSnapshotSlotInfo, RpcSupply, RpcVersionInfo, RpcVoteAccountInfo, + RpcVoteAccountStatus, }, }, solana_sdk::{ account::Account, clock::{Slot, UnixTimestamp}, epoch_info::EpochInfo, - fee_calculator::{FeeCalculator, FeeRateGovernor}, instruction::InstructionError, message::MessageHeader, pubkey::Pubkey, @@ -117,13 +116,6 @@ impl RpcSender for MockSender { context: RpcResponseContext { slot: 1, api_version: None }, value: Value::Number(Number::from(50)), })?, - "getRecentBlockhash" => serde_json::to_value(Response { - context: RpcResponseContext { slot: 1, api_version: None }, - value: ( - Value::String(PUBKEY.to_string()), - serde_json::to_value(FeeCalculator::default()).unwrap(), - ), - })?, "getEpochInfo" => serde_json::to_value(EpochInfo { epoch: 1, slot_index: 2, @@ -132,31 +124,6 @@ impl RpcSender for MockSender { block_height: 34, transaction_count: Some(123), })?, - "getFeeCalculatorForBlockhash" => { - let value = if self.url == "blockhash_expired" { - Value::Null - } else { - serde_json::to_value(Some(FeeCalculator::default())).unwrap() - }; - serde_json::to_value(Response { - context: RpcResponseContext { slot: 1, api_version: None }, - value, - })? - } - "getFeeRateGovernor" => serde_json::to_value(Response { - context: RpcResponseContext { slot: 1, api_version: None }, - value: serde_json::to_value(FeeRateGovernor::default()).unwrap(), - })?, - "getFees" => serde_json::to_value(Response { - context: RpcResponseContext { slot: 1, api_version: None }, - value: serde_json::to_value(RpcFees { - blockhash: PUBKEY.to_string(), - fee_calculator: FeeCalculator::default(), - last_valid_slot: 42, - last_valid_block_height: 42, - }) - .unwrap(), - })?, "getSignatureStatuses" => { let status: transaction::Result<()> = if self.url == "account_in_use" { Err(TransactionError::AccountInUse) @@ -242,7 +209,6 @@ impl RpcSender for MockSender { "getSlot" => json![0], "getMaxShredInsertSlot" => json![0], "requestAirdrop" => Value::String(Signature::from([8; 64]).to_string()), - "getSnapshotSlot" => Value::Number(Number::from(0)), "getHighestSnapshotSlot" => json!(RpcSnapshotSlotInfo { full: 100, incremental: Some(110), @@ -287,11 +253,6 @@ impl RpcSender for MockSender { }) } } - "getStakeActivation" => json!(RpcStakeActivation { - state: StakeActivationState::Activating, - active: 123, - inactive: 12, - }), "getStakeMinimumDelegation" => json!(Response { context: RpcResponseContext { slot: 1, api_version: None }, value: 123_456_789, @@ -376,8 +337,13 @@ impl RpcSender for MockSender { "getClusterNodes" => serde_json::to_value(vec![RpcContactInfo { pubkey: PUBKEY.to_string(), gossip: Some(SocketAddr::from(([10, 239, 6, 48], 8899))), + tvu: Some(SocketAddr::from(([10, 239, 6, 48], 8865))), tpu: Some(SocketAddr::from(([10, 239, 6, 48], 8856))), tpu_quic: Some(SocketAddr::from(([10, 239, 6, 48], 8862))), + tpu_forwards: Some(SocketAddr::from(([10, 239, 6, 48], 8857))), + tpu_forwards_quic: Some(SocketAddr::from(([10, 239, 6, 48], 8863))), + tpu_vote: Some(SocketAddr::from(([10, 239, 6, 48], 8870))), + serve_repair: Some(SocketAddr::from(([10, 239, 6, 48], 8880))), rpc: Some(SocketAddr::from(([10, 239, 6, 48], 8899))), pubsub: Some(SocketAddr::from(([10, 239, 6, 48], 8900))), version: Some("1.0.0 c375ce1f".to_string()), @@ -401,6 +367,7 @@ impl RpcSender for MockSender { version: Some(TransactionVersion::LEGACY), }], rewards: Rewards::new(), + num_partitions: None, block_time: None, block_height: Some(428), })?, diff --git a/rpc-client/src/nonblocking/rpc_client.rs b/rpc-client/src/nonblocking/rpc_client.rs index fceb338e20756e..0ca5f76a49f829 100644 --- a/rpc-client/src/nonblocking/rpc_client.rs +++ b/rpc-client/src/nonblocking/rpc_client.rs @@ -7,11 +7,6 @@ //! [JSON-RPC]: https://www.jsonrpc.org/specification pub use crate::mock_sender::Mocks; -#[allow(deprecated)] -use solana_rpc_client_api::deprecated_config::{ - RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, - RpcGetConfirmedSignaturesForAddress2Config, -}; #[cfg(feature = "spinner")] use {crate::spinner, solana_sdk::clock::MAX_HASH_AGE_IN_SECONDS, std::cmp::min}; use { @@ -37,17 +32,15 @@ use { Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult, }, config::{RpcAccountInfoConfig, *}, - filter::{self, RpcFilterType}, request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter}, response::*, }, solana_sdk::{ account::Account, clock::{Epoch, Slot, UnixTimestamp, DEFAULT_MS_PER_SLOT}, - commitment_config::{CommitmentConfig, CommitmentLevel}, + commitment_config::CommitmentConfig, epoch_info::EpochInfo, epoch_schedule::EpochSchedule, - fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, pubkey::Pubkey, signature::Signature, @@ -63,7 +56,7 @@ use { str::FromStr, time::{Duration, Instant}, }, - tokio::{sync::RwLock, time::sleep}, + tokio::time::sleep, }; /// A client of a remote Solana node. @@ -147,7 +140,6 @@ use { pub struct RpcClient { sender: Box, config: RpcClientConfig, - node_version: RwLock>, } impl RpcClient { @@ -163,7 +155,6 @@ impl RpcClient { ) -> Self { Self { sender: Box::new(sender), - node_version: RwLock::new(None), config, } } @@ -515,30 +506,11 @@ impl RpcClient { self.sender.url() } - pub async fn set_node_version(&self, version: semver::Version) -> Result<(), ()> { - let mut w_node_version = self.node_version.write().await; - *w_node_version = Some(version); + #[deprecated(since = "2.0.2", note = "RpcClient::node_version is no longer used")] + pub async fn set_node_version(&self, _version: semver::Version) -> Result<(), ()> { Ok(()) } - async fn get_node_version(&self) -> Result { - let r_node_version = self.node_version.read().await; - if let Some(version) = &*r_node_version { - Ok(version.clone()) - } else { - drop(r_node_version); - let mut w_node_version = self.node_version.write().await; - let node_version = self.get_version().await.map_err(|e| { - RpcError::RpcRequestError(format!("cluster version query failed: {e}")) - })?; - let node_version = semver::Version::parse(&node_version.solana_core).map_err(|e| { - RpcError::RpcRequestError(format!("failed to parse cluster version: {e}")) - })?; - *w_node_version = Some(node_version.clone()); - Ok(node_version) - } - } - /// Get the configured default [commitment level][cl]. /// /// [cl]: https://solana.com/docs/rpc#configuring-state-commitment @@ -556,54 +528,6 @@ impl RpcClient { self.config.commitment_config } - async fn use_deprecated_commitment(&self) -> Result { - Ok(self.get_node_version().await? < semver::Version::new(1, 5, 5)) - } - - async fn maybe_map_commitment( - &self, - requested_commitment: CommitmentConfig, - ) -> Result { - if matches!( - requested_commitment.commitment, - CommitmentLevel::Finalized | CommitmentLevel::Confirmed | CommitmentLevel::Processed - ) && self.use_deprecated_commitment().await? - { - return Ok(CommitmentConfig::use_deprecated_commitment( - requested_commitment, - )); - } - Ok(requested_commitment) - } - - #[allow(deprecated)] - async fn maybe_map_request(&self, mut request: RpcRequest) -> Result { - if self.get_node_version().await? < semver::Version::new(1, 7, 0) { - request = match request { - RpcRequest::GetBlock => RpcRequest::GetConfirmedBlock, - RpcRequest::GetBlocks => RpcRequest::GetConfirmedBlocks, - RpcRequest::GetBlocksWithLimit => RpcRequest::GetConfirmedBlocksWithLimit, - RpcRequest::GetSignaturesForAddress => { - RpcRequest::GetConfirmedSignaturesForAddress2 - } - RpcRequest::GetTransaction => RpcRequest::GetConfirmedTransaction, - _ => request, - }; - } - Ok(request) - } - - #[allow(deprecated)] - async fn maybe_map_filters( - &self, - mut filters: Vec, - ) -> Result, RpcError> { - let node_version = self.get_node_version().await?; - filter::maybe_map_filters(Some(node_version), &mut filters) - .map_err(RpcError::RpcRequestError)?; - Ok(filters) - } - /// Submit a transaction and wait for confirmation. /// /// Once this function returns successfully, the given transaction is @@ -845,11 +769,7 @@ impl RpcClient { self.send_transaction_with_config( transaction, RpcSendTransactionConfig { - preflight_commitment: Some( - self.maybe_map_commitment(self.commitment()) - .await? - .commitment, - ), + preflight_commitment: Some(self.commitment().commitment), ..RpcSendTransactionConfig::default() }, ) @@ -942,15 +862,10 @@ impl RpcClient { transaction: &impl SerializableTransaction, config: RpcSendTransactionConfig, ) -> ClientResult { - let encoding = if let Some(encoding) = config.encoding { - encoding - } else { - self.default_cluster_transaction_encoding().await? - }; + let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base64); let preflight_commitment = CommitmentConfig { commitment: config.preflight_commitment.unwrap_or_default(), }; - let preflight_commitment = self.maybe_map_commitment(preflight_commitment).await?; let config = RpcSendTransactionConfig { encoding: Some(encoding), preflight_commitment: Some(preflight_commitment.commitment), @@ -1233,16 +1148,6 @@ impl RpcClient { } } - async fn default_cluster_transaction_encoding( - &self, - ) -> Result { - if self.get_node_version().await? < semver::Version::new(1, 3, 16) { - Ok(UiTransactionEncoding::Base58) - } else { - Ok(UiTransactionEncoding::Base64) - } - } - /// Simulates sending a transaction. /// /// If the transaction fails, then the [`err`] field of the returned @@ -1392,13 +1297,8 @@ impl RpcClient { transaction: &impl SerializableTransaction, config: RpcSimulateTransactionConfig, ) -> RpcResult { - let encoding = if let Some(encoding) = config.encoding { - encoding - } else { - self.default_cluster_transaction_encoding().await? - }; + let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base64); let commitment = config.commitment.unwrap_or_default(); - let commitment = self.maybe_map_commitment(commitment).await?; let config = RpcSimulateTransactionConfig { encoding: Some(encoding), commitment: Some(commitment), @@ -1436,27 +1336,8 @@ impl RpcClient { /// # Ok::<(), Error>(()) /// ``` pub async fn get_highest_snapshot_slot(&self) -> ClientResult { - if self.get_node_version().await? < semver::Version::new(1, 9, 0) { - #[allow(deprecated)] - self.get_snapshot_slot() - .await - .map(|full| RpcSnapshotSlotInfo { - full, - incremental: None, - }) - } else { - self.send(RpcRequest::GetHighestSnapshotSlot, Value::Null) - .await - } - } - - #[deprecated( - since = "1.8.0", - note = "Please use RpcClient::get_highest_snapshot_slot() instead" - )] - #[allow(deprecated)] - pub async fn get_snapshot_slot(&self) -> ClientResult { - self.send(RpcRequest::GetSnapshotSlot, Value::Null).await + self.send(RpcRequest::GetHighestSnapshotSlot, Value::Null) + .await } /// Check if a transaction has been processed with the default [commitment level][cl]. @@ -1886,11 +1767,8 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetSlot, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) - .await + self.send(RpcRequest::GetSlot, json!([commitment_config])) + .await } /// Returns the block height that has reached the configured [commitment level][cl]. @@ -1950,11 +1828,8 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetBlockHeight, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) - .await + self.send(RpcRequest::GetBlockHeight, json!([commitment_config])) + .await } /// Returns the slot leaders for a given slot range. @@ -2076,99 +1951,6 @@ impl RpcClient { .await } - /// Returns epoch activation information for a stake account. - /// - /// This method uses the configured [commitment level]. - /// - /// [cl]: https://solana.com/docs/rpc#configuring-state-commitment - /// - /// # RPC Reference - /// - /// This method corresponds directly to the [`getStakeActivation`] RPC method. - /// - /// [`getStakeActivation`]: https://solana.com/docs/rpc/http/getstakeactivation - /// - /// # Examples - /// - /// ``` - /// # use solana_rpc_client_api::{ - /// # client_error::Error, - /// # response::StakeActivationState, - /// # }; - /// # use solana_rpc_client::nonblocking::rpc_client::RpcClient; - /// # use solana_sdk::{ - /// # signer::keypair::Keypair, - /// # signature::Signer, - /// # pubkey::Pubkey, - /// # stake, - /// # stake::state::{Authorized, Lockup}, - /// # transaction::Transaction - /// # }; - /// # use std::str::FromStr; - /// # futures::executor::block_on(async { - /// # let alice = Keypair::new(); - /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); - /// // Find some vote account to delegate to - /// let vote_accounts = rpc_client.get_vote_accounts().await?; - /// let vote_account = vote_accounts.current.get(0).unwrap_or_else(|| &vote_accounts.delinquent[0]); - /// let vote_account_pubkey = &vote_account.vote_pubkey; - /// let vote_account_pubkey = Pubkey::from_str(vote_account_pubkey).expect("pubkey"); - /// - /// // Create a stake account - /// let stake_account = Keypair::new(); - /// let stake_account_pubkey = stake_account.pubkey(); - /// - /// // Build the instructions to create new stake account, - /// // funded by alice, and delegate to a validator's vote account. - /// let instrs = stake::instruction::create_account_and_delegate_stake( - /// &alice.pubkey(), - /// &stake_account_pubkey, - /// &vote_account_pubkey, - /// &Authorized::auto(&stake_account_pubkey), - /// &Lockup::default(), - /// 1_000_000, - /// ); - /// - /// let latest_blockhash = rpc_client.get_latest_blockhash().await?; - /// let tx = Transaction::new_signed_with_payer( - /// &instrs, - /// Some(&alice.pubkey()), - /// &[&alice, &stake_account], - /// latest_blockhash, - /// ); - /// - /// rpc_client.send_and_confirm_transaction(&tx).await?; - /// - /// let epoch_info = rpc_client.get_epoch_info().await?; - /// let activation = rpc_client.get_stake_activation( - /// stake_account_pubkey, - /// Some(epoch_info.epoch), - /// ).await?; - /// - /// assert_eq!(activation.state, StakeActivationState::Activating); - /// # Ok::<(), Error>(()) - /// # })?; - /// # Ok::<(), Error>(()) - /// ``` - pub async fn get_stake_activation( - &self, - stake_account: Pubkey, - epoch: Option, - ) -> ClientResult { - self.send( - RpcRequest::GetStakeActivation, - json!([ - stake_account.to_string(), - RpcEpochConfig { - epoch, - commitment: Some(self.commitment()), - min_context_slot: None, - } - ]), - ) - .await - } - /// Returns information about the current supply. /// /// This method uses the configured [commitment level][cl]. @@ -2225,11 +2007,8 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> RpcResult { - self.send( - RpcRequest::GetSupply, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) - .await + self.send(RpcRequest::GetSupply, json!([commitment_config])) + .await } /// Returns the 20 largest accounts, by lamport balance. @@ -2257,6 +2036,7 @@ impl RpcClient { /// let config = RpcLargestAccountsConfig { /// commitment: Some(commitment_config), /// filter: Some(RpcLargestAccountsFilter::Circulating), + /// sort_results: None, /// }; /// let accounts = rpc_client.get_largest_accounts_with_config( /// config, @@ -2270,7 +2050,6 @@ impl RpcClient { config: RpcLargestAccountsConfig, ) -> RpcResult> { let commitment = config.commitment.unwrap_or_default(); - let commitment = self.maybe_map_commitment(commitment).await?; let config = RpcLargestAccountsConfig { commitment: Some(commitment), ..config @@ -2340,7 +2119,7 @@ impl RpcClient { commitment_config: CommitmentConfig, ) -> ClientResult { self.get_vote_accounts_with_config(RpcGetVoteAccountsConfig { - commitment: Some(self.maybe_map_commitment(commitment_config).await?), + commitment: Some(commitment_config), ..RpcGetVoteAccountsConfig::default() }) .await @@ -2518,11 +2297,8 @@ impl RpcClient { slot: Slot, encoding: UiTransactionEncoding, ) -> ClientResult { - self.send( - self.maybe_map_request(RpcRequest::GetBlock).await?, - json!([slot, encoding]), - ) - .await + self.send(RpcRequest::GetBlock, json!([slot, encoding])) + .await } /// Returns identity and transaction information about a confirmed block in the ledger. @@ -2568,46 +2344,7 @@ impl RpcClient { slot: Slot, config: RpcBlockConfig, ) -> ClientResult { - self.send( - self.maybe_map_request(RpcRequest::GetBlock).await?, - json!([slot, config]), - ) - .await - } - - #[deprecated(since = "1.7.0", note = "Please use RpcClient::get_block() instead")] - #[allow(deprecated)] - pub async fn get_confirmed_block(&self, slot: Slot) -> ClientResult { - self.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Json) - .await - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_block_with_encoding() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_block_with_encoding( - &self, - slot: Slot, - encoding: UiTransactionEncoding, - ) -> ClientResult { - self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding])) - .await - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_block_with_config() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_block_with_config( - &self, - slot: Slot, - config: RpcConfirmedBlockConfig, - ) -> ClientResult { - self.send(RpcRequest::GetConfirmedBlock, json!([slot, config])) - .await + self.send(RpcRequest::GetBlock, json!([slot, config])).await } /// Returns a list of finalized blocks between two slots. @@ -2634,12 +2371,9 @@ impl RpcClient { /// /// # RPC Reference /// - /// This method corresponds directly to the [`getBlocks`] RPC method, unless - /// the remote node version is less than 1.7, in which case it maps to the - /// [`getConfirmedBlocks`] RPC method. + /// This method corresponds directly to the [`getBlocks`] RPC method. /// /// [`getBlocks`]: https://solana.com/docs/rpc/http/getblocks - /// [`getConfirmedBlocks`]: https://solana.com/docs/rpc/deprecated/getconfirmedblocks /// /// # Examples /// @@ -2661,11 +2395,8 @@ impl RpcClient { start_slot: Slot, end_slot: Option, ) -> ClientResult> { - self.send( - self.maybe_map_request(RpcRequest::GetBlocks).await?, - json!([start_slot, end_slot]), - ) - .await + self.send(RpcRequest::GetBlocks, json!([start_slot, end_slot])) + .await } /// Returns a list of confirmed blocks between two slots. @@ -2696,12 +2427,9 @@ impl RpcClient { /// /// # RPC Reference /// - /// This method corresponds directly to the [`getBlocks`] RPC method, unless - /// the remote node version is less than 1.7, in which case it maps to the - /// [`getConfirmedBlocks`] RPC method. + /// This method corresponds directly to the [`getBlocks`] RPC method. /// /// [`getBlocks`]: https://solana.com/docs/rpc/http/getblocks - /// [`getConfirmedBlocks`]: https://solana.com/docs/rpc/deprecated/getconfirmedblocks /// /// # Examples /// @@ -2732,19 +2460,11 @@ impl RpcClient { commitment_config: CommitmentConfig, ) -> ClientResult> { let json = if end_slot.is_some() { - json!([ - start_slot, - end_slot, - self.maybe_map_commitment(commitment_config).await? - ]) + json!([start_slot, end_slot, commitment_config]) } else { - json!([ - start_slot, - self.maybe_map_commitment(commitment_config).await? - ]) + json!([start_slot, commitment_config]) }; - self.send(self.maybe_map_request(RpcRequest::GetBlocks).await?, json) - .await + self.send(RpcRequest::GetBlocks, json).await } /// Returns a list of finalized blocks starting at the given slot. @@ -2761,11 +2481,9 @@ impl RpcClient { /// # RPC Reference /// /// This method corresponds directly to the [`getBlocksWithLimit`] RPC - /// method, unless the remote node version is less than 1.7, in which case - /// it maps to the [`getConfirmedBlocksWithLimit`] RPC method. + /// method. /// /// [`getBlocksWithLimit`]: https://solana.com/docs/rpc/http/getblockswithlimit - /// [`getConfirmedBlocksWithLimit`]: https://solana.com/docs/rpc/deprecated/getconfirmedblockswithlimit /// /// # Examples /// @@ -2787,12 +2505,8 @@ impl RpcClient { start_slot: Slot, limit: usize, ) -> ClientResult> { - self.send( - self.maybe_map_request(RpcRequest::GetBlocksWithLimit) - .await?, - json!([start_slot, limit]), - ) - .await + self.send(RpcRequest::GetBlocksWithLimit, json!([start_slot, limit])) + .await } /// Returns a list of confirmed blocks starting at the given slot. @@ -2810,11 +2524,9 @@ impl RpcClient { /// # RPC Reference /// /// This method corresponds directly to the [`getBlocksWithLimit`] RPC - /// method, unless the remote node version is less than 1.7, in which case - /// it maps to the `getConfirmedBlocksWithLimit` RPC method. + /// method. /// /// [`getBlocksWithLimit`]: https://solana.com/docs/rpc/http/getblockswithlimit - /// [`getConfirmedBlocksWithLimit`]: https://solana.com/docs/rpc/deprecated/getconfirmedblockswithlimit /// /// # Examples /// @@ -2844,92 +2556,8 @@ impl RpcClient { commitment_config: CommitmentConfig, ) -> ClientResult> { self.send( - self.maybe_map_request(RpcRequest::GetBlocksWithLimit) - .await?, - json!([ - start_slot, - limit, - self.maybe_map_commitment(commitment_config).await? - ]), - ) - .await - } - - #[deprecated(since = "1.7.0", note = "Please use RpcClient::get_blocks() instead")] - #[allow(deprecated)] - pub async fn get_confirmed_blocks( - &self, - start_slot: Slot, - end_slot: Option, - ) -> ClientResult> { - self.send( - RpcRequest::GetConfirmedBlocks, - json!([start_slot, end_slot]), - ) - .await - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_blocks_with_commitment() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_blocks_with_commitment( - &self, - start_slot: Slot, - end_slot: Option, - commitment_config: CommitmentConfig, - ) -> ClientResult> { - let json = if end_slot.is_some() { - json!([ - start_slot, - end_slot, - self.maybe_map_commitment(commitment_config).await? - ]) - } else { - json!([ - start_slot, - self.maybe_map_commitment(commitment_config).await? - ]) - }; - self.send(RpcRequest::GetConfirmedBlocks, json).await - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_blocks_with_limit() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_blocks_with_limit( - &self, - start_slot: Slot, - limit: usize, - ) -> ClientResult> { - self.send( - RpcRequest::GetConfirmedBlocksWithLimit, - json!([start_slot, limit]), - ) - .await - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_blocks_with_limit_and_commitment() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_blocks_with_limit_and_commitment( - &self, - start_slot: Slot, - limit: usize, - commitment_config: CommitmentConfig, - ) -> ClientResult> { - self.send( - RpcRequest::GetConfirmedBlocksWithLimit, - json!([ - start_slot, - limit, - self.maybe_map_commitment(commitment_config).await? - ]), + RpcRequest::GetBlocksWithLimit, + json!([start_slot, limit, commitment_config]), ) .await } @@ -3049,51 +2677,7 @@ impl RpcClient { let result: Vec = self .send( - self.maybe_map_request(RpcRequest::GetSignaturesForAddress) - .await?, - json!([address.to_string(), config]), - ) - .await?; - - Ok(result) - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_signatures_for_address() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_signatures_for_address2( - &self, - address: &Pubkey, - ) -> ClientResult> { - self.get_confirmed_signatures_for_address2_with_config( - address, - GetConfirmedSignaturesForAddress2Config::default(), - ) - .await - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_signatures_for_address_with_config() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_signatures_for_address2_with_config( - &self, - address: &Pubkey, - config: GetConfirmedSignaturesForAddress2Config, - ) -> ClientResult> { - let config = RpcGetConfirmedSignaturesForAddress2Config { - before: config.before.map(|signature| signature.to_string()), - until: config.until.map(|signature| signature.to_string()), - limit: config.limit, - commitment: config.commitment, - }; - - let result: Vec = self - .send( - RpcRequest::GetConfirmedSignaturesForAddress2, + RpcRequest::GetSignaturesForAddress, json!([address.to_string(), config]), ) .await?; @@ -3110,12 +2694,9 @@ impl RpcClient { /// /// # RPC Reference /// - /// This method corresponds directly to the [`getTransaction`] RPC method, - /// unless the remote node version is less than 1.7, in which case it maps - /// to the [`getConfirmedTransaction`] RPC method. + /// This method corresponds directly to the [`getTransaction`] RPC method. /// /// [`getTransaction`]: https://solana.com/docs/rpc/http/gettransaction - /// [`getConfirmedTransaction`]: https://solana.com/docs/rpc/deprecated/getConfirmedTransaction /// /// # Examples /// @@ -3151,7 +2732,7 @@ impl RpcClient { encoding: UiTransactionEncoding, ) -> ClientResult { self.send( - self.maybe_map_request(RpcRequest::GetTransaction).await?, + RpcRequest::GetTransaction, json!([signature.to_string(), encoding]), ) .await @@ -3169,12 +2750,9 @@ impl RpcClient { /// /// # RPC Reference /// - /// This method corresponds directly to the [`getTransaction`] RPC method, - /// unless the remote node version is less than 1.7, in which case it maps - /// to the [`getConfirmedTransaction`] RPC method. + /// This method corresponds directly to the [`getTransaction`] RPC method. /// /// [`getTransaction`]: https://solana.com/docs/rpc/http/gettransaction - /// [`getConfirmedTransaction`]: https://solana.com/docs/rpc/deprecated/getConfirmedTransaction /// /// # Examples /// @@ -3219,41 +2797,7 @@ impl RpcClient { config: RpcTransactionConfig, ) -> ClientResult { self.send( - self.maybe_map_request(RpcRequest::GetTransaction).await?, - json!([signature.to_string(), config]), - ) - .await - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_transaction() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_transaction( - &self, - signature: &Signature, - encoding: UiTransactionEncoding, - ) -> ClientResult { - self.send( - RpcRequest::GetConfirmedTransaction, - json!([signature.to_string(), encoding]), - ) - .await - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_transaction_with_config() instead" - )] - #[allow(deprecated)] - pub async fn get_confirmed_transaction_with_config( - &self, - signature: &Signature, - config: RpcConfirmedTransactionConfig, - ) -> ClientResult { - self.send( - RpcRequest::GetConfirmedTransaction, + RpcRequest::GetTransaction, json!([signature.to_string(), config]), ) .await @@ -3354,11 +2898,8 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetEpochInfo, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) - .await + self.send(RpcRequest::GetEpochInfo, json!([commitment_config])) + .await } /// Returns the leader schedule for an epoch. @@ -3431,7 +2972,7 @@ impl RpcClient { self.get_leader_schedule_with_config( slot, RpcLeaderScheduleConfig { - commitment: Some(self.maybe_map_commitment(commitment_config).await?), + commitment: Some(commitment_config), ..RpcLeaderScheduleConfig::default() }, ) @@ -3877,7 +3418,7 @@ impl RpcClient { ) -> RpcResult> { let config = RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::Base64Zstd), - commitment: Some(self.maybe_map_commitment(commitment_config).await?), + commitment: Some(commitment_config), data_slice: None, min_context_slot: None, }; @@ -4104,7 +3645,7 @@ impl RpcClient { pubkeys, RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::Base64Zstd), - commitment: Some(self.maybe_map_commitment(commitment_config).await?), + commitment: Some(commitment_config), data_slice: None, min_context_slot: None, }, @@ -4333,10 +3874,7 @@ impl RpcClient { ) -> RpcResult { self.send( RpcRequest::GetBalance, - json!([ - pubkey.to_string(), - self.maybe_map_commitment(commitment_config).await? - ]), + json!([pubkey.to_string(), commitment_config]), ) .await } @@ -4456,11 +3994,7 @@ impl RpcClient { .account_config .commitment .unwrap_or_else(|| self.commitment()); - let commitment = self.maybe_map_commitment(commitment).await?; config.account_config.commitment = Some(commitment); - if let Some(filters) = config.filters { - config.filters = Some(self.maybe_map_filters(filters).await?); - } let accounts = self .send::>>( @@ -4525,7 +4059,7 @@ impl RpcClient { Ok(self .send::>( RpcRequest::GetStakeMinimumDelegation, - json!([self.maybe_map_commitment(commitment_config).await?]), + json!([commitment_config]), ) .await? .value) @@ -4541,233 +4075,8 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetTransactionCount, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) - .await - } - - #[deprecated( - since = "1.9.0", - note = "Please use `get_latest_blockhash` and `get_fee_for_message` instead" - )] - #[allow(deprecated)] - pub async fn get_fees(&self) -> ClientResult { - #[allow(deprecated)] - Ok(self - .get_fees_with_commitment(self.commitment()) - .await? - .value) - } - - #[deprecated( - since = "1.9.0", - note = "Please use `get_latest_blockhash_with_commitment` and `get_fee_for_message` instead" - )] - #[allow(deprecated)] - pub async fn get_fees_with_commitment( - &self, - commitment_config: CommitmentConfig, - ) -> RpcResult { - let Response { - context, - value: fees, - } = self - .send::>( - RpcRequest::GetFees, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) - .await?; - let blockhash = fees.blockhash.parse().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Hash".to_string()).into(), - RpcRequest::GetFees, - ) - })?; - Ok(Response { - context, - value: Fees { - blockhash, - fee_calculator: fees.fee_calculator, - last_valid_block_height: fees.last_valid_block_height, - }, - }) - } - - #[deprecated(since = "1.9.0", note = "Please use `get_latest_blockhash` instead")] - #[allow(deprecated)] - pub async fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> { - #[allow(deprecated)] - let (blockhash, fee_calculator, _last_valid_slot) = self - .get_recent_blockhash_with_commitment(self.commitment()) - .await? - .value; - Ok((blockhash, fee_calculator)) - } - - #[deprecated( - since = "1.9.0", - note = "Please use `get_latest_blockhash_with_commitment` instead" - )] - #[allow(deprecated)] - pub async fn get_recent_blockhash_with_commitment( - &self, - commitment_config: CommitmentConfig, - ) -> RpcResult<(Hash, FeeCalculator, Slot)> { - let (context, blockhash, fee_calculator, last_valid_slot) = if let Ok(Response { - context, - value: - RpcFees { - blockhash, - fee_calculator, - last_valid_slot, - .. - }, - }) = self - .send::>( - RpcRequest::GetFees, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) - .await - { - (context, blockhash, fee_calculator, last_valid_slot) - } else if let Ok(Response { - context, - value: - DeprecatedRpcFees { - blockhash, - fee_calculator, - last_valid_slot, - }, - }) = self - .send::>( - RpcRequest::GetFees, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) - .await - { - (context, blockhash, fee_calculator, last_valid_slot) - } else if let Ok(Response { - context, - value: - RpcBlockhashFeeCalculator { - blockhash, - fee_calculator, - }, - }) = self - .send::>( - RpcRequest::GetRecentBlockhash, - json!([self.maybe_map_commitment(commitment_config).await?]), - ) + self.send(RpcRequest::GetTransactionCount, json!([commitment_config])) .await - { - (context, blockhash, fee_calculator, 0) - } else { - return Err(ClientError::new_with_request( - RpcError::ParseError("RpcBlockhashFeeCalculator or RpcFees".to_string()).into(), - RpcRequest::GetRecentBlockhash, - )); - }; - - let blockhash = blockhash.parse().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Hash".to_string()).into(), - RpcRequest::GetRecentBlockhash, - ) - })?; - Ok(Response { - context, - value: (blockhash, fee_calculator, last_valid_slot), - }) - } - - #[deprecated(since = "1.9.0", note = "Please `get_fee_for_message` instead")] - #[allow(deprecated)] - pub async fn get_fee_calculator_for_blockhash( - &self, - blockhash: &Hash, - ) -> ClientResult> { - #[allow(deprecated)] - Ok(self - .get_fee_calculator_for_blockhash_with_commitment(blockhash, self.commitment()) - .await? - .value) - } - - #[deprecated( - since = "1.9.0", - note = "Please `get_latest_blockhash_with_commitment` and `get_fee_for_message` instead" - )] - #[allow(deprecated)] - pub async fn get_fee_calculator_for_blockhash_with_commitment( - &self, - blockhash: &Hash, - commitment_config: CommitmentConfig, - ) -> RpcResult> { - let Response { context, value } = self - .send::>>( - RpcRequest::GetFeeCalculatorForBlockhash, - json!([ - blockhash.to_string(), - self.maybe_map_commitment(commitment_config).await? - ]), - ) - .await?; - - Ok(Response { - context, - value: value.map(|rf| rf.fee_calculator), - }) - } - - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - #[allow(deprecated)] - pub async fn get_fee_rate_governor(&self) -> RpcResult { - let Response { - context, - value: RpcFeeRateGovernor { fee_rate_governor }, - } = self - .send::>(RpcRequest::GetFeeRateGovernor, Value::Null) - .await?; - - Ok(Response { - context, - value: fee_rate_governor, - }) - } - - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - #[allow(deprecated)] - pub async fn get_new_blockhash(&self, blockhash: &Hash) -> ClientResult<(Hash, FeeCalculator)> { - let mut num_retries = 0; - let start = Instant::now(); - while start.elapsed().as_secs() < 5 { - #[allow(deprecated)] - if let Ok((new_blockhash, fee_calculator)) = self.get_recent_blockhash().await { - if new_blockhash != *blockhash { - return Ok((new_blockhash, fee_calculator)); - } - } - debug!("Got same blockhash ({:?}), will retry...", blockhash); - - // Retry ~twice during a slot - sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT / 2)).await; - num_retries += 1; - } - Err(RpcError::ForUser(format!( - "Unable to get new blockhash after {}ms (retried {} times), stuck at {}", - start.elapsed().as_millis(), - num_retries, - blockhash - )) - .into()) } pub async fn get_first_available_block(&self) -> ClientResult { @@ -4806,7 +4115,7 @@ impl RpcClient { ) -> RpcResult> { let config = RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::JsonParsed), - commitment: Some(self.maybe_map_commitment(commitment_config).await?), + commitment: Some(commitment_config), data_slice: None, min_context_slot: None, }; @@ -4869,10 +4178,7 @@ impl RpcClient { ) -> RpcResult { self.send( RpcRequest::GetTokenAccountBalance, - json!([ - pubkey.to_string(), - self.maybe_map_commitment(commitment_config).await? - ]), + json!([pubkey.to_string(), commitment_config]), ) .await } @@ -4907,7 +4213,7 @@ impl RpcClient { let config = RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::JsonParsed), - commitment: Some(self.maybe_map_commitment(commitment_config).await?), + commitment: Some(commitment_config), data_slice: None, min_context_slot: None, }; @@ -4949,7 +4255,7 @@ impl RpcClient { let config = RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::JsonParsed), - commitment: Some(self.maybe_map_commitment(commitment_config).await?), + commitment: Some(commitment_config), data_slice: None, min_context_slot: None, }; @@ -4978,10 +4284,7 @@ impl RpcClient { ) -> RpcResult> { self.send( RpcRequest::GetTokenLargestAccounts, - json!([ - mint.to_string(), - self.maybe_map_commitment(commitment_config).await? - ]), + json!([mint.to_string(), commitment_config]), ) .await } @@ -5000,10 +4303,7 @@ impl RpcClient { ) -> RpcResult { self.send( RpcRequest::GetTokenSupply, - json!([ - mint.to_string(), - self.maybe_map_commitment(commitment_config).await? - ]), + json!([mint.to_string(), commitment_config]), ) .await } @@ -5044,7 +4344,6 @@ impl RpcClient { config: RpcRequestAirdropConfig, ) -> ClientResult { let commitment = config.commitment.unwrap_or_default(); - let commitment = self.maybe_map_commitment(commitment).await?; let config = RpcRequestAirdropConfig { commitment: Some(commitment), ..config @@ -5259,64 +4558,40 @@ impl RpcClient { Ok(blockhash) } - #[allow(deprecated)] pub async fn get_latest_blockhash_with_commitment( &self, commitment: CommitmentConfig, ) -> ClientResult<(Hash, u64)> { - let (blockhash, last_valid_block_height) = - if self.get_node_version().await? < semver::Version::new(1, 9, 0) { - let Fees { - blockhash, - last_valid_block_height, - .. - } = self.get_fees_with_commitment(commitment).await?.value; - (blockhash, last_valid_block_height) - } else { - let RpcBlockhash { - blockhash, - last_valid_block_height, - } = self - .send::>( - RpcRequest::GetLatestBlockhash, - json!([self.maybe_map_commitment(commitment).await?]), - ) - .await? - .value; - let blockhash = blockhash.parse().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Hash".to_string()).into(), - RpcRequest::GetLatestBlockhash, - ) - })?; - (blockhash, last_valid_block_height) - }; + let RpcBlockhash { + blockhash, + last_valid_block_height, + } = self + .send::>(RpcRequest::GetLatestBlockhash, json!([commitment])) + .await? + .value; + let blockhash = blockhash.parse().map_err(|_| { + ClientError::new_with_request( + RpcError::ParseError("Hash".to_string()).into(), + RpcRequest::GetLatestBlockhash, + ) + })?; Ok((blockhash, last_valid_block_height)) } - #[allow(deprecated)] pub async fn is_blockhash_valid( &self, blockhash: &Hash, commitment: CommitmentConfig, ) -> ClientResult { - let result = if self.get_node_version().await? < semver::Version::new(1, 9, 0) { - self.get_fee_calculator_for_blockhash_with_commitment(blockhash, commitment) - .await? - .value - .is_some() - } else { - self.send::>( + Ok(self + .send::>( RpcRequest::IsBlockhashValid, json!([blockhash.to_string(), commitment,]), ) .await? - .value - }; - Ok(result) + .value) } - #[allow(deprecated)] pub async fn get_fee_for_message( &self, message: &impl SerializableMessage, diff --git a/rpc-client/src/rpc_client.rs b/rpc-client/src/rpc_client.rs index 119039269a7bac..32bd08cef49f03 100644 --- a/rpc-client/src/rpc_client.rs +++ b/rpc-client/src/rpc_client.rs @@ -10,10 +10,6 @@ //! in [`crate::nonblocking::rpc_client`]. pub use crate::mock_sender::Mocks; -#[allow(deprecated)] -use solana_rpc_client_api::deprecated_config::{ - RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, -}; use { crate::{ http_sender::HttpSender, @@ -40,7 +36,6 @@ use { epoch_info::EpochInfo, epoch_schedule::EpochSchedule, feature::Feature, - fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, message::{v0, Message as LegacyMessage}, pubkey::Pubkey, @@ -1175,15 +1170,6 @@ impl RpcClient { self.invoke((self.rpc_client.as_ref()).get_highest_snapshot_slot()) } - #[deprecated( - since = "1.8.0", - note = "Please use RpcClient::get_highest_snapshot_slot() instead" - )] - #[allow(deprecated)] - pub fn get_snapshot_slot(&self) -> ClientResult { - self.invoke((self.rpc_client.as_ref()).get_snapshot_slot()) - } - /// Check if a transaction has been processed with the default [commitment level][cl]. /// /// [cl]: https://solana.com/docs/rpc#configuring-state-commitment @@ -1717,85 +1703,6 @@ impl RpcClient { self.invoke((self.rpc_client.as_ref()).get_block_production_with_config(config)) } - /// Returns epoch activation information for a stake account. - /// - /// This method uses the configured [commitment level]. - /// - /// [cl]: https://solana.com/docs/rpc#configuring-state-commitment - /// - /// # RPC Reference - /// - /// This method corresponds directly to the [`getStakeActivation`] RPC method. - /// - /// [`getStakeActivation`]: https://solana.com/docs/rpc/http/getstakeactivation - /// - /// # Examples - /// - /// ``` - /// # use solana_rpc_client_api::{ - /// # client_error::Error, - /// # response::StakeActivationState, - /// # }; - /// # use solana_rpc_client::rpc_client::RpcClient; - /// # use solana_sdk::{ - /// # signer::keypair::Keypair, - /// # signature::Signer, - /// # pubkey::Pubkey, - /// # stake, - /// # stake::state::{Authorized, Lockup}, - /// # transaction::Transaction - /// # }; - /// # use std::str::FromStr; - /// # let alice = Keypair::new(); - /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); - /// // Find some vote account to delegate to - /// let vote_accounts = rpc_client.get_vote_accounts()?; - /// let vote_account = vote_accounts.current.get(0).unwrap_or_else(|| &vote_accounts.delinquent[0]); - /// let vote_account_pubkey = &vote_account.vote_pubkey; - /// let vote_account_pubkey = Pubkey::from_str(vote_account_pubkey).expect("pubkey"); - /// - /// // Create a stake account - /// let stake_account = Keypair::new(); - /// let stake_account_pubkey = stake_account.pubkey(); - /// - /// // Build the instructions to create new stake account, - /// // funded by alice, and delegate to a validator's vote account. - /// let instrs = stake::instruction::create_account_and_delegate_stake( - /// &alice.pubkey(), - /// &stake_account_pubkey, - /// &vote_account_pubkey, - /// &Authorized::auto(&stake_account_pubkey), - /// &Lockup::default(), - /// 1_000_000, - /// ); - /// - /// let latest_blockhash = rpc_client.get_latest_blockhash()?; - /// let tx = Transaction::new_signed_with_payer( - /// &instrs, - /// Some(&alice.pubkey()), - /// &[&alice, &stake_account], - /// latest_blockhash, - /// ); - /// - /// rpc_client.send_and_confirm_transaction(&tx)?; - /// - /// let epoch_info = rpc_client.get_epoch_info()?; - /// let activation = rpc_client.get_stake_activation( - /// stake_account_pubkey, - /// Some(epoch_info.epoch), - /// )?; - /// - /// assert_eq!(activation.state, StakeActivationState::Activating); - /// # Ok::<(), Error>(()) - /// ``` - pub fn get_stake_activation( - &self, - stake_account: Pubkey, - epoch: Option, - ) -> ClientResult { - self.invoke((self.rpc_client.as_ref()).get_stake_activation(stake_account, epoch)) - } - /// Returns information about the current supply. /// /// This method uses the configured [commitment level][cl]. @@ -1872,6 +1779,7 @@ impl RpcClient { /// let config = RpcLargestAccountsConfig { /// commitment: Some(commitment_config), /// filter: Some(RpcLargestAccountsFilter::Circulating), + /// sort_results: None, /// }; /// let accounts = rpc_client.get_largest_accounts_with_config( /// config, @@ -2121,38 +2029,6 @@ impl RpcClient { self.invoke((self.rpc_client.as_ref()).get_block_with_config(slot, config)) } - #[deprecated(since = "1.7.0", note = "Please use RpcClient::get_block() instead")] - #[allow(deprecated)] - pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult { - self.invoke((self.rpc_client.as_ref()).get_confirmed_block(slot)) - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_block_with_encoding() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_block_with_encoding( - &self, - slot: Slot, - encoding: UiTransactionEncoding, - ) -> ClientResult { - self.invoke((self.rpc_client.as_ref()).get_confirmed_block_with_encoding(slot, encoding)) - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_block_with_config() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_block_with_config( - &self, - slot: Slot, - config: RpcConfirmedBlockConfig, - ) -> ClientResult { - self.invoke((self.rpc_client.as_ref()).get_confirmed_block_with_config(slot, config)) - } - /// Returns a list of finalized blocks between two slots. /// /// The range is inclusive, with results including the block for both @@ -2177,12 +2053,9 @@ impl RpcClient { /// /// # RPC Reference /// - /// This method corresponds directly to the [`getBlocks`] RPC method, unless - /// the remote node version is less than 1.7, in which case it maps to the - /// [`getConfirmedBlocks`] RPC method. + /// This method corresponds directly to the [`getBlocks`] RPC method. /// /// [`getBlocks`]: https://solana.com/docs/rpc/http/getblocks - /// [`getConfirmedBlocks`]: https://solana.com/docs/rpc/deprecated/getconfirmedblocks /// /// # Examples /// @@ -2228,12 +2101,9 @@ impl RpcClient { /// /// # RPC Reference /// - /// This method corresponds directly to the [`getBlocks`] RPC method, unless - /// the remote node version is less than 1.7, in which case it maps to the - /// [`getConfirmedBlocks`] RPC method. + /// This method corresponds directly to the [`getBlocks`] RPC method. /// /// [`getBlocks`]: https://solana.com/docs/rpc/http/getblocks - /// [`getConfirmedBlocks`]: https://solana.com/docs/rpc/deprecated/getconfirmedblocks /// /// # Examples /// @@ -2281,11 +2151,9 @@ impl RpcClient { /// # RPC Reference /// /// This method corresponds directly to the [`getBlocksWithLimit`] RPC - /// method, unless the remote node version is less than 1.7, in which case - /// it maps to the [`getConfirmedBlocksWithLimit`] RPC method. + /// method. /// /// [`getBlocksWithLimit`]: https://solana.com/docs/rpc/http/getblockswithlimit - /// [`getConfirmedBlocksWithLimit`]: https://solana.com/docs/rpc/deprecated/getconfirmedblockswithlimit /// /// # Examples /// @@ -2318,11 +2186,9 @@ impl RpcClient { /// # RPC Reference /// /// This method corresponds directly to the [`getBlocksWithLimit`] RPC - /// method, unless the remote node version is less than 1.7, in which case - /// it maps to the `getConfirmedBlocksWithLimit` RPC method. + /// method. /// /// [`getBlocksWithLimit`]: https://solana.com/docs/rpc/http/getblockswithlimit - /// [`getConfirmedBlocksWithLimit`]: https://solana.com/docs/rpc/deprecated/getconfirmedblockswithlimit /// /// # Examples /// @@ -2357,69 +2223,6 @@ impl RpcClient { ) } - #[deprecated(since = "1.7.0", note = "Please use RpcClient::get_blocks() instead")] - #[allow(deprecated)] - pub fn get_confirmed_blocks( - &self, - start_slot: Slot, - end_slot: Option, - ) -> ClientResult> { - self.invoke((self.rpc_client.as_ref()).get_confirmed_blocks(start_slot, end_slot)) - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_blocks_with_commitment() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_blocks_with_commitment( - &self, - start_slot: Slot, - end_slot: Option, - commitment_config: CommitmentConfig, - ) -> ClientResult> { - self.invoke( - (self.rpc_client.as_ref()).get_confirmed_blocks_with_commitment( - start_slot, - end_slot, - commitment_config, - ), - ) - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_blocks_with_limit() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_blocks_with_limit( - &self, - start_slot: Slot, - limit: usize, - ) -> ClientResult> { - self.invoke((self.rpc_client.as_ref()).get_confirmed_blocks_with_limit(start_slot, limit)) - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_blocks_with_limit_and_commitment() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_blocks_with_limit_and_commitment( - &self, - start_slot: Slot, - limit: usize, - commitment_config: CommitmentConfig, - ) -> ClientResult> { - self.invoke( - (self.rpc_client.as_ref()).get_confirmed_blocks_with_limit_and_commitment( - start_slot, - limit, - commitment_config, - ), - ) - } - /// Get confirmed signatures for transactions involving an address. /// /// Returns up to 1000 signatures, ordered from newest to oldest. @@ -2517,34 +2320,6 @@ impl RpcClient { ) } - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_signatures_for_address() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_signatures_for_address2( - &self, - address: &Pubkey, - ) -> ClientResult> { - self.invoke((self.rpc_client.as_ref()).get_confirmed_signatures_for_address2(address)) - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_signatures_for_address_with_config() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_signatures_for_address2_with_config( - &self, - address: &Pubkey, - config: GetConfirmedSignaturesForAddress2Config, - ) -> ClientResult> { - self.invoke( - (self.rpc_client.as_ref()) - .get_confirmed_signatures_for_address2_with_config(address, config), - ) - } - /// Returns transaction details for a confirmed transaction. /// /// This method uses the [`Finalized`] [commitment level][cl]. @@ -2554,12 +2329,9 @@ impl RpcClient { /// /// # RPC Reference /// - /// This method corresponds directly to the [`getTransaction`] RPC method, - /// unless the remote node version is less than 1.7, in which case it maps - /// to the [`getConfirmedTransaction`] RPC method. + /// This method corresponds directly to the [`getTransaction`] RPC method. /// /// [`getTransaction`]: https://solana.com/docs/rpc/http/gettransaction - /// [`getConfirmedTransaction`]: https://solana.com/docs/rpc/deprecated/getConfirmedTransaction /// /// # Examples /// @@ -2606,12 +2378,9 @@ impl RpcClient { /// /// # RPC Reference /// - /// This method corresponds directly to the [`getTransaction`] RPC method, - /// unless the remote node version is less than 1.7, in which case it maps - /// to the [`getConfirmedTransaction`] RPC method. + /// This method corresponds directly to the [`getTransaction`] RPC method. /// /// [`getTransaction`]: https://solana.com/docs/rpc/http/gettransaction - /// [`getConfirmedTransaction`]: https://solana.com/docs/rpc/deprecated/getConfirmedTransaction /// /// # Examples /// @@ -2655,34 +2424,6 @@ impl RpcClient { self.invoke((self.rpc_client.as_ref()).get_transaction_with_config(signature, config)) } - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_transaction() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_transaction( - &self, - signature: &Signature, - encoding: UiTransactionEncoding, - ) -> ClientResult { - self.invoke((self.rpc_client.as_ref()).get_confirmed_transaction(signature, encoding)) - } - - #[deprecated( - since = "1.7.0", - note = "Please use RpcClient::get_transaction_with_config() instead" - )] - #[allow(deprecated)] - pub fn get_confirmed_transaction_with_config( - &self, - signature: &Signature, - config: RpcConfirmedTransactionConfig, - ) -> ClientResult { - self.invoke( - (self.rpc_client.as_ref()).get_confirmed_transaction_with_config(signature, config), - ) - } - /// Returns the estimated production time of a block. /// /// # RPC Reference @@ -3696,87 +3437,6 @@ impl RpcClient { ) } - #[deprecated( - since = "1.9.0", - note = "Please use `get_latest_blockhash` and `get_fee_for_message` instead" - )] - #[allow(deprecated)] - pub fn get_fees(&self) -> ClientResult { - self.invoke((self.rpc_client.as_ref()).get_fees()) - } - - #[deprecated( - since = "1.9.0", - note = "Please use `get_latest_blockhash_with_commitment` and `get_fee_for_message` instead" - )] - #[allow(deprecated)] - pub fn get_fees_with_commitment(&self, commitment_config: CommitmentConfig) -> RpcResult { - self.invoke((self.rpc_client.as_ref()).get_fees_with_commitment(commitment_config)) - } - - #[deprecated(since = "1.9.0", note = "Please use `get_latest_blockhash` instead")] - #[allow(deprecated)] - pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> { - self.invoke((self.rpc_client.as_ref()).get_recent_blockhash()) - } - - #[deprecated( - since = "1.9.0", - note = "Please use `get_latest_blockhash_with_commitment` instead" - )] - #[allow(deprecated)] - pub fn get_recent_blockhash_with_commitment( - &self, - commitment_config: CommitmentConfig, - ) -> RpcResult<(Hash, FeeCalculator, Slot)> { - self.invoke( - (self.rpc_client.as_ref()).get_recent_blockhash_with_commitment(commitment_config), - ) - } - - #[deprecated(since = "1.9.0", note = "Please `get_fee_for_message` instead")] - #[allow(deprecated)] - pub fn get_fee_calculator_for_blockhash( - &self, - blockhash: &Hash, - ) -> ClientResult> { - self.invoke((self.rpc_client.as_ref()).get_fee_calculator_for_blockhash(blockhash)) - } - - #[deprecated( - since = "1.9.0", - note = "Please `get_latest_blockhash_with_commitment` and `get_fee_for_message` instead" - )] - #[allow(deprecated)] - pub fn get_fee_calculator_for_blockhash_with_commitment( - &self, - blockhash: &Hash, - commitment_config: CommitmentConfig, - ) -> RpcResult> { - self.invoke( - (self.rpc_client.as_ref()) - .get_fee_calculator_for_blockhash_with_commitment(blockhash, commitment_config), - ) - } - - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - #[allow(deprecated)] - pub fn get_fee_rate_governor(&self) -> RpcResult { - self.invoke((self.rpc_client.as_ref()).get_fee_rate_governor()) - } - - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - #[allow(deprecated)] - pub fn get_new_blockhash(&self, blockhash: &Hash) -> ClientResult<(Hash, FeeCalculator)> { - self.invoke((self.rpc_client.as_ref()).get_new_blockhash(blockhash)) - } - pub fn get_first_available_block(&self) -> ClientResult { self.invoke((self.rpc_client.as_ref()).get_first_available_block()) } @@ -3995,7 +3655,6 @@ impl RpcClient { self.invoke((self.rpc_client.as_ref()).get_latest_blockhash()) } - #[allow(deprecated)] pub fn get_latest_blockhash_with_commitment( &self, commitment: CommitmentConfig, @@ -4003,7 +3662,6 @@ impl RpcClient { self.invoke((self.rpc_client.as_ref()).get_latest_blockhash_with_commitment(commitment)) } - #[allow(deprecated)] pub fn is_blockhash_valid( &self, blockhash: &Hash, @@ -4012,7 +3670,6 @@ impl RpcClient { self.invoke((self.rpc_client.as_ref()).is_blockhash_valid(blockhash, commitment)) } - #[allow(deprecated)] pub fn get_fee_for_message(&self, message: &impl SerializableMessage) -> ClientResult { self.invoke((self.rpc_client.as_ref()).get_fee_for_message(message)) } @@ -4136,7 +3793,7 @@ mod tests { future::ok(Value::Number(Number::from(50))) }); // Failed request - io.add_method("getRecentBlockhash", |params: Params| { + io.add_method("getLatestBlockhash", |params: Params| { if params != Params::None { future::err(Error::invalid_request()) } else { @@ -4168,16 +3825,14 @@ mod tests { .unwrap(); assert_eq!(balance, 50); - #[allow(deprecated)] let blockhash: String = rpc_client - .send(RpcRequest::GetRecentBlockhash, Value::Null) + .send(RpcRequest::GetLatestBlockhash, Value::Null) .unwrap(); assert_eq!(blockhash, "deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"); // Send erroneous parameter - #[allow(deprecated)] let blockhash: ClientResult = - rpc_client.send(RpcRequest::GetRecentBlockhash, json!(["parameter"])); + rpc_client.send(RpcRequest::GetLatestBlockhash, json!(["parameter"])); assert!(blockhash.is_err()); } @@ -4204,22 +3859,6 @@ mod tests { assert!(signature.is_err()); } - #[test] - fn test_get_recent_blockhash() { - let rpc_client = RpcClient::new_mock("succeeds".to_string()); - - let expected_blockhash: Hash = PUBKEY.parse().unwrap(); - - let blockhash = rpc_client.get_latest_blockhash().expect("blockhash ok"); - assert_eq!(blockhash, expected_blockhash); - - let rpc_client = RpcClient::new_mock("fails".to_string()); - - #[allow(deprecated)] - let result = rpc_client.get_recent_blockhash(); - assert!(result.is_err()); - } - #[test] fn test_custom_request() { let rpc_client = RpcClient::new_mock("succeeds".to_string()); @@ -4319,7 +3958,6 @@ mod tests { let rpc_client = RpcClient::new_mock("fails".to_string()); - #[allow(deprecated)] let is_err = rpc_client.get_latest_blockhash().is_err(); assert!(is_err); } diff --git a/rpc-test/tests/rpc.rs b/rpc-test/tests/rpc.rs index 19bf50d2e4341b..463e2046867346 100644 --- a/rpc-test/tests/rpc.rs +++ b/rpc-test/tests/rpc.rs @@ -77,7 +77,7 @@ fn test_rpc_send_tx() { let bob_pubkey = solana_sdk::pubkey::new_rand(); - let req = json_req!("getRecentBlockhash", json!([])); + let req = json_req!("getLatestBlockhash", json!([])); let json = post_rpc(req, &rpc_url); let blockhash: Hash = json["result"]["value"]["blockhash"] diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 7ac36cef70b103..d62a61ec81fe00 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -39,13 +39,12 @@ use { solana_rpc_client_api::{ config::*, custom_error::RpcCustomError, - deprecated_config::*, - filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, + filter::{Memcmp, RpcFilterType}, request::{ TokenAccountsFilter, DELINQUENT_VALIDATOR_SLOT_DISTANCE, MAX_GET_CONFIRMED_BLOCKS_RANGE, MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT, - MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE, MAX_GET_PROGRAM_ACCOUNT_FILTERS, - MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS, MAX_GET_SLOT_LEADERS, MAX_MULTIPLE_ACCOUNTS, + MAX_GET_PROGRAM_ACCOUNT_FILTERS, MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS, + MAX_GET_SLOT_LEADERS, MAX_MULTIPLE_ACCOUNTS, MAX_RPC_VOTE_ACCOUNT_INFO_EPOCH_CREDITS_HISTORY, NUM_LARGEST_ACCOUNTS, }, response::{Response as RpcResponse, *}, @@ -62,22 +61,18 @@ use { }, solana_sdk::{ account::{AccountSharedData, ReadableAccount}, - account_utils::StateMut, clock::{Slot, UnixTimestamp, MAX_PROCESSING_AGE}, commitment_config::{CommitmentConfig, CommitmentLevel}, epoch_info::EpochInfo, + epoch_rewards_hasher::EpochRewardsHasher, epoch_schedule::EpochSchedule, exit::Exit, feature_set, - fee_calculator::FeeCalculator, hash::Hash, message::SanitizedMessage, pubkey::{Pubkey, PUBKEY_BYTES}, signature::{Keypair, Signature, Signer}, - stake::state::{StakeActivationStatus, StakeStateV2}, - stake_history::StakeHistory, system_instruction, - sysvar::stake_history, transaction::{ self, AddressLoader, MessageHash, SanitizedTransaction, TransactionError, VersionedTransaction, MAX_TX_ACCOUNT_LOCKS, @@ -93,8 +88,9 @@ use { solana_transaction_status::{ map_inner_instructions, BlockEncodingOptions, ConfirmedBlock, ConfirmedTransactionStatusWithSignature, ConfirmedTransactionWithStatusMeta, - EncodedConfirmedTransactionWithStatusMeta, Reward, RewardType, TransactionBinaryEncoding, - TransactionConfirmationStatus, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding, + EncodedConfirmedTransactionWithStatusMeta, Reward, RewardType, Rewards, + TransactionBinaryEncoding, TransactionConfirmationStatus, TransactionStatus, + UiConfirmedBlock, UiTransactionEncoding, }, solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}, spl_token_2022::{ @@ -156,7 +152,6 @@ pub struct JsonRpcConfig { pub rpc_threads: usize, pub rpc_niceness_adj: i8, pub full_api: bool, - pub obsolete_v1_7_api: bool, pub rpc_scan_and_fix_roots: bool, pub max_request_body_size: Option, /// Disable the health check, used for tests and TestValidator @@ -270,23 +265,13 @@ impl JsonRpcRequestProcessor { .slot_with_commitment(commitment.commitment); match commitment.commitment { - // Recent variant is deprecated - CommitmentLevel::Recent | CommitmentLevel::Processed => { + CommitmentLevel::Processed => { debug!("RPC using the heaviest slot: {:?}", slot); } - // Root variant is deprecated - CommitmentLevel::Root => { - debug!("RPC using node root: {:?}", slot); - } - // Single variant is deprecated - CommitmentLevel::Single => { - debug!("RPC using confirmed slot: {:?}", slot); - } - // Max variant is deprecated - CommitmentLevel::Max | CommitmentLevel::Finalized => { + CommitmentLevel::Finalized => { debug!("RPC using block: {:?}", slot); } - CommitmentLevel::SingleGossip | CommitmentLevel::Confirmed => unreachable!(), // SingleGossip variant is deprecated + CommitmentLevel::Confirmed => unreachable!(), // SingleGossip variant is deprecated }; let r_bank_forks = self.bank_forks.read().unwrap(); @@ -548,6 +533,34 @@ impl JsonRpcRequestProcessor { }) } + fn filter_map_rewards<'a, F>( + rewards: &'a Option, + slot: Slot, + addresses: &'a [String], + reward_type_filter: &'a F, + ) -> HashMap + where + F: Fn(RewardType) -> bool, + { + Self::filter_rewards(rewards, reward_type_filter) + .filter(|reward| addresses.contains(&reward.pubkey)) + .map(|reward| (reward.pubkey.clone(), (reward.clone(), slot))) + .collect() + } + + fn filter_rewards<'a, F>( + rewards: &'a Option, + reward_type_filter: &'a F, + ) -> impl Iterator + where + F: Fn(RewardType) -> bool, + { + rewards + .iter() + .flatten() + .filter(move |reward| reward.reward_type.is_some_and(reward_type_filter)) + } + pub async fn get_inflation_reward( &self, addresses: Vec, @@ -592,7 +605,22 @@ impl JsonRpcRequestProcessor { slot: first_slot_in_epoch, })?; - let Ok(Some(first_confirmed_block)) = self + // Determine if partitioned epoch rewards were enabled for the desired + // epoch + let bank = self.get_bank_with_config(context_config)?; + + // DO NOT CLEAN UP with feature_set::enable_partitioned_epoch_reward + // This logic needs to be retained indefinitely to support historical + // rewards before and after feature activation. + let partitioned_epoch_reward_enabled_slot = bank + .feature_set + .activated_slot(&feature_set::enable_partitioned_epoch_reward::id()); + let partitioned_epoch_reward_enabled = partitioned_epoch_reward_enabled_slot + .map(|slot| slot <= first_confirmed_block_in_epoch) + .unwrap_or(false); + + // Get first block in the epoch + let Ok(Some(epoch_boundary_block)) = self .get_block( first_confirmed_block_in_epoch, Some(RpcBlockConfig::rewards_with_commitment(config.commitment).into()), @@ -605,30 +633,109 @@ impl JsonRpcRequestProcessor { .into()); }; - let addresses: Vec = addresses - .into_iter() - .map(|pubkey| pubkey.to_string()) - .collect(); + // Collect rewards from first block in the epoch if partitioned epoch + // rewards not enabled, or address is a vote account + let mut reward_map: HashMap = { + let addresses: Vec = + addresses.iter().map(|pubkey| pubkey.to_string()).collect(); + Self::filter_map_rewards( + &epoch_boundary_block.rewards, + first_confirmed_block_in_epoch, + &addresses, + &|reward_type| -> bool { + reward_type == RewardType::Voting + || (!partitioned_epoch_reward_enabled && reward_type == RewardType::Staking) + }, + ) + }; - let reward_hash: HashMap = first_confirmed_block - .rewards - .unwrap_or_default() - .into_iter() - .filter_map(|reward| match reward.reward_type? { - RewardType::Staking | RewardType::Voting => addresses - .contains(&reward.pubkey) - .then(|| (reward.clone().pubkey, reward)), - _ => None, - }) - .collect(); + // Append stake account rewards from partitions if partitions epoch + // rewards is enabled + if partitioned_epoch_reward_enabled { + let num_partitions = epoch_boundary_block.num_reward_partitions.expect( + "epoch-boundary block should have num_reward_partitions after partitioned epoch \ + rewards enabled", + ); + + let num_partitions = usize::try_from(num_partitions) + .expect("num_partitions should never exceed usize::MAX"); + let hasher = EpochRewardsHasher::new( + num_partitions, + &Hash::from_str(&epoch_boundary_block.previous_blockhash) + .expect("UiConfirmedBlock::previous_blockhash should be properly formed"), + ); + let mut partition_index_addresses: HashMap> = HashMap::new(); + for address in addresses.iter() { + let address_string = address.to_string(); + // Skip this address if (Voting) rewards were already found in + // the first block of the epoch + if !reward_map.contains_key(&address_string) { + let partition_index = hasher.clone().hash_address_to_partition(address); + partition_index_addresses + .entry(partition_index) + .and_modify(|list| list.push(address_string.clone())) + .or_insert(vec![address_string]); + } + } + + let block_list = self + .get_blocks_with_limit( + first_confirmed_block_in_epoch + 1, + num_partitions, + Some(context_config), + ) + .await?; + + for (partition_index, addresses) in partition_index_addresses.iter() { + let slot = *block_list.get(*partition_index).ok_or_else(|| { + // If block_list.len() too short to contain + // partition_index, the epoch rewards period must be + // currently active. + let rewards_complete_block_height = epoch_boundary_block + .block_height + .map(|block_height| { + block_height + .saturating_add(num_partitions as u64) + .saturating_add(1) + }) + .expect( + "every block after partitioned_epoch_reward_enabled should have a \ + populated block_height", + ); + RpcCustomError::EpochRewardsPeriodActive { + slot: bank.slot(), + current_block_height: bank.block_height(), + rewards_complete_block_height, + } + })?; + + let Ok(Some(block)) = self + .get_block( + slot, + Some(RpcBlockConfig::rewards_with_commitment(config.commitment).into()), + ) + .await + else { + return Err(RpcCustomError::BlockNotAvailable { slot }.into()); + }; + + let index_reward_map = Self::filter_map_rewards( + &block.rewards, + slot, + addresses, + &|reward_type| -> bool { reward_type == RewardType::Staking }, + ); + reward_map.extend(index_reward_map); + } + } let rewards = addresses .iter() .map(|address| { - if let Some(reward) = reward_hash.get(address) { + if let Some((reward, slot)) = reward_map.get(&address.to_string()) { return Some(RpcInflationReward { epoch, - effective_slot: first_confirmed_block_in_epoch, + effective_slot: *slot, amount: reward.lamports.unsigned_abs(), post_balance: reward.post_balance, commission: reward.commission, @@ -678,75 +785,6 @@ impl JsonRpcRequestProcessor { Ok(new_response(&bank, bank.get_balance(pubkey))) } - fn get_recent_blockhash( - &self, - commitment: Option, - ) -> Result> { - let bank = self.bank(commitment); - let blockhash = bank.confirmed_last_blockhash(); - let lamports_per_signature = bank - .get_lamports_per_signature_for_blockhash(&blockhash) - .unwrap(); - Ok(new_response( - &bank, - RpcBlockhashFeeCalculator { - blockhash: blockhash.to_string(), - fee_calculator: FeeCalculator::new(lamports_per_signature), - }, - )) - } - - fn get_fees(&self, commitment: Option) -> Result> { - let bank = self.bank(commitment); - let blockhash = bank.confirmed_last_blockhash(); - let lamports_per_signature = bank - .get_lamports_per_signature_for_blockhash(&blockhash) - .unwrap(); - #[allow(deprecated)] - let last_valid_slot = bank - .get_blockhash_last_valid_slot(&blockhash) - .expect("bank blockhash queue should contain blockhash"); - let last_valid_block_height = bank - .get_blockhash_last_valid_block_height(&blockhash) - .expect("bank blockhash queue should contain blockhash"); - Ok(new_response( - &bank, - RpcFees { - blockhash: blockhash.to_string(), - fee_calculator: FeeCalculator::new(lamports_per_signature), - last_valid_slot, - last_valid_block_height, - }, - )) - } - - fn get_fee_calculator_for_blockhash( - &self, - blockhash: &Hash, - commitment: Option, - ) -> Result>> { - let bank = self.bank(commitment); - let lamports_per_signature = bank.get_lamports_per_signature_for_blockhash(blockhash); - Ok(new_response( - &bank, - lamports_per_signature.map(|lamports_per_signature| RpcFeeCalculator { - fee_calculator: FeeCalculator::new(lamports_per_signature), - }), - )) - } - - fn get_fee_rate_governor(&self) -> RpcResponse { - let bank = self.bank(None); - #[allow(deprecated)] - let fee_rate_governor = bank.get_fee_rate_governor(); - new_response( - &bank, - RpcFeeRateGovernor { - fee_rate_governor: fee_rate_governor.clone(), - }, - ) - } - pub fn confirm_transaction( &self, signature: &Signature, @@ -847,11 +885,6 @@ impl JsonRpcRequestProcessor { Ok(bank.transaction_count()) } - fn get_total_supply(&self, commitment: Option) -> Result { - let bank = self.bank(commitment); - Ok(bank.capitalization()) - } - fn get_cached_largest_accounts( &self, filter: &Option, @@ -876,6 +909,7 @@ impl JsonRpcRequestProcessor { ) -> RpcCustomResult>> { let config = config.unwrap_or_default(); let bank = self.bank(config.commitment); + let sort_results = config.sort_results.unwrap_or(true); if let Some((slot, accounts)) = self.get_cached_largest_accounts(&config.filter) { Ok(RpcResponse { @@ -900,7 +934,12 @@ impl JsonRpcRequestProcessor { (HashSet::new(), AccountAddressFilter::Exclude) }; let accounts = bank - .get_largest_accounts(NUM_LARGEST_ACCOUNTS, &addresses, address_filter) + .get_largest_accounts( + NUM_LARGEST_ACCOUNTS, + &addresses, + address_filter, + sort_results, + ) .map_err(|e| RpcCustomError::ScanError { message: e.to_string(), })? @@ -1770,87 +1809,6 @@ impl JsonRpcRequestProcessor { slot } - pub fn get_stake_activation( - &self, - pubkey: &Pubkey, - config: Option, - ) -> Result { - let config = config.unwrap_or_default(); - let bank = self.get_bank_with_config(RpcContextConfig { - commitment: config.commitment, - min_context_slot: config.min_context_slot, - })?; - let epoch = config.epoch.unwrap_or_else(|| bank.epoch()); - if epoch != bank.epoch() { - return Err(Error::invalid_params(format!( - "Invalid param: epoch {epoch:?}. Only the current epoch ({:?}) is supported", - bank.epoch() - ))); - } - - let stake_account = bank - .get_account(pubkey) - .ok_or_else(|| Error::invalid_params("Invalid param: account not found".to_string()))?; - let stake_state: StakeStateV2 = stake_account - .state() - .map_err(|_| Error::invalid_params("Invalid param: not a stake account".to_string()))?; - let delegation = stake_state.delegation(); - - let rent_exempt_reserve = stake_state - .meta() - .ok_or_else(|| { - Error::invalid_params("Invalid param: stake account not initialized".to_string()) - })? - .rent_exempt_reserve; - - let delegation = match delegation { - None => { - return Ok(RpcStakeActivation { - state: StakeActivationState::Inactive, - active: 0, - inactive: stake_account.lamports().saturating_sub(rent_exempt_reserve), - }) - } - Some(delegation) => delegation, - }; - - let stake_history_account = bank - .get_account(&stake_history::id()) - .ok_or_else(Error::internal_error)?; - let stake_history = - solana_sdk::account::from_account::(&stake_history_account) - .ok_or_else(Error::internal_error)?; - let new_rate_activation_epoch = bank.new_warmup_cooldown_rate_epoch(); - - let StakeActivationStatus { - effective, - activating, - deactivating, - } = delegation.stake_activating_and_deactivating( - epoch, - &stake_history, - new_rate_activation_epoch, - ); - let stake_activation_state = if deactivating > 0 { - StakeActivationState::Deactivating - } else if activating > 0 { - StakeActivationState::Activating - } else if effective > 0 { - StakeActivationState::Active - } else { - StakeActivationState::Inactive - }; - let inactive_stake = stake_account - .lamports() - .saturating_sub(effective) - .saturating_sub(rent_exempt_reserve); - Ok(RpcStakeActivation { - state: stake_activation_state, - active: effective, - inactive: inactive_stake, - }) - } - pub fn get_token_account_balance( &self, pubkey: &Pubkey, @@ -2421,7 +2379,7 @@ fn encode_account( /// Analyze custom filters to determine if the result will be a subset of spl-token accounts by /// owner. /// NOTE: `optimize_filters()` should almost always be called before using this method because of -/// the strict match on `MemcmpEncodedBytes::Bytes`. +/// the requirement that `Memcmp::raw_bytes_as_ref().is_some()`. fn get_spl_token_owner_filter(program_id: &Pubkey, filters: &[RpcFilterType]) -> Option { if !is_known_spl_token_id(program_id) { return None; @@ -2435,28 +2393,21 @@ fn get_spl_token_owner_filter(program_id: &Pubkey, filters: &[RpcFilterType]) -> for filter in filters { match filter { RpcFilterType::DataSize(size) => data_size_filter = Some(*size), - #[allow(deprecated)] - RpcFilterType::Memcmp(Memcmp { - offset, - bytes: MemcmpEncodedBytes::Bytes(bytes), - .. - }) if *offset == account_packed_len && *program_id == token_2022::id() => { - memcmp_filter = Some(bytes) - } - #[allow(deprecated)] - RpcFilterType::Memcmp(Memcmp { - offset, - bytes: MemcmpEncodedBytes::Bytes(bytes), - .. - }) if *offset == SPL_TOKEN_ACCOUNT_OWNER_OFFSET => { - if bytes.len() == PUBKEY_BYTES { - owner_key = Pubkey::try_from(&bytes[..]).ok(); - } else { - incorrect_owner_len = Some(bytes.len()); + RpcFilterType::Memcmp(memcmp) => { + let offset = memcmp.offset(); + if let Some(bytes) = memcmp.raw_bytes_as_ref() { + if offset == account_packed_len && *program_id == token_2022::id() { + memcmp_filter = Some(bytes); + } else if offset == SPL_TOKEN_ACCOUNT_OWNER_OFFSET { + if bytes.len() == PUBKEY_BYTES { + owner_key = Pubkey::try_from(bytes).ok(); + } else { + incorrect_owner_len = Some(bytes.len()); + } + } } } RpcFilterType::TokenAccountState => token_account_state_filter = true, - _ => {} } } if data_size_filter == Some(account_packed_len as u64) @@ -2479,7 +2430,7 @@ fn get_spl_token_owner_filter(program_id: &Pubkey, filters: &[RpcFilterType]) -> /// Analyze custom filters to determine if the result will be a subset of spl-token accounts by /// mint. /// NOTE: `optimize_filters()` should almost always be called before using this method because of -/// the strict match on `MemcmpEncodedBytes::Bytes`. +/// the requirement that `Memcmp::raw_bytes_as_ref().is_some()`. fn get_spl_token_mint_filter(program_id: &Pubkey, filters: &[RpcFilterType]) -> Option { if !is_known_spl_token_id(program_id) { return None; @@ -2493,28 +2444,21 @@ fn get_spl_token_mint_filter(program_id: &Pubkey, filters: &[RpcFilterType]) -> for filter in filters { match filter { RpcFilterType::DataSize(size) => data_size_filter = Some(*size), - #[allow(deprecated)] - RpcFilterType::Memcmp(Memcmp { - offset, - bytes: MemcmpEncodedBytes::Bytes(bytes), - .. - }) if *offset == account_packed_len && *program_id == token_2022::id() => { - memcmp_filter = Some(bytes) - } - #[allow(deprecated)] - RpcFilterType::Memcmp(Memcmp { - offset, - bytes: MemcmpEncodedBytes::Bytes(bytes), - .. - }) if *offset == SPL_TOKEN_ACCOUNT_MINT_OFFSET => { - if bytes.len() == PUBKEY_BYTES { - mint = Pubkey::try_from(&bytes[..]).ok(); - } else { - incorrect_mint_len = Some(bytes.len()); + RpcFilterType::Memcmp(memcmp) => { + let offset = memcmp.offset(); + if let Some(bytes) = memcmp.raw_bytes_as_ref() { + if offset == account_packed_len && *program_id == token_2022::id() { + memcmp_filter = Some(bytes); + } else if offset == SPL_TOKEN_ACCOUNT_MINT_OFFSET { + if bytes.len() == PUBKEY_BYTES { + mint = Pubkey::try_from(bytes).ok(); + } else { + incorrect_mint_len = Some(bytes.len()); + } + } } } RpcFilterType::TokenAccountState => token_account_state_filter = true, - _ => {} } } if data_size_filter == Some(account_packed_len as u64) @@ -3552,6 +3496,10 @@ pub mod rpc_full { Some(RpcContactInfo { pubkey: contact_info.pubkey().to_string(), gossip: contact_info.gossip().ok(), + tvu: contact_info + .tvu(Protocol::UDP) + .ok() + .filter(|addr| socket_addr_space.check(addr)), tpu: contact_info .tpu(Protocol::UDP) .ok() @@ -3560,6 +3508,22 @@ pub mod rpc_full { .tpu(Protocol::QUIC) .ok() .filter(|addr| socket_addr_space.check(addr)), + tpu_forwards: contact_info + .tpu_forwards(Protocol::UDP) + .ok() + .filter(|addr| socket_addr_space.check(addr)), + tpu_forwards_quic: contact_info + .tpu_forwards(Protocol::QUIC) + .ok() + .filter(|addr| socket_addr_space.check(addr)), + tpu_vote: contact_info + .tpu_vote() + .ok() + .filter(|addr| socket_addr_space.check(addr)), + serve_repair: contact_info + .serve_repair(Protocol::UDP) + .ok() + .filter(|addr| socket_addr_space.check(addr)), rpc: contact_info .rpc() .ok() @@ -4167,452 +4131,6 @@ fn rpc_perf_sample_from_perf_sample(slot: u64, sample: PerfSample) -> RpcPerfSam } } -pub mod rpc_deprecated_v1_18 { - use super::*; - #[rpc] - pub trait DeprecatedV1_18 { - type Metadata; - - // DEPRECATED - #[rpc(meta, name = "getStakeActivation")] - fn get_stake_activation( - &self, - meta: Self::Metadata, - pubkey_str: String, - config: Option, - ) -> Result; - } - - pub struct DeprecatedV1_18Impl; - impl DeprecatedV1_18 for DeprecatedV1_18Impl { - type Metadata = JsonRpcRequestProcessor; - - fn get_stake_activation( - &self, - meta: Self::Metadata, - pubkey_str: String, - config: Option, - ) -> Result { - debug!( - "get_stake_activation rpc request received: {:?}", - pubkey_str - ); - let pubkey = verify_pubkey(&pubkey_str)?; - meta.get_stake_activation(&pubkey, config) - } - } -} - -// RPC methods deprecated in v1.9 -pub mod rpc_deprecated_v1_9 { - #![allow(deprecated)] - use super::*; - #[rpc] - pub trait DeprecatedV1_9 { - type Metadata; - - #[rpc(meta, name = "getRecentBlockhash")] - fn get_recent_blockhash( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getFees")] - fn get_fees( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getFeeCalculatorForBlockhash")] - fn get_fee_calculator_for_blockhash( - &self, - meta: Self::Metadata, - blockhash: String, - commitment: Option, - ) -> Result>>; - - #[rpc(meta, name = "getFeeRateGovernor")] - fn get_fee_rate_governor( - &self, - meta: Self::Metadata, - ) -> Result>; - - #[rpc(meta, name = "getSnapshotSlot")] - fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result; - } - - pub struct DeprecatedV1_9Impl; - impl DeprecatedV1_9 for DeprecatedV1_9Impl { - type Metadata = JsonRpcRequestProcessor; - - fn get_recent_blockhash( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result> { - debug!("get_recent_blockhash rpc request received"); - meta.get_recent_blockhash(commitment) - } - - fn get_fees( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result> { - debug!("get_fees rpc request received"); - meta.get_fees(commitment) - } - - fn get_fee_calculator_for_blockhash( - &self, - meta: Self::Metadata, - blockhash: String, - commitment: Option, - ) -> Result>> { - debug!("get_fee_calculator_for_blockhash rpc request received"); - let blockhash = - Hash::from_str(&blockhash).map_err(|e| Error::invalid_params(format!("{e:?}")))?; - meta.get_fee_calculator_for_blockhash(&blockhash, commitment) - } - - fn get_fee_rate_governor( - &self, - meta: Self::Metadata, - ) -> Result> { - debug!("get_fee_rate_governor rpc request received"); - Ok(meta.get_fee_rate_governor()) - } - - fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result { - debug!("get_snapshot_slot rpc request received"); - - meta.snapshot_config - .and_then(|snapshot_config| { - snapshot_utils::get_highest_full_snapshot_archive_slot( - snapshot_config.full_snapshot_archives_dir, - ) - }) - .ok_or_else(|| RpcCustomError::NoSnapshot.into()) - } - } -} - -// RPC methods deprecated in v1.7 -pub mod rpc_deprecated_v1_7 { - #![allow(deprecated)] - use super::*; - #[rpc] - pub trait DeprecatedV1_7 { - type Metadata; - - // DEPRECATED - #[rpc(meta, name = "getConfirmedBlock")] - fn get_confirmed_block( - &self, - meta: Self::Metadata, - slot: Slot, - config: Option>, - ) -> BoxFuture>>; - - // DEPRECATED - #[rpc(meta, name = "getConfirmedBlocks")] - fn get_confirmed_blocks( - &self, - meta: Self::Metadata, - start_slot: Slot, - config: Option, - commitment: Option, - ) -> BoxFuture>>; - - // DEPRECATED - #[rpc(meta, name = "getConfirmedBlocksWithLimit")] - fn get_confirmed_blocks_with_limit( - &self, - meta: Self::Metadata, - start_slot: Slot, - limit: usize, - commitment: Option, - ) -> BoxFuture>>; - - // DEPRECATED - #[rpc(meta, name = "getConfirmedTransaction")] - fn get_confirmed_transaction( - &self, - meta: Self::Metadata, - signature_str: String, - config: Option>, - ) -> BoxFuture>>; - - // DEPRECATED - #[rpc(meta, name = "getConfirmedSignaturesForAddress2")] - fn get_confirmed_signatures_for_address2( - &self, - meta: Self::Metadata, - address: String, - config: Option, - ) -> BoxFuture>>; - } - - pub struct DeprecatedV1_7Impl; - impl DeprecatedV1_7 for DeprecatedV1_7Impl { - type Metadata = JsonRpcRequestProcessor; - - fn get_confirmed_block( - &self, - meta: Self::Metadata, - slot: Slot, - config: Option>, - ) -> BoxFuture>> { - debug!("get_confirmed_block rpc request received: {:?}", slot); - Box::pin(async move { - meta.get_block(slot, config.map(|config| config.convert())) - .await - }) - } - - fn get_confirmed_blocks( - &self, - meta: Self::Metadata, - start_slot: Slot, - config: Option, - commitment: Option, - ) -> BoxFuture>> { - let (end_slot, maybe_commitment) = - config.map(|config| config.unzip()).unwrap_or_default(); - debug!( - "get_confirmed_blocks rpc request received: {}-{:?}", - start_slot, end_slot - ); - Box::pin(async move { - meta.get_blocks( - start_slot, - end_slot, - Some(RpcContextConfig { - commitment: commitment.or(maybe_commitment), - min_context_slot: None, - }), - ) - .await - }) - } - - fn get_confirmed_blocks_with_limit( - &self, - meta: Self::Metadata, - start_slot: Slot, - limit: usize, - commitment: Option, - ) -> BoxFuture>> { - debug!( - "get_confirmed_blocks_with_limit rpc request received: {}-{}", - start_slot, limit, - ); - Box::pin(async move { - meta.get_blocks_with_limit( - start_slot, - limit, - Some(RpcContextConfig { - commitment, - min_context_slot: None, - }), - ) - .await - }) - } - - fn get_confirmed_transaction( - &self, - meta: Self::Metadata, - signature_str: String, - config: Option>, - ) -> BoxFuture>> { - debug!( - "get_confirmed_transaction rpc request received: {:?}", - signature_str - ); - let signature = verify_signature(&signature_str); - if let Err(err) = signature { - return Box::pin(future::err(err)); - } - Box::pin(async move { - meta.get_transaction(signature.unwrap(), config.map(|config| config.convert())) - .await - }) - } - - fn get_confirmed_signatures_for_address2( - &self, - meta: Self::Metadata, - address: String, - config: Option, - ) -> BoxFuture>> { - let config = config.unwrap_or_default(); - let commitment = config.commitment; - let verification = verify_and_parse_signatures_for_address_params( - address, - config.before, - config.until, - config.limit, - ); - - match verification { - Err(err) => Box::pin(future::err(err)), - Ok((address, before, until, limit)) => Box::pin(async move { - meta.get_signatures_for_address( - address, - before, - until, - limit, - RpcContextConfig { - commitment, - min_context_slot: None, - }, - ) - .await - }), - } - } - } -} - -// Obsolete RPC methods, collected for easy deactivation and removal -pub mod rpc_obsolete_v1_7 { - use super::*; - #[rpc] - pub trait ObsoleteV1_7 { - type Metadata; - - // DEPRECATED - #[rpc(meta, name = "confirmTransaction")] - fn confirm_transaction( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result>; - - // DEPRECATED - #[rpc(meta, name = "getSignatureStatus")] - fn get_signature_status( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result>>; - - // DEPRECATED (used by Trust Wallet) - #[rpc(meta, name = "getSignatureConfirmation")] - fn get_signature_confirmation( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result>; - - // DEPRECATED - #[rpc(meta, name = "getTotalSupply")] - fn get_total_supply( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result; - - // DEPRECATED - #[rpc(meta, name = "getConfirmedSignaturesForAddress")] - fn get_confirmed_signatures_for_address( - &self, - meta: Self::Metadata, - pubkey_str: String, - start_slot: Slot, - end_slot: Slot, - ) -> Result>; - } - - pub struct ObsoleteV1_7Impl; - impl ObsoleteV1_7 for ObsoleteV1_7Impl { - type Metadata = JsonRpcRequestProcessor; - - fn confirm_transaction( - &self, - meta: Self::Metadata, - id: String, - commitment: Option, - ) -> Result> { - debug!("confirm_transaction rpc request received: {:?}", id); - let signature = verify_signature(&id)?; - meta.confirm_transaction(&signature, commitment) - } - - fn get_signature_status( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result>> { - debug!( - "get_signature_status rpc request received: {:?}", - signature_str - ); - let signature = verify_signature(&signature_str)?; - meta.get_signature_status(signature, commitment) - } - - fn get_signature_confirmation( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result> { - debug!( - "get_signature_confirmation rpc request received: {:?}", - signature_str - ); - let signature = verify_signature(&signature_str)?; - meta.get_signature_confirmation_status(signature, commitment) - } - - fn get_total_supply( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result { - debug!("get_total_supply rpc request received"); - meta.get_total_supply(commitment) - } - - fn get_confirmed_signatures_for_address( - &self, - meta: Self::Metadata, - pubkey_str: String, - start_slot: Slot, - end_slot: Slot, - ) -> Result> { - debug!( - "get_confirmed_signatures_for_address rpc request received: {:?} {:?}-{:?}", - pubkey_str, start_slot, end_slot - ); - let pubkey = verify_pubkey(&pubkey_str)?; - if end_slot < start_slot { - return Err(Error::invalid_params(format!( - "start_slot {start_slot} must be less than or equal to end_slot {end_slot}" - ))); - } - if end_slot - start_slot > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE { - return Err(Error::invalid_params(format!( - "Slot range too large; max {MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE}" - ))); - } - Ok(meta - .get_confirmed_signatures_for_address(pubkey, start_slot, end_slot) - .iter() - .map(|signature| signature.to_string()) - .collect()) - } - } -} - const MAX_BASE58_SIZE: usize = 1683; // Golden, bump if PACKET_DATA_SIZE changes const MAX_BASE64_SIZE: usize = 1644; // Golden, bump if PACKET_DATA_SIZE changes fn decode_and_deserialize( @@ -4787,8 +4305,7 @@ pub fn populate_blockstore_for_tests( pub mod tests { use { super::{ - rpc_accounts::*, rpc_accounts_scan::*, rpc_bank::*, rpc_deprecated_v1_9::*, - rpc_full::*, rpc_minimal::*, *, + rpc_accounts::*, rpc_accounts_scan::*, rpc_bank::*, rpc_full::*, rpc_minimal::*, *, }, crate::{ optimistically_confirmed_bank_tracker::{ @@ -4813,7 +4330,7 @@ pub mod tests { JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE, JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION, }, - filter::{Memcmp, MemcmpEncodedBytes}, + filter::MemcmpEncodedBytes, }, solana_runtime::{ accounts_background_service::AbsRequestSender, bank::BankTestConfig, @@ -4825,9 +4342,8 @@ pub mod tests { self, state::{AddressLookupTable, LookupTableMeta}, }, - clock::MAX_PROCESSING_AGE, compute_budget::ComputeBudgetInstruction, - fee_calculator::{FeeRateGovernor, DEFAULT_BURN_PERCENT}, + fee_calculator::FeeRateGovernor, hash::{hash, Hash}, instruction::InstructionError, message::{ @@ -4989,7 +4505,6 @@ pub mod tests { io.extend_with(rpc_accounts::AccountsDataImpl.to_delegate()); io.extend_with(rpc_accounts_scan::AccountsScanImpl.to_delegate()); io.extend_with(rpc_full::FullImpl.to_delegate()); - io.extend_with(rpc_deprecated_v1_9::DeprecatedV1_9Impl.to_delegate()); Self { io, meta, @@ -5321,8 +4836,13 @@ pub mod tests { "pubkey": rpc.identity.to_string(), "gossip": "127.0.0.1:8000", "shredVersion": 0u16, + "tvu": "127.0.0.1:8001", "tpu": "127.0.0.1:8003", "tpuQuic": "127.0.0.1:8009", + "tpuForwards": "127.0.0.1:8004", + "tpuForwardsQuic": "127.0.0.1:8010", + "tpuVote": "127.0.0.1:8005", + "serveRepair": "127.0.0.1:8008", "rpc": format!("127.0.0.1:{}", rpc_port::DEFAULT_RPC_PORT), "pubsub": format!("127.0.0.1:{}", rpc_port::DEFAULT_RPC_PUBSUB_PORT), "version": format!("{version}"), @@ -5331,8 +4851,13 @@ pub mod tests { "pubkey": rpc.leader_pubkey().to_string(), "gossip": "127.0.0.1:1235", "shredVersion": 0u16, + "tvu": "127.0.0.1:1236", "tpu": "127.0.0.1:1234", "tpuQuic": "127.0.0.1:1240", + "tpuForwards": "127.0.0.1:1239", + "tpuForwardsQuic": "127.0.0.1:1245", + "tpuVote": "127.0.0.1:1241", + "serveRepair": "127.0.0.1:1242", "rpc": format!("127.0.0.1:{}", rpc_port::DEFAULT_RPC_PORT), "pubsub": format!("127.0.0.1:{}", rpc_port::DEFAULT_RPC_PUBSUB_PORT), "version": format!("{version}"), @@ -6847,146 +6372,6 @@ pub mod tests { ); } - #[test] - fn test_rpc_get_recent_blockhash() { - let rpc = RpcHandler::start(); - let bank = rpc.working_bank(); - let recent_blockhash = bank.confirmed_last_blockhash(); - let RpcHandler { meta, io, .. } = rpc; - - let req = r#"{"jsonrpc":"2.0","id":1,"method":"getRecentBlockhash"}"#; - let res = io.handle_request_sync(req, meta); - let expected = json!({ - "jsonrpc": "2.0", - "result": { - "context": {"slot": 0, "apiVersion": RpcApiVersion::default()}, - "value":{ - "blockhash": recent_blockhash.to_string(), - "feeCalculator": { - "lamportsPerSignature": TEST_SIGNATURE_FEE, - } - }, - }, - "id": 1 - }); - let expected: Response = - serde_json::from_value(expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(result, expected); - } - - #[test] - fn test_rpc_get_fees() { - let rpc = RpcHandler::start(); - let bank = rpc.working_bank(); - let recent_blockhash = bank.confirmed_last_blockhash(); - let RpcHandler { meta, io, .. } = rpc; - - let req = r#"{"jsonrpc":"2.0","id":1,"method":"getFees"}"#; - let res = io.handle_request_sync(req, meta); - let expected = json!({ - "jsonrpc": "2.0", - "result": { - "context": {"slot": 0, "apiVersion": RpcApiVersion::default()}, - "value": { - "blockhash": recent_blockhash.to_string(), - "feeCalculator": { - "lamportsPerSignature": TEST_SIGNATURE_FEE, - }, - "lastValidSlot": MAX_PROCESSING_AGE, - "lastValidBlockHeight": MAX_PROCESSING_AGE, - }, - }, - "id": 1 - }); - let expected: Response = - serde_json::from_value(expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(result, expected); - } - - #[test] - fn test_rpc_get_fee_calculator_for_blockhash() { - let rpc = RpcHandler::start(); - let bank = rpc.working_bank(); - let recent_blockhash = bank.confirmed_last_blockhash(); - let RpcHandler { meta, io, .. } = rpc; - - let lamports_per_signature = bank.get_lamports_per_signature(); - let fee_calculator = RpcFeeCalculator { - fee_calculator: FeeCalculator::new(lamports_per_signature), - }; - - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"getFeeCalculatorForBlockhash","params":["{recent_blockhash:?}"]}}"# - ); - let res = io.handle_request_sync(&req, meta.clone()); - let expected = json!({ - "jsonrpc": "2.0", - "result": { - "context": {"slot": 0, "apiVersion": RpcApiVersion::default()}, - "value":fee_calculator, - }, - "id": 1 - }); - let expected: Response = - serde_json::from_value(expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(result, expected); - - // Expired (non-existent) blockhash - let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"getFeeCalculatorForBlockhash","params":["{:?}"]}}"#, - Hash::default() - ); - let res = io.handle_request_sync(&req, meta); - let expected = json!({ - "jsonrpc": "2.0", - "result": { - "context": {"slot": 0, "apiVersion": RpcApiVersion::default()}, - "value":Value::Null, - }, - "id": 1 - }); - let expected: Response = - serde_json::from_value(expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(result, expected); - } - - #[test] - fn test_rpc_get_fee_rate_governor() { - let RpcHandler { meta, io, .. } = RpcHandler::start(); - - let req = r#"{"jsonrpc":"2.0","id":1,"method":"getFeeRateGovernor"}"#; - let res = io.handle_request_sync(req, meta); - let expected = json!({ - "jsonrpc": "2.0", - "result": { - "context": {"slot": 0, "apiVersion": RpcApiVersion::default()}, - "value":{ - "feeRateGovernor": { - "burnPercent": DEFAULT_BURN_PERCENT, - "maxLamportsPerSignature": TEST_SIGNATURE_FEE, - "minLamportsPerSignature": TEST_SIGNATURE_FEE, - "targetLamportsPerSignature": TEST_SIGNATURE_FEE, - "targetSignaturesPerSlot": 0 - } - }, - }, - "id": 1 - }); - let expected: Response = - serde_json::from_value(expected).expect("expected response deserialization"); - let result: Response = serde_json::from_str(&res.expect("actual response")) - .expect("actual response deserialization"); - assert_eq!(result, expected); - } - #[test] fn test_rpc_fail_request_airdrop() { let RpcHandler { meta, io, .. } = RpcHandler::start(); diff --git a/rpc/src/rpc_service.rs b/rpc/src/rpc_service.rs index 00f319f1c27b6f..fed748d709472b 100644 --- a/rpc/src/rpc_service.rs +++ b/rpc/src/rpc_service.rs @@ -5,11 +5,7 @@ use { cluster_tpu_info::ClusterTpuInfo, max_slots::MaxSlots, optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank, - rpc::{ - rpc_accounts::*, rpc_accounts_scan::*, rpc_bank::*, rpc_deprecated_v1_18::*, - rpc_deprecated_v1_7::*, rpc_deprecated_v1_9::*, rpc_full::*, rpc_minimal::*, - rpc_obsolete_v1_7::*, *, - }, + rpc::{rpc_accounts::*, rpc_accounts_scan::*, rpc_bank::*, rpc_full::*, rpc_minimal::*, *}, rpc_cache::LargestAccountsCache, rpc_health::*, }, @@ -453,7 +449,6 @@ impl JsonRpcService { }; let full_api = config.full_api; - let obsolete_v1_7_api = config.obsolete_v1_7_api; let max_request_body_size = config .max_request_body_size .unwrap_or(MAX_REQUEST_BODY_SIZE); @@ -508,12 +503,6 @@ impl JsonRpcService { io.extend_with(rpc_accounts::AccountsDataImpl.to_delegate()); io.extend_with(rpc_accounts_scan::AccountsScanImpl.to_delegate()); io.extend_with(rpc_full::FullImpl.to_delegate()); - io.extend_with(rpc_deprecated_v1_7::DeprecatedV1_7Impl.to_delegate()); - io.extend_with(rpc_deprecated_v1_9::DeprecatedV1_9Impl.to_delegate()); - io.extend_with(rpc_deprecated_v1_18::DeprecatedV1_18Impl.to_delegate()); - } - if obsolete_v1_7_api { - io.extend_with(rpc_obsolete_v1_7::ObsoleteV1_7Impl.to_delegate()); } let request_middleware = RpcRequestMiddleware::new( diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index aef87f84eb4dea..f8356296e970a6 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -330,7 +330,6 @@ pub(crate) mod tests { log_messages: None, inner_instructions: None, fee_details: FeeDetails::default(), - is_nonce: true, return_data: None, executed_units: 0, accounts_data_len_delta: 0, diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index ec0ca35fdfc6ca..54a54e9905b201 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -70,6 +70,8 @@ solana-transaction-status = { workspace = true } solana-version = { workspace = true } solana-vote = { workspace = true } solana-vote-program = { workspace = true } +solana-zk-elgamal-proof-program = { workspace = true } +solana-zk-sdk = { workspace = true } solana-zk-token-proof-program = { workspace = true } solana-zk-token-sdk = { workspace = true } static_assertions = { workspace = true } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 1156b5c3338b1b..fdfb140c98779d 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -33,9 +33,6 @@ //! It offers a high-level API that signs transactions //! on behalf of the caller, and a low-level API for when they have //! already been signed and verified. -#[allow(deprecated)] -use solana_sdk::recent_blockhashes_account; -pub use solana_sdk::reward_type::RewardType; use { crate::{ bank::{ @@ -99,10 +96,7 @@ use { // solana_perf::perf_libs, solana_program_runtime::{ invoke_context::BuiltinFunctionWithContext, - loaded_programs::{ - ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType, - ProgramCacheMatchCriteria, - }, + loaded_programs::{ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType}, timings::{ExecuteTimingType, ExecuteTimings}, }, solana_sdk::{ @@ -110,8 +104,7 @@ use { create_account_shared_data_with_fields as create_account, from_account, Account, AccountSharedData, InheritableAccountFields, ReadableAccount, WritableAccount, }, - account_utils::StateMut, - bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + bpf_loader_upgradeable, clock::{ BankId, Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_HASHES_PER_TICK, DEFAULT_TICKS_PER_SECOND, INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, @@ -134,7 +127,6 @@ use { incinerator, inflation::Inflation, inner_instruction::InnerInstructions, - loader_v4, message::{AccountKeys, SanitizedMessage}, native_loader, native_token::LAMPORTS_PER_SOL, @@ -183,7 +175,6 @@ use { TransactionLoadedAccountsStats, TransactionResults, }, }, - solana_system_program::{get_system_account_kind, SystemAccountKind}, solana_vote::vote_account::{VoteAccount, VoteAccountsHashMap}, solana_vote_program::vote_state::VoteState, std::{ @@ -205,6 +196,9 @@ use { time::{Duration, Instant}, }, }; +pub use { + partitioned_epoch_rewards::KeyedRewardsAndNumPartitions, solana_sdk::reward_type::RewardType, +}; #[cfg(feature = "dev-context-only-utils")] use { solana_accounts_db::accounts_db::{ @@ -212,6 +206,7 @@ use { }, solana_program_runtime::{loaded_programs::ProgramCacheForTxBatch, sysvar_cache::SysvarCache}, solana_svm::program_loader::load_program_with_pubkey, + solana_system_program::{get_system_account_kind, SystemAccountKind}, }; /// params to `verify_accounts_hash` @@ -231,6 +226,7 @@ pub mod epoch_accounts_hash_utils; mod fee_distribution; mod metrics; pub(crate) mod partitioned_epoch_rewards; +mod recent_blockhashes_account; mod serde_snapshot; mod sysvar_cache; pub(crate) mod tests; @@ -602,6 +598,7 @@ impl PartialEq for Bank { collector_fee_details: _, compute_budget: _, transaction_account_lock_limit: _, + fee_structure: _, // Ignore new fields explicitly if they do not impact PartialEq. // Adding ".." will remove compile-time checks that if a new field // is added to the struct, this PartialEq is accordingly updated. @@ -887,6 +884,9 @@ pub struct Bank { /// The max number of accounts that a transaction may lock. transaction_account_lock_limit: Option, + + /// Fee structure to use for assessing transaction fees. + fee_structure: FeeStructure, } struct VoteWithStakeDelegations { @@ -933,10 +933,10 @@ struct PrevEpochInflationRewards { foundation_rate: f64, } -pub struct CommitTransactionCounts { - pub committed_transactions_count: u64, - pub committed_non_vote_transactions_count: u64, - pub committed_with_failure_result_count: u64, +pub struct ExecutedTransactionCounts { + pub executed_transactions_count: u64, + pub executed_non_vote_transactions_count: u64, + pub executed_with_failure_result_count: u64, pub signature_count: u64, } @@ -1004,14 +1004,11 @@ impl Bank { collector_fee_details: RwLock::new(CollectorFeeDetails::default()), compute_budget: None, transaction_account_lock_limit: None, + fee_structure: FeeStructure::default(), }; - bank.transaction_processor = TransactionBatchProcessor::new( - bank.slot, - bank.epoch, - bank.epoch_schedule.clone(), - HashSet::default(), - ); + bank.transaction_processor = + TransactionBatchProcessor::new(bank.slot, bank.epoch, HashSet::default()); let accounts_data_size_initial = bank.get_total_accounts_stats().unwrap().data_len as u64; bank.accounts_data_size_initial = accounts_data_size_initial; @@ -1253,6 +1250,7 @@ impl Bank { collector_fee_details: RwLock::new(CollectorFeeDetails::default()), compute_budget: parent.compute_budget, transaction_account_lock_limit: parent.transaction_account_lock_limit, + fee_structure: parent.fee_structure.clone(), }; let (_, ancestors_time_us) = measure_us!({ @@ -1283,12 +1281,17 @@ impl Bank { } }); + let (_epoch, slot_index) = new.epoch_schedule.get_epoch_and_slot_index(new.slot); + let slots_in_epoch = new.epoch_schedule.get_slots_in_epoch(new.epoch); + let (_, cache_preparation_time_us) = measure_us!(new .transaction_processor .prepare_program_cache_for_upcoming_feature_set( &new, &new.compute_active_feature_set(true).0, &new.compute_budget.unwrap_or_default(), + slot_index, + slots_in_epoch, )); // Update sysvars before processing transactions @@ -1296,7 +1299,6 @@ impl Bank { new.update_slot_hashes(); new.update_stake_history(Some(parent.epoch())); new.update_clock(Some(parent.epoch())); - new.update_fees(); new.update_last_restart_slot() }); @@ -1639,14 +1641,11 @@ impl Bank { collector_fee_details: RwLock::new(CollectorFeeDetails::default()), compute_budget: runtime_config.compute_budget, transaction_account_lock_limit: runtime_config.transaction_account_lock_limit, + fee_structure: FeeStructure::default(), }; - bank.transaction_processor = TransactionBatchProcessor::new( - bank.slot, - bank.epoch, - bank.epoch_schedule.clone(), - HashSet::default(), - ); + bank.transaction_processor = + TransactionBatchProcessor::new(bank.slot, bank.epoch, HashSet::default()); let thread_pool = ThreadPoolBuilder::new() .thread_name(|i| format!("solBnkNewFlds{i:02}")) @@ -2034,21 +2033,6 @@ impl Bank { } } - #[allow(deprecated)] - fn update_fees(&self) { - if !self - .feature_set - .is_active(&feature_set::disable_fees_sysvar::id()) - { - self.update_sysvar_account(&sysvar::fees::id(), |account| { - create_account( - &sysvar::fees::Fees::new(&self.fee_rate_governor.create_fee_calculator()), - self.inherit_specially_retained_account_fields(account), - ) - }); - } - } - fn update_rent(&self) { self.update_sysvar_account(&sysvar::rent::id(), |account| { create_account( @@ -2935,9 +2919,6 @@ impl Bank { self.capitalization.fetch_add(account.lamports(), Relaxed); self.accounts_data_size_initial += account.data().len() as u64; } - // updating sysvars (the fees sysvar in this case) now depends on feature activations in - // genesis_config.accounts above - self.update_fees(); for (pubkey, account) in genesis_config.rewards_pools.iter() { assert!( @@ -2971,7 +2952,6 @@ impl Bank { self.slots_per_year = genesis_config.slots_per_year(); self.epoch_schedule = genesis_config.epoch_schedule.clone(); - self.transaction_processor.epoch_schedule = genesis_config.epoch_schedule.clone(); self.inflation = Arc::new(RwLock::new(genesis_config.inflation)); @@ -3074,11 +3054,6 @@ impl Bank { blockhash_queue.get_lamports_per_signature(hash) } - #[deprecated(since = "1.9.0", note = "Please use `get_fee_for_message` instead")] - pub fn get_fee_rate_governor(&self) -> &FeeRateGovernor { - &self.fee_rate_governor - } - pub fn get_fee_for_message(&self, message: &SanitizedMessage) -> Option { let lamports_per_signature = { let blockhash_queue = self.blockhash_queue.read().unwrap(); @@ -3132,19 +3107,6 @@ impl Bank { ) } - #[deprecated( - since = "1.6.11", - note = "Please use `get_blockhash_last_valid_block_height`" - )] - pub fn get_blockhash_last_valid_slot(&self, blockhash: &Hash) -> Option { - let blockhash_queue = self.blockhash_queue.read().unwrap(); - // This calculation will need to be updated to consider epoch boundaries if BlockhashQueue - // length is made variable by epoch - blockhash_queue - .get_hash_age(blockhash) - .map(|age| self.slot + MAX_PROCESSING_AGE as u64 - age) - } - pub fn get_blockhash_last_valid_block_height(&self, blockhash: &Hash) -> Option { let blockhash_queue = self.blockhash_queue.read().unwrap(); // This calculation will need to be updated to consider epoch boundaries if BlockhashQueue @@ -3404,6 +3366,7 @@ impl Bank { &mut timings, TransactionProcessingConfig { account_overrides: Some(&account_overrides), + check_program_modification_slot: self.check_program_modification_slot, compute_budget: self.compute_budget(), log_messages_bytes_limit: None, limit_to_load_programs: true, @@ -3720,6 +3683,7 @@ impl Bank { epoch_total_stake: self.epoch_total_stake(self.epoch()), epoch_vote_accounts: self.epoch_vote_accounts(self.epoch()), feature_set: Arc::clone(&self.feature_set), + fee_structure: Some(&self.fee_structure), lamports_per_signature, rent_collector: Some(&self.rent_collector), }; @@ -3935,31 +3899,18 @@ impl Bank { fn filter_program_errors_and_collect_fee( &self, - txs: &[SanitizedTransaction], execution_results: &[TransactionExecutionResult], ) -> Vec> { let mut fees = 0; - let results = txs + let results = execution_results .iter() - .zip(execution_results) - .map(|(tx, execution_result)| { - let message = tx.message(); - let details = match &execution_result { - TransactionExecutionResult::Executed { details, .. } => details, - TransactionExecutionResult::NotExecuted(err) => return Err(err.clone()), - }; - - let fee = details.fee_details.total_fee(); - self.check_execution_status_and_charge_fee( - message, - &details.status, - details.is_nonce, - fee, - )?; - - fees += fee; - Ok(()) + .map(|execution_result| match execution_result { + TransactionExecutionResult::Executed { details, .. } => { + fees += details.fee_details.total_fee(); + Ok(()) + } + TransactionExecutionResult::NotExecuted(err) => Err(err.clone()), }) .collect(); @@ -3970,31 +3921,18 @@ impl Bank { // Note: this function is not yet used; next PR will call it behind a feature gate fn filter_program_errors_and_collect_fee_details( &self, - txs: &[SanitizedTransaction], execution_results: &[TransactionExecutionResult], ) -> Vec> { let mut accumulated_fee_details = FeeDetails::default(); - let results = txs + let results = execution_results .iter() - .zip(execution_results) - .map(|(tx, execution_result)| { - let message = tx.message(); - let details = match &execution_result { - TransactionExecutionResult::Executed { details, .. } => details, - TransactionExecutionResult::NotExecuted(err) => return Err(err.clone()), - }; - - self.check_execution_status_and_charge_fee( - message, - &details.status, - details.is_nonce, - details.fee_details.total_fee(), - )?; - - accumulated_fee_details.accumulate(&details.fee_details); - - Ok(()) + .map(|execution_result| match execution_result { + TransactionExecutionResult::Executed { details, .. } => { + accumulated_fee_details.accumulate(&details.fee_details); + Ok(()) + } + TransactionExecutionResult::NotExecuted(err) => Err(err.clone()), }) .collect(); @@ -4005,31 +3943,6 @@ impl Bank { results } - fn check_execution_status_and_charge_fee( - &self, - message: &SanitizedMessage, - execution_status: &transaction::Result<()>, - is_nonce: bool, - fee: u64, - ) -> Result<()> { - // In case of instruction error, even though no accounts - // were stored we still need to charge the payer the - // fee. - // - //...except nonce accounts, which already have their - // post-load, fee deducted, pre-execute account state - // stored - if execution_status.is_err() && !is_nonce { - self.withdraw(message.fee_payer(), fee)?; - } - - Ok(()) - } - - /// `committed_transactions_count` is the number of transactions out of `sanitized_txs` - /// that was executed. Of those, `committed_transactions_count`, - /// `committed_with_failure_result_count` is the number of executed transactions that returned - /// a failure result. pub fn commit_transactions( &self, sanitized_txs: &[SanitizedTransaction], @@ -4037,7 +3950,7 @@ impl Bank { execution_results: Vec, last_blockhash: Hash, lamports_per_signature: u64, - counts: CommitTransactionCounts, + counts: ExecutedTransactionCounts, timings: &mut ExecuteTimings, ) -> TransactionResults { assert!( @@ -4045,30 +3958,30 @@ impl Bank { "commit_transactions() working on a bank that is already frozen or is undergoing freezing!" ); - let CommitTransactionCounts { - committed_transactions_count, - committed_non_vote_transactions_count, - committed_with_failure_result_count, + let ExecutedTransactionCounts { + executed_transactions_count, + executed_non_vote_transactions_count, + executed_with_failure_result_count, signature_count, } = counts; - self.increment_transaction_count(committed_transactions_count); + self.increment_transaction_count(executed_transactions_count); self.increment_non_vote_transaction_count_since_restart( - committed_non_vote_transactions_count, + executed_non_vote_transactions_count, ); self.increment_signature_count(signature_count); - if committed_with_failure_result_count > 0 { + if executed_with_failure_result_count > 0 { self.transaction_error_count - .fetch_add(committed_with_failure_result_count, Relaxed); + .fetch_add(executed_with_failure_result_count, Relaxed); } - // Should be equivalent to checking `committed_transactions_count > 0` + // Should be equivalent to checking `executed_transactions_count > 0` if execution_results.iter().any(|result| result.was_executed()) { self.is_delta.store(true, Relaxed); self.transaction_entries_count.fetch_add(1, Relaxed); self.transactions_per_entry_max - .fetch_max(committed_transactions_count, Relaxed); + .fetch_max(executed_transactions_count, Relaxed); } let mut write_time = Measure::start("write_time"); @@ -4144,9 +4057,9 @@ impl Bank { self.update_transaction_statuses(sanitized_txs, &execution_results); let fee_collection_results = if self.feature_set.is_active(&reward_full_priority_fee::id()) { - self.filter_program_errors_and_collect_fee_details(sanitized_txs, &execution_results) + self.filter_program_errors_and_collect_fee_details(&execution_results) } else { - self.filter_program_errors_and_collect_fee(sanitized_txs, &execution_results) + self.filter_program_errors_and_collect_fee(&execution_results) }; update_transaction_statuses_time.stop(); timings.saturating_add_in_place( @@ -4887,6 +4800,7 @@ impl Bank { timings, TransactionProcessingConfig { account_overrides: None, + check_program_modification_slot: self.check_program_modification_slot, compute_budget: self.compute_budget(), log_messages_bytes_limit, limit_to_load_programs: false, @@ -4903,10 +4817,10 @@ impl Bank { execution_results, last_blockhash, lamports_per_signature, - CommitTransactionCounts { - committed_transactions_count: executed_transactions_count as u64, - committed_non_vote_transactions_count: executed_non_vote_transactions_count as u64, - committed_with_failure_result_count: executed_transactions_count + ExecutedTransactionCounts { + executed_transactions_count: executed_transactions_count as u64, + executed_non_vote_transactions_count: executed_non_vote_transactions_count as u64, + executed_with_failure_result_count: executed_transactions_count .saturating_sub(executed_with_successful_result_count) as u64, signature_count, @@ -5134,32 +5048,6 @@ impl Bank { ); } - fn withdraw(&self, pubkey: &Pubkey, lamports: u64) -> Result<()> { - match self.get_account_with_fixed_root_no_cache(pubkey) { - Some(mut account) => { - let min_balance = match get_system_account_kind(&account) { - Some(SystemAccountKind::Nonce) => self - .rent_collector - .rent - .minimum_balance(nonce::State::size()), - _ => 0, - }; - - lamports - .checked_add(min_balance) - .filter(|required_balance| *required_balance <= account.lamports()) - .ok_or(TransactionError::InsufficientFundsForFee)?; - account - .checked_sub_lamports(lamports) - .map_err(|_| TransactionError::InsufficientFundsForFee)?; - self.store_account(pubkey, &account); - - Ok(()) - } - None => Err(TransactionError::AccountNotFound), - } - } - pub fn accounts(&self) -> Arc { self.rc.accounts.clone() } @@ -5377,18 +5265,20 @@ impl Bank { } /// Returns all the accounts this bank can load - pub fn get_all_accounts(&self) -> ScanResult> { - self.rc.accounts.load_all(&self.ancestors, self.bank_id) + pub fn get_all_accounts(&self, sort_results: bool) -> ScanResult> { + self.rc + .accounts + .load_all(&self.ancestors, self.bank_id, sort_results) } // Scans all the accounts this bank can load, applying `scan_func` - pub fn scan_all_accounts(&self, scan_func: F) -> ScanResult<()> + pub fn scan_all_accounts(&self, scan_func: F, sort_results: bool) -> ScanResult<()> where F: FnMut(Option<(&Pubkey, AccountSharedData, Slot)>), { self.rc .accounts - .scan_all(&self.ancestors, self.bank_id, scan_func) + .scan_all(&self.ancestors, self.bank_id, scan_func, sort_results) } pub fn get_program_accounts_modified_since_parent( @@ -5434,6 +5324,7 @@ impl Bank { num: usize, filter_by_address: &HashSet, filter: AccountAddressFilter, + sort_results: bool, ) -> ScanResult> { self.rc.accounts.load_largest_accounts( &self.ancestors, @@ -5441,6 +5332,7 @@ impl Bank { num, filter_by_address, filter, + sort_results, ) } @@ -6723,7 +6615,7 @@ impl Bank { /// Get all the accounts for this bank and calculate stats pub fn get_total_accounts_stats(&self) -> ScanResult { - let accounts = self.get_all_accounts()?; + let accounts = self.get_all_accounts(false)?; Ok(self.calculate_total_accounts_stats( accounts .iter() @@ -6814,21 +6706,23 @@ impl Bank { pub fn is_in_slot_hashes_history(&self, slot: &Slot) -> bool { if slot < &self.slot { - if let Ok(sysvar_cache) = self.transaction_processor.sysvar_cache.read() { - if let Ok(slot_hashes) = sysvar_cache.get_slot_hashes() { - return slot_hashes.get(slot).is_some(); - } + if let Ok(slot_hashes) = self.transaction_processor.sysvar_cache().get_slot_hashes() { + return slot_hashes.get(slot).is_some(); } } false } - pub fn check_program_modification_slot(&mut self) { - self.check_program_modification_slot = true; + pub fn check_program_modification_slot(&self) -> bool { + self.check_program_modification_slot + } + + pub fn set_check_program_modification_slot(&mut self, check: bool) { + self.check_program_modification_slot = check; } pub fn fee_structure(&self) -> &FeeStructure { - &self.transaction_processor.fee_structure + &self.fee_structure } pub fn compute_budget(&self) -> Option { @@ -6839,40 +6733,6 @@ impl Bank { self.transaction_processor .add_builtin(self, program_id, name, builtin) } - - /// Find the slot in which the program was most recently modified. - /// Returns slot 0 for programs deployed with v1/v2 loaders, since programs deployed - /// with those loaders do not retain deployment slot information. - /// Returns an error if the program's account state can not be found or parsed. - fn program_modification_slot(&self, pubkey: &Pubkey) -> transaction::Result { - let program = self - .get_account(pubkey) - .ok_or(TransactionError::ProgramAccountNotFound)?; - if bpf_loader_upgradeable::check_id(program.owner()) { - if let Ok(UpgradeableLoaderState::Program { - programdata_address, - }) = program.state() - { - let programdata = self - .get_account(&programdata_address) - .ok_or(TransactionError::ProgramAccountNotFound)?; - if let Ok(UpgradeableLoaderState::ProgramData { - slot, - upgrade_authority_address: _, - }) = programdata.state() - { - return Ok(slot); - } - } - Err(TransactionError::ProgramAccountNotFound) - } else if loader_v4::check_id(program.owner()) { - let state = solana_loader_v4_program::get_state(program.data()) - .map_err(|_| TransactionError::ProgramAccountNotFound)?; - Ok(state.slot) - } else { - Ok(0) - } - } } impl TransactionProcessingCallback for Bank { @@ -6892,17 +6752,6 @@ impl TransactionProcessingCallback for Bank { .map(|(acc, _)| acc) } - fn get_program_match_criteria(&self, program: &Pubkey) -> ProgramCacheMatchCriteria { - if self.check_program_modification_slot { - self.program_modification_slot(program) - .map_or(ProgramCacheMatchCriteria::Tombstone, |slot| { - ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) - }) - } else { - ProgramCacheMatchCriteria::NoCriteria - } - } - // NOTE: must hold idempotent for the same set of arguments /// Add a builtin program account fn add_builtin_account(&self, name: &str, program_id: &Pubkey) { @@ -7150,7 +6999,7 @@ impl Bank { } pub fn set_fee_structure(&mut self, fee_structure: &FeeStructure) { - self.transaction_processor.fee_structure = fee_structure.clone(); + self.fee_structure = fee_structure.clone(); } pub fn load_program( @@ -7162,14 +7011,33 @@ impl Bank { let environments = self .transaction_processor .get_environments_for_epoch(effective_epoch)?; - load_program_with_pubkey( - self, - &environments, - pubkey, - self.slot(), - self.epoch_schedule(), - reload, - ) + load_program_with_pubkey(self, &environments, pubkey, self.slot(), reload) + } + + pub fn withdraw(&self, pubkey: &Pubkey, lamports: u64) -> Result<()> { + match self.get_account_with_fixed_root(pubkey) { + Some(mut account) => { + let min_balance = match get_system_account_kind(&account) { + Some(SystemAccountKind::Nonce) => self + .rent_collector + .rent + .minimum_balance(nonce::State::size()), + _ => 0, + }; + + lamports + .checked_add(min_balance) + .filter(|required_balance| *required_balance <= account.lamports()) + .ok_or(TransactionError::InsufficientFundsForFee)?; + account + .checked_sub_lamports(lamports) + .map_err(|_| TransactionError::InsufficientFundsForFee)?; + self.store_account(pubkey, &account); + + Ok(()) + } + None => Err(TransactionError::AccountNotFound), + } } } diff --git a/runtime/src/bank/address_lookup_table.rs b/runtime/src/bank/address_lookup_table.rs index 51eee794803e14..344f1e8bdf09aa 100644 --- a/runtime/src/bank/address_lookup_table.rs +++ b/runtime/src/bank/address_lookup_table.rs @@ -28,9 +28,7 @@ impl AddressLoader for &Bank { ) -> Result { let slot_hashes = self .transaction_processor - .sysvar_cache - .read() - .unwrap() + .sysvar_cache() .get_slot_hashes() .map_err(|_| AddressLoaderError::SlotHashesSysvarNotFound)?; diff --git a/runtime/src/bank/builtins/core_bpf_migration/mod.rs b/runtime/src/bank/builtins/core_bpf_migration/mod.rs index 4b1a5618bdb4c1..4c820185ca3b83 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/mod.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/mod.rs @@ -169,17 +169,11 @@ impl Bank { let elf = &programdata[progradata_metadata_size..]; // Set up the two `LoadedProgramsForTxBatch` instances, as if // processing a new transaction batch. - let program_cache_for_tx_batch = ProgramCacheForTxBatch::new_from_cache( + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new_from_cache( self.slot, self.epoch, &self.transaction_processor.program_cache.read().unwrap(), ); - let mut programs_modified = ProgramCacheForTxBatch::new( - self.slot, - program_cache_for_tx_batch.environments.clone(), - program_cache_for_tx_batch.upcoming_environments.clone(), - program_cache_for_tx_batch.latest_root_epoch, - ); // Configure a dummy `InvokeContext` from the runtime's current // environment, as well as the two `ProgramCacheForTxBatch` @@ -196,13 +190,13 @@ impl Bank { let mut dummy_transaction_context = TransactionContext::new( vec![], self.rent_collector.rent.clone(), - compute_budget.max_invoke_stack_height, + compute_budget.max_instruction_stack_depth, compute_budget.max_instruction_trace_length, ); let mut dummy_invoke_context = InvokeContext::new( &mut dummy_transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, EnvironmentConfig::new( Hash::default(), None, @@ -213,7 +207,6 @@ impl Bank { ), None, compute_budget, - &mut programs_modified, ); solana_bpf_loader_program::direct_deploy_program( @@ -232,7 +225,7 @@ impl Bank { .program_cache .write() .unwrap() - .merge(programs_modified.entries()); + .merge(&program_cache_for_tx_batch.drain_modified_entries()); Ok(()) } diff --git a/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs b/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs index fdd8c3279fd54f..b89a951ffd2cdf 100644 --- a/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs +++ b/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs @@ -120,6 +120,10 @@ mod tests { solana_zk_token_sdk::zk_token_proof_program::id(), Some(feature_set::zk_token_sdk_enabled::id()) )] + #[test_case( + solana_zk_sdk::zk_elgamal_proof_program::id(), + Some(feature_set::zk_elgamal_proof_program_enabled::id()) + )] fn test_target_program_builtin(program_address: Pubkey, activation_feature: Option) { let migration_target = CoreBpfMigrationTargetType::Builtin; let mut bank = create_simple_test_bank(0); diff --git a/runtime/src/bank/builtins/mod.rs b/runtime/src/bank/builtins/mod.rs index 8a7760850dc64c..6e1797be11a8cb 100644 --- a/runtime/src/bank/builtins/mod.rs +++ b/runtime/src/bank/builtins/mod.rs @@ -121,6 +121,13 @@ pub static BUILTINS: &[BuiltinPrototype] = &[ program_id: solana_sdk::loader_v4::id(), entrypoint: solana_loader_v4_program::Entrypoint::vm, }), + testable_prototype!(BuiltinPrototype { + core_bpf_migration_config: None, + name: zk_elgamal_proof_program, + enable_feature_id: Some(feature_set::zk_elgamal_proof_program_enabled::id()), + program_id: solana_zk_sdk::zk_elgamal_proof_program::id(), + entrypoint: solana_zk_elgamal_proof_program::Entrypoint::vm, + }), ]; pub static STATELESS_BUILTINS: &[StatelessBuiltinPrototype] = &[StatelessBuiltinPrototype { @@ -328,6 +335,25 @@ mod test_only { datapoint_name: "migrate_builtin_to_core_bpf_loader_v4_program", }; } + + pub mod zk_elgamal_proof_program { + pub mod feature { + solana_sdk::declare_id!("EYtuxScWqGWmcPEDmeUsEt3iPkvWE26EWLfSxUvWP2WN"); + } + pub mod source_buffer { + solana_sdk::declare_id!("AaVrLPurAUmjw6XRNGr6ezQfHaJWpBGHhcRSJmNjoVpQ"); + } + pub mod upgrade_authority { + solana_sdk::declare_id!("EyGkQYHgynUdvdNPNiWbJQk9roFCexgdJQMNcWbuvp78"); + } + pub const CONFIG: super::CoreBpfMigrationConfig = super::CoreBpfMigrationConfig { + source_buffer_address: source_buffer::id(), + upgrade_authority_address: Some(upgrade_authority::id()), + feature_id: feature::id(), + migration_target: super::CoreBpfMigrationTargetType::Builtin, + datapoint_name: "migrate_builtin_to_core_bpf_zk_elgamal_proof_program", + }; + } } #[cfg(test)] @@ -377,6 +403,10 @@ mod tests { &super::BUILTINS[10].core_bpf_migration_config, &Some(super::test_only::loader_v4::CONFIG) ); + assert_eq!( + &super::BUILTINS[11].core_bpf_migration_config, + &Some(super::test_only::zk_elgamal_proof_program::CONFIG) + ); // Feature Gate has a live migration config, so it has no test-only // configs to test here. } diff --git a/runtime/src/bank/partitioned_epoch_rewards/distribution.rs b/runtime/src/bank/partitioned_epoch_rewards/distribution.rs index 1784fb139d2fa3..ee5c716b2bb091 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/distribution.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/distribution.rs @@ -50,7 +50,7 @@ impl Bank { distribution_starting_block_height + status.stake_rewards_by_partition.len() as u64; assert!( self.epoch_schedule.get_slots_in_epoch(self.epoch) - > distribution_end_exclusive.saturating_sub(distribution_starting_block_height) + > status.stake_rewards_by_partition.len() as u64 ); if height >= distribution_starting_block_height && height < distribution_end_exclusive { @@ -150,7 +150,10 @@ impl Bank { let (mut account, stake_state): (AccountSharedData, StakeStateV2) = stake_account.into(); let StakeStateV2::Stake(meta, stake, flags) = stake_state else { // StakesCache only stores accounts where StakeStateV2::delegation().is_some() - unreachable!() + unreachable!( + "StakesCache entry {:?} failed StakeStateV2 deserialization", + partitioned_stake_reward.stake_pubkey + ) }; account .checked_add_lamports(partitioned_stake_reward.stake_reward_info.lamports as u64) @@ -207,8 +210,8 @@ impl Bank { } Err(err) => { error!( - "bank::distribution::store_stake_accounts_in_partition() failed for {}: {:?}", - stake_pubkey, err + "bank::distribution::store_stake_accounts_in_partition() failed for \ + {stake_pubkey}, {reward_amount} lamports burned: {err:?}" ); lamports_burned += reward_amount; } diff --git a/runtime/src/bank/partitioned_epoch_rewards/mod.rs b/runtime/src/bank/partitioned_epoch_rewards/mod.rs index bb965933f571ab..09343976211c9d 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/mod.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/mod.rs @@ -151,7 +151,36 @@ pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult { pub(crate) type StakeRewards = Vec; +#[derive(Debug, PartialEq)] +pub struct KeyedRewardsAndNumPartitions { + pub keyed_rewards: Vec<(Pubkey, RewardInfo)>, + pub num_partitions: Option, +} + +impl KeyedRewardsAndNumPartitions { + pub fn should_record(&self) -> bool { + !self.keyed_rewards.is_empty() || self.num_partitions.is_some() + } +} + impl Bank { + pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions { + let keyed_rewards = self.rewards.read().unwrap().clone(); + let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar(); + // If partitioned epoch rewards are active and this Bank is the + // epoch-boundary block, populate num_partitions + let epoch_schedule = self.epoch_schedule(); + let parent_epoch = epoch_schedule.get_epoch(self.parent_slot()); + let is_first_block_in_epoch = self.epoch() > parent_epoch; + + let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch) + .then_some(epoch_rewards_sysvar.num_partitions); + KeyedRewardsAndNumPartitions { + keyed_rewards, + num_partitions, + } + } + pub(super) fn is_partitioned_rewards_feature_enabled(&self) -> bool { self.feature_set .is_active(&feature_set::enable_partitioned_epoch_reward::id()) @@ -249,6 +278,7 @@ mod tests { account::Account, epoch_schedule::EpochSchedule, native_token::LAMPORTS_PER_SOL, + reward_type::RewardType, signature::Signer, signer::keypair::Keypair, stake::instruction::StakeError, @@ -685,7 +715,7 @@ mod tests { /// Test that program execution that attempts to mutate a stake account /// incorrectly should fail during reward period. A credit should succeed, - /// but a withdrawal shoudl fail. + /// but a withdrawal should fail. #[test] fn test_program_execution_restricted_for_stake_account_in_reward_period() { use solana_sdk::transaction::TransactionError::InstructionError; @@ -801,4 +831,242 @@ mod tests { previous_bank = bank; } } + + #[test] + fn test_get_rewards_and_partitions() { + let starting_slot = SLOTS_PER_EPOCH - 1; + let num_rewards = 100; + let stake_account_stores_per_block = 50; + let RewardBank { bank, .. } = + create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot); + + assert!(bank.is_partitioned_rewards_feature_enabled()); + // Slot before the epoch boundary contains empty rewards (since fees are + // off), and no partitions because not at the epoch boundary + assert_eq!( + bank.get_rewards_and_num_partitions(), + KeyedRewardsAndNumPartitions { + keyed_rewards: vec![], + num_partitions: None, + } + ); + + let epoch_boundary_bank = Arc::new(Bank::new_from_parent( + bank, + &Pubkey::default(), + SLOTS_PER_EPOCH, + )); + assert!(epoch_boundary_bank.is_partitioned_rewards_feature_enabled()); + // Slot at the epoch boundary contains voting rewards only, as well as partition data + let KeyedRewardsAndNumPartitions { + keyed_rewards, + num_partitions, + } = epoch_boundary_bank.get_rewards_and_num_partitions(); + for (_pubkey, reward) in keyed_rewards.iter() { + assert_eq!(reward.reward_type, RewardType::Voting); + } + assert_eq!(keyed_rewards.len(), num_rewards); + assert_eq!( + num_partitions, + Some(num_rewards as u64 / stake_account_stores_per_block) + ); + + let mut total_staking_rewards = 0; + + let partition0_bank = Arc::new(Bank::new_from_parent( + epoch_boundary_bank, + &Pubkey::default(), + SLOTS_PER_EPOCH + 1, + )); + assert!(partition0_bank.is_partitioned_rewards_feature_enabled()); + // Slot after the epoch boundary contains first partition of staking + // rewards, and no partitions because not at the epoch boundary + let KeyedRewardsAndNumPartitions { + keyed_rewards, + num_partitions, + } = partition0_bank.get_rewards_and_num_partitions(); + for (_pubkey, reward) in keyed_rewards.iter() { + assert_eq!(reward.reward_type, RewardType::Staking); + } + total_staking_rewards += keyed_rewards.len(); + assert_eq!(num_partitions, None); + + let partition1_bank = Arc::new(Bank::new_from_parent( + partition0_bank, + &Pubkey::default(), + SLOTS_PER_EPOCH + 2, + )); + assert!(partition1_bank.is_partitioned_rewards_feature_enabled()); + // Slot 2 after the epoch boundary contains second partition of staking + // rewards, and no partitions because not at the epoch boundary + let KeyedRewardsAndNumPartitions { + keyed_rewards, + num_partitions, + } = partition1_bank.get_rewards_and_num_partitions(); + for (_pubkey, reward) in keyed_rewards.iter() { + assert_eq!(reward.reward_type, RewardType::Staking); + } + total_staking_rewards += keyed_rewards.len(); + assert_eq!(num_partitions, None); + + // All rewards are recorded + assert_eq!(total_staking_rewards, num_rewards); + + let bank = Bank::new_from_parent(partition1_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 3); + assert!(bank.is_partitioned_rewards_feature_enabled()); + // Next slot contains empty rewards (since fees are off), and no + // partitions because not at the epoch boundary + assert_eq!( + bank.get_rewards_and_num_partitions(), + KeyedRewardsAndNumPartitions { + keyed_rewards: vec![], + num_partitions: None, + } + ); + } + + #[test] + fn test_get_rewards_and_partitions_before_feature() { + let starting_slot = SLOTS_PER_EPOCH - 1; + let num_rewards = 100; + + let validator_keypairs = (0..num_rewards) + .map(|_| ValidatorVoteKeypairs::new_rand()) + .collect::>(); + + let GenesisConfigInfo { + mut genesis_config, .. + } = create_genesis_config_with_vote_accounts( + 1_000_000_000, + &validator_keypairs, + vec![2_000_000_000; num_rewards], + ); + genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH); + + // Set feature to inactive + genesis_config + .accounts + .remove(&feature_set::enable_partitioned_epoch_reward::id()); + + let bank = Bank::new_for_tests(&genesis_config); + + for validator_vote_keypairs in &validator_keypairs { + let vote_id = validator_vote_keypairs.vote_keypair.pubkey(); + let mut vote_account = bank.get_account(&vote_id).unwrap(); + // generate some rewards + let mut vote_state = Some(vote_state::from(&vote_account).unwrap()); + for i in 0..MAX_LOCKOUT_HISTORY + 42 { + if let Some(v) = vote_state.as_mut() { + vote_state::process_slot_vote_unchecked(v, i as u64) + } + let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap())); + vote_state::to(&versioned, &mut vote_account).unwrap(); + match versioned { + VoteStateVersions::Current(v) => { + vote_state = Some(*v); + } + _ => panic!("Has to be of type Current"), + }; + } + bank.store_account_and_update_capitalization(&vote_id, &vote_account); + } + + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); + let bank = new_bank_from_parent_with_bank_forks( + &bank_forks, + bank, + &Pubkey::default(), + starting_slot, + ); + + assert!(!bank.is_partitioned_rewards_feature_enabled()); + // Slot before the epoch boundary contains empty rewards (since fees are + // off), and no partitions because feature is inactive + assert_eq!( + bank.get_rewards_and_num_partitions(), + KeyedRewardsAndNumPartitions { + keyed_rewards: vec![], + num_partitions: None, + } + ); + + let epoch_boundary_bank = Arc::new(Bank::new_from_parent( + bank, + &Pubkey::default(), + SLOTS_PER_EPOCH, + )); + assert!(!epoch_boundary_bank.is_partitioned_rewards_feature_enabled()); + // Slot at the epoch boundary contains voting rewards and staking rewards; still no partitions + let KeyedRewardsAndNumPartitions { + keyed_rewards, + num_partitions, + } = epoch_boundary_bank.get_rewards_and_num_partitions(); + let mut voting_rewards_count = 0; + let mut staking_rewards_count = 0; + for (_pubkey, reward) in keyed_rewards.iter() { + match reward.reward_type { + RewardType::Voting => { + voting_rewards_count += 1; + } + RewardType::Staking => { + staking_rewards_count += 1; + } + _ => {} + } + } + assert_eq!( + keyed_rewards.len(), + voting_rewards_count + staking_rewards_count + ); + assert_eq!(voting_rewards_count, num_rewards); + assert_eq!(staking_rewards_count, num_rewards); + assert!(num_partitions.is_none()); + + let bank = + Bank::new_from_parent(epoch_boundary_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 1); + assert!(!bank.is_partitioned_rewards_feature_enabled()); + // Slot after the epoch boundary contains empty rewards (since fees are + // off), and no partitions because feature is inactive + assert_eq!( + bank.get_rewards_and_num_partitions(), + KeyedRewardsAndNumPartitions { + keyed_rewards: vec![], + num_partitions: None, + } + ); + } + + #[test] + fn test_rewards_and_partitions_should_record() { + let reward = RewardInfo { + reward_type: RewardType::Voting, + lamports: 55, + post_balance: 5555, + commission: Some(5), + }; + + let rewards_and_partitions = KeyedRewardsAndNumPartitions { + keyed_rewards: vec![], + num_partitions: None, + }; + assert!(!rewards_and_partitions.should_record()); + + let rewards_and_partitions = KeyedRewardsAndNumPartitions { + keyed_rewards: vec![(Pubkey::new_unique(), reward)], + num_partitions: None, + }; + assert!(rewards_and_partitions.should_record()); + + let rewards_and_partitions = KeyedRewardsAndNumPartitions { + keyed_rewards: vec![], + num_partitions: Some(42), + }; + assert!(rewards_and_partitions.should_record()); + + let rewards_and_partitions = KeyedRewardsAndNumPartitions { + keyed_rewards: vec![(Pubkey::new_unique(), reward)], + num_partitions: Some(42), + }; + assert!(rewards_and_partitions.should_record()); + } } diff --git a/sdk/src/recent_blockhashes_account.rs b/runtime/src/bank/recent_blockhashes_account.rs similarity index 72% rename from sdk/src/recent_blockhashes_account.rs rename to runtime/src/bank/recent_blockhashes_account.rs index 4235fc798a0b87..71815ef00f4e02 100644 --- a/sdk/src/recent_blockhashes_account.rs +++ b/runtime/src/bank/recent_blockhashes_account.rs @@ -1,29 +1,19 @@ //! Helpers for the recent blockhashes sysvar. #[allow(deprecated)] -use solana_program::sysvar::recent_blockhashes::{ +use solana_sdk::sysvar::recent_blockhashes::{ IntoIterSorted, IterItem, RecentBlockhashes, MAX_ENTRIES, }; use { - crate::{ - account::{ - create_account_shared_data_with_fields, to_account, AccountSharedData, - InheritableAccountFields, DUMMY_INHERITABLE_ACCOUNT_FIELDS, - }, - clock::INITIAL_RENT_EPOCH, + solana_sdk::account::{ + create_account_shared_data_with_fields, to_account, AccountSharedData, + InheritableAccountFields, }, std::{collections::BinaryHeap, iter::FromIterator}, }; -#[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" -)] #[allow(deprecated)] -pub fn update_account<'a, I>( - account: &mut AccountSharedData, - recent_blockhash_iter: I, -) -> Option<()> +fn update_account<'a, I>(account: &mut AccountSharedData, recent_blockhash_iter: I) -> Option<()> where I: IntoIterator>, { @@ -37,25 +27,8 @@ where to_account(&recent_blockhashes, account) } -#[deprecated( - since = "1.5.17", - note = "Please use `create_account_with_data_for_test` instead" -)] -#[allow(deprecated)] -pub fn create_account_with_data<'a, I>(lamports: u64, recent_blockhash_iter: I) -> AccountSharedData -where - I: IntoIterator>, -{ - #[allow(deprecated)] - create_account_with_data_and_fields(recent_blockhash_iter, (lamports, INITIAL_RENT_EPOCH)) -} - -#[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" -)] #[allow(deprecated)] -pub fn create_account_with_data_and_fields<'a, I>( +pub(in crate::bank) fn create_account_with_data_and_fields<'a, I>( recent_blockhash_iter: I, fields: InheritableAccountFields, ) -> AccountSharedData @@ -70,31 +43,26 @@ where account } -#[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" -)] -#[allow(deprecated)] -pub fn create_account_with_data_for_test<'a, I>(recent_blockhash_iter: I) -> AccountSharedData -where - I: IntoIterator>, -{ - create_account_with_data_and_fields(recent_blockhash_iter, DUMMY_INHERITABLE_ACCOUNT_FIELDS) -} - #[cfg(test)] mod tests { #![allow(deprecated)] use { super::*, - crate::account::from_account, rand::{seq::SliceRandom, thread_rng}, - solana_program::{ + solana_sdk::{ + account::{from_account, DUMMY_INHERITABLE_ACCOUNT_FIELDS}, hash::{Hash, HASH_BYTES}, sysvar::recent_blockhashes::Entry, }, }; + fn create_account_with_data_for_test<'a, I>(recent_blockhash_iter: I) -> AccountSharedData + where + I: IntoIterator>, + { + create_account_with_data_and_fields(recent_blockhash_iter, DUMMY_INHERITABLE_ACCOUNT_FIELDS) + } + #[test] fn test_create_account_empty() { let account = create_account_with_data_for_test(vec![]); diff --git a/runtime/src/bank/sysvar_cache.rs b/runtime/src/bank/sysvar_cache.rs index ccf1436905bac4..b350b6f37c018f 100644 --- a/runtime/src/bank/sysvar_cache.rs +++ b/runtime/src/bank/sysvar_cache.rs @@ -18,15 +18,13 @@ mod tests { let (genesis_config, _mint_keypair) = create_genesis_config(100_000); let bank0 = Arc::new(Bank::new_for_tests(&genesis_config)); - let bank0_sysvar_cache = bank0.transaction_processor.sysvar_cache.read().unwrap(); + let bank0_sysvar_cache = bank0.transaction_processor.sysvar_cache(); let bank0_cached_clock = bank0_sysvar_cache.get_clock(); let bank0_cached_epoch_schedule = bank0_sysvar_cache.get_epoch_schedule(); - let bank0_cached_fees = bank0_sysvar_cache.get_fees(); let bank0_cached_rent = bank0_sysvar_cache.get_rent(); assert!(bank0_cached_clock.is_ok()); assert!(bank0_cached_epoch_schedule.is_ok()); - assert!(bank0_cached_fees.is_ok()); assert!(bank0_cached_rent.is_ok()); assert!(bank0_sysvar_cache.get_slot_hashes().is_err()); assert!(bank0_sysvar_cache.get_epoch_rewards().is_err()); // partitioned epoch reward feature is not enabled @@ -38,43 +36,37 @@ mod tests { bank1_slot, )); - let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache.read().unwrap(); + let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache(); let bank1_cached_clock = bank1_sysvar_cache.get_clock(); let bank1_cached_epoch_schedule = bank1_sysvar_cache.get_epoch_schedule(); - let bank1_cached_fees = bank1_sysvar_cache.get_fees(); let bank1_cached_rent = bank1_sysvar_cache.get_rent(); assert!(bank1_cached_clock.is_ok()); assert!(bank1_cached_epoch_schedule.is_ok()); - assert!(bank1_cached_fees.is_ok()); assert!(bank1_cached_rent.is_ok()); assert!(bank1_sysvar_cache.get_slot_hashes().is_ok()); assert!(bank1_sysvar_cache.get_epoch_rewards().is_err()); assert_ne!(bank0_cached_clock, bank1_cached_clock); assert_eq!(bank0_cached_epoch_schedule, bank1_cached_epoch_schedule); - assert_ne!(bank0_cached_fees, bank1_cached_fees); assert_eq!(bank0_cached_rent, bank1_cached_rent); let bank2_slot = bank1.slot() + 1; let bank2 = Bank::new_from_parent(bank1.clone(), &Pubkey::default(), bank2_slot); - let bank2_sysvar_cache = bank2.transaction_processor.sysvar_cache.read().unwrap(); + let bank2_sysvar_cache = bank2.transaction_processor.sysvar_cache(); let bank2_cached_clock = bank2_sysvar_cache.get_clock(); let bank2_cached_epoch_schedule = bank2_sysvar_cache.get_epoch_schedule(); - let bank2_cached_fees = bank2_sysvar_cache.get_fees(); let bank2_cached_rent = bank2_sysvar_cache.get_rent(); assert!(bank2_cached_clock.is_ok()); assert!(bank2_cached_epoch_schedule.is_ok()); - assert!(bank2_cached_fees.is_ok()); assert!(bank2_cached_rent.is_ok()); assert!(bank2_sysvar_cache.get_slot_hashes().is_ok()); assert!(bank2_sysvar_cache.get_epoch_rewards().is_err()); // partitioned epoch reward feature is not enabled assert_ne!(bank1_cached_clock, bank2_cached_clock); assert_eq!(bank1_cached_epoch_schedule, bank2_cached_epoch_schedule); - assert_eq!(bank1_cached_fees, bank2_cached_fees); assert_eq!(bank1_cached_rent, bank2_cached_rent); assert_ne!( bank1_sysvar_cache.get_slot_hashes(), @@ -90,7 +82,7 @@ mod tests { let bank1_slot = bank0.slot() + 1; let mut bank1 = Bank::new_from_parent(bank0, &Pubkey::default(), bank1_slot); - let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache.read().unwrap(); + let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache(); let bank1_cached_clock = bank1_sysvar_cache.get_clock(); let bank1_cached_epoch_schedule = bank1_sysvar_cache.get_epoch_schedule(); let bank1_cached_fees = bank1_sysvar_cache.get_fees(); @@ -100,7 +92,6 @@ mod tests { assert!(bank1_cached_clock.is_ok()); assert!(bank1_cached_epoch_schedule.is_ok()); - assert!(bank1_cached_fees.is_ok()); assert!(bank1_cached_rent.is_ok()); assert!(bank1_cached_slot_hashes.is_ok()); assert!(bank1_cached_epoch_rewards.is_err()); @@ -108,10 +99,9 @@ mod tests { drop(bank1_sysvar_cache); bank1.transaction_processor.reset_sysvar_cache(); - let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache.read().unwrap(); + let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache(); assert!(bank1_sysvar_cache.get_clock().is_err()); assert!(bank1_sysvar_cache.get_epoch_schedule().is_err()); - assert!(bank1_sysvar_cache.get_fees().is_err()); assert!(bank1_sysvar_cache.get_rent().is_err()); assert!(bank1_sysvar_cache.get_slot_hashes().is_err()); assert!(bank1_sysvar_cache.get_epoch_rewards().is_err()); @@ -143,7 +133,7 @@ mod tests { .transaction_processor .fill_missing_sysvar_cache_entries(&bank1); - let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache.read().unwrap(); + let bank1_sysvar_cache = bank1.transaction_processor.sysvar_cache(); assert_eq!(bank1_sysvar_cache.get_clock(), bank1_cached_clock); assert_eq!( bank1_sysvar_cache.get_epoch_schedule(), diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 54d23ba203e17e..a5a6022875defe 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -1,6 +1,4 @@ #![cfg(test)] -#[allow(deprecated)] -use solana_sdk::sysvar::fees::Fees; use { super::{ test_utils::{goto_end_of_slot, update_vote_account_timestamp}, @@ -73,7 +71,6 @@ use { incinerator, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, loader_upgradeable_instruction::UpgradeableLoaderInstruction, - loader_v4::{LoaderV4State, LoaderV4Status}, message::{Message, MessageHeader, SanitizedMessage}, native_loader, native_token::{sol_to_lamports, LAMPORTS_PER_SOL}, @@ -104,7 +101,7 @@ use { transaction_context::TransactionAccount, }, solana_stake_program::stake_state::{self, StakeStateV2}, - solana_svm::nonce_info::NonceFull, + solana_svm::nonce_info::NoncePartial, solana_vote_program::{ vote_instruction, vote_state::{ @@ -231,18 +228,13 @@ fn test_race_register_tick_freeze() { } } -fn new_execution_result( - status: Result<()>, - nonce: Option<&NonceFull>, - fee_details: FeeDetails, -) -> TransactionExecutionResult { +fn new_execution_result(status: Result<()>, fee_details: FeeDetails) -> TransactionExecutionResult { TransactionExecutionResult::Executed { details: TransactionExecutionDetails { status, log_messages: None, inner_instructions: None, fee_details, - is_nonce: nonce.is_some(), return_data: None, executed_units: 0, accounts_data_len_delta: 0, @@ -2867,45 +2859,27 @@ fn test_bank_blockhash_compute_unit_fee_structure() { #[test] fn test_filter_program_errors_and_collect_fee() { let leader = solana_sdk::pubkey::new_rand(); - let GenesisConfigInfo { - genesis_config, - mint_keypair, - .. - } = create_genesis_config_with_leader(100_000, &leader, 3); + let GenesisConfigInfo { genesis_config, .. } = + create_genesis_config_with_leader(100_000, &leader, 3); let mut bank = Bank::new_for_tests(&genesis_config); // this test is only for when `feature_set::reward_full_priority_fee` inactivated bank.deactivate_feature(&feature_set::reward_full_priority_fee::id()); - let key = solana_sdk::pubkey::new_rand(); - let tx1 = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( - &mint_keypair, - &key, - 2, - genesis_config.hash(), - )); - let tx2 = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( - &mint_keypair, - &key, - 5, - genesis_config.hash(), - )); - let tx_fee = 42; let fee_details = FeeDetails::new_for_tests(tx_fee, 0, false); let results = vec![ - new_execution_result(Ok(()), None, fee_details), + new_execution_result(Ok(()), fee_details), new_execution_result( Err(TransactionError::InstructionError( 1, SystemError::ResultWithNegativeLamports.into(), )), - None, fee_details, ), ]; let initial_balance = bank.get_balance(&leader); - let results = bank.filter_program_errors_and_collect_fee(&[tx1, tx2], &results); + let results = bank.filter_program_errors_and_collect_fee(&results); bank.freeze(); assert_eq!( bank.get_balance(&leader), @@ -2918,45 +2892,27 @@ fn test_filter_program_errors_and_collect_fee() { #[test] fn test_filter_program_errors_and_collect_priority_fee() { let leader = solana_sdk::pubkey::new_rand(); - let GenesisConfigInfo { - genesis_config, - mint_keypair, - .. - } = create_genesis_config_with_leader(1000000, &leader, 3); + let GenesisConfigInfo { genesis_config, .. } = + create_genesis_config_with_leader(1000000, &leader, 3); let mut bank = Bank::new_for_tests(&genesis_config); // this test is only for when `feature_set::reward_full_priority_fee` inactivated bank.deactivate_feature(&feature_set::reward_full_priority_fee::id()); - let key = solana_sdk::pubkey::new_rand(); - let tx1 = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( - &mint_keypair, - &key, - 2, - genesis_config.hash(), - )); - let tx2 = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( - &mint_keypair, - &key, - 5, - genesis_config.hash(), - )); - let priority_fee = 42; let fee_details: FeeDetails = FeeDetails::new_for_tests(0, priority_fee, false); let results = vec![ - new_execution_result(Ok(()), None, fee_details), + new_execution_result(Ok(()), fee_details), new_execution_result( Err(TransactionError::InstructionError( 1, SystemError::ResultWithNegativeLamports.into(), )), - None, fee_details, ), ]; let initial_balance = bank.get_balance(&leader); - let results = bank.filter_program_errors_and_collect_fee(&[tx1, tx2], &results); + let results = bank.filter_program_errors_and_collect_fee(&results); bank.freeze(); assert_eq!( bank.get_balance(&leader), @@ -4295,22 +4251,6 @@ fn test_bank_cloned_stake_delegations() { assert!(stake_delegations.get(&stake_keypair.pubkey()).is_some()); } -#[allow(deprecated)] -#[test] -fn test_bank_fees_account() { - let (mut genesis_config, _) = create_genesis_config(500); - genesis_config.fee_rate_governor = FeeRateGovernor::new(12345, 0); - let bank = Arc::new(Bank::new_for_tests(&genesis_config)); - - let fees_account = bank.get_account(&sysvar::fees::id()).unwrap(); - let fees = from_account::(&fees_account).unwrap(); - assert_eq!( - bank.fee_rate_governor.lamports_per_signature, - fees.fee_calculator.lamports_per_signature - ); - assert_eq!(fees.fee_calculator.lamports_per_signature, 12345); -} - #[test] fn test_is_delta_with_no_committables() { let (genesis_config, mint_keypair) = create_genesis_config(8000); @@ -4352,7 +4292,7 @@ fn test_bank_get_program_accounts() { let parent = Arc::new(Bank::new_for_tests(&genesis_config)); parent.restore_old_behavior_for_fragile_tests(); - let genesis_accounts: Vec<_> = parent.get_all_accounts().unwrap(); + let genesis_accounts: Vec<_> = parent.get_all_accounts(false).unwrap(); assert!( genesis_accounts .iter() @@ -6455,26 +6395,26 @@ fn test_bank_hash_consistency() { if bank.slot == 0 { assert_eq!( bank.hash().to_string(), - "i5hGiQ3WtEehNrvhbfPFkUdm267t18fSpujcYtkBioW", + "Hn2FoJuoFWXVFVnwcQ6peuT24mUPmhDtXHXVjKD7M4yP", ); } if bank.slot == 32 { assert_eq!( bank.hash().to_string(), - "7NmBtNvbhoqzatJv8NgBs84qWrm4ZhpuC75DCpbqwiS" + "7FPfwBut4b7bXtKPsobQS1cuFgF47SZHDb4teQcJRomv" ); } if bank.slot == 64 { assert_eq!( bank.hash().to_string(), - "A1jjuUaENeDcsSvwejFGaZ5zWmnJ77doSzqdKtfzpoFk" + "28CWiEuA3izdt5xe4LyS4Q1DTALmYgrVctSTazFiPVcW" ); } if bank.slot == 128 { assert_eq!( bank.hash().to_string(), - "ApnMkFt5Bs4yDJ8S2CCPsQRL1He6vWXw6vMzAyc5i811" + "AdCmEvRXWKpvXb9fG6AFQhzGgB5ciAXnDajvaNK7YUg8" ); break; } @@ -6697,7 +6637,7 @@ fn test_shrink_candidate_slots_cached() { // No more slots should be shrunk assert_eq!(bank2.shrink_candidate_slots(), 0); // alive_counts represents the count of alive accounts in the three slots 0,1,2 - assert_eq!(alive_counts, vec![15, 1, 7]); + assert_eq!(alive_counts, vec![15, 1, 6]); } #[test] @@ -7985,7 +7925,7 @@ fn test_reserved_account_keys() { assert_eq!( bank.get_reserved_account_keys().len(), - 29, + 30, "after activating the new feature, bank should have new active reserved keys" ); } @@ -9116,10 +9056,7 @@ fn test_epoch_schedule_from_genesis_config() { Arc::default(), )); - assert_eq!( - &bank.transaction_processor.epoch_schedule, - &genesis_config.epoch_schedule - ); + assert_eq!(bank.epoch_schedule(), &genesis_config.epoch_schedule); } fn check_stake_vote_account_validity(check_owner_change: bool, load_vote_and_stake_accounts: F) @@ -9481,24 +9418,24 @@ fn test_get_largest_accounts() { // Return only one largest account assert_eq!( - bank.get_largest_accounts(1, &pubkeys_hashset, AccountAddressFilter::Include) + bank.get_largest_accounts(1, &pubkeys_hashset, AccountAddressFilter::Include, false) .unwrap(), vec![(pubkeys[4], sol_to_lamports(5.0))] ); assert_eq!( - bank.get_largest_accounts(1, &HashSet::new(), AccountAddressFilter::Exclude) + bank.get_largest_accounts(1, &HashSet::new(), AccountAddressFilter::Exclude, false) .unwrap(), vec![(pubkeys[4], sol_to_lamports(5.0))] ); assert_eq!( - bank.get_largest_accounts(1, &exclude4, AccountAddressFilter::Exclude) + bank.get_largest_accounts(1, &exclude4, AccountAddressFilter::Exclude, false) .unwrap(), vec![(pubkeys[3], sol_to_lamports(4.0))] ); // Return all added accounts let results = bank - .get_largest_accounts(10, &pubkeys_hashset, AccountAddressFilter::Include) + .get_largest_accounts(10, &pubkeys_hashset, AccountAddressFilter::Include, false) .unwrap(); assert_eq!(results.len(), sorted_accounts.len()); for pubkey_balance in sorted_accounts.iter() { @@ -9510,7 +9447,7 @@ fn test_get_largest_accounts() { let expected_accounts = sorted_accounts[1..].to_vec(); let results = bank - .get_largest_accounts(10, &exclude4, AccountAddressFilter::Exclude) + .get_largest_accounts(10, &exclude4, AccountAddressFilter::Exclude, false) .unwrap(); // results include 5 Bank builtins assert_eq!(results.len(), 10); @@ -9524,7 +9461,7 @@ fn test_get_largest_accounts() { // Return 3 added accounts let expected_accounts = sorted_accounts[0..4].to_vec(); let results = bank - .get_largest_accounts(4, &pubkeys_hashset, AccountAddressFilter::Include) + .get_largest_accounts(4, &pubkeys_hashset, AccountAddressFilter::Include, false) .unwrap(); assert_eq!(results.len(), expected_accounts.len()); for pubkey_balance in expected_accounts.iter() { @@ -9533,7 +9470,7 @@ fn test_get_largest_accounts() { let expected_accounts = expected_accounts[1..4].to_vec(); let results = bank - .get_largest_accounts(3, &exclude4, AccountAddressFilter::Exclude) + .get_largest_accounts(3, &exclude4, AccountAddressFilter::Exclude, false) .unwrap(); assert_eq!(results.len(), expected_accounts.len()); for pubkey_balance in expected_accounts.iter() { @@ -9546,7 +9483,7 @@ fn test_get_largest_accounts() { .cloned() .collect(); assert_eq!( - bank.get_largest_accounts(2, &exclude, AccountAddressFilter::Exclude) + bank.get_largest_accounts(2, &exclude, AccountAddressFilter::Exclude, false) .unwrap(), vec![pubkeys_balances[3], pubkeys_balances[1]] ); @@ -12938,33 +12875,23 @@ fn test_failed_simulation_compute_units() { #[test] fn test_filter_program_errors_and_collect_fee_details() { - // TX | EXECUTION RESULT | is nonce | COLLECT | ADDITIONAL | COLLECT - // | | | (TX_FEE, PRIO_FEE) | WITHDRAW FROM PAYER | RESULT - // ------------------------------------------------------------------------------------------------------ - // tx1 | not executed | n/a | (0 , 0) | 0 | Original Err - // tx2 | executed and no error | n/a | (5_000, 1_000) | 0 | Ok - // tx3 | executed has error | true | (5_000, 1_000) | 0 | Ok - // tx4 | executed has error | false | (5_000, 1_000) | 6_000 | Ok - // tx5 | executed error, - // payer insufficient fund | false | (0 , 0) | 0 | InsufficientFundsForFee + // TX | EXECUTION RESULT | COLLECT | COLLECT + // | | (TX_FEE, PRIO_FEE) | RESULT + // --------------------------------------------------------------------------------- + // tx1 | not executed | (0 , 0) | Original Err + // tx2 | executed and no error | (5_000, 1_000) | Ok + // tx3 | executed has error | (5_000, 1_000) | Ok // let initial_payer_balance = 7_000; - let additional_payer_withdraw = 6_000; let tx_fee = 5000; let priority_fee = 1000; let tx_fee_details = FeeDetails::new_for_tests(tx_fee, priority_fee, false); let expected_collected_fee_details = CollectorFeeDetails { - transaction_fee: 3 * tx_fee, - priority_fee: 3 * priority_fee, + transaction_fee: 2 * tx_fee, + priority_fee: 2 * priority_fee, }; - let expected_collect_results = vec![ - Err(TransactionError::AccountNotFound), - Ok(()), - Ok(()), - Ok(()), - Err(TransactionError::InsufficientFundsForFee), - ]; + let expected_collect_results = vec![Err(TransactionError::AccountNotFound), Ok(()), Ok(())]; let GenesisConfigInfo { genesis_config, @@ -12973,106 +12900,31 @@ fn test_filter_program_errors_and_collect_fee_details() { } = create_genesis_config_with_leader(initial_payer_balance, &Pubkey::new_unique(), 3); let bank = Bank::new_for_tests(&genesis_config); - let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer( - &[system_instruction::transfer( - &mint_keypair.pubkey(), - &Pubkey::new_unique(), - 2, - )], - Some(&mint_keypair.pubkey()), - &[&mint_keypair], - genesis_config.hash(), - )); - let txs = vec![tx.clone(), tx.clone(), tx.clone(), tx.clone(), tx]; - let results = vec![ TransactionExecutionResult::NotExecuted(TransactionError::AccountNotFound), - new_execution_result(Ok(()), None, tx_fee_details), + new_execution_result(Ok(()), tx_fee_details), new_execution_result( Err(TransactionError::InstructionError( 0, SystemError::ResultWithNegativeLamports.into(), )), - Some(&NonceFull::new( - Pubkey::new_unique(), - AccountSharedData::default(), - None, - )), tx_fee_details, ), - new_execution_result( - Err(TransactionError::InstructionError( - 0, - SystemError::ResultWithNegativeLamports.into(), - )), - None, - tx_fee_details, - ), - new_execution_result(Err(TransactionError::AccountNotFound), None, tx_fee_details), ]; - let results = bank.filter_program_errors_and_collect_fee_details(&txs, &results); + let results = bank.filter_program_errors_and_collect_fee_details(&results); assert_eq!( expected_collected_fee_details, *bank.collector_fee_details.read().unwrap() ); assert_eq!( - initial_payer_balance - additional_payer_withdraw, + initial_payer_balance, bank.get_balance(&mint_keypair.pubkey()) ); assert_eq!(expected_collect_results, results); } -#[test] -fn test_check_execution_status_and_charge_fee() { - let fee = 5000; - let initial_balance = fee - 1000; - let tx_error = - TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature); - let GenesisConfigInfo { - mut genesis_config, - mint_keypair, - .. - } = create_genesis_config_with_leader(initial_balance, &Pubkey::new_unique(), 3); - genesis_config.fee_rate_governor = FeeRateGovernor::new(5000, 0); - let bank = Bank::new_for_tests(&genesis_config); - let message = new_sanitized_message(Message::new( - &[system_instruction::transfer( - &mint_keypair.pubkey(), - &Pubkey::new_unique(), - 1, - )], - Some(&mint_keypair.pubkey()), - )); - - [Ok(()), Err(tx_error)] - .iter() - .flat_map(|result| [true, false].iter().map(move |is_nonce| (result, is_nonce))) - .for_each(|(result, is_nonce)| { - if result.is_err() && !is_nonce { - assert_eq!( - Err(TransactionError::InsufficientFundsForFee), - bank.check_execution_status_and_charge_fee(&message, result, *is_nonce, fee) - ); - assert_eq!(initial_balance, bank.get_balance(&mint_keypair.pubkey())); - - let small_fee = 1; - assert!(bank - .check_execution_status_and_charge_fee(&message, result, *is_nonce, small_fee) - .is_ok()); - assert_eq!( - initial_balance - small_fee, - bank.get_balance(&mint_keypair.pubkey()) - ); - } else { - assert!(bank - .check_execution_status_and_charge_fee(&message, result, *is_nonce, fee) - .is_ok()); - assert_eq!(initial_balance, bank.get_balance(&mint_keypair.pubkey())); - } - }); -} #[test] fn test_deploy_last_epoch_slot() { solana_logger::setup(); @@ -13186,91 +13038,6 @@ fn test_deploy_last_epoch_slot() { assert_eq!(result_with_feature_enabled, Ok(())); } -#[test] -fn test_program_modification_slot_account_not_found() { - let genesis_config = GenesisConfig::default(); - let bank = Bank::new_for_tests(&genesis_config); - let key = Pubkey::new_unique(); - - let result = bank.program_modification_slot(&key); - assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); - - let mut account_data = AccountSharedData::new(100, 100, &bpf_loader_upgradeable::id()); - bank.store_account(&key, &account_data); - - let result = bank.program_modification_slot(&key); - assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); - - let state = UpgradeableLoaderState::Program { - programdata_address: Pubkey::new_unique(), - }; - account_data.set_data(bincode::serialize(&state).unwrap()); - bank.store_account(&key, &account_data); - - let result = bank.program_modification_slot(&key); - assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); - - account_data.set_owner(loader_v4::id()); - bank.store_account(&key, &account_data); - - let result = bank.program_modification_slot(&key); - assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); -} - -#[test] -fn test_program_modification_slot_success() { - let genesis_config = GenesisConfig::default(); - let bank = Bank::new_for_tests(&genesis_config); - - let key1 = Pubkey::new_unique(); - let key2 = Pubkey::new_unique(); - - let account_data = AccountSharedData::new_data( - 100, - &UpgradeableLoaderState::Program { - programdata_address: key2, - }, - &bpf_loader_upgradeable::id(), - ) - .unwrap(); - bank.store_account(&key1, &account_data); - - let account_data = AccountSharedData::new_data( - 100, - &UpgradeableLoaderState::ProgramData { - slot: 77, - upgrade_authority_address: None, - }, - &bpf_loader_upgradeable::id(), - ) - .unwrap(); - bank.store_account(&key2, &account_data); - - let result = bank.program_modification_slot(&key1); - assert_eq!(result.unwrap(), 77); - - let state = LoaderV4State { - slot: 58, - authority_address: Pubkey::new_unique(), - status: LoaderV4Status::Deployed, - }; - let encoded = unsafe { - std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>(&state) - }; - let mut account_data = AccountSharedData::new(100, encoded.len(), &loader_v4::id()); - account_data.set_data(encoded.to_vec()); - bank.store_account(&key1, &account_data); - - let result = bank.program_modification_slot(&key1); - assert_eq!(result.unwrap(), 58); - - account_data.set_owner(Pubkey::new_unique()); - bank.store_account(&key2, &account_data); - - let result = bank.program_modification_slot(&key2); - assert_eq!(result.unwrap(), 0); -} - #[test] fn test_blockhash_last_valid_block_height() { let genesis_config = GenesisConfig::default(); diff --git a/runtime/src/bank_client.rs b/runtime/src/bank_client.rs index 634cb0e28b09ab..e8c8f7774ec66e 100644 --- a/runtime/src/bank_client.rs +++ b/runtime/src/bank_client.rs @@ -6,7 +6,6 @@ use { client::{AsyncClient, Client, SyncClient}, commitment_config::CommitmentConfig, epoch_info::EpochInfo, - fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, instruction::Instruction, message::{Message, SanitizedMessage}, @@ -120,42 +119,6 @@ impl SyncClient for BankClient { Ok(self.bank.get_minimum_balance_for_rent_exemption(data_len)) } - fn get_recent_blockhash(&self) -> Result<(Hash, FeeCalculator)> { - Ok(( - self.bank.last_blockhash(), - FeeCalculator::new(self.bank.get_lamports_per_signature()), - )) - } - - fn get_recent_blockhash_with_commitment( - &self, - _commitment_config: CommitmentConfig, - ) -> Result<(Hash, FeeCalculator, u64)> { - let blockhash = self.bank.last_blockhash(); - #[allow(deprecated)] - let last_valid_slot = self - .bank - .get_blockhash_last_valid_slot(&blockhash) - .expect("bank blockhash queue should contain blockhash"); - Ok(( - blockhash, - FeeCalculator::new(self.bank.get_lamports_per_signature()), - last_valid_slot, - )) - } - - fn get_fee_calculator_for_blockhash(&self, blockhash: &Hash) -> Result> { - Ok(self - .bank - .get_lamports_per_signature_for_blockhash(blockhash) - .map(FeeCalculator::new)) - } - - fn get_fee_rate_governor(&self) -> Result { - #[allow(deprecated)] - Ok(self.bank.get_fee_rate_governor().clone()) - } - fn get_signature_status( &self, signature: &Signature, @@ -241,21 +204,6 @@ impl SyncClient for BankClient { Ok(()) } - fn get_new_blockhash(&self, blockhash: &Hash) -> Result<(Hash, FeeCalculator)> { - let recent_blockhash = self.get_latest_blockhash()?; - if recent_blockhash != *blockhash { - Ok(( - recent_blockhash, - FeeCalculator::new(self.bank.get_lamports_per_signature()), - )) - } else { - Err(TransportError::IoError(io::Error::new( - io::ErrorKind::Other, - "Unable to get new blockhash", - ))) - } - } - fn get_epoch_info(&self) -> Result { Ok(self.bank.get_epoch_info()) } diff --git a/runtime/src/bank_forks.rs b/runtime/src/bank_forks.rs index faa4413ede4d09..426908b288db6c 100644 --- a/runtime/src/bank_forks.rs +++ b/runtime/src/bank_forks.rs @@ -227,7 +227,7 @@ impl BankForks { pub fn insert(&mut self, mut bank: Bank) -> BankWithScheduler { if self.root.load(Ordering::Relaxed) < self.highest_slot_at_startup { - bank.check_program_modification_slot(); + bank.set_check_program_modification_slot(true); } let bank = Arc::new(bank); diff --git a/runtime/src/commitment.rs b/runtime/src/commitment.rs index 3f600a9401ae9b..632e4bda3d8505 100644 --- a/runtime/src/commitment.rs +++ b/runtime/src/commitment.rs @@ -111,16 +111,11 @@ impl BlockCommitmentCache { self.highest_confirmed_slot() } - #[allow(deprecated)] pub fn slot_with_commitment(&self, commitment_level: CommitmentLevel) -> Slot { match commitment_level { - CommitmentLevel::Recent | CommitmentLevel::Processed => self.slot(), - CommitmentLevel::Root => self.root(), - CommitmentLevel::Single => self.highest_confirmed_slot(), - CommitmentLevel::SingleGossip | CommitmentLevel::Confirmed => { - self.highest_gossip_confirmed_slot() - } - CommitmentLevel::Max | CommitmentLevel::Finalized => self.highest_super_majority_root(), + CommitmentLevel::Processed => self.slot(), + CommitmentLevel::Confirmed => self.highest_gossip_confirmed_slot(), + CommitmentLevel::Finalized => self.highest_super_majority_root(), } } diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index 379750b1743381..f257c19033847b 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -29,15 +29,13 @@ pub fn bootstrap_validator_stake_lamports() -> u64 { pub const fn genesis_sysvar_and_builtin_program_lamports() -> u64 { const NUM_BUILTIN_PROGRAMS: u64 = 9; const NUM_PRECOMPILES: u64 = 2; - const FEES_SYSVAR_MIN_BALANCE: u64 = 946_560; const STAKE_HISTORY_MIN_BALANCE: u64 = 114_979_200; const CLOCK_SYSVAR_MIN_BALANCE: u64 = 1_169_280; const RENT_SYSVAR_MIN_BALANCE: u64 = 1_009_200; const EPOCH_SCHEDULE_SYSVAR_MIN_BALANCE: u64 = 1_120_560; const RECENT_BLOCKHASHES_SYSVAR_MIN_BALANCE: u64 = 42_706_560; - FEES_SYSVAR_MIN_BALANCE - + STAKE_HISTORY_MIN_BALANCE + STAKE_HISTORY_MIN_BALANCE + CLOCK_SYSVAR_MIN_BALANCE + RENT_SYSVAR_MIN_BALANCE + EPOCH_SCHEDULE_SYSVAR_MIN_BALANCE diff --git a/runtime/src/installed_scheduler_pool.rs b/runtime/src/installed_scheduler_pool.rs index d7326da99ef43b..aeededab8ee784 100644 --- a/runtime/src/installed_scheduler_pool.rs +++ b/runtime/src/installed_scheduler_pool.rs @@ -25,8 +25,8 @@ use { log::*, solana_program_runtime::timings::ExecuteTimings, solana_sdk::{ + clock::Slot, hash::Hash, - slot_history::Slot, transaction::{Result, SanitizedTransaction, TransactionError}, }, std::{ @@ -34,6 +34,7 @@ use { mem, ops::Deref, sync::{Arc, RwLock}, + thread, }, }; #[cfg(feature = "dev-context-only-utils")] @@ -292,6 +293,8 @@ impl WaitReason { pub enum SchedulerStatus { /// Unified scheduler is disabled or installed scheduler is consumed by wait_for_termination(). /// Note that transition to Unavailable from {Active, Stale} is one-way (i.e. one-time). + /// Also, this variant is transiently used as a placeholder internally when transitioning + /// scheduler statuses, which isn't observable unless panic is happening. Unavailable, /// Scheduler is installed into a bank; could be running or just be idling. /// This will be transitioned to Stale after certain time has passed if its bank hasn't been @@ -329,7 +332,7 @@ impl SchedulerStatus { return; } let Self::Active(scheduler) = mem::replace(self, Self::Unavailable) else { - unreachable!("not active: {:?}", self); + unreachable!("not active: {self:?}"); }; let (pool, result_with_timings) = f(scheduler); *self = Self::Stale(pool, result_with_timings); @@ -491,7 +494,8 @@ impl BankWithScheduler { ); assert!( maybe_result_with_timings.is_none(), - "Premature result was returned from scheduler after paused" + "Premature result was returned from scheduler after paused (slot: {})", + bank.slot(), ); } @@ -548,7 +552,8 @@ impl BankWithSchedulerInner { let scheduler = self.scheduler.read().unwrap(); // Re-register a new timeout listener only after acquiring the read lock; // Otherwise, the listener would again put scheduler into Stale before the read - // lock under an extremely-rare race condition, causing panic below. + // lock under an extremely-rare race condition, causing panic below in + // active_scheduler(). pool.register_timeout_listener(self.do_create_timeout_listener()); f(scheduler.active_scheduler()) } @@ -619,7 +624,7 @@ impl BankWithSchedulerInner { "wait_for_scheduler_termination(slot: {}, reason: {:?}): started at {:?}...", bank.slot(), reason, - std::thread::current(), + thread::current(), ); let mut scheduler = scheduler.write().unwrap(); @@ -635,6 +640,11 @@ impl BankWithSchedulerInner { uninstalled_scheduler.return_to_pool(); (false, Some(result_with_timings)) } + SchedulerStatus::Stale(_pool, _result_with_timings) if reason.is_paused() => { + // Do nothing for pauses because the scheduler termination is guaranteed to be + // called later. + (true, None) + } SchedulerStatus::Stale(_pool, _result_with_timings) => { let result_with_timings = scheduler.transition_from_stale_to_unavailable(); (true, Some(result_with_timings)) @@ -647,7 +657,7 @@ impl BankWithSchedulerInner { reason, was_noop, result_with_timings.as_ref().map(|(result, _)| result), - std::thread::current(), + thread::current(), ); trace!( "wait_for_scheduler_termination(result_with_timings: {:?})", @@ -658,7 +668,7 @@ impl BankWithSchedulerInner { } fn drop_scheduler(&self) { - if std::thread::panicking() { + if thread::panicking() { error!( "BankWithSchedulerInner::drop_scheduler(): slot: {} skipping due to already panicking...", self.bank.slot(), diff --git a/runtime/src/loader_utils.rs b/runtime/src/loader_utils.rs index 7265641e900bc1..436a7242d93fd7 100644 --- a/runtime/src/loader_utils.rs +++ b/runtime/src/loader_utils.rs @@ -28,9 +28,18 @@ const CHUNK_SIZE: usize = 512; // Size of chunk just needs to fit into tx pub fn load_program_from_file(name: &str) -> Vec { let mut pathbuf = { let current_exe = env::current_exe().unwrap(); - PathBuf::from(current_exe.parent().unwrap().parent().unwrap()) + PathBuf::from( + current_exe + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap(), + ) }; - pathbuf.push("sbf/"); + pathbuf.push("sbf-solana-solana"); + pathbuf.push("release"); pathbuf.push(name); pathbuf.set_extension("so"); let mut file = File::open(&pathbuf).unwrap_or_else(|err| { diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 0bb1317dc7f965..1f553f66a52272 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -49,7 +49,8 @@ bincode = { workspace = true } bitflags = { workspace = true, features = ["serde"] } borsh = { workspace = true, optional = true } bs58 = { workspace = true } -bytemuck = { workspace = true, features = ["derive"] } +bytemuck = { workspace = true } +bytemuck_derive = { workspace = true } byteorder = { workspace = true, optional = true } chrono = { workspace = true, features = ["alloc"], optional = true } curve25519-dalek = { workspace = true, optional = true } @@ -85,11 +86,11 @@ solana-program = { workspace = true } solana-sdk-macro = { workspace = true } thiserror = { workspace = true } uriparse = { workspace = true } -wasm-bindgen = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.1", features = ["wasm-bindgen"] } js-sys = { workspace = true } +wasm-bindgen = { workspace = true } [dev-dependencies] anyhow = { workspace = true } @@ -97,6 +98,7 @@ assert_matches = { workspace = true } curve25519-dalek = { workspace = true } hex = { workspace = true } solana-logger = { workspace = true } +solana-program = { workspace = true, features = ["dev-context-only-utils"] } solana-sdk = { path = ".", features = ["dev-context-only-utils"] } static_assertions = { workspace = true } tiny-bip39 = { workspace = true } diff --git a/sdk/cargo-build-sbf/tests/crates/fail/Cargo.toml b/sdk/cargo-build-sbf/tests/crates/fail/Cargo.toml index bcb610f7e38c47..08d34024383d90 100644 --- a/sdk/cargo-build-sbf/tests/crates/fail/Cargo.toml +++ b/sdk/cargo-build-sbf/tests/crates/fail/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fail" -version = "2.0.0" +version = "2.0.2" description = "Solana SBF test program written in Rust" authors = ["Anza Maintainers "] repository = "https://github.com/anza-xyz/agave" @@ -10,7 +10,7 @@ edition = "2021" publish = false [dependencies] -solana-program = { path = "../../../../program", version = "=2.0.0" } +solana-program = { path = "../../../../program", version = "=2.0.2" } [lib] crate-type = ["cdylib"] diff --git a/sdk/cargo-build-sbf/tests/crates/noop/Cargo.toml b/sdk/cargo-build-sbf/tests/crates/noop/Cargo.toml index 0dad397a2d7b00..ccc5824fbe7c77 100644 --- a/sdk/cargo-build-sbf/tests/crates/noop/Cargo.toml +++ b/sdk/cargo-build-sbf/tests/crates/noop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "noop" -version = "2.0.0" +version = "2.0.2" description = "Solana SBF test program written in Rust" authors = ["Anza Maintainers "] repository = "https://github.com/anza-xyz/agave" @@ -10,7 +10,7 @@ edition = "2021" publish = false [dependencies] -solana-program = { path = "../../../../program", version = "=2.0.0" } +solana-program = { path = "../../../../program", version = "=2.0.2" } [lib] crate-type = ["cdylib"] diff --git a/sdk/macro/src/lib.rs b/sdk/macro/src/lib.rs index 73121b568003cd..9808fad0ccd08a 100644 --- a/sdk/macro/src/lib.rs +++ b/sdk/macro/src/lib.rs @@ -378,34 +378,6 @@ pub fn pubkeys(input: TokenStream) -> TokenStream { TokenStream::from(quote! {#pubkeys}) } -// The normal `wasm_bindgen` macro generates a .bss section which causes the resulting -// SBF program to fail to load, so for now this stub should be used when building for SBF -#[proc_macro_attribute] -pub fn wasm_bindgen_stub(_attr: TokenStream, item: TokenStream) -> TokenStream { - match parse_macro_input!(item as syn::Item) { - syn::Item::Struct(mut item_struct) => { - if let syn::Fields::Named(fields) = &mut item_struct.fields { - // Strip out any `#[wasm_bindgen]` added to struct fields. This is custom - // syntax supplied by the normal `wasm_bindgen` macro. - for field in fields.named.iter_mut() { - field.attrs.retain(|attr| { - !attr - .path() - .segments - .iter() - .any(|segment| segment.ident == "wasm_bindgen") - }); - } - } - quote! { #item_struct } - } - item => { - quote!(#item) - } - } - .into() -} - // Sets padding in structures to zero explicitly. // Otherwise padding could be inconsistent across the network and lead to divergence / consensus failures. #[proc_macro_derive(CloneZeroed)] diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index 7cc2ed2ecc9573..c37cd182bbb869 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -18,13 +18,14 @@ borsh = { workspace = true, optional = true } borsh0-10 = { package = "borsh", version = "0.10.3", optional = true } bs58 = { workspace = true } bv = { workspace = true, features = ["serde"] } -bytemuck = { workspace = true, features = ["derive"] } -itertools = { workspace = true } +bytemuck = { workspace = true } +bytemuck_derive = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } memoffset = { workspace = true } num-derive = { workspace = true } num-traits = { workspace = true, features = ["i128"] } +qualifier_attr = { workspace = true, optional = true } rustversion = { workspace = true } serde = { workspace = true } serde_bytes = { workspace = true } @@ -53,11 +54,9 @@ ark-serialize = { workspace = true } base64 = { workspace = true, features = ["alloc", "std"] } bitflags = { workspace = true } curve25519-dalek = { workspace = true } -itertools = { workspace = true } libsecp256k1 = { workspace = true } num-bigint = { workspace = true } rand = { workspace = true } -wasm-bindgen = { workspace = true } [target.'cfg(not(target_os = "solana"))'.dev-dependencies] arbitrary = { workspace = true, features = ["derive"] } @@ -69,6 +68,7 @@ console_error_panic_hook = { workspace = true } console_log = { workspace = true } getrandom = { workspace = true, features = ["js", "wasm-bindgen"] } js-sys = { workspace = true } +wasm-bindgen = { workspace = true } [target.'cfg(not(target_pointer_width = "64"))'.dependencies] parking_lot = { workspace = true } @@ -77,15 +77,13 @@ parking_lot = { workspace = true } anyhow = { workspace = true } array-bytes = { workspace = true } assert_matches = { workspace = true } +itertools = { workspace = true } serde_json = { workspace = true } static_assertions = { workspace = true } [build-dependencies] rustc_version = { workspace = true } -[target.'cfg(any(unix, windows))'.build-dependencies] -cc = { workspace = true, features = ["jobserver", "parallel"] } - [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -95,4 +93,5 @@ crate-type = ["cdylib", "rlib"] [features] default = ["borsh"] borsh = ["dep:borsh", "dep:borsh0-10"] +dev-context-only-utils = ["dep:qualifier_attr"] frozen-abi = ["dep:solana-frozen-abi", "dep:solana-frozen-abi-macro"] diff --git a/sdk/program/src/alt_bn128/mod.rs b/sdk/program/src/alt_bn128/mod.rs index 4919df3ac845ad..c7e1d8e5d28250 100644 --- a/sdk/program/src/alt_bn128/mod.rs +++ b/sdk/program/src/alt_bn128/mod.rs @@ -4,7 +4,7 @@ pub mod prelude { } use { - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, consts::*, thiserror::Error, }; diff --git a/sdk/program/src/borsh0_9.rs b/sdk/program/src/borsh0_9.rs deleted file mode 100644 index d7d1e97013f898..00000000000000 --- a/sdk/program/src/borsh0_9.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![allow(clippy::arithmetic_side_effects)] -//! Utilities for the [borsh] serialization format, version 0.9. -//! -//! This file is provided for backwards compatibility with types that still use -//! borsh 0.9, even though this crate canonically uses borsh 0.10. -//! -//! [borsh]: https://borsh.io/ -use { - crate::borsh::{ - impl_get_instance_packed_len, impl_get_packed_len_v0, impl_try_from_slice_unchecked, - }, - borsh0_9::maybestd::io, -}; - -impl_get_packed_len_v0!( - borsh0_9, - #[deprecated( - since = "1.17.0", - note = "Please upgrade to Borsh 1.X and use `borsh1::get_packed_len` instead" - )] -); -impl_try_from_slice_unchecked!( - borsh0_9, - io, - #[deprecated( - since = "1.17.0", - note = "Please upgrade to Borsh 1.X and use `borsh1::try_from_slice_unchecked` instead" - )] -); -impl_get_instance_packed_len!( - borsh0_9, - io, - #[deprecated( - since = "1.17.0", - note = "Please upgrade to Borsh 1.X and use `borsh1::get_instance_packed_len` instead" - )] -); - -#[cfg(test)] -#[allow(deprecated)] -mod tests { - use {crate::borsh::impl_tests, borsh0_9::maybestd::io}; - impl_tests!(borsh0_9, io); -} diff --git a/sdk/program/src/bpf_loader_upgradeable.rs b/sdk/program/src/bpf_loader_upgradeable.rs index d0f95ffe166db5..82e9292fde2429 100644 --- a/sdk/program/src/bpf_loader_upgradeable.rs +++ b/sdk/program/src/bpf_loader_upgradeable.rs @@ -82,42 +82,6 @@ impl UpgradeableLoaderState { pub const fn size_of_programdata(program_len: usize) -> usize { Self::size_of_programdata_metadata().saturating_add(program_len) } - - /// Length of a Buffer account's data. - #[deprecated(since = "1.11.0", note = "Please use `size_of_buffer` instead")] - pub fn buffer_len(program_len: usize) -> Result { - Ok(Self::size_of_buffer(program_len)) - } - - /// Offset into the Buffer account's data of the program bits. - #[deprecated( - since = "1.11.0", - note = "Please use `size_of_buffer_metadata` instead" - )] - pub fn buffer_data_offset() -> Result { - Ok(Self::size_of_buffer_metadata()) - } - - /// Length of a Program account's data. - #[deprecated(since = "1.11.0", note = "Please use `size_of_program` instead")] - pub fn program_len() -> Result { - Ok(Self::size_of_program()) - } - - /// Length of a ProgramData account's data. - #[deprecated(since = "1.11.0", note = "Please use `size_of_programdata` instead")] - pub fn programdata_len(program_len: usize) -> Result { - Ok(Self::size_of_programdata(program_len)) - } - - /// Offset into the ProgramData account's data of the program bits. - #[deprecated( - since = "1.11.0", - note = "Please use `size_of_programdata_metadata` instead" - )] - pub fn programdata_data_offset() -> Result { - Ok(Self::size_of_programdata_metadata()) - } } /// Returns the program data address for a program ID @@ -425,24 +389,6 @@ mod tests { assert_eq!(UpgradeableLoaderState::size_of_program() as u64, size); } - #[test] - #[allow(deprecated)] - fn test_account_lengths() { - assert_eq!( - 4, - serialized_size(&UpgradeableLoaderState::Uninitialized).unwrap() - ); - assert_eq!(36, UpgradeableLoaderState::program_len().unwrap()); - assert_eq!( - 45, - UpgradeableLoaderState::programdata_data_offset().unwrap() - ); - assert_eq!( - 45 + 42, - UpgradeableLoaderState::programdata_len(42).unwrap() - ); - } - fn assert_is_instruction( is_instruction_fn: F, expected_instruction: UpgradeableLoaderInstruction, diff --git a/sdk/program/src/clock.rs b/sdk/program/src/clock.rs index e19c4c84486ced..5cf609d3000c26 100644 --- a/sdk/program/src/clock.rs +++ b/sdk/program/src/clock.rs @@ -33,10 +33,6 @@ static_assertions::const_assert_eq!(MS_PER_TICK, 6); /// The number of milliseconds per tick (6). pub const MS_PER_TICK: u64 = 1000 / DEFAULT_TICKS_PER_SECOND; -#[deprecated(since = "1.15.0", note = "Please use DEFAULT_MS_PER_SLOT instead")] -/// The expected duration of a slot (400 milliseconds). -pub const SLOT_MS: u64 = DEFAULT_MS_PER_SLOT; - // At 160 ticks/s, 64 ticks per slot implies that leader rotation and voting will happen // every 400 ms. A fast voting cadence ensures faster finality and convergence pub const DEFAULT_TICKS_PER_SLOT: u64 = 64; diff --git a/sdk/program/src/example_mocks.rs b/sdk/program/src/example_mocks.rs index ebde291ced11bd..b528812e36f6b3 100644 --- a/sdk/program/src/example_mocks.rs +++ b/sdk/program/src/example_mocks.rs @@ -274,44 +274,5 @@ pub mod solana_sdk { } } - #[deprecated( - since = "1.17.0", - note = "Please use `solana_sdk::address_lookup_table` instead" - )] - pub use crate::address_lookup_table as address_lookup_table_account; -} - -#[deprecated( - since = "1.17.0", - note = "Please use `solana_sdk::address_lookup_table` instead" -)] -pub mod solana_address_lookup_table_program { - pub use crate::address_lookup_table::program::{check_id, id, ID}; - - pub mod state { - use { - crate::{instruction::InstructionError, pubkey::Pubkey}, - std::borrow::Cow, - }; - - pub struct AddressLookupTable<'a> { - pub addresses: Cow<'a, [Pubkey]>, - } - - impl<'a> AddressLookupTable<'a> { - pub fn serialize_for_tests(self) -> Result, InstructionError> { - let mut data = vec![]; - self.addresses.iter().for_each(|address| { - data.extend_from_slice(address.as_ref()); - }); - Ok(data) - } - - pub fn deserialize(data: &'a [u8]) -> Result, InstructionError> { - Ok(Self { - addresses: Cow::Borrowed(bytemuck::try_cast_slice(data).unwrap()), - }) - } - } - } + pub use crate::address_lookup_table; } diff --git a/sdk/program/src/fee_calculator.rs b/sdk/program/src/fee_calculator.rs index 361e00c98b6b47..5d753e4acaed3a 100644 --- a/sdk/program/src/fee_calculator.rs +++ b/sdk/program/src/fee_calculator.rs @@ -1,10 +1,7 @@ //! Calculation of transaction fees. #![allow(clippy::arithmetic_side_effects)] -use { - crate::{clock::DEFAULT_MS_PER_SLOT, ed25519_program, message::Message, secp256k1_program}, - log::*, -}; +use {crate::clock::DEFAULT_MS_PER_SLOT, log::*}; #[repr(C)] #[cfg_attr(feature = "frozen-abi", derive(AbiExample))] @@ -24,29 +21,6 @@ impl FeeCalculator { lamports_per_signature, } } - - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - pub fn calculate_fee(&self, message: &Message) -> u64 { - let mut num_signatures: u64 = 0; - for instruction in &message.instructions { - let program_index = instruction.program_id_index as usize; - // Message may not be sanitized here - if program_index < message.account_keys.len() { - let id = message.account_keys[program_index]; - if (secp256k1_program::check_id(&id) || ed25519_program::check_id(&id)) - && !instruction.data.is_empty() - { - num_signatures += instruction.data[0] as u64; - } - } - } - - self.lamports_per_signature - * (u64::from(message.header.num_required_signatures) + num_signatures) - } } #[cfg_attr(feature = "frozen-abi", derive(AbiExample))] @@ -188,10 +162,7 @@ impl FeeRateGovernor { #[cfg(test)] mod tests { - use { - super::*, - crate::{pubkey::Pubkey, system_instruction}, - }; + use super::*; #[test] fn test_fee_rate_governor_burn() { @@ -205,64 +176,6 @@ mod tests { assert_eq!(fee_rate_governor.burn(2), (0, 2)); } - #[test] - #[allow(deprecated)] - fn test_fee_calculator_calculate_fee() { - // Default: no fee. - let message = Message::default(); - assert_eq!(FeeCalculator::default().calculate_fee(&message), 0); - - // No signature, no fee. - assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0); - - // One signature, a fee. - let pubkey0 = Pubkey::from([0; 32]); - let pubkey1 = Pubkey::from([1; 32]); - let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); - let message = Message::new(&[ix0], Some(&pubkey0)); - assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2); - - // Two signatures, double the fee. - let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); - let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1); - let message = Message::new(&[ix0, ix1], Some(&pubkey0)); - assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4); - } - - #[test] - #[allow(deprecated)] - fn test_fee_calculator_calculate_fee_secp256k1() { - use crate::instruction::Instruction; - let pubkey0 = Pubkey::from([0; 32]); - let pubkey1 = Pubkey::from([1; 32]); - let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); - let mut secp_instruction = Instruction { - program_id: crate::secp256k1_program::id(), - accounts: vec![], - data: vec![], - }; - let mut secp_instruction2 = Instruction { - program_id: crate::secp256k1_program::id(), - accounts: vec![], - data: vec![1], - }; - - let message = Message::new( - &[ - ix0.clone(), - secp_instruction.clone(), - secp_instruction2.clone(), - ], - Some(&pubkey0), - ); - assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 2); - - secp_instruction.data = vec![0]; - secp_instruction2.data = vec![10]; - let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0)); - assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 11); - } - #[test] fn test_fee_rate_governor_derived_default() { solana_logger::setup(); diff --git a/sdk/program/src/hash.rs b/sdk/program/src/hash.rs index 6652c37001cb1a..eb6b467de6935e 100644 --- a/sdk/program/src/hash.rs +++ b/sdk/program/src/hash.rs @@ -3,11 +3,13 @@ //! [SHA-256]: https://en.wikipedia.org/wiki/SHA-2 //! [`Hash`]: struct@Hash +#[cfg(target_arch = "wasm32")] +use crate::wasm_bindgen; #[cfg(feature = "borsh")] use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use { - crate::{sanitize::Sanitize, wasm_bindgen}, - bytemuck::{Pod, Zeroable}, + crate::sanitize::Sanitize, + bytemuck_derive::{Pod, Zeroable}, sha2::{Digest, Sha256}, std::{convert::TryFrom, fmt, mem, str::FromStr}, thiserror::Error, @@ -28,7 +30,7 @@ const MAX_BASE58_LEN: usize = 44; /// [blake3]: https://github.com/BLAKE3-team/BLAKE3 /// [`blake3`]: crate::blake3 /// [`Message::hash`]: crate::message::Message::hash -#[wasm_bindgen] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[cfg_attr(feature = "frozen-abi", derive(AbiExample))] #[cfg_attr( feature = "borsh", diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 19be50d9b693b9..9603431d6d2146 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -13,10 +13,12 @@ #![allow(clippy::arithmetic_side_effects)] +#[cfg(target_arch = "wasm32")] +use crate::wasm_bindgen; #[cfg(feature = "borsh")] use borsh::BorshSerialize; use { - crate::{pubkey::Pubkey, sanitize::Sanitize, short_vec, wasm_bindgen}, + crate::{pubkey::Pubkey, sanitize::Sanitize, short_vec}, bincode::serialize, serde::Serialize, thiserror::Error, @@ -325,16 +327,27 @@ pub enum InstructionError { /// Programs may require signatures from some accounts, in which case they /// should be specified as signers during `Instruction` construction. The /// program must still validate during execution that the account is a signer. -#[wasm_bindgen] +#[cfg(not(target_arch = "wasm32"))] #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct Instruction { /// Pubkey of the program that executes this instruction. - #[wasm_bindgen(skip)] pub program_id: Pubkey, /// Metadata describing accounts that should be passed to the program. - #[wasm_bindgen(skip)] pub accounts: Vec, /// Opaque data passed to the program for its own interpretation. + pub data: Vec, +} + +/// wasm-bindgen version of the Instruction struct. +/// This duplication is required until https://github.com/rustwasm/wasm-bindgen/issues/3671 +/// is fixed. This must not diverge from the regular non-wasm Instruction struct. +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct Instruction { + #[wasm_bindgen(skip)] + pub program_id: Pubkey, + #[wasm_bindgen(skip)] + pub accounts: Vec, #[wasm_bindgen(skip)] pub data: Vec, } @@ -504,14 +517,6 @@ impl Instruction { data: data.to_vec(), } } - - #[deprecated( - since = "1.6.0", - note = "Please use another Instruction constructor instead, such as `Instruction::new_with_borsh`" - )] - pub fn new(program_id: Pubkey, data: &T, accounts: Vec) -> Self { - Self::new_with_bincode(program_id, data, accounts) - } } /// Addition that returns [`InstructionError::InsufficientFunds`] on overflow. diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 017ac3a067744d..fc6a89976b37df 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -537,20 +537,7 @@ pub mod sysvar; pub mod vote; pub mod wasm; -#[deprecated( - since = "1.17.0", - note = "Please use `solana_sdk::address_lookup_table::AddressLookupTableAccount` instead" -)] -pub mod address_lookup_table_account { - pub use crate::address_lookup_table::AddressLookupTableAccount; -} - -#[cfg(target_os = "solana")] -pub use solana_sdk_macro::wasm_bindgen_stub as wasm_bindgen; -/// Re-export of [wasm-bindgen]. -/// -/// [wasm-bindgen]: https://rustwasm.github.io/docs/wasm-bindgen/ -#[cfg(not(target_os = "solana"))] +#[cfg(target_arch = "wasm32")] pub use wasm_bindgen::prelude::wasm_bindgen; /// The [config native program][np]. diff --git a/sdk/program/src/log.rs b/sdk/program/src/log.rs index 4f3463f8dc1201..5febb4dbc5ad64 100644 --- a/sdk/program/src/log.rs +++ b/sdk/program/src/log.rs @@ -35,24 +35,6 @@ use crate::account_info::AccountInfo; -/// Print a message to the log. -#[macro_export] -#[deprecated(since = "1.4.14", note = "Please use `msg` macro instead")] -macro_rules! info { - ($msg:expr) => { - $crate::log::sol_log($msg) - }; - ($arg1:expr, $arg2:expr, $arg3:expr, $arg4:expr, $arg5:expr) => { - $crate::log::sol_log_64( - $arg1 as u64, - $arg2 as u64, - $arg3 as u64, - $arg4 as u64, - $arg5 as u64, - ) - }; -} - /// Print a message to the log. /// /// Supports simple strings as well as Rust [format strings][fs]. When passed a diff --git a/sdk/program/src/message/compiled_keys.rs b/sdk/program/src/message/compiled_keys.rs index 7e9b19a10591e1..a9964c33448be2 100644 --- a/sdk/program/src/message/compiled_keys.rs +++ b/sdk/program/src/message/compiled_keys.rs @@ -1,6 +1,6 @@ #[cfg(not(target_os = "solana"))] use crate::{ - address_lookup_table_account::AddressLookupTableAccount, + address_lookup_table::AddressLookupTableAccount, message::v0::{LoadedAddresses, MessageAddressTableLookup}, }; use { diff --git a/sdk/program/src/message/legacy.rs b/sdk/program/src/message/legacy.rs index b9dc518e028ed0..3296af72c699ba 100644 --- a/sdk/program/src/message/legacy.rs +++ b/sdk/program/src/message/legacy.rs @@ -11,6 +11,8 @@ #![allow(clippy::arithmetic_side_effects)] +#[cfg(target_arch = "wasm32")] +use crate::wasm_bindgen; #[allow(deprecated)] pub use builtins::{BUILTIN_PROGRAMS_KEYS, MAYBE_BUILTIN_KEY_OR_SYSVAR}; use { @@ -21,7 +23,7 @@ use { message::{compiled_keys::CompiledKeys, MessageHeader}, pubkey::Pubkey, sanitize::{Sanitize, SanitizeError}, - short_vec, system_instruction, system_program, sysvar, wasm_bindgen, + short_vec, system_instruction, system_program, sysvar, }, std::{collections::HashSet, convert::TryFrom, str::FromStr}, }; @@ -117,7 +119,7 @@ fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec Vec, @@ -141,6 +141,33 @@ pub struct Message { /// Programs that will be executed in sequence and committed in one atomic transaction if all /// succeed. + #[serde(with = "short_vec")] + pub instructions: Vec, +} + +/// wasm-bindgen version of the Message struct. +/// This duplication is required until https://github.com/rustwasm/wasm-bindgen/issues/3671 +/// is fixed. This must not diverge from the regular non-wasm Message struct. +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +#[cfg_attr( + feature = "frozen-abi", + frozen_abi(digest = "2KnLEqfLcTBQqitE22Pp8JYkaqVVbAkGbCfdeHoyxcAU"), + derive(AbiExample) +)] +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Message { + #[wasm_bindgen(skip)] + pub header: MessageHeader, + + #[wasm_bindgen(skip)] + #[serde(with = "short_vec")] + pub account_keys: Vec, + + /// The id of a recent ledger entry. + pub recent_blockhash: Hash, + #[wasm_bindgen(skip)] #[serde(with = "short_vec")] pub instructions: Vec, @@ -635,29 +662,6 @@ impl Message { i < self.header.num_required_signatures as usize } - #[deprecated] - pub fn get_account_keys_by_lock_type(&self) -> (Vec<&Pubkey>, Vec<&Pubkey>) { - let mut writable_keys = vec![]; - let mut readonly_keys = vec![]; - for (i, key) in self.account_keys.iter().enumerate() { - if self.is_maybe_writable(i, None) { - writable_keys.push(key); - } else { - readonly_keys.push(key); - } - } - (writable_keys, readonly_keys) - } - - #[deprecated] - pub fn deserialize_instruction( - index: usize, - data: &[u8], - ) -> Result { - #[allow(deprecated)] - sysvar::instructions::load_instruction_at(index, data) - } - pub fn signer_keys(&self) -> Vec<&Pubkey> { // Clamp in case we're working on un-`sanitize()`ed input let last_key = self @@ -880,36 +884,6 @@ mod tests { assert!(!message.is_account_maybe_reserved(2, None)); } - #[test] - fn test_get_account_keys_by_lock_type() { - let program_id = Pubkey::default(); - let id0 = Pubkey::new_unique(); - let id1 = Pubkey::new_unique(); - let id2 = Pubkey::new_unique(); - let id3 = Pubkey::new_unique(); - let message = Message::new( - &[ - Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]), - Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id1, true)]), - Instruction::new_with_bincode( - program_id, - &0, - vec![AccountMeta::new_readonly(id2, false)], - ), - Instruction::new_with_bincode( - program_id, - &0, - vec![AccountMeta::new_readonly(id3, true)], - ), - ], - Some(&id1), - ); - assert_eq!( - message.get_account_keys_by_lock_type(), - (vec![&id1, &id0], vec![&id3, &program_id, &id2]) - ); - } - #[test] fn test_program_ids() { let key0 = Pubkey::new_unique(); diff --git a/sdk/program/src/message/versions/v0/mod.rs b/sdk/program/src/message/versions/v0/mod.rs index 41e8ec34494c71..a7cb6ce41d3a33 100644 --- a/sdk/program/src/message/versions/v0/mod.rs +++ b/sdk/program/src/message/versions/v0/mod.rs @@ -12,7 +12,7 @@ pub use loaded::*; use { crate::{ - address_lookup_table_account::AddressLookupTableAccount, + address_lookup_table::AddressLookupTableAccount, bpf_loader_upgradeable, hash::Hash, instruction::{CompiledInstruction, Instruction}, @@ -200,7 +200,7 @@ impl Message { /// use solana_rpc_client::rpc_client::RpcClient; /// use solana_program::address_lookup_table::{self, state::{AddressLookupTable, LookupTableMeta}}; /// use solana_sdk::{ - /// address_lookup_table_account::AddressLookupTableAccount, + /// address_lookup_table::AddressLookupTableAccount, /// instruction::{AccountMeta, Instruction}, /// message::{VersionedMessage, v0}, /// pubkey::Pubkey, diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index 7fd31358090118..e06f6b1d201a42 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -8,7 +8,6 @@ use { program_error::UNSUPPORTED_SYSVAR, pubkey::Pubkey, }, base64::{prelude::BASE64_STANDARD, Engine}, - itertools::Itertools, std::sync::{Arc, RwLock}, }; @@ -114,7 +113,11 @@ pub trait SyscallStubs: Sync + Send { fn sol_log_data(&self, fields: &[&[u8]]) { println!( "data: {}", - fields.iter().map(|v| BASE64_STANDARD.encode(v)).join(" ") + fields + .iter() + .map(|v| BASE64_STANDARD.encode(v)) + .collect::>() + .join(" ") ); } fn sol_get_processed_sibling_instruction(&self, _index: usize) -> Option { diff --git a/sdk/program/src/pubkey.rs b/sdk/program/src/pubkey.rs index 508ec483bbe647..3ade0b1a4b2f69 100644 --- a/sdk/program/src/pubkey.rs +++ b/sdk/program/src/pubkey.rs @@ -2,13 +2,15 @@ #![allow(clippy::arithmetic_side_effects)] +#[cfg(target_arch = "wasm32")] +use crate::wasm_bindgen; #[cfg(test)] use arbitrary::Arbitrary; #[cfg(feature = "borsh")] use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use { - crate::{decode_error::DecodeError, hash::hashv, wasm_bindgen}, - bytemuck::{Pod, Zeroable}, + crate::{decode_error::DecodeError, hash::hashv}, + bytemuck_derive::{Pod, Zeroable}, num_derive::{FromPrimitive, ToPrimitive}, std::{ convert::{Infallible, TryFrom}, @@ -68,7 +70,7 @@ impl From for PubkeyError { /// [ed25519]: https://ed25519.cr.yp.to/ /// [pdas]: https://solana.com/docs/core/cpi#program-derived-addresses /// [`Keypair`]: https://docs.rs/solana-sdk/latest/solana_sdk/signer/keypair/struct.Keypair.html -#[wasm_bindgen] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[repr(transparent)] #[cfg_attr(feature = "frozen-abi", derive(AbiExample))] #[cfg_attr( @@ -178,25 +180,10 @@ pub fn bytes_are_curve_point>(_bytes: T) -> bool { } impl Pubkey { - #[deprecated( - since = "1.14.14", - note = "Please use 'Pubkey::from' or 'Pubkey::try_from' instead" - )] - pub fn new(pubkey_vec: &[u8]) -> Self { - Self::try_from(pubkey_vec).expect("Slice must be the same length as a Pubkey") - } - pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self { Self(pubkey_array) } - #[deprecated(since = "1.3.9", note = "Please use 'Pubkey::new_unique' instead")] - #[cfg(not(target_os = "solana"))] - pub fn new_rand() -> Self { - // Consider removing Pubkey::new_rand() entirely in the v1.5 or v1.6 timeframe - Pubkey::from(rand::random::<[u8; 32]>()) - } - /// unique Pubkey for tests and benchmarks. pub fn new_unique() -> Self { use crate::atomic_u64::AtomicU64; diff --git a/sdk/program/src/sysvar/instructions.rs b/sdk/program/src/sysvar/instructions.rs index 249b11b5f9452f..cf74f3552e59de 100644 --- a/sdk/program/src/sysvar/instructions.rs +++ b/sdk/program/src/sysvar/instructions.rs @@ -37,6 +37,8 @@ use crate::{ sanitize::SanitizeError, serialize_utils::{read_pubkey, read_slice, read_u16, read_u8}, }; +#[cfg(feature = "dev-context-only-utils")] +use qualifier_attr::qualifiers; #[cfg(not(target_os = "solana"))] use { crate::serialize_utils::{append_slice, append_u16, append_u8}, @@ -147,11 +149,10 @@ fn serialize_instructions(instructions: &[BorrowedInstruction]) -> Vec { /// `Transaction`. /// /// `data` is the instructions sysvar account data. -#[deprecated( - since = "1.8.0", - note = "Unsafe because the sysvar accounts address is not checked, please use `load_current_index_checked` instead" -)] -pub fn load_current_index(data: &[u8]) -> u16 { +/// +/// Unsafe because the sysvar accounts address is not checked; only used +/// internally after such a check. +fn load_current_index(data: &[u8]) -> u16 { let mut instr_fixed_data = [0u8; 2]; let len = data.len(); instr_fixed_data.copy_from_slice(&data[len - 2..len]); @@ -172,10 +173,8 @@ pub fn load_current_index_checked( } let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?; - let mut instr_fixed_data = [0u8; 2]; - let len = instruction_sysvar.len(); - instr_fixed_data.copy_from_slice(&instruction_sysvar[len - 2..len]); - Ok(u16::from_le_bytes(instr_fixed_data)) + let index = load_current_index(&instruction_sysvar); + Ok(index) } /// Store the current `Instruction`'s index in the instructions sysvar data. @@ -232,11 +231,11 @@ fn deserialize_instruction(index: usize, data: &[u8]) -> Result Result { +/// +/// Unsafe because the sysvar accounts address is not checked; only used +/// internally after such a check. +#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))] +fn load_instruction_at(index: usize, data: &[u8]) -> Result { deserialize_instruction(index, data) } @@ -255,7 +254,7 @@ pub fn load_instruction_at_checked( } let instruction_sysvar = instruction_sysvar_account_info.try_borrow_data()?; - deserialize_instruction(index, &instruction_sysvar).map_err(|err| match err { + load_instruction_at(index, &instruction_sysvar).map_err(|err| match err { SanitizeError::IndexOutOfBounds => ProgramError::InvalidArgument, _ => ProgramError::InvalidInstructionData, }) @@ -276,13 +275,11 @@ pub fn get_instruction_relative( } let instruction_sysvar = instruction_sysvar_account_info.data.borrow(); - #[allow(deprecated)] let current_index = load_current_index(&instruction_sysvar) as i64; let index = current_index.saturating_add(index_relative_to_current); if index < 0 { return Err(ProgramError::InvalidArgument); } - #[allow(deprecated)] load_instruction_at( current_index.saturating_add(index_relative_to_current) as usize, &instruction_sysvar, diff --git a/sdk/program/src/sysvar/slot_hashes.rs b/sdk/program/src/sysvar/slot_hashes.rs index 9d66fab235b585..4a1904039d7d8e 100644 --- a/sdk/program/src/sysvar/slot_hashes.rs +++ b/sdk/program/src/sysvar/slot_hashes.rs @@ -55,7 +55,7 @@ use { slot_hashes::MAX_ENTRIES, sysvar::{get_sysvar, Sysvar, SysvarId}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; crate::declare_sysvar_id!("SysvarS1otHashes111111111111111111111111111", SlotHashes); diff --git a/sdk/program/src/vote/instruction.rs b/sdk/program/src/vote/instruction.rs index 2c4cb4157f5721..b5d43b5c24c602 100644 --- a/sdk/program/src/vote/instruction.rs +++ b/sdk/program/src/vote/instruction.rs @@ -257,49 +257,6 @@ impl<'a> Default for CreateVoteAccountConfig<'a> { } } -#[deprecated( - since = "1.16.0", - note = "Please use `create_account_with_config()` instead." -)] -pub fn create_account( - from_pubkey: &Pubkey, - vote_pubkey: &Pubkey, - vote_init: &VoteInit, - lamports: u64, -) -> Vec { - create_account_with_config( - from_pubkey, - vote_pubkey, - vote_init, - lamports, - CreateVoteAccountConfig::default(), - ) -} - -#[deprecated( - since = "1.16.0", - note = "Please use `create_account_with_config()` instead." -)] -pub fn create_account_with_seed( - from_pubkey: &Pubkey, - vote_pubkey: &Pubkey, - base: &Pubkey, - seed: &str, - vote_init: &VoteInit, - lamports: u64, -) -> Vec { - create_account_with_config( - from_pubkey, - vote_pubkey, - vote_init, - lamports, - CreateVoteAccountConfig { - with_seed: Some((base, seed)), - ..CreateVoteAccountConfig::default() - }, - ) -} - pub fn create_account_with_config( from_pubkey: &Pubkey, vote_pubkey: &Pubkey, diff --git a/sdk/src/account.rs b/sdk/src/account.rs index c0f3d783c14038..0541e1f8d9ce44 100644 --- a/sdk/src/account.rs +++ b/sdk/src/account.rs @@ -675,15 +675,6 @@ impl AccountSharedData { pub type InheritableAccountFields = (u64, Epoch); pub const DUMMY_INHERITABLE_ACCOUNT_FIELDS: InheritableAccountFields = (1, INITIAL_RENT_EPOCH); -/// Create an `Account` from a `Sysvar`. -#[deprecated( - since = "1.5.17", - note = "Please use `create_account_for_test` instead" -)] -pub fn create_account(sysvar: &S, lamports: u64) -> Account { - create_account_with_fields(sysvar, (lamports, INITIAL_RENT_EPOCH)) -} - pub fn create_account_with_fields( sysvar: &S, (lamports, rent_epoch): InheritableAccountFields, @@ -700,17 +691,6 @@ pub fn create_account_for_test(sysvar: &S) -> Account { } /// Create an `Account` from a `Sysvar`. -#[deprecated( - since = "1.5.17", - note = "Please use `create_account_shared_data_for_test` instead" -)] -pub fn create_account_shared_data(sysvar: &S, lamports: u64) -> AccountSharedData { - AccountSharedData::from(create_account_with_fields( - sysvar, - (lamports, INITIAL_RENT_EPOCH), - )) -} - pub fn create_account_shared_data_with_fields( sysvar: &S, fields: InheritableAccountFields, diff --git a/sdk/src/client.rs b/sdk/src/client.rs index f9e435c81d0b66..185b9aeeb0d40b 100644 --- a/sdk/src/client.rs +++ b/sdk/src/client.rs @@ -14,7 +14,6 @@ use crate::{ clock::Slot, commitment_config::CommitmentConfig, epoch_info::EpochInfo, - fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, instruction::Instruction, message::Message, @@ -82,35 +81,6 @@ pub trait SyncClient { fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> Result; - /// Get recent blockhash - #[deprecated(since = "1.9.0", note = "Please use `get_latest_blockhash` instead")] - fn get_recent_blockhash(&self) -> Result<(Hash, FeeCalculator)>; - - /// Get recent blockhash. Uses explicit commitment configuration. - #[deprecated( - since = "1.9.0", - note = "Please use `get_latest_blockhash_with_commitment` and `get_latest_blockhash_with_commitment` instead" - )] - fn get_recent_blockhash_with_commitment( - &self, - commitment_config: CommitmentConfig, - ) -> Result<(Hash, FeeCalculator, Slot)>; - - /// Get `Some(FeeCalculator)` associated with `blockhash` if it is still in - /// the BlockhashQueue`, otherwise `None` - #[deprecated( - since = "1.9.0", - note = "Please use `get_fee_for_message` or `is_blockhash_valid` instead" - )] - fn get_fee_calculator_for_blockhash(&self, blockhash: &Hash) -> Result>; - - /// Get recent fee rate governor - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - fn get_fee_rate_governor(&self) -> Result; - /// Get signature status. fn get_signature_status( &self, @@ -151,12 +121,6 @@ pub trait SyncClient { /// Poll to confirm a transaction. fn poll_for_signature(&self, signature: &Signature) -> Result<()>; - #[deprecated( - since = "1.9.0", - note = "Please do not use, will no longer be available in the future" - )] - fn get_new_blockhash(&self, blockhash: &Hash) -> Result<(Hash, FeeCalculator)>; - /// Get last known blockhash fn get_latest_blockhash(&self) -> Result; diff --git a/sdk/src/commitment_config.rs b/sdk/src/commitment_config.rs index f0068659f4d7b4..7aca56b8947dbd 100644 --- a/sdk/src/commitment_config.rs +++ b/sdk/src/commitment_config.rs @@ -1,6 +1,5 @@ //! Definitions of commitment levels. -#![allow(deprecated)] #![cfg(feature = "full")] use {std::str::FromStr, thiserror::Error}; @@ -12,56 +11,6 @@ pub struct CommitmentConfig { } impl CommitmentConfig { - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentConfig::processed() instead" - )] - pub fn recent() -> Self { - Self { - commitment: CommitmentLevel::Recent, - } - } - - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentConfig::finalized() instead" - )] - pub fn max() -> Self { - Self { - commitment: CommitmentLevel::Max, - } - } - - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentConfig::finalized() instead" - )] - pub fn root() -> Self { - Self { - commitment: CommitmentLevel::Root, - } - } - - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentConfig::confirmed() instead" - )] - pub fn single() -> Self { - Self { - commitment: CommitmentLevel::Single, - } - } - - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentConfig::confirmed() instead" - )] - pub fn single_gossip() -> Self { - Self { - commitment: CommitmentLevel::SingleGossip, - } - } - pub const fn finalized() -> Self { Self { commitment: CommitmentLevel::Finalized, @@ -89,37 +38,27 @@ impl CommitmentConfig { } pub fn is_finalized(&self) -> bool { - matches!( - &self.commitment, - CommitmentLevel::Finalized | CommitmentLevel::Max | CommitmentLevel::Root - ) + self.commitment == CommitmentLevel::Finalized } pub fn is_confirmed(&self) -> bool { - matches!( - &self.commitment, - CommitmentLevel::Confirmed | CommitmentLevel::SingleGossip | CommitmentLevel::Single - ) + self.commitment == CommitmentLevel::Confirmed } pub fn is_processed(&self) -> bool { - matches!( - &self.commitment, - CommitmentLevel::Processed | CommitmentLevel::Recent - ) + self.commitment == CommitmentLevel::Processed } pub fn is_at_least_confirmed(&self) -> bool { self.is_confirmed() || self.is_finalized() } + #[deprecated( + since = "2.0.2", + note = "Returns self. Please do not use. Will be removed in the future." + )] pub fn use_deprecated_commitment(commitment: CommitmentConfig) -> Self { - match commitment.commitment { - CommitmentLevel::Finalized => CommitmentConfig::max(), - CommitmentLevel::Confirmed => CommitmentConfig::single_gossip(), - CommitmentLevel::Processed => CommitmentConfig::recent(), - _ => commitment, - } + commitment } } @@ -138,48 +77,6 @@ impl FromStr for CommitmentConfig { /// finalized. When querying the ledger state, use lower levels of commitment to report progress and higher /// levels to ensure state changes will not be rolled back. pub enum CommitmentLevel { - /// (DEPRECATED) The highest slot having reached max vote lockout, as recognized by a supermajority of the cluster. - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentLevel::Finalized instead" - )] - Max, - - /// (DEPRECATED) The highest slot of the heaviest fork. Ledger state at this slot is not derived from a finalized - /// block, but if multiple forks are present, is from the fork the validator believes is most likely - /// to finalize. - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentLevel::Processed instead" - )] - Recent, - - /// (DEPRECATED) The highest slot having reached max vote lockout. - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentLevel::Finalized instead" - )] - Root, - - /// (DEPRECATED) The highest slot having reached 1 confirmation by supermajority of the cluster. - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentLevel::Confirmed instead" - )] - Single, - - /// (DEPRECATED) The highest slot that has been voted on by supermajority of the cluster - /// This differs from `single` in that: - /// 1) It incorporates votes from gossip and replay. - /// 2) It does not count votes on descendants of a block, only direct votes on that block. - /// 3) This confirmation level also upholds "optimistic confirmation" guarantees in - /// release 1.3 and onwards. - #[deprecated( - since = "1.5.5", - note = "Please use CommitmentLevel::Confirmed instead" - )] - SingleGossip, - /// The highest slot of the heaviest fork processed by the node. Ledger state at this slot is /// not derived from a confirmed or finalized block, but if multiple forks are present, is from /// the fork the validator believes is most likely to finalize. @@ -207,11 +104,6 @@ impl FromStr for CommitmentLevel { fn from_str(s: &str) -> Result { match s { - "max" => Ok(CommitmentLevel::Max), - "recent" => Ok(CommitmentLevel::Recent), - "root" => Ok(CommitmentLevel::Root), - "single" => Ok(CommitmentLevel::Single), - "singleGossip" => Ok(CommitmentLevel::SingleGossip), "processed" => Ok(CommitmentLevel::Processed), "confirmed" => Ok(CommitmentLevel::Confirmed), "finalized" => Ok(CommitmentLevel::Finalized), @@ -223,11 +115,6 @@ impl FromStr for CommitmentLevel { impl std::fmt::Display for CommitmentLevel { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let s = match self { - CommitmentLevel::Max => "max", - CommitmentLevel::Recent => "recent", - CommitmentLevel::Root => "root", - CommitmentLevel::Single => "single", - CommitmentLevel::SingleGossip => "singleGossip", CommitmentLevel::Processed => "processed", CommitmentLevel::Confirmed => "confirmed", CommitmentLevel::Finalized => "finalized", diff --git a/sdk/src/ed25519_instruction.rs b/sdk/src/ed25519_instruction.rs index 64c3de083fc0e5..10ae533f478171 100644 --- a/sdk/src/ed25519_instruction.rs +++ b/sdk/src/ed25519_instruction.rs @@ -6,7 +6,8 @@ use { crate::{feature_set::FeatureSet, instruction::Instruction, precompiles::PrecompileError}, - bytemuck::{bytes_of, Pod, Zeroable}, + bytemuck::bytes_of, + bytemuck_derive::{Pod, Zeroable}, ed25519_dalek::{ed25519::signature::Signature, Signer, Verifier}, }; diff --git a/sdk/src/entrypoint.rs b/sdk/src/entrypoint.rs index e83bdf0575b1f0..38c85e00af958a 100644 --- a/sdk/src/entrypoint.rs +++ b/sdk/src/entrypoint.rs @@ -5,30 +5,3 @@ //! [`bpf_loader`]: crate::bpf_loader pub use solana_program::entrypoint::*; - -#[macro_export] -#[deprecated( - since = "1.4.3", - note = "use solana_program::entrypoint::entrypoint instead" -)] -macro_rules! entrypoint { - ($process_instruction:ident) => { - #[cfg(all(not(feature = "custom-heap"), not(test)))] - #[global_allocator] - static A: $crate::entrypoint::BumpAllocator = $crate::entrypoint::BumpAllocator { - start: $crate::entrypoint::HEAP_START_ADDRESS, - len: $crate::entrypoint::HEAP_LENGTH, - }; - - /// # Safety - #[no_mangle] - pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { - let (program_id, accounts, instruction_data) = - unsafe { $crate::entrypoint::deserialize(input) }; - match $process_instruction(&program_id, &accounts, &instruction_data) { - Ok(()) => $crate::entrypoint::SUCCESS, - Err(error) => error.into(), - } - } - }; -} diff --git a/sdk/src/entrypoint_deprecated.rs b/sdk/src/entrypoint_deprecated.rs index c75b9c47a57a49..443a9bc0893f12 100644 --- a/sdk/src/entrypoint_deprecated.rs +++ b/sdk/src/entrypoint_deprecated.rs @@ -8,23 +8,3 @@ //! [`bpf_loader_deprecated`]: crate::bpf_loader_deprecated pub use solana_program::entrypoint_deprecated::*; - -#[macro_export] -#[deprecated( - since = "1.4.3", - note = "use solana_program::entrypoint::entrypoint instead" -)] -macro_rules! entrypoint_deprecated { - ($process_instruction:ident) => { - /// # Safety - #[no_mangle] - pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { - let (program_id, accounts, instruction_data) = - unsafe { $crate::entrypoint_deprecated::deserialize(input) }; - match $process_instruction(&program_id, &accounts, &instruction_data) { - Ok(()) => $crate::entrypoint_deprecated::SUCCESS, - Err(error) => error.into(), - } - } - }; -} diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index e44f83d0db7a4a..e4970cf8cda0a3 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -824,6 +824,10 @@ pub mod migrate_address_lookup_table_program_to_core_bpf { solana_sdk::declare_id!("C97eKZygrkU4JxJsZdjgbUY7iQR7rKTr4NyDWo2E5pRm"); } +pub mod zk_elgamal_proof_program_enabled { + solana_sdk::declare_id!("zkhiy5oLowR7HY4zogXjCjeMXyruLqBwSWH21qcFtnv"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -1025,6 +1029,7 @@ lazy_static! { (migrate_config_program_to_core_bpf::id(), "Migrate Config program to Core BPF #1378"), (enable_get_epoch_stake_syscall::id(), "Enable syscall: sol_get_epoch_stake #884"), (migrate_address_lookup_table_program_to_core_bpf::id(), "Migrate Address Lookup Table program to Core BPF #1651"), + (zk_elgamal_proof_program_enabled::id(), "Enable ZkElGamalProof program SIMD-0153"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index e6a1f66415c25e..f5b0a5682f17a4 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -41,6 +41,10 @@ pub use signer::signers; pub use solana_program::program_stubs; // These solana_program imports could be *-imported, but that causes a bunch of // confusing duplication in the docs due to a rustdoc bug. #26211 +#[allow(deprecated)] +pub use solana_program::sdk_ids; +#[cfg(target_arch = "wasm32")] +pub use solana_program::wasm_bindgen; pub use solana_program::{ account_info, address_lookup_table, alt_bn128, big_mod_exp, blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, clock, config, custom_heap_default, @@ -51,10 +55,8 @@ pub use solana_program::{ program_memory, program_option, program_pack, rent, sanitize, secp256k1_program, secp256k1_recover, serde_varint, serialize_utils, short_vec, slot_hashes, slot_history, stable_layout, stake, stake_history, syscalls, system_instruction, system_program, sysvar, - unchecked_div_by_const, vote, wasm_bindgen, + unchecked_div_by_const, vote, }; -#[allow(deprecated)] -pub use solana_program::{address_lookup_table_account, sdk_ids}; #[cfg(feature = "borsh")] pub use solana_program::{borsh, borsh0_10, borsh1}; @@ -91,7 +93,6 @@ pub mod precompiles; pub mod program_utils; pub mod pubkey; pub mod quic; -pub mod recent_blockhashes_account; pub mod rent_collector; pub mod rent_debits; pub mod reserved_account_keys; @@ -155,16 +156,6 @@ pub use solana_sdk_macro::pubkeys; #[rustversion::since(1.46.0)] pub use solana_sdk_macro::respan; -// Unused `solana_sdk::program_stubs!()` macro retained for source backwards compatibility with older programs -#[macro_export] -#[deprecated( - since = "1.4.3", - note = "program_stubs macro is obsolete and can be safely removed" -)] -macro_rules! program_stubs { - () => {}; -} - /// Convenience macro for `AddAssign` with saturating arithmetic. /// Replace by `std::num::Saturating` once stable #[macro_export] diff --git a/sdk/src/log.rs b/sdk/src/log.rs index 78a45afaf4a1e8..748b241c02b77f 100644 --- a/sdk/src/log.rs +++ b/sdk/src/log.rs @@ -1,14 +1,3 @@ #![cfg(feature = "program")] pub use solana_program::log::*; - -#[macro_export] -#[deprecated( - since = "1.4.3", - note = "Please use `solana_program::log::info` instead" -)] -macro_rules! info { - ($msg:expr) => { - $crate::log::sol_log($msg) - }; -} diff --git a/sdk/src/native_loader.rs b/sdk/src/native_loader.rs index 3f10fc527ad8a6..53a7ded4b61f54 100644 --- a/sdk/src/native_loader.rs +++ b/sdk/src/native_loader.rs @@ -1,23 +1,12 @@ //! The native loader native program. -use crate::{ - account::{ - Account, AccountSharedData, InheritableAccountFields, DUMMY_INHERITABLE_ACCOUNT_FIELDS, - }, - clock::INITIAL_RENT_EPOCH, +use crate::account::{ + Account, AccountSharedData, InheritableAccountFields, DUMMY_INHERITABLE_ACCOUNT_FIELDS, }; crate::declare_id!("NativeLoader1111111111111111111111111111111"); /// Create an executable account with the given shared object name. -#[deprecated( - since = "1.5.17", - note = "Please use `create_loadable_account_for_test` instead" -)] -pub fn create_loadable_account(name: &str, lamports: u64) -> AccountSharedData { - create_loadable_account_with_fields(name, (lamports, INITIAL_RENT_EPOCH)) -} - pub fn create_loadable_account_with_fields( name: &str, (lamports, rent_epoch): InheritableAccountFields, diff --git a/sdk/src/reserved_account_keys.rs b/sdk/src/reserved_account_keys.rs index a1a1d367d18ffd..e3c6bed5c973be 100644 --- a/sdk/src/reserved_account_keys.rs +++ b/sdk/src/reserved_account_keys.rs @@ -22,6 +22,11 @@ mod zk_token_proof_program { solana_sdk::declare_id!("ZkTokenProof1111111111111111111111111111111"); } +// Inline zk-elgamal-proof program id since it isn't available in the sdk +mod zk_elgamal_proof_program { + solana_sdk::declare_id!("ZkE1Gama1Proof11111111111111111111111111111"); +} + // ReservedAccountKeys is not serialized into or deserialized from bank // snapshots but the bank requires this trait to be implemented anyways. #[cfg(all(RUSTC_WITH_SPECIALIZATION, feature = "frozen-abi"))] @@ -162,6 +167,7 @@ lazy_static! { ReservedAccount::new_active(stake::program::id()), ReservedAccount::new_active(system_program::id()), ReservedAccount::new_active(vote::program::id()), + ReservedAccount::new_pending(zk_elgamal_proof_program::id(), feature_set::add_new_reserved_account_keys::id()), ReservedAccount::new_pending(zk_token_proof_program::id(), feature_set::add_new_reserved_account_keys::id()), // sysvars diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index 158fa5ceeaf136..3509e96522a657 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -28,14 +28,6 @@ pub struct Signature(GenericArray); impl crate::sanitize::Sanitize for Signature {} impl Signature { - #[deprecated( - since = "1.16.4", - note = "Please use 'Signature::from' or 'Signature::try_from' instead" - )] - pub fn new(signature_slice: &[u8]) -> Self { - Self(GenericArray::clone_from_slice(signature_slice)) - } - pub fn new_unique() -> Self { Self::from(std::array::from_fn(|_| rand::random())) } diff --git a/sdk/src/signer/keypair.rs b/sdk/src/signer/keypair.rs index 1873996a399391..9e6088c1b5444f 100644 --- a/sdk/src/signer/keypair.rs +++ b/sdk/src/signer/keypair.rs @@ -1,5 +1,7 @@ #![cfg(feature = "full")] +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; use { crate::{ derivation_path::DerivationPath, @@ -16,11 +18,10 @@ use { io::{Read, Write}, path::Path, }, - wasm_bindgen::prelude::*, }; /// A vanilla Ed25519 key pair -#[wasm_bindgen] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Debug)] pub struct Keypair(ed25519_dalek::Keypair); diff --git a/sdk/src/transaction/mod.rs b/sdk/src/transaction/mod.rs index 520a80d0289684..d0eb01a3af96e0 100644 --- a/sdk/src/transaction/mod.rs +++ b/sdk/src/transaction/mod.rs @@ -111,6 +111,8 @@ #![cfg(feature = "full")] +#[cfg(target_arch = "wasm32")] +use crate::wasm_bindgen; use { crate::{ hash::Hash, @@ -124,7 +126,6 @@ use { short_vec, signature::{Signature, SignerError}, signers::Signers, - wasm_bindgen, }, serde::Serialize, solana_program::{system_instruction::SystemInstruction, system_program}, @@ -167,7 +168,7 @@ pub type Result = result::Result; /// if the caller has knowledge that the first account of the constructed /// transaction's `Message` is both a signer and the expected fee-payer, then /// redundantly specifying the fee-payer is not strictly required. -#[wasm_bindgen] +#[cfg(not(target_arch = "wasm32"))] #[cfg_attr( feature = "frozen-abi", derive(AbiExample), @@ -184,11 +185,29 @@ pub struct Transaction { /// [`MessageHeader`]: crate::message::MessageHeader /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures // NOTE: Serialization-related changes must be paired with the direct read at sigverify. - #[wasm_bindgen(skip)] #[serde(with = "short_vec")] pub signatures: Vec, /// The message to sign. + pub message: Message, +} + +/// wasm-bindgen version of the Transaction struct. +/// This duplication is required until https://github.com/rustwasm/wasm-bindgen/issues/3671 +/// is fixed. This must not diverge from the regular non-wasm Transaction struct. +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +#[cfg_attr( + feature = "frozen-abi", + derive(AbiExample), + frozen_abi(digest = "FZtncnS1Xk8ghHfKiXE5oGiUbw2wJhmfXQuNgQR3K6Mc") +)] +#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] +pub struct Transaction { + #[wasm_bindgen(skip)] + #[serde(with = "short_vec")] + pub signatures: Vec, + #[wasm_bindgen(skip)] pub message: Message, } @@ -1106,17 +1125,6 @@ pub fn uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> { }) } -#[deprecated] -pub fn get_nonce_pubkey_from_instruction<'a>( - ix: &CompiledInstruction, - tx: &'a Transaction, -) -> Option<&'a Pubkey> { - ix.accounts.first().and_then(|idx| { - let idx = *idx as usize; - tx.message().account_keys.get(idx) - }) -} - #[cfg(test)] mod tests { #![allow(deprecated)] @@ -1611,34 +1619,6 @@ mod tests { assert!(uses_durable_nonce(&tx).is_none()); } - #[test] - fn get_nonce_pub_from_ix_ok() { - let (_, nonce_pubkey, tx) = nonced_transfer_tx(); - let nonce_ix = uses_durable_nonce(&tx).unwrap(); - assert_eq!( - get_nonce_pubkey_from_instruction(nonce_ix, &tx), - Some(&nonce_pubkey), - ); - } - - #[test] - fn get_nonce_pub_from_ix_no_accounts_fail() { - let (_, _, tx) = nonced_transfer_tx(); - let nonce_ix = uses_durable_nonce(&tx).unwrap(); - let mut nonce_ix = nonce_ix.clone(); - nonce_ix.accounts.clear(); - assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,); - } - - #[test] - fn get_nonce_pub_from_ix_bad_acc_idx_fail() { - let (_, _, tx) = nonced_transfer_tx(); - let nonce_ix = uses_durable_nonce(&tx).unwrap(); - let mut nonce_ix = nonce_ix.clone(); - nonce_ix.accounts[0] = 255u8; - assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,); - } - #[test] fn tx_keypair_pubkey_mismatch() { let from_keypair = Keypair::new(); diff --git a/send-transaction-service/Cargo.toml b/send-transaction-service/Cargo.toml index 35e76524d9017a..a69c366a358fdc 100644 --- a/send-transaction-service/Cargo.toml +++ b/send-transaction-service/Cargo.toml @@ -13,6 +13,7 @@ edition = { workspace = true } crossbeam-channel = { workspace = true } log = { workspace = true } solana-client = { workspace = true } +solana-connection-cache = { workspace = true } solana-measure = { workspace = true } solana-metrics = { workspace = true } solana-runtime = { workspace = true } diff --git a/send-transaction-service/src/send_transaction_service.rs b/send-transaction-service/src/send_transaction_service.rs index abe53b236d2e75..8cc21b12359639 100644 --- a/send-transaction-service/src/send_transaction_service.rs +++ b/send-transaction-service/src/send_transaction_service.rs @@ -2,10 +2,8 @@ use { crate::tpu_info::TpuInfo, crossbeam_channel::{Receiver, RecvTimeoutError}, log::*, - solana_client::{ - connection_cache::{ConnectionCache, Protocol}, - tpu_connection::TpuConnection, - }, + solana_client::connection_cache::{ConnectionCache, Protocol}, + solana_connection_cache::client_connection::ClientConnection as TpuConnection, solana_measure::measure::Measure, solana_runtime::{bank::Bank, bank_forks::BankForks}, solana_sdk::{ diff --git a/storage-bigtable/src/bigtable.rs b/storage-bigtable/src/bigtable.rs index fdebb5ab8d0214..b4bfe040a30963 100644 --- a/storage-bigtable/src/bigtable.rs +++ b/storage-bigtable/src/bigtable.rs @@ -985,6 +985,7 @@ mod tests { parent_slot, transactions, rewards, + num_partitions, block_time, block_height, } = confirmed_block; @@ -995,6 +996,8 @@ mod tests { parent_slot, transactions: transactions.into_iter().map(|tx| tx.into()).collect(), rewards: rewards.into_iter().map(|r| r.into()).collect(), + num_partitions: num_partitions + .map(|num_partitions| generated::NumPartitions { num_partitions }), block_time: block_time.map(|timestamp| generated::UnixTimestamp { timestamp }), block_height: block_height.map(|block_height| generated::BlockHeight { block_height }), } @@ -1028,6 +1031,7 @@ mod tests { blockhash: Hash::default().to_string(), previous_blockhash: Hash::default().to_string(), rewards: vec![], + num_partitions: None, block_time: Some(1_234_567_890), block_height: Some(1), }; diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index 240ae44c3d07fe..3af928a626d834 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -141,6 +141,7 @@ impl From for StoredConfirmedBlock { parent_slot, transactions, rewards, + num_partitions: _num_partitions, block_time, block_height, } = confirmed_block; @@ -175,6 +176,7 @@ impl From for ConfirmedBlock { parent_slot, transactions: transactions.into_iter().map(|tx| tx.into()).collect(), rewards: rewards.into_iter().map(|reward| reward.into()).collect(), + num_partitions: None, block_time, block_height, } diff --git a/storage-proto/proto/confirmed_block.proto b/storage-proto/proto/confirmed_block.proto index 47548ea13bc6a4..6d26ce7bce2cad 100644 --- a/storage-proto/proto/confirmed_block.proto +++ b/storage-proto/proto/confirmed_block.proto @@ -10,6 +10,7 @@ message ConfirmedBlock { repeated Reward rewards = 5; UnixTimestamp block_time = 6; BlockHeight block_height = 7; + NumPartitions num_partitions = 8; } message ConfirmedTransaction { @@ -130,6 +131,7 @@ message Reward { message Rewards { repeated Reward rewards = 1; + NumPartitions num_partitions = 2; } message UnixTimestamp { @@ -139,3 +141,7 @@ message UnixTimestamp { message BlockHeight { uint64 block_height = 1; } + +message NumPartitions { + uint64 num_partitions = 1; +} diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index 8d6669e44b43f1..8315bcf99a4dac 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -16,8 +16,9 @@ use { }, solana_transaction_status::{ ConfirmedBlock, EntrySummary, InnerInstruction, InnerInstructions, Reward, RewardType, - TransactionByAddrInfo, TransactionStatusMeta, TransactionTokenBalance, - TransactionWithStatusMeta, VersionedConfirmedBlock, VersionedTransactionWithStatusMeta, + RewardsAndNumPartitions, TransactionByAddrInfo, TransactionStatusMeta, + TransactionTokenBalance, TransactionWithStatusMeta, VersionedConfirmedBlock, + VersionedTransactionWithStatusMeta, }, std::{ convert::{TryFrom, TryInto}, @@ -47,6 +48,16 @@ impl From> for generated::Rewards { fn from(rewards: Vec) -> Self { Self { rewards: rewards.into_iter().map(|r| r.into()).collect(), + num_partitions: None, + } + } +} + +impl From for generated::Rewards { + fn from(input: RewardsAndNumPartitions) -> Self { + Self { + rewards: input.rewards.into_iter().map(|r| r.into()).collect(), + num_partitions: input.num_partitions.map(|n| n.into()), } } } @@ -57,6 +68,17 @@ impl From for Vec { } } +impl From for (Vec, Option) { + fn from(rewards: generated::Rewards) -> Self { + ( + rewards.rewards.into_iter().map(|r| r.into()).collect(), + rewards + .num_partitions + .map(|generated::NumPartitions { num_partitions }| num_partitions), + ) + } +} + impl From for generated::Rewards { fn from(rewards: StoredExtendedRewards) -> Self { Self { @@ -67,6 +89,7 @@ impl From for generated::Rewards { r.into() }) .collect(), + num_partitions: None, } } } @@ -121,6 +144,12 @@ impl From for Reward { } } +impl From for generated::NumPartitions { + fn from(num_partitions: u64) -> Self { + Self { num_partitions } + } +} + impl From for generated::ConfirmedBlock { fn from(confirmed_block: VersionedConfirmedBlock) -> Self { let VersionedConfirmedBlock { @@ -129,6 +158,7 @@ impl From for generated::ConfirmedBlock { parent_slot, transactions, rewards, + num_partitions, block_time, block_height, } = confirmed_block; @@ -139,6 +169,7 @@ impl From for generated::ConfirmedBlock { parent_slot, transactions: transactions.into_iter().map(|tx| tx.into()).collect(), rewards: rewards.into_iter().map(|r| r.into()).collect(), + num_partitions: num_partitions.map(Into::into), block_time: block_time.map(|timestamp| generated::UnixTimestamp { timestamp }), block_height: block_height.map(|block_height| generated::BlockHeight { block_height }), } @@ -156,6 +187,7 @@ impl TryFrom for ConfirmedBlock { parent_slot, transactions, rewards, + num_partitions, block_time, block_height, } = confirmed_block; @@ -169,6 +201,8 @@ impl TryFrom for ConfirmedBlock { .map(|tx| tx.try_into()) .collect::, Self::Error>>()?, rewards: rewards.into_iter().map(|r| r.into()).collect(), + num_partitions: num_partitions + .map(|generated::NumPartitions { num_partitions }| num_partitions), block_time: block_time.map(|generated::UnixTimestamp { timestamp }| timestamp), block_height: block_height.map(|generated::BlockHeight { block_height }| block_height), }) diff --git a/svm/Cargo.toml b/svm/Cargo.toml index c6ae6db41f13fb..41918ca261f013 100644 --- a/svm/Cargo.toml +++ b/svm/Cargo.toml @@ -26,6 +26,7 @@ solana-measure = { workspace = true } solana-program-runtime = { workspace = true } solana-sdk = { workspace = true } solana-system-program = { workspace = true } +solana-type-overrides = { workspace = true } solana-vote = { workspace = true } [lib] @@ -62,3 +63,9 @@ frozen-abi = [ "solana-program-runtime/frozen-abi", "solana-sdk/frozen-abi", ] +shuttle-test = [ + "solana-type-overrides/shuttle-test", + "solana-program-runtime/shuttle-test", + "solana-bpf-loader-program/shuttle-test", + "solana-loader-v4-program/shuttle-test", +] diff --git a/svm/doc/spec.md b/svm/doc/spec.md index c88b6b83f6b268..8f148b557debf0 100644 --- a/svm/doc/spec.md +++ b/svm/doc/spec.md @@ -81,8 +81,7 @@ Validator and in third-party applications. The interface to SVM is represented by the `transaction_processor::TransactionBatchProcessor` struct. To create a `TransactionBatchProcessor` object the client need to specify the -`slot`, `epoch`, `epoch_schedule`, `fee_structure`, `runtime_config`, -and `program_cache`. +`slot`, `epoch`, and `program_cache`. - `slot: Slot` is a u64 value representing the ordinal number of a particular blockchain state in context of which the transactions @@ -92,22 +91,6 @@ and `program_cache`. a Solana epoch, in which the slot was created. This is another index used to locate the onchain programs used in the execution of transactions in the batch. -- `epoch_schedule: EpochSchedule` is a struct that contains - information about epoch configuration, such as number of slots per - epoch, etc. TransactionBatchProcessor needs an instance of - EpochSchedule to obtain the first slot in the epoch in which the - transactions batch is being executed. This slot is sometimes - required for updating the information about the slot when a program - account has been accessed most recently. This is needed for - program cache bookkeeping. -- `fee_structure: FeeStructure` an instance of `FeeStructure` is - needed to check the validity of every transaction in a batch when - the transaction accounts are being loaded and checked for - compliance with required fees for transaction execution. -- `runtime_config: Arc` is a reference to a - RuntimeConfig struct instance. The `RuntimeConfig` is a collection - of fields that control parameters of runtime, such as compute - budget, the maximal size of log messages in bytes, etc. - `program_cache: Arc>>` is a reference to a ProgramCache instance. All on chain programs used in transaction batch execution are loaded from the program cache. @@ -119,45 +102,116 @@ The main entry point to the SVM is the method `load_and_execute_sanitized_transactions`. The method `load_and_execute_sanitized_transactions` takes the -following arguments - - `callbacks` is a `TransactionProcessingCallback` trait instance - that enables access to data available from accounts-db and from - Bank, - - `sanitized_txs` a slice of `SanitizedTransaction` - - `SanitizedTransaction` contains - - `SanitizedMessage` is an enum with two kinds of messages - - `LegacyMessage` and `LoadedMessage` - Both `LegacyMessage` and `LoadedMessage` consist of - - `MessageHeader` - - vector of `Pubkey` of accounts used in the transaction - - `Hash` of recent block - - vector of `CompiledInstruction` - In addition `LoadedMessage` contains a vector of - `MessageAddressTableLookup` -- list of address table lookups to - load additional accounts for this transaction. - - a Hash of the message - - a boolean flag `is_simple_vote_tx` -- explain - - a vector of `Signature` -- explain which signatures are in this vector - - `check_results` is a mutable slice of `TransactionCheckResult` - - `error_counters` is a mutable reference to `TransactionErrorMetrics` - - `recording_config` is a value of `ExecutionRecordingConfig` configuration parameters - - `timings` is a mutable reference to `ExecuteTimings` - - `account_overrides` is an optional reference to `AccountOverrides` - - `builtin_programs` is an iterator of `Pubkey` that represents builtin programs - - `log_messages_bytes_limit` is an optional `usize` limit on the size of log messages in bytes - - `limit_to_load_programs` is a boolean flag that instruct the function to only load the - programs and do not execute the transactions. - -The method returns a value of -`LoadAndExecuteSanitizedTransactionsOutput` which consists of two -vectors - - a vector of `TransactionLoadResult`, and - - a vector of `TransactionExecutionResult`. +following arguments: + +- `callbacks`: A `TransactionProcessingCallback` trait instance which allows + the transaction processor to summon information about accounts, most + importantly loading them for transaction execution. +- `sanitized_txs`: A slice of sanitized transactions. +- `check_results`: A mutable slice of transaction check results. +- `environment`: The runtime environment for transaction batch processing. +- `config`: Configurations for customizing transaction processing behavior. + +The method returns a `LoadAndExecuteSanitizedTransactionsOutput`, which is +defined below in more detail. An integration test `svm_integration` contains an example of instantiating `TransactionBatchProcessor` and calling its method `load_and_execute_sanitized_transactions`. +### `TransactionProcessingCallback` + +Downstream consumers of the SVM must implement the +`TransactionProcessingCallback` trait in order to provide the transaction +processor with the ability to load accounts and retrieve other account-related +information. + +```rust +pub trait TransactionProcessingCallback { + fn account_matches_owners(&self, account: &Pubkey, owners: &[Pubkey]) -> Option; + + fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option; + + fn add_builtin_account(&self, _name: &str, _program_id: &Pubkey) {} +} +``` + +Consumers can customize this plug-in to use their own Solana account source, +caching, and more. + +### `SanitizedTransaction` + +A "sanitized" Solana transaction is a transaction that has undergone the +various checks required to evaluate a transaction against the Solana protocol +ruleset. Some of these rules include signature verification and validation +of account indices (`num_readonly_signers`, etc.). + +A `SanitizedTransaction` contains: + +- `SanitizedMessage`: Enum with two kinds of messages - `LegacyMessage` and + `LoadedMessage` - both of which contain: + - `MessageHeader`: Vector of `Pubkey` of accounts used in the transaction. + - `Hash` of recent block. + - Vector of `CompiledInstruction`. + - In addition, `LoadedMessage` contains a vector of + `MessageAddressTableLookup` - list of address table lookups to + load additional accounts for this transaction. +- A Hash of the message +- A boolean flag `is_simple_vote_tx` - shortcut for determining if the + transaction is merely a simple vote transaction produced by a validator. +- A vector of `Signature` - the hash of the transaction message encrypted using + the signing key (for each signer in the transaction). + +### `TransactionCheckResult` + +Simply stores details about a transaction, including whether or not it contains +a nonce, the nonce it contains (if applicable), and the lamports per signature +to charge for fees. + +### `TransactionProcessingEnvironment` + +The transaction processor requires consumers to provide values describing +the runtime environment to use for processing transactions. + +- `blockhash`: The blockhash to use for the transaction batch. +- `epoch_total_stake`: The total stake for the current epoch. +- `epoch_vote_accounts`: The vote accounts for the current epoch. +- `feature_set`: Runtime feature set to use for the transaction batch. +- `fee_structure`: Fee structure to use for assessing transaction fees. +- `lamports_per_signature`: Lamports per signature to charge per transaction. +- `rent_collector`: Rent collector to use for the transaction batch. + +### `TransactionProcessingConfig` + +Consumers can provide various configurations to adjust the default behavior of +the transaction processor. + +- `account_overrides`: Encapsulates overridden accounts, typically used for + transaction simulation. +- `compute_budget`: The compute budget to use for transaction execution. +- `check_program_modification_slot`: Whether or not to check a program's + modification slot when replenishing a program cache instance. +- `log_messages_bytes_limit`: The maximum number of bytes that log messages can + consume. +- `limit_to_load_programs`: Whether to limit the number of programs loaded for + the transaction batch. +- `recording_config`: Recording capabilities for transaction execution. +- `transaction_account_lock_limit`: The max number of accounts that a + transaction may lock. + +### `LoadAndExecuteSanitizedTransactionsOutput` + +The output of the transaction batch processor's +`load_and_execute_sanitized_transactions` method. + +- `error_metrics`: Error metrics for transactions that were processed. +- `execute_timings`: Timings for transaction batch execution. +- `execution_results`: Vector of results indicating whether a transaction was + executed or could not be executed. Note executed transactions can still have + failed! +- `loaded_transactions`: Vector of loaded transactions from transactions that + were processed. + # Functional Model In this section, we describe the functionality (logic) of the SVM in @@ -209,11 +263,10 @@ Steps of `load_and_execute_sanitized_transactions` - Validate the fee payer and the loaded accounts - Validate the programs accounts that have been loaded and checks if they are builtin programs. - Return `struct LoadedTransaction` containing the accounts (pubkey and data), - indices to the excutabe accounts in `TransactionContext` (or `InstructionContext`), + indices to the executable accounts in `TransactionContext` (or `InstructionContext`), the transaction rent, and the `struct RentDebit`. - - Generate a `NonceFull` struct (holds fee subtracted nonce info) when possible, `None` otherwise. - - Returns `TransactionLoadedResult`, a tuple containing the `LoadTransaction` we obtained from `loaded_transaction_accounts`, - and a `Option`. + - Generate a `RollbackAccounts` struct which holds fee-subtracted fee payer account and pre-execution nonce state used for rolling back account state on execution failure. + - Returns `TransactionLoadedResult`, containing the `LoadTransaction` we obtained from `loaded_transaction_accounts` 3. Execute each loaded transactions 1. Compute the sum of transaction accounts' balances. This sum is diff --git a/svm/src/account_loader.rs b/svm/src/account_loader.rs index 8eb2f9fa3e81f3..c62963c09e0934 100644 --- a/svm/src/account_loader.rs +++ b/svm/src/account_loader.rs @@ -1,13 +1,14 @@ use { crate::{ - account_overrides::AccountOverrides, - account_rent_state::RentState, - nonce_info::{NonceFull, NoncePartial}, + account_overrides::AccountOverrides, account_rent_state::RentState, + nonce_info::NoncePartial, rollback_accounts::RollbackAccounts, transaction_error_metrics::TransactionErrorMetrics, transaction_processing_callback::TransactionProcessingCallback, }, itertools::Itertools, - solana_compute_budget::compute_budget_processor::process_compute_budget_instructions, + solana_compute_budget::compute_budget_processor::{ + process_compute_budget_instructions, ComputeBudgetLimits, + }, solana_program_runtime::loaded_programs::{ProgramCacheEntry, ProgramCacheForTxBatch}, solana_sdk::{ account::{Account, AccountSharedData, ReadableAccount, WritableAccount}, @@ -45,7 +46,8 @@ pub struct CheckedTransactionDetails { #[derive(PartialEq, Eq, Debug, Clone)] #[cfg_attr(feature = "dev-context-only-utils", derive(Default))] pub struct ValidatedTransactionDetails { - pub nonce: Option, + pub rollback_accounts: RollbackAccounts, + pub compute_budget_limits: ComputeBudgetLimits, pub fee_details: FeeDetails, pub fee_payer_account: AccountSharedData, pub fee_payer_rent_debit: u64, @@ -55,19 +57,14 @@ pub struct ValidatedTransactionDetails { pub struct LoadedTransaction { pub accounts: Vec, pub program_indices: TransactionProgramIndices, - pub nonce: Option, pub fee_details: FeeDetails, + pub rollback_accounts: RollbackAccounts, + pub compute_budget_limits: ComputeBudgetLimits, pub rent: TransactionRent, pub rent_debits: RentDebits, pub loaded_accounts_data_size: usize, } -impl LoadedTransaction { - pub fn fee_payer_account(&self) -> Option<&TransactionAccount> { - self.accounts.first() - } -} - /// Collect rent from an account if rent is still enabled and regardless of /// whether rent is enabled, set the rent epoch to u64::MAX if the account is /// rent exempt. @@ -363,8 +360,9 @@ fn load_transaction_accounts( Ok(LoadedTransaction { accounts, program_indices, - nonce: tx_details.nonce, fee_details: tx_details.fee_details, + rollback_accounts: tx_details.rollback_accounts, + compute_budget_limits: tx_details.compute_budget_limits, rent: tx_rent, rent_debits, loaded_accounts_data_size: accumulated_accounts_data_size, @@ -439,7 +437,7 @@ mod tests { use { super::*, crate::{ - nonce_info::NonceFull, transaction_account_state_info::TransactionAccountStateInfo, + transaction_account_state_info::TransactionAccountStateInfo, transaction_processing_callback::TransactionProcessingCallback, }, nonce::state::Versions as NonceVersions, @@ -1114,6 +1112,70 @@ mod tests { assert_eq!(shared_data, expected); } + #[test] + fn test_load_transaction_accounts_fee_payer() { + let fee_payer_address = Pubkey::new_unique(); + let message = Message { + account_keys: vec![fee_payer_address], + header: MessageHeader::default(), + instructions: vec![], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut mock_bank = TestCallbacks::default(); + + let fee_payer_balance = 200; + let mut fee_payer_account_data = AccountSharedData::default(); + fee_payer_account_data.set_lamports(fee_payer_balance); + mock_bank + .accounts_map + .insert(fee_payer_address, fee_payer_account_data.clone()); + let fee_payer_rent_debit = 42; + + let mut error_metrics = TransactionErrorMetrics::default(); + let loaded_programs = ProgramCacheForTxBatch::default(); + + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + let result = load_transaction_accounts( + &mock_bank, + sanitized_transaction.message(), + ValidatedTransactionDetails { + fee_payer_account: fee_payer_account_data.clone(), + fee_payer_rent_debit, + ..ValidatedTransactionDetails::default() + }, + &mut error_metrics, + None, + &FeatureSet::default(), + &RentCollector::default(), + &loaded_programs, + ); + + let expected_rent_debits = { + let mut rent_debits = RentDebits::default(); + rent_debits.insert(&fee_payer_address, fee_payer_rent_debit, fee_payer_balance); + rent_debits + }; + assert_eq!( + result.unwrap(), + LoadedTransaction { + accounts: vec![(fee_payer_address, fee_payer_account_data),], + program_indices: vec![], + fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), + rent: fee_payer_rent_debit, + rent_debits: expected_rent_debits, + loaded_accounts_data_size: 0, + } + ); + } + #[test] fn test_load_transaction_accounts_native_loader() { let key1 = Keypair::new(); @@ -1147,15 +1209,12 @@ mod tests { vec![Signature::new_unique()], false, ); - let fee_details = FeeDetails::new_for_tests(32, 0, false); let result = load_transaction_accounts( &mock_bank, sanitized_transaction.message(), ValidatedTransactionDetails { - nonce: None, - fee_details, - fee_payer_account: fee_payer_account_data, - fee_payer_rent_debit: 0, + fee_payer_account: fee_payer_account_data.clone(), + ..ValidatedTransactionDetails::default() }, &mut error_metrics, None, @@ -1168,18 +1227,16 @@ mod tests { result.unwrap(), LoadedTransaction { accounts: vec![ - ( - key1.pubkey(), - mock_bank.accounts_map[&key1.pubkey()].clone() - ), + (key1.pubkey(), fee_payer_account_data), ( native_loader::id(), mock_bank.accounts_map[&native_loader::id()].clone() ) ], program_indices: vec![vec![]], - nonce: None, - fee_details, + fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1359,15 +1416,12 @@ mod tests { vec![Signature::new_unique()], false, ); - let fee_details = FeeDetails::new_for_tests(32, 0, false); let result = load_transaction_accounts( &mock_bank, sanitized_transaction.message(), ValidatedTransactionDetails { - nonce: None, - fee_details, - fee_payer_account: fee_payer_account_data, - fee_payer_rent_debit: 0, + fee_payer_account: fee_payer_account_data.clone(), + ..ValidatedTransactionDetails::default() }, &mut error_metrics, None, @@ -1380,17 +1434,15 @@ mod tests { result.unwrap(), LoadedTransaction { accounts: vec![ - ( - key2.pubkey(), - mock_bank.accounts_map[&key2.pubkey()].clone() - ), + (key2.pubkey(), fee_payer_account_data), ( key1.pubkey(), mock_bank.accounts_map[&key1.pubkey()].clone() ), ], - nonce: None, - fee_details, + fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), program_indices: vec![vec![1]], rent: 0, rent_debits: RentDebits::default(), @@ -1545,15 +1597,12 @@ mod tests { vec![Signature::new_unique()], false, ); - let fee_details = FeeDetails::new_for_tests(32, 0, false); let result = load_transaction_accounts( &mock_bank, sanitized_transaction.message(), ValidatedTransactionDetails { - nonce: None, - fee_details, - fee_payer_account: fee_payer_account_data, - fee_payer_rent_debit: 0, + fee_payer_account: fee_payer_account_data.clone(), + ..ValidatedTransactionDetails::default() }, &mut error_metrics, None, @@ -1566,10 +1615,7 @@ mod tests { result.unwrap(), LoadedTransaction { accounts: vec![ - ( - key2.pubkey(), - mock_bank.accounts_map[&key2.pubkey()].clone() - ), + (key2.pubkey(), fee_payer_account_data), ( key1.pubkey(), mock_bank.accounts_map[&key1.pubkey()].clone() @@ -1580,8 +1626,9 @@ mod tests { ), ], program_indices: vec![vec![2, 1]], - nonce: None, - fee_details, + fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1640,15 +1687,12 @@ mod tests { vec![Signature::new_unique()], false, ); - let fee_details = FeeDetails::new_for_tests(32, 0, false); let result = load_transaction_accounts( &mock_bank, sanitized_transaction.message(), ValidatedTransactionDetails { - nonce: None, - fee_details, - fee_payer_account: fee_payer_account_data, - fee_payer_rent_debit: 0, + fee_payer_account: fee_payer_account_data.clone(), + ..ValidatedTransactionDetails::default() }, &mut error_metrics, None, @@ -1663,10 +1707,7 @@ mod tests { result.unwrap(), LoadedTransaction { accounts: vec![ - ( - key2.pubkey(), - mock_bank.accounts_map[&key2.pubkey()].clone() - ), + (key2.pubkey(), fee_payer_account_data), ( key1.pubkey(), mock_bank.accounts_map[&key1.pubkey()].clone() @@ -1678,8 +1719,9 @@ mod tests { ), ], program_indices: vec![vec![3, 1], vec![3, 1]], - nonce: None, - fee_details, + fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1733,7 +1775,7 @@ mod tests { let transaction_context = TransactionContext::new( loaded_txs[0].as_ref().unwrap().accounts.clone(), Rent::default(), - compute_budget.max_invoke_stack_height, + compute_budget.max_instruction_stack_depth, compute_budget.max_instruction_trace_length, ); @@ -1800,10 +1842,8 @@ mod tests { false, ); let validation_result = Ok(ValidatedTransactionDetails { - nonce: None, - fee_details: FeeDetails::default(), fee_payer_account: fee_payer_account_data, - fee_payer_rent_debit: 0, + ..ValidatedTransactionDetails::default() }); let results = load_accounts( @@ -1841,8 +1881,9 @@ mod tests { ), ], program_indices: vec![vec![3, 1], vec![3, 1]], - nonce: None, fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1874,13 +1915,7 @@ mod tests { false, ); - let validation_result = Ok(ValidatedTransactionDetails { - nonce: Some(NonceFull::default()), - fee_details: FeeDetails::default(), - fee_payer_account: AccountSharedData::default(), - fee_payer_rent_debit: 0, - }); - + let validation_result = Ok(ValidatedTransactionDetails::default()); let result = load_accounts( &mock_bank, &[sanitized_transaction.clone()], diff --git a/svm/src/lib.rs b/svm/src/lib.rs index b3c6a51cb1a0eb..4fcf894f6fe8ff 100644 --- a/svm/src/lib.rs +++ b/svm/src/lib.rs @@ -7,6 +7,7 @@ pub mod account_rent_state; pub mod message_processor; pub mod nonce_info; pub mod program_loader; +pub mod rollback_accounts; pub mod runtime_config; pub mod transaction_account_state_info; pub mod transaction_error_metrics; diff --git a/svm/src/message_processor.rs b/svm/src/message_processor.rs index eb442a23266064..95e5223b3ce53c 100644 --- a/svm/src/message_processor.rs +++ b/svm/src/message_processor.rs @@ -275,7 +275,6 @@ mod tests { ]), )); let sysvar_cache = SysvarCache::default(); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); let environment_config = EnvironmentConfig::new( Hash::default(), None, @@ -286,11 +285,10 @@ mod tests { ); let mut invoke_context = InvokeContext::new( &mut transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, environment_config, None, ComputeBudget::default(), - &mut programs_modified_by_tx, ); let result = MessageProcessor::process_message( &message, @@ -331,7 +329,6 @@ mod tests { ), ]), )); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); let environment_config = EnvironmentConfig::new( Hash::default(), None, @@ -342,11 +339,10 @@ mod tests { ); let mut invoke_context = InvokeContext::new( &mut transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, environment_config, None, ComputeBudget::default(), - &mut programs_modified_by_tx, ); let result = MessageProcessor::process_message( &message, @@ -377,7 +373,6 @@ mod tests { ), ]), )); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); let environment_config = EnvironmentConfig::new( Hash::default(), None, @@ -388,11 +383,10 @@ mod tests { ); let mut invoke_context = InvokeContext::new( &mut transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, environment_config, None, ComputeBudget::default(), - &mut programs_modified_by_tx, ); let result = MessageProcessor::process_message( &message, @@ -514,7 +508,6 @@ mod tests { Some(transaction_context.get_key_of_account_at_index(0).unwrap()), )); let sysvar_cache = SysvarCache::default(); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); let environment_config = EnvironmentConfig::new( Hash::default(), None, @@ -525,11 +518,10 @@ mod tests { ); let mut invoke_context = InvokeContext::new( &mut transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, environment_config, None, ComputeBudget::default(), - &mut programs_modified_by_tx, ); let result = MessageProcessor::process_message( &message, @@ -555,7 +547,6 @@ mod tests { )], Some(transaction_context.get_key_of_account_at_index(0).unwrap()), )); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); let environment_config = EnvironmentConfig::new( Hash::default(), None, @@ -566,11 +557,10 @@ mod tests { ); let mut invoke_context = InvokeContext::new( &mut transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, environment_config, None, ComputeBudget::default(), - &mut programs_modified_by_tx, ); let result = MessageProcessor::process_message( &message, @@ -593,7 +583,6 @@ mod tests { )], Some(transaction_context.get_key_of_account_at_index(0).unwrap()), )); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); let environment_config = EnvironmentConfig::new( Hash::default(), None, @@ -604,11 +593,10 @@ mod tests { ); let mut invoke_context = InvokeContext::new( &mut transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, environment_config, None, ComputeBudget::default(), - &mut programs_modified_by_tx, ); let result = MessageProcessor::process_message( &message, @@ -669,7 +657,7 @@ mod tests { // copies the `random` implementation at: // https://docs.rs/libsecp256k1/latest/src/libsecp256k1/lib.rs.html#430 let secret_key = { - use rand::RngCore; + use solana_type_overrides::rand::RngCore; let mut rng = rand::thread_rng(); loop { let mut ret = [0u8; libsecp256k1::util::SECRET_KEY_SIZE]; @@ -692,7 +680,6 @@ mod tests { mock_program_id, Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)), ); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); let environment_config = EnvironmentConfig::new( Hash::default(), None, @@ -703,11 +690,10 @@ mod tests { ); let mut invoke_context = InvokeContext::new( &mut transaction_context, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, environment_config, None, ComputeBudget::default(), - &mut programs_modified_by_tx, ); let result = MessageProcessor::process_message( &message, diff --git a/svm/src/nonce_info.rs b/svm/src/nonce_info.rs index 5088adb5b5965f..062b8fc221f87f 100644 --- a/svm/src/nonce_info.rs +++ b/svm/src/nonce_info.rs @@ -1,8 +1,4 @@ -use solana_sdk::{ - account::{AccountSharedData, ReadableAccount, WritableAccount}, - nonce_account, - pubkey::Pubkey, -}; +use solana_sdk::{account::AccountSharedData, nonce_account, pubkey::Pubkey}; pub trait NonceInfo { fn address(&self) -> &Pubkey; @@ -39,107 +35,27 @@ impl NonceInfo for NoncePartial { } } -/// Holds fee subtracted nonce info -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct NonceFull { - address: Pubkey, - account: AccountSharedData, - fee_payer_account: Option, -} - -impl NonceFull { - pub fn new( - address: Pubkey, - account: AccountSharedData, - fee_payer_account: Option, - ) -> Self { - Self { - address, - account, - fee_payer_account, - } - } - - pub fn from_partial( - partial: NoncePartial, - fee_payer_address: &Pubkey, - mut fee_payer_account: AccountSharedData, - fee_payer_rent_debit: u64, - ) -> Self { - fee_payer_account.set_lamports( - fee_payer_account - .lamports() - .saturating_add(fee_payer_rent_debit), - ); - - let NoncePartial { - address: nonce_address, - account: nonce_account, - } = partial; - - if *fee_payer_address == nonce_address { - Self::new(nonce_address, fee_payer_account, None) - } else { - Self::new(nonce_address, nonce_account, Some(fee_payer_account)) - } - } -} - -impl NonceInfo for NonceFull { - fn address(&self) -> &Pubkey { - &self.address - } - fn account(&self) -> &AccountSharedData { - &self.account - } - fn lamports_per_signature(&self) -> Option { - nonce_account::lamports_per_signature_of(&self.account) - } - fn fee_payer_account(&self) -> Option<&AccountSharedData> { - self.fee_payer_account.as_ref() - } -} - #[cfg(test)] mod tests { use { super::*, solana_sdk::{ hash::Hash, - instruction::Instruction, - message::{Message, SanitizedMessage}, - nonce::{self, state::DurableNonce}, - reserved_account_keys::ReservedAccountKeys, - signature::{keypair_from_seed, Signer}, - system_instruction, system_program, + nonce::state::{ + Data as NonceData, DurableNonce, State as NonceState, Versions as NonceVersions, + }, + system_program, }, }; - fn new_sanitized_message( - instructions: &[Instruction], - payer: Option<&Pubkey>, - ) -> SanitizedMessage { - SanitizedMessage::try_from_legacy_message( - Message::new(instructions, payer), - &ReservedAccountKeys::empty_key_set(), - ) - .unwrap() - } - #[test] fn test_nonce_info() { - let lamports_per_signature = 42; - - let nonce_authority = keypair_from_seed(&[0; 32]).unwrap(); - let nonce_address = nonce_authority.pubkey(); - let from = keypair_from_seed(&[1; 32]).unwrap(); - let from_address = from.pubkey(); - let to_address = Pubkey::new_unique(); - + let nonce_address = Pubkey::new_unique(); let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); + let lamports_per_signature = 42; let nonce_account = AccountSharedData::new_data( 43, - &nonce::state::Versions::new(nonce::State::Initialized(nonce::state::Data::new( + &NonceVersions::new(NonceState::Initialized(NonceData::new( Pubkey::default(), durable_nonce, lamports_per_signature, @@ -147,71 +63,15 @@ mod tests { &system_program::id(), ) .unwrap(); - let from_account = AccountSharedData::new(44, 0, &Pubkey::default()); - - const TEST_RENT_DEBIT: u64 = 1; - let rent_collected_nonce_account = { - let mut account = nonce_account.clone(); - account.set_lamports(nonce_account.lamports() - TEST_RENT_DEBIT); - account - }; - let rent_collected_from_account = { - let mut account = from_account.clone(); - account.set_lamports(from_account.lamports() - TEST_RENT_DEBIT); - account - }; - - let instructions = vec![ - system_instruction::advance_nonce_account(&nonce_address, &nonce_authority.pubkey()), - system_instruction::transfer(&from_address, &to_address, 42), - ]; // NoncePartial create + NonceInfo impl - let partial = NoncePartial::new(nonce_address, rent_collected_nonce_account.clone()); + let partial = NoncePartial::new(nonce_address, nonce_account.clone()); assert_eq!(*partial.address(), nonce_address); - assert_eq!(*partial.account(), rent_collected_nonce_account); + assert_eq!(*partial.account(), nonce_account); assert_eq!( partial.lamports_per_signature(), Some(lamports_per_signature) ); assert_eq!(partial.fee_payer_account(), None); - - // NonceFull create + NonceInfo impl - { - let message = new_sanitized_message(&instructions, Some(&from_address)); - let fee_payer_address = message.account_keys().get(0).unwrap(); - let fee_payer_account = rent_collected_from_account.clone(); - let full = NonceFull::from_partial( - partial.clone(), - fee_payer_address, - fee_payer_account, - TEST_RENT_DEBIT, - ); - assert_eq!(*full.address(), nonce_address); - assert_eq!(*full.account(), rent_collected_nonce_account); - assert_eq!(full.lamports_per_signature(), Some(lamports_per_signature)); - assert_eq!( - full.fee_payer_account(), - Some(&from_account), - "rent debit should be refunded in captured fee account" - ); - } - - // Nonce account is fee-payer - { - let message = new_sanitized_message(&instructions, Some(&nonce_address)); - let fee_payer_address = message.account_keys().get(0).unwrap(); - let fee_payer_account = rent_collected_nonce_account; - let full = NonceFull::from_partial( - partial, - fee_payer_address, - fee_payer_account, - TEST_RENT_DEBIT, - ); - assert_eq!(*full.address(), nonce_address); - assert_eq!(*full.account(), nonce_account); - assert_eq!(full.lamports_per_signature(), Some(lamports_per_signature)); - assert_eq!(full.fee_payer_account(), None); - } } } diff --git a/svm/src/program_loader.rs b/svm/src/program_loader.rs index 6060e8bb3db453..f77780fc9fecfa 100644 --- a/svm/src/program_loader.rs +++ b/svm/src/program_loader.rs @@ -11,14 +11,14 @@ use { account::{AccountSharedData, ReadableAccount}, account_utils::StateMut, bpf_loader, bpf_loader_deprecated, - bpf_loader_upgradeable::UpgradeableLoaderState, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::Slot, - epoch_schedule::EpochSchedule, instruction::InstructionError, loader_v4::{self, LoaderV4State, LoaderV4Status}, pubkey::Pubkey, + transaction::{self, TransactionError}, }, - std::sync::Arc, + solana_type_overrides::sync::Arc, }; #[derive(Debug)] @@ -126,7 +126,6 @@ pub fn load_program_with_pubkey( environments: &ProgramRuntimeEnvironments, pubkey: &Pubkey, slot: Slot, - _epoch_schedule: &EpochSchedule, reload: bool, ) -> Option> { let mut load_program_metrics = LoadProgramMetrics { @@ -219,6 +218,43 @@ pub fn load_program_with_pubkey( Some(Arc::new(loaded_program)) } +/// Find the slot in which the program was most recently modified. +/// Returns slot 0 for programs deployed with v1/v2 loaders, since programs deployed +/// with those loaders do not retain deployment slot information. +/// Returns an error if the program's account state can not be found or parsed. +pub(crate) fn get_program_modification_slot( + callbacks: &CB, + pubkey: &Pubkey, +) -> transaction::Result { + let program = callbacks + .get_account_shared_data(pubkey) + .ok_or(TransactionError::ProgramAccountNotFound)?; + if bpf_loader_upgradeable::check_id(program.owner()) { + if let Ok(UpgradeableLoaderState::Program { + programdata_address, + }) = program.state() + { + let programdata = callbacks + .get_account_shared_data(&programdata_address) + .ok_or(TransactionError::ProgramAccountNotFound)?; + if let Ok(UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: _, + }) = programdata.state() + { + return Ok(slot); + } + } + Err(TransactionError::ProgramAccountNotFound) + } else if loader_v4::check_id(program.owner()) { + let state = solana_loader_v4_program::get_state(program.data()) + .map_err(|_| TransactionError::ProgramAccountNotFound)?; + Ok(state.slot) + } else { + Ok(0) + } +} + #[cfg(test)] mod tests { use { @@ -494,7 +530,6 @@ mod tests { &batch_processor.get_environments_for_epoch(50).unwrap(), &key, 500, - &batch_processor.epoch_schedule, false, ); assert!(result.is_none()); @@ -517,7 +552,6 @@ mod tests { &batch_processor.get_environments_for_epoch(20).unwrap(), &key, 0, // Slot 0 - &batch_processor.epoch_schedule, false, ); @@ -552,7 +586,6 @@ mod tests { &batch_processor.get_environments_for_epoch(20).unwrap(), &key, 200, - &batch_processor.epoch_schedule, false, ); let loaded_program = ProgramCacheEntry::new_tombstone( @@ -580,7 +613,6 @@ mod tests { &batch_processor.get_environments_for_epoch(20).unwrap(), &key, 200, - &batch_processor.epoch_schedule, false, ); @@ -634,7 +666,6 @@ mod tests { &batch_processor.get_environments_for_epoch(0).unwrap(), &key1, 0, - &batch_processor.epoch_schedule, false, ); let loaded_program = ProgramCacheEntry::new_tombstone( @@ -672,7 +703,6 @@ mod tests { &batch_processor.get_environments_for_epoch(20).unwrap(), &key1, 200, - &batch_processor.epoch_schedule, false, ); @@ -722,7 +752,6 @@ mod tests { &batch_processor.get_environments_for_epoch(0).unwrap(), &key, 0, - &batch_processor.epoch_schedule, false, ); let loaded_program = ProgramCacheEntry::new_tombstone( @@ -756,7 +785,6 @@ mod tests { &batch_processor.get_environments_for_epoch(20).unwrap(), &key, 200, - &batch_processor.epoch_schedule, false, ); @@ -807,7 +835,6 @@ mod tests { .unwrap(), &key, 200, - &batch_processor.epoch_schedule, false, ) .unwrap(); @@ -827,4 +854,111 @@ mod tests { ); } } + + #[test] + fn test_program_modification_slot_account_not_found() { + let mock_bank = MockBankCallback::default(); + + let key = Pubkey::new_unique(); + + let result = get_program_modification_slot(&mock_bank, &key); + assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); + + let mut account_data = AccountSharedData::new(100, 100, &bpf_loader_upgradeable::id()); + mock_bank + .account_shared_data + .borrow_mut() + .insert(key, account_data.clone()); + + let result = get_program_modification_slot(&mock_bank, &key); + assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); + + let state = UpgradeableLoaderState::Program { + programdata_address: Pubkey::new_unique(), + }; + account_data.set_data(bincode::serialize(&state).unwrap()); + mock_bank + .account_shared_data + .borrow_mut() + .insert(key, account_data.clone()); + + let result = get_program_modification_slot(&mock_bank, &key); + assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); + + account_data.set_owner(loader_v4::id()); + mock_bank + .account_shared_data + .borrow_mut() + .insert(key, account_data); + + let result = get_program_modification_slot(&mock_bank, &key); + assert_eq!(result.err(), Some(TransactionError::ProgramAccountNotFound)); + } + + #[test] + fn test_program_modification_slot_success() { + let mock_bank = MockBankCallback::default(); + + let key1 = Pubkey::new_unique(); + let key2 = Pubkey::new_unique(); + + let account_data = AccountSharedData::new_data( + 100, + &UpgradeableLoaderState::Program { + programdata_address: key2, + }, + &bpf_loader_upgradeable::id(), + ) + .unwrap(); + mock_bank + .account_shared_data + .borrow_mut() + .insert(key1, account_data); + + let account_data = AccountSharedData::new_data( + 100, + &UpgradeableLoaderState::ProgramData { + slot: 77, + upgrade_authority_address: None, + }, + &bpf_loader_upgradeable::id(), + ) + .unwrap(); + mock_bank + .account_shared_data + .borrow_mut() + .insert(key2, account_data); + + let result = get_program_modification_slot(&mock_bank, &key1); + assert_eq!(result.unwrap(), 77); + + let state = LoaderV4State { + slot: 58, + authority_address: Pubkey::new_unique(), + status: LoaderV4Status::Deployed, + }; + let encoded = unsafe { + std::mem::transmute::<&LoaderV4State, &[u8; LoaderV4State::program_data_offset()]>( + &state, + ) + }; + let mut account_data = AccountSharedData::new(100, encoded.len(), &loader_v4::id()); + account_data.set_data(encoded.to_vec()); + mock_bank + .account_shared_data + .borrow_mut() + .insert(key1, account_data.clone()); + + let result = get_program_modification_slot(&mock_bank, &key1); + assert_eq!(result.unwrap(), 58); + + account_data.set_owner(Pubkey::new_unique()); + mock_bank + .account_shared_data + .borrow_mut() + .insert(key2, account_data); + + let result = get_program_modification_slot(&mock_bank, &key2); + assert_eq!(result.unwrap(), 0); + } } diff --git a/svm/src/rollback_accounts.rs b/svm/src/rollback_accounts.rs new file mode 100644 index 00000000000000..6fbd3a9c2e91e8 --- /dev/null +++ b/svm/src/rollback_accounts.rs @@ -0,0 +1,229 @@ +use { + crate::nonce_info::{NonceInfo, NoncePartial}, + solana_sdk::{ + account::{AccountSharedData, ReadableAccount, WritableAccount}, + clock::Epoch, + pubkey::Pubkey, + }, +}; + +/// Captured account state used to rollback account state for nonce and fee +/// payer accounts after a failed executed transaction. +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum RollbackAccounts { + FeePayerOnly { + fee_payer_account: AccountSharedData, + }, + SameNonceAndFeePayer { + nonce: NoncePartial, + }, + SeparateNonceAndFeePayer { + nonce: NoncePartial, + fee_payer_account: AccountSharedData, + }, +} + +#[cfg(feature = "dev-context-only-utils")] +impl Default for RollbackAccounts { + fn default() -> Self { + Self::FeePayerOnly { + fee_payer_account: AccountSharedData::default(), + } + } +} + +impl RollbackAccounts { + pub fn new( + nonce: Option, + fee_payer_address: Pubkey, + mut fee_payer_account: AccountSharedData, + fee_payer_rent_debit: u64, + fee_payer_loaded_rent_epoch: Epoch, + ) -> Self { + // When the fee payer account is rolled back due to transaction failure, + // rent should not be charged so credit the previously debited rent + // amount. + fee_payer_account.set_lamports( + fee_payer_account + .lamports() + .saturating_add(fee_payer_rent_debit), + ); + + if let Some(nonce) = nonce { + if &fee_payer_address == nonce.address() { + RollbackAccounts::SameNonceAndFeePayer { + nonce: NoncePartial::new(fee_payer_address, fee_payer_account), + } + } else { + RollbackAccounts::SeparateNonceAndFeePayer { + nonce, + fee_payer_account, + } + } + } else { + // When rolling back failed transactions which don't use nonces, the + // runtime should not update the fee payer's rent epoch so reset the + // rollback fee payer acocunt's rent epoch to its originally loaded + // rent epoch value. In the future, a feature gate could be used to + // alter this behavior such that rent epoch updates are handled the + // same for both nonce and non-nonce failed transactions. + fee_payer_account.set_rent_epoch(fee_payer_loaded_rent_epoch); + RollbackAccounts::FeePayerOnly { fee_payer_account } + } + } + + pub fn nonce(&self) -> Option<&NoncePartial> { + match self { + Self::FeePayerOnly { .. } => None, + Self::SameNonceAndFeePayer { nonce } | Self::SeparateNonceAndFeePayer { nonce, .. } => { + Some(nonce) + } + } + } + + pub fn fee_payer_account(&self) -> &AccountSharedData { + match self { + Self::FeePayerOnly { fee_payer_account } + | Self::SeparateNonceAndFeePayer { + fee_payer_account, .. + } => fee_payer_account, + Self::SameNonceAndFeePayer { nonce } => nonce.account(), + } + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + solana_sdk::{ + account::{ReadableAccount, WritableAccount}, + hash::Hash, + nonce::state::{ + Data as NonceData, DurableNonce, State as NonceState, Versions as NonceVersions, + }, + system_program, + }, + }; + + #[test] + fn test_new_fee_payer_only() { + let fee_payer_address = Pubkey::new_unique(); + let fee_payer_account = AccountSharedData::new(100, 0, &Pubkey::default()); + let fee_payer_rent_epoch = fee_payer_account.rent_epoch(); + + const TEST_RENT_DEBIT: u64 = 1; + let rent_collected_fee_payer_account = { + let mut account = fee_payer_account.clone(); + account.set_lamports(fee_payer_account.lamports() - TEST_RENT_DEBIT); + account.set_rent_epoch(fee_payer_rent_epoch + 1); + account + }; + + let rollback_accounts = RollbackAccounts::new( + None, + fee_payer_address, + rent_collected_fee_payer_account, + TEST_RENT_DEBIT, + fee_payer_rent_epoch, + ); + + let expected_fee_payer_account = fee_payer_account; + match rollback_accounts { + RollbackAccounts::FeePayerOnly { fee_payer_account } => { + assert_eq!(expected_fee_payer_account, fee_payer_account); + } + _ => panic!("Expected FeePayerOnly variant"), + } + } + + #[test] + fn test_new_same_nonce_and_fee_payer() { + let nonce_address = Pubkey::new_unique(); + let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); + let lamports_per_signature = 42; + let nonce_account = AccountSharedData::new_data( + 43, + &NonceVersions::new(NonceState::Initialized(NonceData::new( + Pubkey::default(), + durable_nonce, + lamports_per_signature, + ))), + &system_program::id(), + ) + .unwrap(); + + const TEST_RENT_DEBIT: u64 = 1; + let rent_collected_nonce_account = { + let mut account = nonce_account.clone(); + account.set_lamports(nonce_account.lamports() - TEST_RENT_DEBIT); + account + }; + + let nonce = NoncePartial::new(nonce_address, rent_collected_nonce_account.clone()); + let rollback_accounts = RollbackAccounts::new( + Some(nonce), + nonce_address, + rent_collected_nonce_account, + TEST_RENT_DEBIT, + u64::MAX, // ignored + ); + + match rollback_accounts { + RollbackAccounts::SameNonceAndFeePayer { nonce } => { + assert_eq!(nonce.address(), &nonce_address); + assert_eq!(nonce.account(), &nonce_account); + } + _ => panic!("Expected SameNonceAndFeePayer variant"), + } + } + + #[test] + fn test_separate_nonce_and_fee_payer() { + let nonce_address = Pubkey::new_unique(); + let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); + let lamports_per_signature = 42; + let nonce_account = AccountSharedData::new_data( + 43, + &NonceVersions::new(NonceState::Initialized(NonceData::new( + Pubkey::default(), + durable_nonce, + lamports_per_signature, + ))), + &system_program::id(), + ) + .unwrap(); + + let fee_payer_address = Pubkey::new_unique(); + let fee_payer_account = AccountSharedData::new(44, 0, &Pubkey::default()); + + const TEST_RENT_DEBIT: u64 = 1; + let rent_collected_fee_payer_account = { + let mut account = fee_payer_account.clone(); + account.set_lamports(fee_payer_account.lamports() - TEST_RENT_DEBIT); + account + }; + + let nonce = NoncePartial::new(nonce_address, nonce_account.clone()); + let rollback_accounts = RollbackAccounts::new( + Some(nonce), + fee_payer_address, + rent_collected_fee_payer_account.clone(), + TEST_RENT_DEBIT, + u64::MAX, // ignored + ); + + let expected_fee_payer_account = fee_payer_account; + match rollback_accounts { + RollbackAccounts::SeparateNonceAndFeePayer { + nonce, + fee_payer_account, + } => { + assert_eq!(nonce.address(), &nonce_address); + assert_eq!(nonce.account(), &nonce_account); + assert_eq!(expected_fee_payer_account, fee_payer_account); + } + _ => panic!("Expected SeparateNonceAndFeePayer variant"), + } + } +} diff --git a/svm/src/transaction_error_metrics.rs b/svm/src/transaction_error_metrics.rs index 8cb24e36189e06..ad572073ac5ee3 100644 --- a/svm/src/transaction_error_metrics.rs +++ b/svm/src/transaction_error_metrics.rs @@ -16,6 +16,7 @@ pub struct TransactionErrorMetrics { pub invalid_account_for_fee: usize, pub invalid_account_index: usize, pub invalid_program_for_execution: usize, + pub invalid_compute_budget: usize, pub not_allowed_during_cluster_maintenance: usize, pub invalid_writable_account: usize, pub invalid_rent_paying_account: usize, @@ -50,6 +51,7 @@ impl TransactionErrorMetrics { self.invalid_program_for_execution, other.invalid_program_for_execution ); + saturating_add_assign!(self.invalid_compute_budget, other.invalid_compute_budget); saturating_add_assign!( self.not_allowed_during_cluster_maintenance, other.not_allowed_during_cluster_maintenance @@ -128,6 +130,11 @@ impl TransactionErrorMetrics { // i64 // ), // ( + // "invalid_compute_budget", + // self.invalid_compute_budget as i64, + // i64 + // ), + // ( // "not_allowed_during_cluster_maintenance", // self.not_allowed_during_cluster_maintenance as i64, // i64 diff --git a/svm/src/transaction_processing_callback.rs b/svm/src/transaction_processing_callback.rs index bca549b12013cc..760a6606568798 100644 --- a/svm/src/transaction_processing_callback.rs +++ b/svm/src/transaction_processing_callback.rs @@ -1,7 +1,4 @@ -use { - solana_program_runtime::loaded_programs::ProgramCacheMatchCriteria, - solana_sdk::{account::AccountSharedData, pubkey::Pubkey}, -}; +use solana_sdk::{account::AccountSharedData, pubkey::Pubkey}; /// Runtime callbacks for transaction processing. pub trait TransactionProcessingCallback { @@ -9,9 +6,5 @@ pub trait TransactionProcessingCallback { fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option; - fn get_program_match_criteria(&self, _program: &Pubkey) -> ProgramCacheMatchCriteria { - ProgramCacheMatchCriteria::NoCriteria - } - fn add_builtin_account(&self, _name: &str, _program_id: &Pubkey) {} } diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index cc0cbcdac5775e..2037489c0477f0 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -9,8 +9,8 @@ use { }, account_overrides::AccountOverrides, message_processor::MessageProcessor, - nonce_info::NonceFull, - program_loader::load_program_with_pubkey, + program_loader::{get_program_modification_slot, load_program_with_pubkey}, + rollback_accounts::RollbackAccounts, transaction_account_state_info::TransactionAccountStateInfo, transaction_error_metrics::TransactionErrorMetrics, transaction_processing_callback::TransactionProcessingCallback, @@ -38,12 +38,11 @@ use { solana_sdk::{ account::{AccountSharedData, ReadableAccount, PROGRAM_OWNERS}, clock::{Epoch, Slot}, - epoch_schedule::EpochSchedule, feature_set::{ include_loaded_accounts_data_size_in_fee_calculation, remove_rounding_in_fee_calculation, FeatureSet, }, - fee::{FeeDetails, FeeStructure}, + fee::{FeeBudgetLimits, FeeStructure}, hash::Hash, inner_instruction::{InnerInstruction, InnerInstructionsList}, instruction::{CompiledInstruction, TRANSACTION_LEVEL_STACK_HEIGHT}, @@ -54,16 +53,13 @@ use { transaction::{self, SanitizedTransaction, TransactionError}, transaction_context::{ExecutionRecord, TransactionContext}, }, + solana_type_overrides::sync::{atomic::Ordering, Arc, RwLock, RwLockReadGuard}, solana_vote::vote_account::VoteAccountsHashMap, std::{ cell::RefCell, collections::{hash_map::Entry, HashMap, HashSet}, fmt::{Debug, Formatter}, rc::Rc, - sync::{ - atomic::Ordering::{self, Relaxed}, - Arc, RwLock, - }, }, }; @@ -108,6 +104,9 @@ pub struct TransactionProcessingConfig<'a> { /// Encapsulates overridden accounts, typically used for transaction /// simulation. pub account_overrides: Option<&'a AccountOverrides>, + /// Whether or not to check a program's modification slot when replenishing + /// a program cache instance. + pub check_program_modification_slot: bool, /// The compute budget to use for transaction execution. pub compute_budget: Option, /// The maximum number of bytes that log messages can consume. @@ -132,6 +131,8 @@ pub struct TransactionProcessingEnvironment<'a> { pub epoch_vote_accounts: Option<&'a VoteAccountsHashMap>, /// Runtime feature set to use for the transaction batch. pub feature_set: Arc, + /// Fee structure to use for assessing transaction fees. + pub fee_structure: Option<&'a FeeStructure>, /// Lamports per signature to charge per transaction. pub lamports_per_signature: u64, /// Rent collector to use for the transaction batch. @@ -146,16 +147,10 @@ pub struct TransactionBatchProcessor { /// Bank epoch epoch: Epoch, - /// initialized from genesis - pub epoch_schedule: EpochSchedule, - - /// Transaction fee structure - pub fee_structure: FeeStructure, - /// SysvarCache is a collection of system variables that are /// accessible from on chain programs. It is passed to SVM from /// client code (e.g. Bank) and forwarded to the MessageProcessor. - pub sysvar_cache: RwLock, + sysvar_cache: RwLock, /// Programs required for transaction batch processing pub program_cache: Arc>>, @@ -169,8 +164,6 @@ impl Debug for TransactionBatchProcessor { f.debug_struct("TransactionBatchProcessor") .field("slot", &self.slot) .field("epoch", &self.epoch) - .field("epoch_schedule", &self.epoch_schedule) - .field("fee_structure", &self.fee_structure) .field("sysvar_cache", &self.sysvar_cache) .field("program_cache", &self.program_cache) .finish() @@ -182,8 +175,6 @@ impl Default for TransactionBatchProcessor { Self { slot: Slot::default(), epoch: Epoch::default(), - epoch_schedule: EpochSchedule::default(), - fee_structure: FeeStructure::default(), sysvar_cache: RwLock::::default(), program_cache: Arc::new(RwLock::new(ProgramCache::new( Slot::default(), @@ -195,17 +186,10 @@ impl Default for TransactionBatchProcessor { } impl TransactionBatchProcessor { - pub fn new( - slot: Slot, - epoch: Epoch, - epoch_schedule: EpochSchedule, - builtin_program_ids: HashSet, - ) -> Self { + pub fn new(slot: Slot, epoch: Epoch, builtin_program_ids: HashSet) -> Self { Self { slot, epoch, - epoch_schedule, - fee_structure: FeeStructure::default(), sysvar_cache: RwLock::::default(), program_cache: Arc::new(RwLock::new(ProgramCache::new(slot, epoch))), builtin_program_ids: RwLock::new(builtin_program_ids), @@ -216,8 +200,6 @@ impl TransactionBatchProcessor { Self { slot, epoch, - epoch_schedule: self.epoch_schedule.clone(), - fee_structure: self.fee_structure.clone(), sysvar_cache: RwLock::::default(), program_cache: self.program_cache.clone(), builtin_program_ids: RwLock::new(self.builtin_program_ids.read().unwrap().clone()), @@ -234,6 +216,10 @@ impl TransactionBatchProcessor { .map(|cache| cache.get_environments_for_epoch(epoch)) } + pub fn sysvar_cache(&self) -> RwLockReadGuard { + self.sysvar_cache.read().unwrap() + } + /// Main entrypoint to the SVM. pub fn load_and_execute_sanitized_transactions( &self, @@ -252,6 +238,9 @@ impl TransactionBatchProcessor { sanitized_txs, check_results, &environment.feature_set, + environment + .fee_structure + .unwrap_or(&FeeStructure::default()), environment .rent_collector .unwrap_or(&RentCollector::default()), @@ -272,6 +261,7 @@ impl TransactionBatchProcessor { let program_cache_for_tx_batch = Rc::new(RefCell::new(self.replenish_program_cache( callbacks, &program_accounts_map, + config.check_program_modification_slot, config.limit_to_load_programs, ))); @@ -312,34 +302,12 @@ impl TransactionBatchProcessor { .map(|(load_result, tx)| match load_result { Err(e) => TransactionExecutionResult::NotExecuted(e.clone()), Ok(loaded_transaction) => { - let compute_budget = if let Some(compute_budget) = config.compute_budget { - compute_budget - } else { - let mut compute_budget_process_transaction_time = - Measure::start("compute_budget_process_transaction_time"); - let maybe_compute_budget = ComputeBudget::try_from_instructions( - tx.message().program_instructions_iter(), - ); - compute_budget_process_transaction_time.stop(); - saturating_add_assign!( - execute_timings - .execute_accessories - .compute_budget_process_transaction_us, - compute_budget_process_transaction_time.as_us() - ); - if let Err(err) = maybe_compute_budget { - return TransactionExecutionResult::NotExecuted(err); - } - maybe_compute_budget.unwrap() - }; - let result = self.execute_loaded_transaction( tx, loaded_transaction, - compute_budget, &mut execute_timings, &mut error_metrics, - &program_cache_for_tx_batch.borrow(), + &mut program_cache_for_tx_batch.borrow_mut(), environment, config, ); @@ -415,6 +383,7 @@ impl TransactionBatchProcessor { sanitized_txs: &[impl core::borrow::Borrow], check_results: Vec, feature_set: &FeatureSet, + fee_structure: &FeeStructure, rent_collector: &RentCollector, error_counters: &mut TransactionErrorMetrics, ) -> Vec { @@ -422,41 +391,18 @@ impl TransactionBatchProcessor { .iter() .zip(check_results) .map(|(sanitized_tx, check_result)| { - check_result.and_then( - |CheckedTransactionDetails { - nonce, - lamports_per_signature, - }| { - let message = sanitized_tx.borrow().message(); - let (fee_details, fee_payer_account, fee_payer_rent_debit) = self - .validate_transaction_fee_payer( - callbacks, - message, - feature_set, - lamports_per_signature, - rent_collector, - error_counters, - )?; - - // Update nonce with fee-subtracted accounts - let fee_payer_address = message.fee_payer(); - let nonce = nonce.map(|nonce| { - NonceFull::from_partial( - nonce, - fee_payer_address, - fee_payer_account.clone(), - fee_payer_rent_debit, - ) - }); - - Ok(ValidatedTransactionDetails { - nonce, - fee_details, - fee_payer_account, - fee_payer_rent_debit, - }) - }, - ) + check_result.and_then(|checked_details| { + let message = sanitized_tx.borrow().message(); + self.validate_transaction_fee_payer( + callbacks, + message, + checked_details, + feature_set, + fee_structure, + rent_collector, + error_counters, + ) + }) }) .collect() } @@ -468,11 +414,20 @@ impl TransactionBatchProcessor { &self, callbacks: &CB, message: &SanitizedMessage, + checked_details: CheckedTransactionDetails, feature_set: &FeatureSet, - lamports_per_signature: u64, + fee_structure: &FeeStructure, rent_collector: &RentCollector, error_counters: &mut TransactionErrorMetrics, - ) -> transaction::Result<(FeeDetails, AccountSharedData, u64)> { + ) -> transaction::Result { + let compute_budget_limits = process_compute_budget_instructions( + message.program_instructions_iter(), + ) + .map_err(|err| { + error_counters.invalid_compute_budget += 1; + err + })?; + let fee_payer_address = message.fee_payer(); let Some(mut fee_payer_account) = callbacks.get_account_shared_data(fee_payer_address) else { @@ -480,6 +435,7 @@ impl TransactionBatchProcessor { return Err(TransactionError::AccountNotFound); }; + let fee_payer_loaded_rent_epoch = fee_payer_account.rent_epoch(); let fee_payer_rent_debit = collect_rent_from_account( feature_set, rent_collector, @@ -488,12 +444,16 @@ impl TransactionBatchProcessor { ) .rent_amount; - let fee_details = self.fee_structure.calculate_fee_details( + let CheckedTransactionDetails { + nonce, + lamports_per_signature, + } = checked_details; + + let fee_budget_limits = FeeBudgetLimits::from(compute_budget_limits); + let fee_details = fee_structure.calculate_fee_details( message, lamports_per_signature, - &process_compute_budget_instructions(message.program_instructions_iter()) - .unwrap_or_default() - .into(), + &fee_budget_limits, feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()), feature_set.is_active(&remove_rounding_in_fee_calculation::id()), ); @@ -508,7 +468,23 @@ impl TransactionBatchProcessor { fee_details.total_fee(), )?; - Ok((fee_details, fee_payer_account, fee_payer_rent_debit)) + // Capture fee-subtracted fee payer account and original nonce account state + // to rollback to if transaction execution fails. + let rollback_accounts = RollbackAccounts::new( + nonce, + *fee_payer_address, + fee_payer_account.clone(), + fee_payer_rent_debit, + fee_payer_loaded_rent_epoch, + ); + + Ok(ValidatedTransactionDetails { + fee_details, + fee_payer_account, + fee_payer_rent_debit, + rollback_accounts, + compute_budget_limits, + }) } /// Returns a map from executable program accounts (all accounts owned by any loader) @@ -548,16 +524,22 @@ impl TransactionBatchProcessor { &self, callback: &CB, program_accounts_map: &HashMap, + check_program_modification_slot: bool, limit_to_load_programs: bool, ) -> ProgramCacheForTxBatch { let mut missing_programs: Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))> = program_accounts_map .iter() .map(|(pubkey, count)| { - ( - *pubkey, - (callback.get_program_match_criteria(pubkey), *count), - ) + let match_criteria = if check_program_modification_slot { + get_program_modification_slot(callback, pubkey) + .map_or(ProgramCacheMatchCriteria::Tombstone, |slot| { + ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) + }) + } else { + ProgramCacheMatchCriteria::NoCriteria + }; + (*pubkey, (match_criteria, *count)) }) .collect(); @@ -589,7 +571,6 @@ impl TransactionBatchProcessor { &program_cache.get_environments_for_epoch(self.epoch), &key, self.slot, - &self.epoch_schedule, false, ) .expect("called load_program_with_pubkey() with nonexistent account"); @@ -637,10 +618,10 @@ impl TransactionBatchProcessor { callbacks: &CB, upcoming_feature_set: &FeatureSet, compute_budget: &ComputeBudget, + slot_index: u64, + slots_in_epoch: u64, ) { // Recompile loaded programs one at a time before the next epoch hits - let (_epoch, slot_index) = self.epoch_schedule.get_epoch_and_slot_index(self.slot); - let slots_in_epoch = self.epoch_schedule.get_slots_in_epoch(self.epoch); let slots_in_recompilation_phase = (solana_program_runtime::loaded_programs::MAX_LOADED_ENTRY_COUNT as u64) .min(slots_in_epoch) @@ -661,15 +642,20 @@ impl TransactionBatchProcessor { &environments_for_epoch, &key, self.slot, - &self.epoch_schedule, false, ) { - recompiled - .tx_usage_counter - .fetch_add(program_to_recompile.tx_usage_counter.load(Relaxed), Relaxed); - recompiled - .ix_usage_counter - .fetch_add(program_to_recompile.ix_usage_counter.load(Relaxed), Relaxed); + recompiled.tx_usage_counter.fetch_add( + program_to_recompile + .tx_usage_counter + .load(Ordering::Relaxed), + Ordering::Relaxed, + ); + recompiled.ix_usage_counter.fetch_add( + program_to_recompile + .ix_usage_counter + .load(Ordering::Relaxed), + Ordering::Relaxed, + ); let mut program_cache = self.program_cache.write().unwrap(); program_cache.assign_program(key, recompiled); } @@ -719,10 +705,9 @@ impl TransactionBatchProcessor { &self, tx: &SanitizedTransaction, loaded_transaction: &mut LoadedTransaction, - compute_budget: ComputeBudget, execute_timings: &mut ExecuteTimings, error_metrics: &mut TransactionErrorMetrics, - program_cache_for_tx_batch: &ProgramCacheForTxBatch, + program_cache_for_tx_batch: &mut ProgramCacheForTxBatch, environment: &TransactionProcessingEnvironment, config: &TransactionProcessingConfig, ) -> TransactionExecutionResult { @@ -748,10 +733,14 @@ impl TransactionBatchProcessor { let lamports_before_tx = transaction_accounts_lamports_sum(&transaction_accounts, tx.message()).unwrap_or(0); + let compute_budget = config + .compute_budget + .unwrap_or_else(|| ComputeBudget::from(loaded_transaction.compute_budget_limits)); + let mut transaction_context = TransactionContext::new( transaction_accounts, rent.clone(), - compute_budget.max_invoke_stack_height, + compute_budget.max_instruction_stack_depth, compute_budget.max_instruction_trace_length, ); #[cfg(debug_assertions)] @@ -775,12 +764,6 @@ impl TransactionBatchProcessor { let lamports_per_signature = environment.lamports_per_signature; let mut executed_units = 0u64; - let mut programs_modified_by_tx = ProgramCacheForTxBatch::new( - self.slot, - program_cache_for_tx_batch.environments.clone(), - program_cache_for_tx_batch.upcoming_environments.clone(), - program_cache_for_tx_batch.latest_root_epoch, - ); let sysvar_cache = &self.sysvar_cache.read().unwrap(); let mut invoke_context = InvokeContext::new( @@ -796,7 +779,6 @@ impl TransactionBatchProcessor { ), log_collector.clone(), compute_budget, - &mut programs_modified_by_tx, ); let mut process_message_time = Measure::start("process_message_time"); @@ -898,12 +880,11 @@ impl TransactionBatchProcessor { log_messages, inner_instructions, fee_details: loaded_transaction.fee_details, - is_nonce: loaded_transaction.nonce.is_some(), return_data, executed_units, accounts_data_len_delta, }, - programs_modified_by_tx: programs_modified_by_tx.take_entries(), + programs_modified_by_tx: program_cache_for_tx_batch.drain_modified_entries(), } } @@ -1003,13 +984,18 @@ impl TransactionBatchProcessor { mod tests { use { super::*, - crate::account_loader::ValidatedTransactionDetails, + crate::{ + account_loader::ValidatedTransactionDetails, nonce_info::NoncePartial, + rollback_accounts::RollbackAccounts, + }, + solana_compute_budget::compute_budget_processor::ComputeBudgetLimits, solana_program_runtime::loaded_programs::{BlockRelation, ProgramCacheEntryType}, solana_sdk::{ account::{create_account_shared_data_for_test, WritableAccount}, bpf_loader, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, compute_budget::ComputeBudgetInstruction, + epoch_schedule::EpochSchedule, feature_set::FeatureSet, fee::FeeDetails, fee_calculator::FeeCalculator, @@ -1149,7 +1135,7 @@ mod tests { }; let sanitized_message = new_unchecked_sanitized_message(message); - let program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); let batch_processor = TransactionBatchProcessor::::default(); let sanitized_transaction = SanitizedTransaction::new_for_tests( @@ -1161,8 +1147,9 @@ mod tests { let mut loaded_transaction = LoadedTransaction { accounts: vec![(Pubkey::new_unique(), AccountSharedData::default())], program_indices: vec![vec![0]], - nonce: None, fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 32, @@ -1176,10 +1163,9 @@ mod tests { let result = batch_processor.execute_loaded_transaction( &sanitized_transaction, &mut loaded_transaction, - ComputeBudget::default(), &mut ExecuteTimings::default(), &mut TransactionErrorMetrics::default(), - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, &processing_environment, &processing_config, ); @@ -1198,10 +1184,9 @@ mod tests { let result = batch_processor.execute_loaded_transaction( &sanitized_transaction, &mut loaded_transaction, - ComputeBudget::default(), &mut ExecuteTimings::default(), &mut TransactionErrorMetrics::default(), - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, &processing_environment, &processing_config, ); @@ -1228,10 +1213,9 @@ mod tests { let result = batch_processor.execute_loaded_transaction( &sanitized_transaction, &mut loaded_transaction, - ComputeBudget::default(), &mut ExecuteTimings::default(), &mut TransactionErrorMetrics::default(), - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, &processing_environment, &processing_config, ); @@ -1271,7 +1255,7 @@ mod tests { }; let sanitized_message = new_unchecked_sanitized_message(message); - let program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); let batch_processor = TransactionBatchProcessor::::default(); let sanitized_transaction = SanitizedTransaction::new_for_tests( @@ -1288,8 +1272,9 @@ mod tests { (key2, AccountSharedData::default()), ], program_indices: vec![vec![0]], - nonce: None, fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), rent: 0, rent_debits: RentDebits::default(), loaded_accounts_data_size: 0, @@ -1304,10 +1289,9 @@ mod tests { let _ = batch_processor.execute_loaded_transaction( &sanitized_transaction, &mut loaded_transaction, - ComputeBudget::default(), &mut ExecuteTimings::default(), &mut error_metrics, - &program_cache_for_tx_batch, + &mut program_cache_for_tx_batch, &TransactionProcessingEnvironment::default(), &processing_config, ); @@ -1327,7 +1311,7 @@ mod tests { let mut account_maps: HashMap = HashMap::new(); account_maps.insert(key, 4); - batch_processor.replenish_program_cache(&mock_bank, &account_maps, true); + batch_processor.replenish_program_cache(&mock_bank, &account_maps, false, true); } #[test] @@ -1353,6 +1337,7 @@ mod tests { let result = batch_processor.replenish_program_cache( &mock_bank, &account_maps, + false, limit_to_load_programs, ); assert!(!result.hit_max_limit); @@ -1856,12 +1841,7 @@ mod tests { #[test] fn fast_concur_test() { let mut mock_bank = MockBankCallback::default(); - let batch_processor = TransactionBatchProcessor::::new( - 5, - 5, - EpochSchedule::default(), - HashSet::new(), - ); + let batch_processor = TransactionBatchProcessor::::new(5, 5, HashSet::new()); batch_processor.program_cache.write().unwrap().fork_graph = Some(Arc::new(RwLock::new(TestForkGraph {}))); @@ -1889,7 +1869,8 @@ mod tests { let maps = account_maps.clone(); let programs = programs.clone(); thread::spawn(move || { - let result = processor.replenish_program_cache(&local_bank, &maps, true); + let result = + processor.replenish_program_cache(&local_bank, &maps, false, true); for key in &programs { let cache_entry = result.find(key); assert!(matches!( @@ -1973,8 +1954,14 @@ mod tests { Some(&Pubkey::new_unique()), &Hash::new_unique(), )); + let compute_budget_limits = + process_compute_budget_instructions(message.program_instructions_iter()).unwrap(); let fee_payer_address = message.fee_payer(); - let rent_collector = RentCollector::default(); + let current_epoch = 42; + let rent_collector = RentCollector { + epoch: current_epoch, + ..RentCollector::default() + }; let min_balance = rent_collector.rent.minimum_balance(nonce::State::size()); let transaction_fee = lamports_per_signature; let priority_fee = 2_000_000u64; @@ -1985,7 +1972,14 @@ mod tests { so ensure that the starting balance is more than the min balance" ); - let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default()); + let fee_payer_rent_epoch = current_epoch; + let fee_payer_rent_debit = 0; + let fee_payer_account = AccountSharedData::new_rent_epoch( + starting_balance, + 0, + &Pubkey::default(), + fee_payer_rent_epoch, + ); let mut mock_accounts = HashMap::new(); mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); let mock_bank = MockBankCallback { @@ -1997,8 +1991,12 @@ mod tests { let result = batch_processor.validate_transaction_fee_payer( &mock_bank, &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, &FeatureSet::default(), - lamports_per_signature, + &FeeStructure::default(), &rent_collector, &mut error_counters, ); @@ -2012,11 +2010,19 @@ mod tests { assert_eq!( result, - Ok(( - FeeDetails::new_for_tests(transaction_fee, priority_fee, false), - post_validation_fee_payer_account, - 0 // rent due - )) + Ok(ValidatedTransactionDetails { + rollback_accounts: RollbackAccounts::new( + None, // nonce + *fee_payer_address, + post_validation_fee_payer_account.clone(), + fee_payer_rent_debit, + fee_payer_rent_epoch + ), + compute_budget_limits, + fee_details: FeeDetails::new_for_tests(transaction_fee, priority_fee, false), + fee_payer_rent_debit, + fee_payer_account: post_validation_fee_payer_account, + }) ); } @@ -2028,6 +2034,8 @@ mod tests { Some(&Pubkey::new_unique()), &Hash::new_unique(), )); + let compute_budget_limits = + process_compute_budget_instructions(message.program_instructions_iter()).unwrap(); let fee_payer_address = message.fee_payer(); let mut rent_collector = RentCollector::default(); rent_collector.rent.lamports_per_byte_year = 1_000_000; @@ -2035,14 +2043,14 @@ mod tests { let transaction_fee = lamports_per_signature; let starting_balance = min_balance - 1; let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default()); - let rent_due = rent_collector + let fee_payer_rent_debit = rent_collector .get_rent_due( fee_payer_account.lamports(), fee_payer_account.data().len(), fee_payer_account.rent_epoch(), ) .lamports(); - assert!(rent_due > 0); + assert!(fee_payer_rent_debit > 0); let mut mock_accounts = HashMap::new(); mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); @@ -2055,8 +2063,12 @@ mod tests { let result = batch_processor.validate_transaction_fee_payer( &mock_bank, &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, &FeatureSet::default(), - lamports_per_signature, + &FeeStructure::default(), &rent_collector, &mut error_counters, ); @@ -2064,17 +2076,25 @@ mod tests { let post_validation_fee_payer_account = { let mut account = fee_payer_account.clone(); account.set_rent_epoch(1); - account.set_lamports(starting_balance - transaction_fee - rent_due); + account.set_lamports(starting_balance - transaction_fee - fee_payer_rent_debit); account }; assert_eq!( result, - Ok(( - FeeDetails::new_for_tests(transaction_fee, 0, false), - post_validation_fee_payer_account, - rent_due, - )) + Ok(ValidatedTransactionDetails { + rollback_accounts: RollbackAccounts::new( + None, // nonce + *fee_payer_address, + post_validation_fee_payer_account.clone(), + fee_payer_rent_debit, + 0, // rent epoch + ), + compute_budget_limits, + fee_details: FeeDetails::new_for_tests(transaction_fee, 0, false), + fee_payer_rent_debit, + fee_payer_account: post_validation_fee_payer_account, + }) ); } @@ -2090,8 +2110,12 @@ mod tests { let result = batch_processor.validate_transaction_fee_payer( &mock_bank, &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, &FeatureSet::default(), - lamports_per_signature, + &FeeStructure::default(), &RentCollector::default(), &mut error_counters, ); @@ -2118,8 +2142,12 @@ mod tests { let result = batch_processor.validate_transaction_fee_payer( &mock_bank, &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, &FeatureSet::default(), - lamports_per_signature, + &FeeStructure::default(), &RentCollector::default(), &mut error_counters, ); @@ -2150,8 +2178,12 @@ mod tests { let result = batch_processor.validate_transaction_fee_payer( &mock_bank, &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, &FeatureSet::default(), - lamports_per_signature, + &FeeStructure::default(), &rent_collector, &mut error_counters, ); @@ -2180,8 +2212,12 @@ mod tests { let result = batch_processor.validate_transaction_fee_payer( &mock_bank, &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, &FeatureSet::default(), - lamports_per_signature, + &FeeStructure::default(), &RentCollector::default(), &mut error_counters, ); @@ -2190,6 +2226,37 @@ mod tests { assert_eq!(result, Err(TransactionError::InvalidAccountForFee)); } + #[test] + fn test_validate_transaction_fee_payer_invalid_compute_budget() { + let lamports_per_signature = 5000; + let message = new_unchecked_sanitized_message(Message::new( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(2000u32), + ComputeBudgetInstruction::set_compute_unit_limit(42u32), + ], + Some(&Pubkey::new_unique()), + )); + + let mock_bank = MockBankCallback::default(); + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &FeatureSet::default(), + &FeeStructure::default(), + &RentCollector::default(), + &mut error_counters, + ); + + assert_eq!(error_counters.invalid_compute_budget, 1); + assert_eq!(result, Err(TransactionError::DuplicateInstruction(1u8))); + } + #[test] fn test_validate_transaction_fee_payer_is_nonce() { let feature_set = FeatureSet::default(); @@ -2204,6 +2271,8 @@ mod tests { Some(&Pubkey::new_unique()), &Hash::new_unique(), )); + let compute_budget_limits = + process_compute_budget_instructions(message.program_instructions_iter()).unwrap(); let fee_payer_address = message.fee_payer(); let min_balance = Rent::default().minimum_balance(nonce::State::size()); let transaction_fee = lamports_per_signature; @@ -2228,11 +2297,19 @@ mod tests { let mut error_counters = TransactionErrorMetrics::default(); let batch_processor = TransactionBatchProcessor::::default(); + let nonce = Some(NoncePartial::new( + *fee_payer_address, + fee_payer_account.clone(), + )); let result = batch_processor.validate_transaction_fee_payer( &mock_bank, &message, + CheckedTransactionDetails { + nonce: nonce.clone(), + lamports_per_signature, + }, &feature_set, - lamports_per_signature, + &FeeStructure::default(), &rent_collector, &mut error_counters, ); @@ -2246,11 +2323,19 @@ mod tests { assert_eq!( result, - Ok(( - FeeDetails::new_for_tests(transaction_fee, priority_fee, false), - post_validation_fee_payer_account, - 0 // rent due - )) + Ok(ValidatedTransactionDetails { + rollback_accounts: RollbackAccounts::new( + nonce, + *fee_payer_address, + post_validation_fee_payer_account.clone(), + 0, // fee_payer_rent_debit + 0, // fee_payer_rent_epoch + ), + compute_budget_limits, + fee_details: FeeDetails::new_for_tests(transaction_fee, priority_fee, false), + fee_payer_rent_debit: 0, // rent due + fee_payer_account: post_validation_fee_payer_account, + }) ); } @@ -2276,8 +2361,12 @@ mod tests { let result = batch_processor.validate_transaction_fee_payer( &mock_bank, &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, &feature_set, - lamports_per_signature, + &FeeStructure::default(), &rent_collector, &mut error_counters, ); diff --git a/svm/src/transaction_results.rs b/svm/src/transaction_results.rs index 1fcc9f7ce4b7c5..9f829a675267ed 100644 --- a/svm/src/transaction_results.rs +++ b/svm/src/transaction_results.rs @@ -85,7 +85,6 @@ pub struct TransactionExecutionDetails { pub log_messages: Option>, pub inner_instructions: Option, pub fee_details: FeeDetails, - pub is_nonce: bool, pub return_data: Option, pub executed_units: u64, /// The change in accounts data len for this transaction. diff --git a/svm/tests/conformance.rs b/svm/tests/conformance.rs index ec5bbe0c9fd529..865cf325cb2596 100644 --- a/svm/tests/conformance.rs +++ b/svm/tests/conformance.rs @@ -21,7 +21,6 @@ use { solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, bpf_loader_upgradeable, - epoch_schedule::EpochSchedule, feature_set::{FeatureSet, FEATURE_NAMES}, hash::Hash, instruction::AccountMeta, @@ -247,12 +246,7 @@ fn run_fixture(fixture: InstrFixture, filename: OsString, execute_as_instr: bool create_program_runtime_environment_v1(&feature_set, &compute_budget, false, false).unwrap(); mock_bank.override_feature_set(feature_set); - let batch_processor = TransactionBatchProcessor::::new( - 42, - 2, - EpochSchedule::default(), - HashSet::new(), - ); + let batch_processor = TransactionBatchProcessor::::new(42, 2, HashSet::new()); { let mut program_cache = batch_processor.program_cache.write().unwrap(); @@ -271,9 +265,7 @@ fn run_fixture(fixture: InstrFixture, filename: OsString, execute_as_instr: bool #[allow(deprecated)] let (blockhash, lamports_per_signature) = batch_processor - .sysvar_cache - .read() - .unwrap() + .sysvar_cache() .get_recent_blockhashes() .ok() .and_then(|x| (*x).last().cloned()) @@ -287,6 +279,7 @@ fn run_fixture(fixture: InstrFixture, filename: OsString, execute_as_instr: bool }; let processor_config = TransactionProcessingConfig { account_overrides: None, + check_program_modification_slot: false, compute_budget: None, log_messages_bytes_limit: None, limit_to_load_programs: true, @@ -406,7 +399,7 @@ fn execute_fixture_as_instr( filename: OsString, cu_avail: u64, ) { - let rent = if let Ok(rent) = batch_processor.sysvar_cache.read().unwrap().get_rent() { + let rent = if let Ok(rent) = batch_processor.sysvar_cache().get_rent() { (*rent).clone() } else { Rent::default() @@ -421,7 +414,7 @@ fn execute_fixture_as_instr( let mut transaction_context = TransactionContext::new( transaction_accounts, rent, - compute_budget.max_invoke_stack_height, + compute_budget.max_instruction_stack_depth, compute_budget.max_instruction_trace_length, ); @@ -445,7 +438,6 @@ fn execute_fixture_as_instr( &batch_processor.get_environments_for_epoch(2).unwrap(), &program_id, 42, - &batch_processor.epoch_schedule, false, ) .unwrap(); @@ -460,10 +452,9 @@ fn execute_fixture_as_instr( )), ); - let mut programs_modified_by_tx = ProgramCacheForTxBatch::default(); let log_collector = LogCollector::new_ref(); - let sysvar_cache = &batch_processor.sysvar_cache.read().unwrap(); + let sysvar_cache = &batch_processor.sysvar_cache(); let env_config = EnvironmentConfig::new( Hash::default(), None, @@ -475,11 +466,10 @@ fn execute_fixture_as_instr( let mut invoke_context = InvokeContext::new( &mut transaction_context, - &loaded_programs, + &mut loaded_programs, env_config, Some(log_collector.clone()), compute_budget, - &mut programs_modified_by_tx, ); let mut instruction_accounts: Vec = diff --git a/svm/tests/example-programs/clock-sysvar/Cargo.toml b/svm/tests/example-programs/clock-sysvar/Cargo.toml index 082c29bbfe34fd..485b780f533484 100644 --- a/svm/tests/example-programs/clock-sysvar/Cargo.toml +++ b/svm/tests/example-programs/clock-sysvar/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "clock-sysvar-program" -version = "2.0.0" +version = "2.0.2" edition = "2021" [dependencies] -solana-program = { path = "../../../../sdk/program", version = "=2.0.0" } +solana-program = { path = "../../../../sdk/program", version = "=2.0.2" } [lib] crate-type = ["cdylib", "rlib"] diff --git a/svm/tests/example-programs/hello-solana/Cargo.toml b/svm/tests/example-programs/hello-solana/Cargo.toml index 09995d8c6d8d2c..afc99d9b1a3fbb 100644 --- a/svm/tests/example-programs/hello-solana/Cargo.toml +++ b/svm/tests/example-programs/hello-solana/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "hello-solana-program" -version = "2.0.0" +version = "2.0.2" edition = "2021" [dependencies] -solana-program = { path = "../../../../sdk/program", version = "=2.0.0" } +solana-program = { path = "../../../../sdk/program", version = "=2.0.2" } [lib] crate-type = ["cdylib", "rlib"] diff --git a/svm/tests/example-programs/simple-transfer/Cargo.toml b/svm/tests/example-programs/simple-transfer/Cargo.toml index 9ccbf60aa8b8f7..d25bbbc4ffd5f8 100644 --- a/svm/tests/example-programs/simple-transfer/Cargo.toml +++ b/svm/tests/example-programs/simple-transfer/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "simple-transfer-program" -version = "2.0.0" +version = "2.0.2" edition = "2021" [dependencies] -solana-program = { path = "../../../../sdk/program", version = "=2.0.0" } +solana-program = { path = "../../../../sdk/program", version = "=2.0.2" } [lib] crate-type = ["cdylib", "rlib"] diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index 9816d41cbc4dae..c47ce03af9b5a1 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -21,7 +21,6 @@ use { account::{AccountSharedData, ReadableAccount, WritableAccount}, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::{Clock, Epoch, Slot, UnixTimestamp}, - epoch_schedule::EpochSchedule, hash::Hash, instruction::AccountMeta, pubkey::Pubkey, @@ -445,7 +444,6 @@ fn svm_integration() { let batch_processor = TransactionBatchProcessor::::new( EXECUTION_SLOT, EXECUTION_EPOCH, - EpochSchedule::default(), HashSet::new(), ); diff --git a/test-validator/src/lib.rs b/test-validator/src/lib.rs index 364e527542a8e8..da0d669b76393a 100644 --- a/test-validator/src/lib.rs +++ b/test-validator/src/lib.rs @@ -44,8 +44,7 @@ use { epoch_schedule::EpochSchedule, exit::Exit, feature_set::FEATURE_NAMES, - fee_calculator::{FeeCalculator, FeeRateGovernor}, - hash::Hash, + fee_calculator::FeeRateGovernor, instruction::{AccountMeta, Instruction}, message::Message, native_token::sol_to_lamports, @@ -78,14 +77,6 @@ pub struct AccountInfo<'a> { pub filename: &'a str, } -#[deprecated(since = "1.16.0", note = "Please use `UpgradeableProgramInfo` instead")] -#[derive(Clone)] -pub struct ProgramInfo { - pub program_id: Pubkey, - pub loader: Pubkey, - pub program_path: PathBuf, -} - #[derive(Clone)] pub struct UpgradeableProgramInfo { pub program_id: Pubkey, @@ -127,8 +118,6 @@ pub struct TestValidatorGenesis { rpc_ports: Option<(u16, u16)>, // (JsonRpc, JsonRpcPubSub), None == random ports warp_slot: Option, accounts: HashMap, - #[allow(deprecated)] - programs: Vec, upgradeable_programs: Vec, ticks_per_slot: Option, epoch_schedule: Option, @@ -161,8 +150,6 @@ impl Default for TestValidatorGenesis { rpc_ports: Option::<(u16, u16)>::default(), warp_slot: Option::::default(), accounts: HashMap::::default(), - #[allow(deprecated)] - programs: Vec::::default(), upgradeable_programs: Vec::::default(), ticks_per_slot: Option::::default(), epoch_schedule: Option::::default(), @@ -320,11 +307,6 @@ impl TestValidatorGenesis { self } - #[deprecated(note = "Please use `compute_unit_limit` instead")] - pub fn max_compute_units(&mut self, max_compute_units: u64) -> &mut Self { - self.compute_unit_limit(max_compute_units) - } - /// Add an account to the test environment pub fn add_account(&mut self, address: Pubkey, account: AccountSharedData) -> &mut Self { self.accounts.insert(address, account); @@ -582,19 +564,6 @@ impl TestValidatorGenesis { self } - /// Add a list of programs to the test environment. - #[deprecated( - since = "1.16.0", - note = "Please use `add_upgradeable_programs_with_path()` instead" - )] - #[allow(deprecated)] - pub fn add_programs_with_path(&mut self, programs: &[ProgramInfo]) -> &mut Self { - for program in programs { - self.programs.push(program.clone()); - } - self - } - /// Add a list of upgradeable programs to the test environment. pub fn add_upgradeable_programs_with_path( &mut self, @@ -796,20 +765,6 @@ impl TestValidator { for (address, account) in solana_program_test::programs::spl_programs(&config.rent) { accounts.entry(address).or_insert(account); } - #[allow(deprecated)] - for program in &config.programs { - let data = solana_program_test::read_file(&program.program_path); - accounts.insert( - program.program_id, - AccountSharedData::from(Account { - lamports: Rent::default().minimum_balance(data.len()).max(1), - data, - owner: program.loader, - executable: true, - rent_epoch: 0, - }), - ); - } for upgradeable_program in &config.upgradeable_programs { let data = solana_program_test::read_file(&upgradeable_program.program_path); let (programdata_address, _) = Pubkey::find_program_address( @@ -1163,20 +1118,6 @@ impl TestValidator { self.vote_account_address } - /// Return an RpcClient for the validator. As a convenience, also return a recent blockhash and - /// associated fee calculator - #[deprecated(since = "1.9.0", note = "Please use `get_rpc_client` instead")] - pub fn rpc_client(&self) -> (RpcClient, Hash, FeeCalculator) { - let rpc_client = - RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::processed()); - #[allow(deprecated)] - let (recent_blockhash, fee_calculator) = rpc_client - .get_recent_blockhash() - .expect("get_recent_blockhash"); - - (rpc_client, recent_blockhash, fee_calculator) - } - /// Return an RpcClient for the validator. pub fn get_rpc_client(&self) -> RpcClient { RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::processed()) diff --git a/thin-client/README.md b/thin-client/README.md index 147b287b2d62b2..43ca0825a4ab5d 100644 --- a/thin-client/README.md +++ b/thin-client/README.md @@ -1,4 +1,4 @@ # thin-client -This crate for `thin-client` is deprecated as of v1.19.0. It will receive no bugfixes or updates. +This crate for `thin-client` is deprecated as of v2.0.0. It will receive no bugfixes or updates. Please use `tpu-client` or `rpc-client`. \ No newline at end of file diff --git a/thin-client/src/thin_client.rs b/thin-client/src/thin_client.rs index 9e226c254c7a20..f53ae499a8b68f 100644 --- a/thin-client/src/thin_client.rs +++ b/thin-client/src/thin_client.rs @@ -13,14 +13,13 @@ use { }, }, solana_rpc_client::rpc_client::RpcClient, - solana_rpc_client_api::{config::RpcProgramAccountsConfig, response::Response}, + solana_rpc_client_api::config::RpcProgramAccountsConfig, solana_sdk::{ account::Account, client::{AsyncClient, Client, SyncClient}, - clock::{Slot, MAX_PROCESSING_AGE}, + clock::MAX_PROCESSING_AGE, commitment_config::CommitmentConfig, epoch_info::EpochInfo, - fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, instruction::Instruction, message::Message, @@ -111,7 +110,7 @@ impl ClientOptimizer { } /// An object for querying and sending transactions to the network. -#[deprecated(since = "1.19.0", note = "Use [RpcClient] or [TpuClient] instead.")] +#[deprecated(since = "2.0.0", note = "Use [RpcClient] or [TpuClient] instead.")] pub struct ThinClient< P, // ConnectionPool M, // ConnectionManager @@ -419,52 +418,6 @@ where .map_err(|e| e.into()) } - fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> { - #[allow(deprecated)] - let (blockhash, fee_calculator, _last_valid_slot) = - self.get_recent_blockhash_with_commitment(CommitmentConfig::default())?; - Ok((blockhash, fee_calculator)) - } - - fn get_recent_blockhash_with_commitment( - &self, - commitment_config: CommitmentConfig, - ) -> TransportResult<(Hash, FeeCalculator, Slot)> { - let index = self.optimizer.experiment(); - let now = Instant::now(); - #[allow(deprecated)] - let recent_blockhash = - self.rpc_clients[index].get_recent_blockhash_with_commitment(commitment_config); - match recent_blockhash { - Ok(Response { value, .. }) => { - self.optimizer.report(index, duration_as_ms(&now.elapsed())); - Ok((value.0, value.1, value.2)) - } - Err(e) => { - self.optimizer.report(index, u64::MAX); - Err(e.into()) - } - } - } - - fn get_fee_calculator_for_blockhash( - &self, - blockhash: &Hash, - ) -> TransportResult> { - #[allow(deprecated)] - self.rpc_client() - .get_fee_calculator_for_blockhash(blockhash) - .map_err(|e| e.into()) - } - - fn get_fee_rate_governor(&self) -> TransportResult { - #[allow(deprecated)] - self.rpc_client() - .get_fee_rate_governor() - .map_err(|e| e.into()) - .map(|r| r.value) - } - fn get_signature_status( &self, signature: &Signature, @@ -575,13 +528,6 @@ where .map_err(|e| e.into()) } - fn get_new_blockhash(&self, blockhash: &Hash) -> TransportResult<(Hash, FeeCalculator)> { - #[allow(deprecated)] - self.rpc_client() - .get_new_blockhash(blockhash) - .map_err(|e| e.into()) - } - fn get_latest_blockhash(&self) -> TransportResult { let (blockhash, _) = self.get_latest_blockhash_with_commitment(CommitmentConfig::default())?; diff --git a/tps-client/Cargo.toml b/tps-client/Cargo.toml index 1bd4ab96ee05b3..c9bcf76325b5f7 100644 --- a/tps-client/Cargo.toml +++ b/tps-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solana-tps-client" -publish = false +description = "Blockchain, Rebuilt for Scale" version = { workspace = true } authors = { workspace = true } repository = { workspace = true } diff --git a/transaction-dos/src/main.rs b/transaction-dos/src/main.rs index d006972dca5649..72deea1422f161 100644 --- a/transaction-dos/src/main.rs +++ b/transaction-dos/src/main.rs @@ -251,6 +251,7 @@ fn run_transactions_dos( use_rpc: false, skip_fee_check: true, // skip_fee_check auto_extend: true, + skip_feature_verification: true, }); process_command(&config).expect("deploy didn't pass"); diff --git a/transaction-metrics-tracker/Cargo.toml b/transaction-metrics-tracker/Cargo.toml index 9bd82702a3ebb4..c4882603174422 100644 --- a/transaction-metrics-tracker/Cargo.toml +++ b/transaction-metrics-tracker/Cargo.toml @@ -8,7 +8,6 @@ repository = { workspace = true } homepage = { workspace = true } license = { workspace = true } edition = { workspace = true } -publish = false [dependencies] Inflector = { workspace = true } diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 7779cfc5ae9353..489a213c525a94 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -628,6 +628,11 @@ pub struct Reward { pub type Rewards = Vec; +pub struct RewardsAndNumPartitions { + pub rewards: Rewards, + pub num_partitions: Option, +} + #[derive(Debug, Error)] pub enum ConvertBlockError { #[error("transactions missing after converted, before: {0}, after: {1}")] @@ -641,6 +646,7 @@ pub struct ConfirmedBlock { pub parent_slot: Slot, pub transactions: Vec, pub rewards: Rewards, + pub num_partitions: Option, pub block_time: Option, pub block_height: Option, } @@ -654,6 +660,7 @@ pub struct VersionedConfirmedBlock { pub parent_slot: Slot, pub transactions: Vec, pub rewards: Rewards, + pub num_partitions: Option, pub block_time: Option, pub block_height: Option, } @@ -670,6 +677,7 @@ impl From for ConfirmedBlock { .map(TransactionWithStatusMeta::Complete) .collect(), rewards: block.rewards, + num_partitions: block.num_partitions, block_time: block.block_time, block_height: block.block_height, } @@ -704,6 +712,7 @@ impl TryFrom for VersionedConfirmedBlock { parent_slot: block.parent_slot, transactions: txs, rewards: block.rewards, + num_partitions: block.num_partitions, block_time: block.block_time, block_height: block.block_height, }) @@ -768,6 +777,7 @@ impl ConfirmedBlock { } else { None }, + num_reward_partitions: self.num_partitions, block_time: self.block_time, block_height: self.block_height, }) @@ -782,6 +792,7 @@ pub struct EncodedConfirmedBlock { pub parent_slot: Slot, pub transactions: Vec, pub rewards: Rewards, + pub num_partitions: Option, pub block_time: Option, pub block_height: Option, } @@ -794,6 +805,7 @@ impl From for EncodedConfirmedBlock { parent_slot: block.parent_slot, transactions: block.transactions.unwrap_or_default(), rewards: block.rewards.unwrap_or_default(), + num_partitions: block.num_reward_partitions, block_time: block.block_time, block_height: block.block_height, } @@ -812,6 +824,8 @@ pub struct UiConfirmedBlock { pub signatures: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub rewards: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub num_reward_partitions: Option, pub block_time: Option, pub block_height: Option, } diff --git a/transaction-status/src/parse_token.rs b/transaction-status/src/parse_token.rs index d445ba51101607..bc4e57fc467af7 100644 --- a/transaction-status/src/parse_token.rs +++ b/transaction-status/src/parse_token.rs @@ -14,17 +14,11 @@ use { parse_account_data::SplTokenAdditionalData, parse_token::{token_amount_to_ui_amount_v2, UiAccountState}, }, - solana_sdk::{ - instruction::{AccountMeta, CompiledInstruction, Instruction}, - message::AccountKeys, - }, + solana_sdk::{instruction::CompiledInstruction, message::AccountKeys}, spl_token_2022::{ extension::ExtensionType, instruction::{AuthorityType, TokenInstruction}, - solana_program::{ - instruction::Instruction as SplTokenInstruction, program_option::COption, - pubkey::Pubkey, - }, + solana_program::{program_option::COption, pubkey::Pubkey}, }, spl_token_group_interface::instruction::TokenGroupInstruction, spl_token_metadata_interface::instruction::TokenMetadataInstruction, @@ -498,7 +492,7 @@ pub fn parse_token( instruction_type: "amountToUiAmount".to_string(), info: json!({ "mint": account_keys[instruction.accounts[0] as usize].to_string(), - "amount": amount, + "amount": amount.to_string(), }), }) } @@ -851,23 +845,6 @@ fn check_num_token_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInst check_num_accounts(accounts, num, ParsableProgram::SplToken) } -#[deprecated(since = "1.16.0", note = "Instruction conversions no longer needed")] -pub fn spl_token_instruction(instruction: SplTokenInstruction) -> Instruction { - Instruction { - program_id: instruction.program_id, - accounts: instruction - .accounts - .iter() - .map(|meta| AccountMeta { - pubkey: meta.pubkey, - is_signer: meta.is_signer, - is_writable: meta.is_writable, - }) - .collect(), - data: instruction.data, - } -} - fn map_coption_pubkey(pubkey: COption) -> Option { match pubkey { COption::Some(pubkey) => Some(pubkey.to_string()), @@ -1737,7 +1714,7 @@ mod test { instruction_type: "amountToUiAmount".to_string(), info: json!({ "mint": mint_pubkey.to_string(), - "amount": 4242, + "amount": "4242", }) } ); diff --git a/type-overrides/Cargo.toml b/type-overrides/Cargo.toml new file mode 100644 index 00000000000000..07b69542e53e6f --- /dev/null +++ b/type-overrides/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solana-type-overrides" +description = "Type overrides for specialized testing" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +futures = { workspace = true, optional = true } +lazy_static = { workspace = true } +rand = { workspace = true } +shuttle = { workspace = true, optional = true } + +[features] +shuttle-test = ["dep:shuttle"] +executor = ["dep:futures"] diff --git a/type-overrides/src/lib.rs b/type-overrides/src/lib.rs new file mode 100644 index 00000000000000..4d9a2ac1a63922 --- /dev/null +++ b/type-overrides/src/lib.rs @@ -0,0 +1,50 @@ +/// +/// This lib contains both standard imports and imports shuttle. +/// Shuttle is a Rust crate that facilitates multithreaded testing. It has its own scheduler +/// and can efficiently detect bugs in concurrent code. The downside is that we need to replace +/// all imports by those from Shuttle. +/// +/// Instead of importing from std, rand, and so on, import the following from solana-type-override, +/// and include the 'shuttle-test' feature in your crate to use shuttle. + +#[cfg(feature = "executor")] +pub mod executor { + #[cfg(not(feature = "shuttle-test"))] + pub use futures::executor::*; + #[cfg(feature = "shuttle-test")] + pub use shuttle::future::*; +} + +pub mod hint { + #[cfg(feature = "shuttle-test")] + pub use shuttle::hint::*; + #[cfg(not(feature = "shuttle-test"))] + pub use std::hint::*; +} + +pub mod lazy_static { + #[cfg(not(feature = "shuttle-test"))] + pub use lazy_static::*; + #[cfg(feature = "shuttle-test")] + pub use shuttle::lazy_static::*; +} + +pub mod rand { + pub use rand::*; + #[cfg(feature = "shuttle-test")] + pub use shuttle::rand::{thread_rng, Rng, RngCore}; +} + +pub mod sync { + #[cfg(feature = "shuttle-test")] + pub use shuttle::sync::*; + #[cfg(not(feature = "shuttle-test"))] + pub use std::sync::*; +} + +pub mod thread { + #[cfg(feature = "shuttle-test")] + pub use shuttle::thread::*; + #[cfg(not(feature = "shuttle-test"))] + pub use std::thread::*; +} diff --git a/unified-scheduler-pool/src/lib.rs b/unified-scheduler-pool/src/lib.rs index e1309a6d963694..c57217285f4762 100644 --- a/unified-scheduler-pool/src/lib.rs +++ b/unified-scheduler-pool/src/lib.rs @@ -1,8 +1,3 @@ -//! NOTE: While the unified scheduler is fully functional and moderately performant even with -//! mainnet-beta, it has known resource-exhaustion related security issues for replaying -//! specially-crafted blocks produced by malicious leaders. Thus, this experimental and -//! nondefault functionality is exempt from the bug bounty program for now. -//! //! Transaction scheduling code. //! //! This crate implements 3 solana-runtime traits (`InstalledScheduler`, `UninstalledScheduler` and @@ -340,6 +335,8 @@ where context: SchedulingContext, result_with_timings: ResultWithTimings, ) -> S { + assert_matches!(result_with_timings, (Ok(_), _)); + // pop is intentional for filo, expecting relatively warmed-up scheduler due to having been // returned recently if let Some((inner, _pooled_at)) = self.scheduler_inners.lock().expect("not poisoned").pop() @@ -764,23 +761,6 @@ struct ThreadManager, TH: TaskHandler> { handler_threads: Vec>, } -impl PooledScheduler { - fn do_spawn( - pool: Arc>, - initial_context: SchedulingContext, - result_with_timings: ResultWithTimings, - ) -> Self { - Self::from_inner( - PooledSchedulerInner:: { - thread_manager: ThreadManager::new(pool), - usage_queue_loader: UsageQueueLoader::default(), - }, - initial_context, - result_with_timings, - ) - } -} - struct HandlerPanicked; type HandlerResult = std::result::Result, HandlerPanicked>; @@ -850,7 +830,15 @@ impl, TH: TaskHandler> ThreadManager { ); } - fn start_threads(&mut self, context: &SchedulingContext) { + // This method must take same set of session-related arguments as start_session() to avoid + // unneeded channel operations to minimize overhead. Starting threads incurs a very high cost + // already... Also, pre-creating threads isn't desirable as well to avoid `Option`-ed types + // for type safety. + fn start_threads( + &mut self, + context: SchedulingContext, + mut result_with_timings: ResultWithTimings, + ) { // Firstly, setup bi-directional messaging between the scheduler and handlers to pass // around tasks, by creating 2 channels (one for to-be-handled tasks from the scheduler to // the handlers and the other for finished tasks from the handlers to the scheduler). @@ -928,7 +916,7 @@ impl, TH: TaskHandler> ThreadManager { // prioritization further. Consequently, this also contributes to alleviate the known // heuristic's caveat for the first task of linearized runs, which is described above. let (mut runnable_task_sender, runnable_task_receiver) = - chained_channel::unbounded::(context.clone()); + chained_channel::unbounded::(context); // Create two handler-to-scheduler channels to prioritize the finishing of blocked tasks, // because it is more likely that a blocked task will have more blocked tasks behind it, // which should be scheduled while minimizing the delay to clear buffered linearized runs @@ -947,7 +935,7 @@ impl, TH: TaskHandler> ThreadManager { // 4. the handler thread processes the dispatched task. // 5. the handler thread reply back to the scheduler thread as an executed task. // 6. the scheduler thread post-processes the executed task. - let scheduler_main_loop = || { + let scheduler_main_loop = { let handler_count = self.pool.handler_count; let session_result_sender = self.session_result_sender.clone(); // Taking new_task_receiver here is important to ensure there's a single receiver. In @@ -1011,29 +999,14 @@ impl, TH: TaskHandler> ThreadManager { let mut state_machine = unsafe { SchedulingStateMachine::exclusively_initialize_current_thread_for_scheduling() }; - let mut result_with_timings = initialized_result_with_timings(); + // The following loop maintains and updates ResultWithTimings as its + // externally-provided mutable state for each session in this way: + // + // 1. Initial result_with_timing is propagated implicitly by the moved variable. + // 2. Subsequent result_with_timings are propagated explicitly from + // the new_task_receiver.recv() invocation located at the end of loop. 'nonaborted_main_loop: loop { - match new_task_receiver.recv() { - Ok(NewTaskPayload::OpenSubchannel(( - new_context, - new_result_with_timings, - ))) => { - // signal about new SchedulingContext to handler threads - runnable_task_sender - .send_chained_channel(new_context, handler_count) - .unwrap(); - result_with_timings = new_result_with_timings; - } - Ok(_) => { - unreachable!(); - } - Err(_) => { - // This unusual condition must be triggered by ThreadManager::drop(); - break 'nonaborted_main_loop; - } - } - let mut is_finished = false; while !is_finished { // ALL recv selectors are eager-evaluated ALWAYS by current crossbeam impl, @@ -1084,9 +1057,8 @@ impl, TH: TaskHandler> ThreadManager { Ok(NewTaskPayload::CloseSubchannel) => { session_ending = true; } - Ok(NewTaskPayload::OpenSubchannel(_context_and_result_with_timings)) => { - unreachable!(); - } + Ok(NewTaskPayload::OpenSubchannel(_context_and_result_with_timings)) => + unreachable!(), Err(RecvError) => { // Mostly likely is that this scheduler is dropped for pruned blocks of // abandoned forks... @@ -1109,15 +1081,36 @@ impl, TH: TaskHandler> ThreadManager { is_finished = session_ending && state_machine.has_no_active_task(); } - if session_ending { - state_machine.reinitialize(); - session_result_sender - .send(std::mem::replace( - &mut result_with_timings, - initialized_result_with_timings(), - )) - .expect("always outlived receiver"); - session_ending = false; + // Finalize the current session after asserting it's explicitly requested so. + assert!(session_ending); + // Send result first because this is blocking the replay code-path. + session_result_sender + .send(result_with_timings) + .expect("always outlived receiver"); + state_machine.reinitialize(); + session_ending = false; + + // Prepare for the new session. + match new_task_receiver.recv() { + Ok(NewTaskPayload::OpenSubchannel(( + new_context, + new_result_with_timings, + ))) => { + // We just received subsequent (= not initial) session and about to + // enter into the preceding `while(!is_finished) {...}` loop again. + // Before that, propagate new SchedulingContext to handler threads + runnable_task_sender + .send_chained_channel(new_context, handler_count) + .unwrap(); + result_with_timings = new_result_with_timings; + } + Err(_) => { + // This unusual condition must be triggered by ThreadManager::drop(). + // Initialize result_with_timings with a harmless value... + result_with_timings = initialized_result_with_timings(); + break 'nonaborted_main_loop; + } + Ok(_) => unreachable!(), } } @@ -1150,6 +1143,14 @@ impl, TH: TaskHandler> ThreadManager { let finished_blocked_task_sender = finished_blocked_task_sender.clone(); let finished_idle_task_sender = finished_idle_task_sender.clone(); + // The following loop maintains and updates SchedulingContext as its + // externally-provided state for each session in this way: + // + // 1. Initial context is propagated implicitly by the moved runnable_task_receiver, + // which is clone()-d just above for this particular thread. + // 2. Subsequent contexts are propagated explicitly inside `.after_select()` as part of + // `select_biased!`, which are sent from `.send_chained_channel()` in the scheduler + // thread for all-but-initial sessions. move || loop { let (task, sender) = select_biased! { recv(runnable_task_receiver.for_select()) -> message => { @@ -1204,7 +1205,7 @@ impl, TH: TaskHandler> ThreadManager { self.scheduler_thread = Some( thread::Builder::new() .name("solScheduler".to_owned()) - .spawn_tracked(scheduler_main_loop()) + .spawn_tracked(scheduler_main_loop) .unwrap(), ); @@ -1325,13 +1326,14 @@ impl, TH: TaskHandler> ThreadManager { fn start_session( &mut self, - context: &SchedulingContext, + context: SchedulingContext, result_with_timings: ResultWithTimings, ) { + assert!(!self.are_threads_joined()); assert_matches!(self.session_result_with_timings, None); self.new_task_sender .send(NewTaskPayload::OpenSubchannel(( - context.clone(), + context, result_with_timings, ))) .expect("no new session after aborted"); @@ -1351,7 +1353,7 @@ pub trait SpawnableScheduler: InstalledScheduler { fn spawn( pool: Arc>, - initial_context: SchedulingContext, + context: SchedulingContext, result_with_timings: ResultWithTimings, ) -> Self where @@ -1377,21 +1379,23 @@ impl SpawnableScheduler for PooledScheduler { ) -> Self { inner .thread_manager - .start_session(&context, result_with_timings); + .start_session(context.clone(), result_with_timings); Self { inner, context } } fn spawn( pool: Arc>, - initial_context: SchedulingContext, + context: SchedulingContext, result_with_timings: ResultWithTimings, ) -> Self { - let mut scheduler = Self::do_spawn(pool, initial_context, result_with_timings); - scheduler - .inner + let mut inner = Self::Inner { + thread_manager: ThreadManager::new(pool), + usage_queue_loader: UsageQueueLoader::default(), + }; + inner .thread_manager - .start_threads(&scheduler.context); - scheduler + .start_threads(context.clone(), result_with_timings); + Self { inner, context } } } @@ -1711,6 +1715,10 @@ mod tests { &CheckPoint::TimeoutListenerTriggered(0), &CheckPoint::TimeoutListenerTriggered(1), &TestCheckPoint::AfterTimeoutListenerTriggered, + &TestCheckPoint::BeforeTimeoutListenerTriggered, + &CheckPoint::TimeoutListenerTriggered(0), + &CheckPoint::TimeoutListenerTriggered(1), + &TestCheckPoint::AfterTimeoutListenerTriggered, ]); let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); @@ -1778,12 +1786,62 @@ mod tests { bank.schedule_transaction_executions([(tx_after_stale, &1)].into_iter()) .unwrap(); + // Observe second occurrence of TimeoutListenerTriggered(1), which indicates a new timeout + // lister is registered correctly again for reactivated scheduler. + sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered); + sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered); + let (result, timings) = bank.wait_for_completed_scheduler().unwrap(); assert_matches!(result, Ok(())); // ResultWithTimings should be carried over across active=>stale=>active transitions. assert_eq!(timings.metrics[ExecuteTimingType::CheckUs], 246); } + #[test] + fn test_scheduler_pause_after_stale() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeTimeoutListenerTriggered, + &CheckPoint::TimeoutListenerTriggered(0), + &CheckPoint::TimeoutListenerTriggered(1), + &TestCheckPoint::AfterTimeoutListenerTriggered, + ]); + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool_raw = DefaultSchedulerPool::do_new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + SHORTENED_POOL_CLEANER_INTERVAL, + DEFAULT_MAX_POOLING_DURATION, + DEFAULT_MAX_USAGE_QUEUE_COUNT, + SHORTENED_TIMEOUT_DURATION, + ); + let pool = pool_raw.clone(); + + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); + let bank = Bank::new_for_tests(&genesis_config); + let bank = setup_dummy_fork_graph(bank); + + let context = SchedulingContext::new(bank.clone()); + + let scheduler = pool.take_scheduler(context); + let bank = BankWithScheduler::new(bank, Some(scheduler)); + pool.register_timeout_listener(bank.create_timeout_listener()); + + sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered); + sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered); + + // This calls register_recent_blockhash() internally, which in turn calls + // BankWithScheduler::wait_for_paused_scheduler(). + bank.fill_bank_with_ticks_for_tests(); + let (result, _timings) = bank.wait_for_completed_scheduler().unwrap(); + assert_matches!(result, Ok(())); + } + #[test] fn test_scheduler_remain_stale_after_error() { solana_logger::setup(); @@ -2719,13 +2777,13 @@ mod tests { fn spawn( pool: Arc>, - initial_context: SchedulingContext, + context: SchedulingContext, _result_with_timings: ResultWithTimings, ) -> Self { AsyncScheduler::( Mutex::new(initialized_result_with_timings()), Mutex::new(vec![]), - initial_context, + context, pool, ) } diff --git a/unified-scheduler-pool/src/sleepless_testing.rs b/unified-scheduler-pool/src/sleepless_testing.rs index 9c2213f657e86a..901a2f7c4fded7 100644 --- a/unified-scheduler-pool/src/sleepless_testing.rs +++ b/unified-scheduler-pool/src/sleepless_testing.rs @@ -26,20 +26,21 @@ pub(crate) trait BuilderTracked: Sized { } #[cfg(not(test))] -pub(crate) use sleepless_testing_dummy::*; +pub(crate) use dummy::*; #[cfg(test)] -pub(crate) use sleepless_testing_real::*; +pub(crate) use real::*; #[cfg(test)] -mod sleepless_testing_real { +mod real { use { lazy_static::lazy_static, + log::trace, std::{ cmp::Ordering::{Equal, Greater, Less}, - collections::{HashMap, HashSet}, + collections::HashMap, fmt::Debug, sync::{Arc, Condvar, Mutex}, - thread::{current, JoinHandle, ThreadId}, + thread::{current, panicking, JoinHandle, ThreadId}, }, }; @@ -47,7 +48,7 @@ mod sleepless_testing_real { struct Progress { _name: String, check_points: Vec, - current_check_point: Mutex, + current_index: Mutex, condvar: Condvar, } @@ -61,61 +62,88 @@ mod sleepless_testing_real { .into_iter() .chain(check_points) .collect::>(); - let check_points_set = check_points.iter().collect::>(); - assert_eq!(check_points.len(), check_points_set.len()); - Self { _name: name, check_points, - current_check_point: Mutex::new(initial_check_point), + current_index: Mutex::new(0), condvar: Condvar::new(), } } fn change_current_check_point(&self, anchored_check_point: String) { - let Some(anchored_index) = self - .check_points - .iter() - .position(|check_point| check_point == &anchored_check_point) + let mut current_index = self.current_index.lock().unwrap(); + + let Some(anchored_index) = self.anchored_index(*current_index, &anchored_check_point) else { - // Ignore unrecognizable checkpoints... + trace!("Ignore {} at {:?}", anchored_check_point, current()); return; }; - let mut current_check_point = self.current_check_point.lock().unwrap(); - - let should_change = - match anchored_index.cmp(&self.expected_next_index(¤t_check_point)) { - Equal => true, - Greater => { - // anchor is one of future check points; block the current thread until - // that happens - current_check_point = self - .condvar - .wait_while(current_check_point, |current_check_point| { - anchored_index != self.expected_next_index(current_check_point) - }) - .unwrap(); - true - } - // anchor is already observed. - Less => false, - }; + let next_index = self.expected_next_index(*current_index); + let should_change = match anchored_index.cmp(&next_index) { + Equal => true, + Greater => { + trace!("Blocked on {} at {:?}", anchored_check_point, current()); + // anchor is one of future check points; block the current thread until + // that happens + current_index = self + .condvar + .wait_while(current_index, |&mut current_index| { + let Some(anchored_index) = + self.anchored_index(current_index, &anchored_check_point) + else { + // don't wait. seems the progress is made by other threads + // anchored to the same checkpoint. + return false; + }; + let next_index = self.expected_next_index(current_index); + + // determine we should wait further or not + match anchored_index.cmp(&next_index) { + Equal => false, + Greater => { + trace!( + "Re-blocked on {} ({} != {}) at {:?}", + anchored_check_point, + anchored_index, + next_index, + current() + ); + true + } + Less => unreachable!(), + } + }) + .unwrap(); + true + } + Less => unreachable!(), + }; if should_change { - *current_check_point = anchored_check_point; + if *current_index != anchored_index { + trace!("Progressed to: {} at {:?}", anchored_check_point, current()); + *current_index = anchored_index; + } + self.condvar.notify_all(); } } - fn expected_next_index(&self, current_check_point: &String) -> usize { - let current_index = self - .check_points - .iter() - .position(|check_point| check_point == current_check_point) - .unwrap(); + fn expected_next_index(&self, current_index: usize) -> usize { current_index.checked_add(1).unwrap() } + + fn anchored_index( + &self, + current_index: usize, + anchored_check_point: &String, + ) -> Option { + self.check_points[current_index..] + .iter() + .position(|check_point| check_point == anchored_check_point) + .map(|subslice_index| subslice_index.checked_add(current_index).unwrap()) + } } lazy_static! { @@ -142,11 +170,13 @@ mod sleepless_testing_real { } fn deactivate(&self) { - assert_eq!( - *self.0.check_points.last().unwrap(), - *self.0.current_check_point.lock().unwrap(), - "unfinished progress" - ); + if !panicking() { + assert_eq!( + self.0.check_points.len().checked_sub(1).unwrap(), + *self.0.current_index.lock().unwrap(), + "unfinished progress" + ); + } THREAD_REGISTRY.lock().unwrap().remove(&self.1).unwrap(); } } @@ -299,7 +329,7 @@ mod sleepless_testing_real { } #[cfg(not(test))] -mod sleepless_testing_dummy { +mod dummy { use std::fmt::Debug; #[inline] diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 580a6a14027518..7f7865d8ac18e8 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -23,7 +23,6 @@ use { account::AccountSharedData, clock::Slot, epoch_schedule::EpochSchedule, - feature_set, native_token::sol_to_lamports, pubkey::Pubkey, rent::Rent, @@ -352,9 +351,7 @@ fn main() { exit(1); }); - let mut features_to_deactivate = pubkeys_of(&matches, "deactivate_feature").unwrap_or_default(); - // Remove this when client support is ready for the enable_partitioned_epoch_reward feature - features_to_deactivate.push(feature_set::enable_partitioned_epoch_reward::id()); + let features_to_deactivate = pubkeys_of(&matches, "deactivate_feature").unwrap_or_default(); if TestValidatorGenesis::ledger_exists(&ledger_path) { for (name, long) in &[ diff --git a/validator/src/cli.rs b/validator/src/cli.rs index 978d650a2ef1de..2046651652ea58 100644 --- a/validator/src/cli.rs +++ b/validator/src/cli.rs @@ -211,12 +211,6 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .takes_value(false) .help("Expose RPC methods for querying chain state and transaction history"), ) - .arg( - Arg::with_name("obsolete_v1_7_rpc_api") - .long("enable-rpc-obsolete_v1_7") - .takes_value(false) - .help("Enable the obsolete RPC methods removed in v1.7"), - ) .arg( Arg::with_name("private_rpc") .long("private-rpc") @@ -1522,7 +1516,6 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .arg( Arg::with_name("block_verification_method") .long("block-verification-method") - .hidden(hidden_unless_forced()) .value_name("METHOD") .takes_value(true) .possible_values(BlockVerificationMethod::cli_names()) @@ -1539,7 +1532,6 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .arg( Arg::with_name("unified_scheduler_handler_threads") .long("unified-scheduler-handler-threads") - .hidden(hidden_unless_forced()) .value_name("COUNT") .takes_value(true) .validator(|s| is_within_range(s, 1..)) @@ -1966,16 +1958,6 @@ fn deprecated_arguments() -> Vec { (@into-option $v:expr) => { Some($v) }; } - add_arg!(Arg::with_name("accounts_db_caching_enabled").long("accounts-db-caching-enabled")); - add_arg!( - Arg::with_name("accounts_db_index_hashing") - .long("accounts-db-index-hashing") - .help( - "Enables the use of the index in hash calculation in \ - AccountsHashVerifier/Accounts Background Service.", - ), - usage_warning: "The accounts hash is only calculated without using the index.", - ); add_arg!( Arg::with_name("accounts_db_skip_shrink") .long("accounts-db-skip-shrink") @@ -2070,16 +2052,6 @@ fn deprecated_arguments() -> Vec { .long("minimal-rpc-api") .takes_value(false) .help("Only expose the RPC methods required to serve snapshots to other nodes")); - add_arg!( - Arg::with_name("no_accounts_db_index_hashing") - .long("no-accounts-db-index-hashing") - .help( - "This is obsolete. See --accounts-db-index-hashing. \ - Disables the use of the index in hash calculation in \ - AccountsHashVerifier/Accounts Background Service.", - ), - usage_warning: "The accounts hash is only calculated without using the index.", - ); add_arg!( Arg::with_name("no_check_vote_account") .long("no-check-vote-account") diff --git a/validator/src/main.rs b/validator/src/main.rs index a87f6b3f488d9c..321f31fe13d917 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -1402,7 +1402,6 @@ pub fn main() { solana_net_utils::parse_host_port(address).expect("failed to parse faucet address") }), full_api, - obsolete_v1_7_api: matches.is_present("obsolete_v1_7_rpc_api"), max_multiple_accounts: Some(value_t_or_exit!( matches, "rpc_max_multiple_accounts", @@ -1772,16 +1771,26 @@ pub fn main() { None => ShredStorageType::default(), Some(shred_compaction_string) => match shred_compaction_string { "level" => ShredStorageType::RocksLevel, - "fifo" => match matches.value_of("rocksdb_fifo_shred_storage_size") { - None => ShredStorageType::rocks_fifo(default_fifo_shred_storage_size( - &validator_config, - )), - Some(_) => ShredStorageType::rocks_fifo(Some(value_t_or_exit!( - matches, - "rocksdb_fifo_shred_storage_size", - u64 - ))), - }, + "fifo" => { + warn!( + "The value \"fifo\" for --rocksdb-shred-compaction has been deprecated. \ + Use of \"fifo\" will still work for now, but is planned for full removal \ + in v2.1. To update, use \"level\" for --rocksdb-shred-compaction, or \ + remove the --rocksdb-shred-compaction argument altogether. Note that the \ + entire \"rocksdb_fifo\" subdirectory within the ledger directory will \ + need to be manually removed once the validator is running with \"level\"." + ); + match matches.value_of("rocksdb_fifo_shred_storage_size") { + None => ShredStorageType::rocks_fifo(default_fifo_shred_storage_size( + &validator_config, + )), + Some(_) => ShredStorageType::rocks_fifo(Some(value_t_or_exit!( + matches, + "rocksdb_fifo_shred_storage_size", + u64 + ))), + } + } _ => panic!("Unrecognized rocksdb-shred-compaction: {shred_compaction_string}"), }, }, diff --git a/wen-restart/src/heaviest_fork_aggregate.rs b/wen-restart/src/heaviest_fork_aggregate.rs index ce1c19283876ce..0b43b800d18573 100644 --- a/wen-restart/src/heaviest_fork_aggregate.rs +++ b/wen-restart/src/heaviest_fork_aggregate.rs @@ -118,6 +118,9 @@ impl HeaviestForkAggregate { ); return None; } + if from == &self.my_pubkey { + return None; + } if received_heaviest_fork.shred_version != self.my_shred_version { warn!( "Gossip should not accept RestartLastVotedFork with different shred version {} from {:?}", @@ -445,6 +448,23 @@ mod tests { .total_active_stake_seen_supermajority(), 1500 ); + + // test that message from my pubkey is ignored. + assert_eq!( + test_state + .heaviest_fork_aggregate + .aggregate(RestartHeaviestFork { + from: test_state.validator_voting_keypairs[MY_INDEX] + .node_keypair + .pubkey(), + wallclock: timestamp(), + last_slot: test_state.heaviest_slot, + last_slot_hash: test_state.heaviest_hash, + observed_stake: 100, + shred_version: SHRED_VERSION, + },), + None, + ); } #[test] @@ -535,6 +555,27 @@ mod tests { ); // percentage doesn't change since the previous aggregate is ignored. assert_eq!(test_state.heaviest_fork_aggregate.total_active_stake(), 200); + + // Record from my pubkey should be ignored. + assert_eq!( + test_state + .heaviest_fork_aggregate + .aggregate_from_record( + &test_state.validator_voting_keypairs[MY_INDEX] + .node_keypair + .pubkey() + .to_string(), + &HeaviestForkRecord { + wallclock: timestamp(), + slot: test_state.heaviest_slot, + bankhash: test_state.heaviest_hash.to_string(), + shred_version: SHRED_VERSION as u32, + total_active_stake: 100, + } + ) + .unwrap(), + None, + ); } #[test] diff --git a/wen-restart/src/last_voted_fork_slots_aggregate.rs b/wen-restart/src/last_voted_fork_slots_aggregate.rs index b042ba80d9bb51..67cd7c1c77ea87 100644 --- a/wen-restart/src/last_voted_fork_slots_aggregate.rs +++ b/wen-restart/src/last_voted_fork_slots_aggregate.rs @@ -20,6 +20,7 @@ pub(crate) struct LastVotedForkSlotsAggregate { slots_stake_map: HashMap, active_peers: HashSet, slots_to_repair: HashSet, + my_pubkey: Pubkey, } #[derive(Clone, Debug, PartialEq)] @@ -53,6 +54,7 @@ impl LastVotedForkSlotsAggregate { slots_stake_map, active_peers, slots_to_repair: HashSet::new(), + my_pubkey: *my_pubkey, } } @@ -70,6 +72,9 @@ impl LastVotedForkSlotsAggregate { record: &LastVotedForkSlotsRecord, ) -> Result> { let from = Pubkey::from_str(key_string)?; + if from == self.my_pubkey { + return Ok(None); + } let last_voted_hash = Hash::from_str(&record.last_vote_bankhash)?; let converted_record = RestartLastVotedForkSlots::new( from, @@ -88,6 +93,9 @@ impl LastVotedForkSlotsAggregate { let total_stake = self.epoch_stakes.total_stake(); let threshold_stake = (total_stake as f64 * self.repair_threshold) as u64; let from = &new_slots.from; + if from == &self.my_pubkey { + return None; + } let sender_stake = Self::validator_stake(&self.epoch_stakes, from); if sender_stake == 0 { warn!( @@ -354,6 +362,23 @@ mod tests { Vec::from_iter(test_state.slots_aggregate.slots_to_repair_iter().cloned()); actual_slots.sort(); assert_eq!(actual_slots, vec![root_slot + 1]); + + // test that message from my pubkey is ignored. + assert_eq!( + test_state.slots_aggregate.aggregate( + RestartLastVotedForkSlots::new( + test_state.validator_voting_keypairs[MY_INDEX] + .node_keypair + .pubkey(), + timestamp(), + &[root_slot + 1, root_slot + 4, root_slot + 5], + Hash::default(), + SHRED_VERSION, + ) + .unwrap(), + ), + None, + ); } #[test] @@ -446,6 +471,26 @@ mod tests { ); // percentage doesn't change since the previous aggregate is ignored. assert_eq!(test_state.slots_aggregate.active_percent(), 20.0); + + // Record from my pubkey should be ignored. + assert_eq!( + test_state + .slots_aggregate + .aggregate_from_record( + &test_state.validator_voting_keypairs[MY_INDEX] + .node_keypair + .pubkey() + .to_string(), + &LastVotedForkSlotsRecord { + wallclock: timestamp(), + last_voted_fork_slots: vec![root_slot + 10, root_slot + 300], + last_vote_bankhash: Hash::new_unique().to_string(), + shred_version: SHRED_VERSION as u32, + } + ) + .unwrap(), + None, + ); } #[test] diff --git a/zk-sdk/Cargo.toml b/zk-sdk/Cargo.toml index 8b67d12c693987..a57b994e017d2f 100644 --- a/zk-sdk/Cargo.toml +++ b/zk-sdk/Cargo.toml @@ -12,6 +12,7 @@ edition = { workspace = true } [dependencies] base64 = { workspace = true } bytemuck = { workspace = true } +bytemuck_derive = { workspace = true } merlin = { workspace = true } num-derive = { workspace = true } num-traits = { workspace = true } diff --git a/zk-sdk/src/encryption/pod/elgamal.rs b/zk-sdk/src/encryption/pod/elgamal.rs index 8ec72c6f5837bd..9c70724307d43c 100644 --- a/zk-sdk/src/encryption/pod/elgamal.rs +++ b/zk-sdk/src/encryption/pod/elgamal.rs @@ -5,7 +5,7 @@ use { pod::impl_from_str, DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, ELGAMAL_PUBKEY_LEN, }, base64::{prelude::BASE64_STANDARD, Engine}, - bytemuck::{Pod, Zeroable}, + bytemuck::Zeroable, std::fmt, }; #[cfg(not(target_os = "solana"))] @@ -24,7 +24,7 @@ const ELGAMAL_PUBKEY_MAX_BASE64_LEN: usize = 44; const ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN: usize = 88; /// The `ElGamalCiphertext` type as a `Pod`. -#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct PodElGamalCiphertext(pub(crate) [u8; ELGAMAL_CIPHERTEXT_LEN]); @@ -69,7 +69,7 @@ impl TryFrom for ElGamalCiphertext { } /// The `ElGamalPubkey` type as a `Pod`. -#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct PodElGamalPubkey(pub(crate) [u8; ELGAMAL_PUBKEY_LEN]); @@ -108,7 +108,7 @@ impl TryFrom for ElGamalPubkey { } /// The `DecryptHandle` type as a `Pod`. -#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct PodDecryptHandle(pub(crate) [u8; DECRYPT_HANDLE_LEN]); diff --git a/zk-sdk/src/encryption/pod/grouped_elgamal.rs b/zk-sdk/src/encryption/pod/grouped_elgamal.rs index 9202f23098858d..25825bbb474a6d 100644 --- a/zk-sdk/src/encryption/pod/grouped_elgamal.rs +++ b/zk-sdk/src/encryption/pod/grouped_elgamal.rs @@ -10,7 +10,7 @@ use { }, errors::ElGamalError, }, - bytemuck::{Pod, Zeroable}, + bytemuck::Zeroable, std::fmt, }; @@ -61,7 +61,7 @@ const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES: usize = PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN; /// The `GroupedElGamalCiphertext` type with two decryption handles as a `Pod` -#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct PodGroupedElGamalCiphertext2Handles( pub(crate) [u8; GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES], @@ -97,7 +97,7 @@ impl TryFrom for GroupedElGamalCiphertext<2 impl_extract!(TYPE = PodGroupedElGamalCiphertext2Handles); /// The `GroupedElGamalCiphertext` type with three decryption handles as a `Pod` -#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct PodGroupedElGamalCiphertext3Handles( pub(crate) [u8; GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES], diff --git a/zk-sdk/src/encryption/pod/pedersen.rs b/zk-sdk/src/encryption/pod/pedersen.rs index 2d90b100cbbe96..faf39ca949bc09 100644 --- a/zk-sdk/src/encryption/pod/pedersen.rs +++ b/zk-sdk/src/encryption/pod/pedersen.rs @@ -2,7 +2,7 @@ use { crate::encryption::PEDERSEN_COMMITMENT_LEN, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, std::fmt, }; #[cfg(not(target_os = "solana"))] diff --git a/zk-sdk/src/pod.rs b/zk-sdk/src/pod.rs index 416df4c58be767..2240b5c1ebe375 100644 --- a/zk-sdk/src/pod.rs +++ b/zk-sdk/src/pod.rs @@ -1,4 +1,4 @@ -use bytemuck::{Pod, Zeroable}; +use bytemuck_derive::{Pod, Zeroable}; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] #[repr(transparent)] diff --git a/zk-sdk/src/sigma_proofs/pod.rs b/zk-sdk/src/sigma_proofs/pod.rs index b0d4477e09ffa9..fb0bc3a96efba0 100644 --- a/zk-sdk/src/sigma_proofs/pod.rs +++ b/zk-sdk/src/sigma_proofs/pod.rs @@ -192,7 +192,7 @@ impl TryFrom for ZeroCiphertextProof { } /// The `PercentageWithCapProof` type as a `Pod`. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(transparent)] pub struct PodPercentageWithCapProof(pub(crate) [u8; PERCENTAGE_WITH_CAP_PROOF_LEN]); @@ -213,7 +213,7 @@ impl TryFrom for PercentageWithCapProof { } /// The `PubkeyValidityProof` type as a `Pod`. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(transparent)] pub struct PodPubkeyValidityProof(pub(crate) [u8; PUBKEY_VALIDITY_PROOF_LEN]); diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs index 56bc42d8606f30..bd6e980962b1df 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs @@ -13,7 +13,7 @@ use { sigma_proofs::pod::PodBatchedGroupedCiphertext2HandlesValidityProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] use { diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs index a98d9c8f47b526..7423a5427733aa 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs @@ -13,7 +13,7 @@ use { sigma_proofs::pod::PodBatchedGroupedCiphertext3HandlesValidityProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] use { diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u128.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u128.rs index fbfab0d03052d3..df3eea3b53ea58 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u128.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u128.rs @@ -19,7 +19,7 @@ use { batched_range_proof::BatchedRangeProofContext, ProofType, ZkProofData, }, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u256.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u256.rs index a2f7426044ba4c..a51997484e64ba 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u256.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u256.rs @@ -19,7 +19,7 @@ use { batched_range_proof::BatchedRangeProofContext, ProofType, ZkProofData, }, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs index a701ae6a1370de..4043648f01ada3 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs @@ -19,7 +19,7 @@ use { batched_range_proof::BatchedRangeProofContext, ProofType, ZkProofData, }, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs index 828fcc08218e1a..5d8c3c419db652 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs @@ -20,17 +20,14 @@ pub mod batched_range_proof_u128; pub mod batched_range_proof_u256; pub mod batched_range_proof_u64; -use { - crate::encryption::pod::pedersen::PodPedersenCommitment, - bytemuck::{Pod, Zeroable}, -}; +use crate::encryption::pod::pedersen::PodPedersenCommitment; #[cfg(not(target_os = "solana"))] use { crate::{ encryption::pedersen::{PedersenCommitment, PedersenOpening}, zk_elgamal_proof_program::errors::{ProofGenerationError, ProofVerificationError}, }, - bytemuck::bytes_of, + bytemuck::{bytes_of, Zeroable}, curve25519_dalek::traits::IsIdentity, merlin::Transcript, std::convert::TryInto, @@ -48,7 +45,7 @@ const MAX_SINGLE_BIT_LENGTH: usize = 128; /// The context data needed to verify a range-proof for a Pedersen committed value. /// /// The context data is shared by all `VerifyBatchedRangeProof{N}` instructions. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct BatchedRangeProofContext { pub commitments: [PodPedersenCommitment; MAX_COMMITMENTS], diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs index 5e2ba6cac9a7bc..21437bdba7b2b3 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs @@ -11,7 +11,7 @@ use { sigma_proofs::pod::PodCiphertextCiphertextEqualityProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] use { diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs index 86f4dbd2d4055a..bb093436b66f0a 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs @@ -14,7 +14,7 @@ use { sigma_proofs::pod::PodCiphertextCommitmentEqualityProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] use { diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs index 76083014c5ab2e..5c35b1b131729a 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs @@ -13,7 +13,7 @@ use { sigma_proofs::pod::PodGroupedCiphertext2HandlesValidityProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] use { diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs index 48420661b48d4a..5e8852f8584ca1 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs @@ -13,7 +13,7 @@ use { sigma_proofs::pod::PodGroupedCiphertext3HandlesValidityProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] use { diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs index 8a6b18e68bdf9b..6154f1ae43b0c0 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs @@ -24,7 +24,7 @@ use { sigma_proofs::pod::PodPercentageWithCapProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the `ProofInstruction::VerifyPercentageWithCap` diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/pod.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/pod.rs index 50e1a81a582705..2010212c3e35cd 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/pod.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/pod.rs @@ -1,6 +1,6 @@ use { crate::zk_elgamal_proof_program::proof_data::{errors::ProofDataError, ProofType}, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, num_traits::{FromPrimitive, ToPrimitive}, }; diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs index b769458e5fb8c0..8b8ceb9c45be87 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs @@ -22,7 +22,7 @@ use { sigma_proofs::pod::PodPubkeyValidityProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the `ProofInstruction::VerifyPubkeyValidity` diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs index 8a376304e05494..a6c225074974a3 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs @@ -21,7 +21,7 @@ use { sigma_proofs::pod::PodZeroCiphertextProof, zk_elgamal_proof_program::proof_data::{ProofType, ZkProofData}, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the `ProofInstruction::ZeroCiphertext` instruction. diff --git a/zk-sdk/src/zk_elgamal_proof_program/state.rs b/zk-sdk/src/zk_elgamal_proof_program/state.rs index 7cd87bbf5827da..1dd225af02f813 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/state.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/state.rs @@ -53,7 +53,7 @@ impl ProofContextState { /// The `ProofContextState` without the proof context itself. This struct exists to facilitate the /// decoding of generic-independent fields in `ProofContextState`. -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +#[derive(Clone, Copy, Debug, PartialEq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct ProofContextStateMeta { /// The proof context authority that can close the account diff --git a/zk-token-sdk/Cargo.toml b/zk-token-sdk/Cargo.toml index fb3dc25649b5a8..d466d2ba0af22d 100644 --- a/zk-token-sdk/Cargo.toml +++ b/zk-token-sdk/Cargo.toml @@ -11,9 +11,11 @@ edition = { workspace = true } [dependencies] base64 = { workspace = true } -bytemuck = { workspace = true, features = ["derive"] } +bytemuck = { workspace = true } +bytemuck_derive = { workspace = true } num-derive = { workspace = true } num-traits = { workspace = true } +solana-curve25519 = { workspace = true } solana-program = { workspace = true } thiserror = { workspace = true } @@ -25,7 +27,6 @@ aes-gcm-siv = { workspace = true } bincode = { workspace = true } byteorder = { workspace = true } curve25519-dalek = { workspace = true, features = ["serde"] } -getrandom = { version = "0.1", features = ["dummy"] } itertools = { workspace = true } lazy_static = { workspace = true } merlin = { workspace = true } diff --git a/zk-token-sdk/src/curve25519/mod.rs b/zk-token-sdk/src/curve25519/mod.rs deleted file mode 100644 index 19c4aa1388aa9a..00000000000000 --- a/zk-token-sdk/src/curve25519/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Syscall operations for curve25519 -//! -//! This module lives inside the zk-token-sdk for now, but should move to a general location since -//! it is independent of zk-tokens. - -pub mod curve_syscall_traits; -pub mod edwards; -#[cfg(not(target_os = "solana"))] -pub mod errors; -pub mod ristretto; -pub mod scalar; diff --git a/zk-token-sdk/src/encryption/elgamal.rs b/zk-token-sdk/src/encryption/elgamal.rs index 7f0a48820a6f35..130aacef669545 100644 --- a/zk-token-sdk/src/encryption/elgamal.rs +++ b/zk-token-sdk/src/encryption/elgamal.rs @@ -221,7 +221,7 @@ impl ElGamalKeypair { &self.secret } - #[deprecated(note = "please use `into()` instead")] + #[deprecated(since = "2.0.0", note = "please use `into()` instead")] #[allow(deprecated)] pub fn to_bytes(&self) -> [u8; ELGAMAL_KEYPAIR_LEN] { let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN]; @@ -230,7 +230,7 @@ impl ElGamalKeypair { bytes } - #[deprecated(note = "please use `try_from()` instead")] + #[deprecated(since = "2.0.0", note = "please use `try_from()` instead")] #[allow(deprecated)] pub fn from_bytes(bytes: &[u8]) -> Option { if bytes.len() != ELGAMAL_KEYPAIR_LEN { @@ -367,12 +367,12 @@ impl ElGamalPubkey { &self.0 } - #[deprecated(note = "please use `into()` instead")] + #[deprecated(since = "2.0.0", note = "please use `into()` instead")] pub fn to_bytes(&self) -> [u8; ELGAMAL_PUBKEY_LEN] { self.0.compress().to_bytes() } - #[deprecated(note = "please use `try_from()` instead")] + #[deprecated(since = "2.0.0", note = "please use `try_from()` instead")] pub fn from_bytes(bytes: &[u8]) -> Option { if bytes.len() != ELGAMAL_PUBKEY_LEN { return None; @@ -544,12 +544,12 @@ impl ElGamalSecretKey { self.0.as_bytes() } - #[deprecated(note = "please use `into()` instead")] + #[deprecated(since = "2.0.0", note = "please use `into()` instead")] pub fn to_bytes(&self) -> [u8; ELGAMAL_SECRET_KEY_LEN] { self.0.to_bytes() } - #[deprecated(note = "please use `try_from()` instead")] + #[deprecated(since = "2.0.0", note = "please use `try_from()` instead")] pub fn from_bytes(bytes: &[u8]) -> Option { match bytes.try_into() { Ok(bytes) => Scalar::from_canonical_bytes(bytes).map(ElGamalSecretKey), diff --git a/zk-token-sdk/src/errors.rs b/zk-token-sdk/src/errors.rs index 2d3adb74635574..e2c6c78721658a 100644 --- a/zk-token-sdk/src/errors.rs +++ b/zk-token-sdk/src/errors.rs @@ -6,18 +6,6 @@ use { thiserror::Error, }; -#[derive(Error, Clone, Debug, Eq, PartialEq)] -pub enum AuthenticatedEncryptionError { - #[error("key derivation method not supported")] - DerivationMethodNotSupported, - #[error("seed length too short for derivation")] - SeedLengthTooShort, - #[error("seed length too long for derivation")] - SeedLengthTooLong, - #[error("failed to deserialize")] - Deserialization, -} - #[derive(Error, Clone, Debug, Eq, PartialEq)] pub enum ElGamalError { #[error("key derivation method not supported")] @@ -36,6 +24,18 @@ pub enum ElGamalError { SecretKeyDeserialization, } +#[derive(Error, Clone, Debug, Eq, PartialEq)] +pub enum AuthenticatedEncryptionError { + #[error("key derivation method not supported")] + DerivationMethodNotSupported, + #[error("seed length too short for derivation")] + SeedLengthTooShort, + #[error("seed length too long for derivation")] + SeedLengthTooLong, + #[error("failed to deserialize")] + Deserialization, +} + #[cfg(not(target_os = "solana"))] #[derive(Error, Clone, Debug, Eq, PartialEq)] pub enum ProofGenerationError { diff --git a/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity/handles_2.rs b/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity/handles_2.rs index 0be760691f3ee6..6f489f47523728 100644 --- a/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity/handles_2.rs +++ b/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity/handles_2.rs @@ -30,7 +30,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the diff --git a/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity/handles_3.rs b/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity/handles_3.rs index 8a5fc1fca82837..d41b307f188b10 100644 --- a/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity/handles_3.rs +++ b/zk-token-sdk/src/instruction/batched_grouped_ciphertext_validity/handles_3.rs @@ -29,7 +29,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the diff --git a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u128.rs b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u128.rs index a1193c04190629..818af7c1d9e9a1 100644 --- a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u128.rs +++ b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u128.rs @@ -15,7 +15,7 @@ use { instruction::{batched_range_proof::BatchedRangeProofContext, ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the diff --git a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u256.rs b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u256.rs index 39237a3b758470..d728923f07fc6b 100644 --- a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u256.rs +++ b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u256.rs @@ -15,7 +15,7 @@ use { instruction::{batched_range_proof::BatchedRangeProofContext, ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] diff --git a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u64.rs b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u64.rs index 94b76b5beff89d..53d790c2a30fa6 100644 --- a/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u64.rs +++ b/zk-token-sdk/src/instruction/batched_range_proof/batched_range_proof_u64.rs @@ -15,7 +15,7 @@ use { instruction::{batched_range_proof::BatchedRangeProofContext, ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the diff --git a/zk-token-sdk/src/instruction/batched_range_proof/mod.rs b/zk-token-sdk/src/instruction/batched_range_proof/mod.rs index a002ca80ba642b..c94cbb328b2b33 100644 --- a/zk-token-sdk/src/instruction/batched_range_proof/mod.rs +++ b/zk-token-sdk/src/instruction/batched_range_proof/mod.rs @@ -20,17 +20,14 @@ pub mod batched_range_proof_u128; pub mod batched_range_proof_u256; pub mod batched_range_proof_u64; -use { - crate::zk_token_elgamal::pod, - bytemuck::{Pod, Zeroable}, -}; +use crate::zk_token_elgamal::pod; #[cfg(not(target_os = "solana"))] use { crate::{ encryption::pedersen::{PedersenCommitment, PedersenOpening}, errors::{ProofGenerationError, ProofVerificationError}, }, - bytemuck::bytes_of, + bytemuck::{bytes_of, Zeroable}, curve25519_dalek::traits::IsIdentity, merlin::Transcript, std::convert::TryInto, @@ -48,7 +45,7 @@ const MAX_SINGLE_BIT_LENGTH: usize = 128; /// The context data needed to verify a range-proof for a Pedersen committed value. /// /// The context data is shared by all `VerifyBatchedRangeProof{N}` instructions. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct BatchedRangeProofContext { pub commitments: [pod::PedersenCommitment; MAX_COMMITMENTS], diff --git a/zk-token-sdk/src/instruction/ciphertext_ciphertext_equality.rs b/zk-token-sdk/src/instruction/ciphertext_ciphertext_equality.rs index a7a0bd5eaa8531..c858c6c015f8c1 100644 --- a/zk-token-sdk/src/instruction/ciphertext_ciphertext_equality.rs +++ b/zk-token-sdk/src/instruction/ciphertext_ciphertext_equality.rs @@ -27,7 +27,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the diff --git a/zk-token-sdk/src/instruction/ciphertext_commitment_equality.rs b/zk-token-sdk/src/instruction/ciphertext_commitment_equality.rs index 5cd2b3cbc5c670..fc862904a5ecae 100644 --- a/zk-token-sdk/src/instruction/ciphertext_commitment_equality.rs +++ b/zk-token-sdk/src/instruction/ciphertext_commitment_equality.rs @@ -24,7 +24,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction. diff --git a/zk-token-sdk/src/instruction/fee_sigma.rs b/zk-token-sdk/src/instruction/fee_sigma.rs index 500e21a505cf33..adddef5f64fc9d 100644 --- a/zk-token-sdk/src/instruction/fee_sigma.rs +++ b/zk-token-sdk/src/instruction/fee_sigma.rs @@ -24,7 +24,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the `ProofInstruction::VerifyFeeSigma` instruction. diff --git a/zk-token-sdk/src/instruction/grouped_ciphertext_validity/handles_2.rs b/zk-token-sdk/src/instruction/grouped_ciphertext_validity/handles_2.rs index a99a733c748928..da6900043fb4d1 100644 --- a/zk-token-sdk/src/instruction/grouped_ciphertext_validity/handles_2.rs +++ b/zk-token-sdk/src/instruction/grouped_ciphertext_validity/handles_2.rs @@ -28,7 +28,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the `ProofInstruction::VerifyGroupedCiphertextValidity` diff --git a/zk-token-sdk/src/instruction/grouped_ciphertext_validity/handles_3.rs b/zk-token-sdk/src/instruction/grouped_ciphertext_validity/handles_3.rs index 14e025fef3f754..0fb3385247dde1 100644 --- a/zk-token-sdk/src/instruction/grouped_ciphertext_validity/handles_3.rs +++ b/zk-token-sdk/src/instruction/grouped_ciphertext_validity/handles_3.rs @@ -28,7 +28,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the diff --git a/zk-token-sdk/src/instruction/pubkey_validity.rs b/zk-token-sdk/src/instruction/pubkey_validity.rs index 3c264f0cdd31d3..6579611cba8b1e 100644 --- a/zk-token-sdk/src/instruction/pubkey_validity.rs +++ b/zk-token-sdk/src/instruction/pubkey_validity.rs @@ -21,7 +21,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the `ProofInstruction::VerifyPubkeyValidity` diff --git a/zk-token-sdk/src/instruction/range_proof.rs b/zk-token-sdk/src/instruction/range_proof.rs index fd6652e766aff6..823e41da71cf63 100644 --- a/zk-token-sdk/src/instruction/range_proof.rs +++ b/zk-token-sdk/src/instruction/range_proof.rs @@ -20,7 +20,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The context data needed to verify a range-proof for a committed value in a Pedersen commitment. diff --git a/zk-token-sdk/src/instruction/transfer/with_fee.rs b/zk-token-sdk/src/instruction/transfer/with_fee.rs index e8afb7606d6ff7..9fb25579a128c5 100644 --- a/zk-token-sdk/src/instruction/transfer/with_fee.rs +++ b/zk-token-sdk/src/instruction/transfer/with_fee.rs @@ -1,3 +1,7 @@ +use crate::{ + instruction::{ProofType, ZkProofData}, + zk_token_elgamal::pod, +}; #[cfg(not(target_os = "solana"))] use { crate::{ @@ -29,13 +33,6 @@ use { std::convert::TryInto, subtle::{ConditionallySelectable, ConstantTimeGreater}, }; -use { - crate::{ - instruction::{ProofType, ZkProofData}, - zk_token_elgamal::pod, - }, - bytemuck::{Pod, Zeroable}, -}; #[cfg(not(target_os = "solana"))] const MAX_FEE_BASIS_POINTS: u64 = 10_000; @@ -71,7 +68,7 @@ lazy_static::lazy_static! { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct TransferWithFeeData { /// The context data for the transfer with fee proof @@ -82,7 +79,7 @@ pub struct TransferWithFeeData { } /// The context data needed to verify a transfer-with-fee proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct TransferWithFeeProofContext { /// Group encryption of the low 16 bites of the transfer amount @@ -108,7 +105,7 @@ pub struct TransferWithFeeProofContext { } /// The ElGamal public keys needed for a transfer with fee -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct TransferWithFeePubkeys { pub source: pod::ElGamalPubkey, @@ -453,7 +450,7 @@ impl TransferWithFeeProofContext { } #[repr(C)] -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] pub struct TransferWithFeeProof { pub new_source_commitment: pod::PedersenCommitment, pub claimed_commitment: pod::PedersenCommitment, @@ -820,7 +817,7 @@ fn compute_delta_commitment( #[cfg(test)] mod test { - use super::*; + use {super::*, bytemuck::Zeroable}; #[test] fn test_fee_correctness() { diff --git a/zk-token-sdk/src/instruction/transfer/without_fee.rs b/zk-token-sdk/src/instruction/transfer/without_fee.rs index a2f257de65f054..27c8782fa22960 100644 --- a/zk-token-sdk/src/instruction/transfer/without_fee.rs +++ b/zk-token-sdk/src/instruction/transfer/without_fee.rs @@ -29,7 +29,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] @@ -470,7 +470,7 @@ impl TransferProof { #[cfg(test)] mod test { - use {super::*, crate::encryption::elgamal::ElGamalKeypair}; + use {super::*, crate::encryption::elgamal::ElGamalKeypair, bytemuck::Zeroable}; #[test] fn test_transfer_correctness() { diff --git a/zk-token-sdk/src/instruction/withdraw.rs b/zk-token-sdk/src/instruction/withdraw.rs index 530b6de0d75532..07cd8a9d6949a6 100644 --- a/zk-token-sdk/src/instruction/withdraw.rs +++ b/zk-token-sdk/src/instruction/withdraw.rs @@ -18,7 +18,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; #[cfg(not(target_os = "solana"))] diff --git a/zk-token-sdk/src/instruction/zero_balance.rs b/zk-token-sdk/src/instruction/zero_balance.rs index 7d52b80063176e..7671fb21cc4569 100644 --- a/zk-token-sdk/src/instruction/zero_balance.rs +++ b/zk-token-sdk/src/instruction/zero_balance.rs @@ -20,7 +20,7 @@ use { instruction::{ProofType, ZkProofData}, zk_token_elgamal::pod, }, - bytemuck::{Pod, Zeroable}, + bytemuck_derive::{Pod, Zeroable}, }; /// The instruction data that is needed for the `ProofInstruction::ZeroBalance` instruction. diff --git a/zk-token-sdk/src/lib.rs b/zk-token-sdk/src/lib.rs index 2946e177358adc..83d8b188366e7d 100644 --- a/zk-token-sdk/src/lib.rs +++ b/zk-token-sdk/src/lib.rs @@ -17,6 +17,8 @@ // // `clippy::op_ref` is turned off to prevent clippy from warning that this is not idiomatic code. +pub use solana_curve25519 as curve25519; + #[cfg(not(target_os = "solana"))] #[macro_use] pub(crate) mod macros; @@ -27,7 +29,6 @@ mod sigma_proofs; #[cfg(not(target_os = "solana"))] mod transcript; -pub mod curve25519; pub mod errors; pub mod instruction; pub mod zk_token_elgamal; diff --git a/zk-token-sdk/src/zk_token_elgamal/convert.rs b/zk-token-sdk/src/zk_token_elgamal/convert.rs index a437c817b41e72..286383cc1cd98b 100644 --- a/zk-token-sdk/src/zk_token_elgamal/convert.rs +++ b/zk-token-sdk/src/zk_token_elgamal/convert.rs @@ -1,4 +1,4 @@ -use {super::pod, crate::curve25519::ristretto::PodRistrettoPoint}; +use {super::pod, solana_curve25519::ristretto::PodRistrettoPoint}; impl From<(pod::PedersenCommitment, pod::DecryptHandle)> for pod::ElGamalCiphertext { fn from((commitment, handle): (pod::PedersenCommitment, pod::DecryptHandle)) -> Self { @@ -47,26 +47,7 @@ impl From for pod::DecryptHandle { #[cfg(not(target_os = "solana"))] mod target_arch { - use { - super::pod, - crate::{curve25519::scalar::PodScalar, errors::ElGamalError}, - curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar}, - std::convert::TryFrom, - }; - - impl From for PodScalar { - fn from(scalar: Scalar) -> Self { - Self(scalar.to_bytes()) - } - } - - impl TryFrom for Scalar { - type Error = ElGamalError; - - fn try_from(pod: PodScalar) -> Result { - Scalar::from_canonical_bytes(pod.0).ok_or(ElGamalError::CiphertextDeserialization) - } - } + use {super::pod, curve25519_dalek::ristretto::CompressedRistretto}; impl From for pod::CompressedRistretto { fn from(cr: CompressedRistretto) -> Self { diff --git a/zk-token-sdk/src/zk_token_elgamal/ops.rs b/zk-token-sdk/src/zk_token_elgamal/ops.rs index 38da19c1c2e7f1..d0cd41cc799e02 100644 --- a/zk-token-sdk/src/zk_token_elgamal/ops.rs +++ b/zk-token-sdk/src/zk_token_elgamal/ops.rs @@ -1,9 +1,9 @@ -use crate::{ - curve25519::{ +use { + crate::zk_token_elgamal::pod, + solana_curve25519::{ ristretto::{add_ristretto, multiply_ristretto, subtract_ristretto, PodRistrettoPoint}, scalar::PodScalar, }, - zk_token_elgamal::pod, }; const SHIFT_BITS: usize = 16; diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/auth_encryption.rs b/zk-token-sdk/src/zk_token_elgamal/pod/auth_encryption.rs index 3e1cdf1786a4ab..f46307d2367de5 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/auth_encryption.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/auth_encryption.rs @@ -3,8 +3,9 @@ #[cfg(not(target_os = "solana"))] use crate::{encryption::auth_encryption as decoded, errors::AuthenticatedEncryptionError}; use { - crate::zk_token_elgamal::pod::{impl_from_str, Pod, Zeroable}, + crate::zk_token_elgamal::pod::impl_from_str, base64::{prelude::BASE64_STANDARD, Engine}, + bytemuck::{Pod, Zeroable}, std::fmt, }; diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs b/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs index 64c3e794b4816b..2303daadfd1470 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/elgamal.rs @@ -10,10 +10,11 @@ use { }; use { crate::{ - zk_token_elgamal::pod::{impl_from_str, pedersen::PEDERSEN_COMMITMENT_LEN, Pod, Zeroable}, + zk_token_elgamal::pod::{impl_from_str, pedersen::PEDERSEN_COMMITMENT_LEN}, RISTRETTO_POINT_LEN, }, base64::{prelude::BASE64_STANDARD, Engine}, + bytemuck::Zeroable, std::fmt, }; @@ -33,7 +34,7 @@ pub(crate) const ELGAMAL_CIPHERTEXT_LEN: usize = PEDERSEN_COMMITMENT_LEN + DECRY const ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN: usize = 88; /// The `ElGamalCiphertext` type as a `Pod`. -#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct ElGamalCiphertext(pub [u8; ELGAMAL_CIPHERTEXT_LEN]); @@ -78,7 +79,7 @@ impl TryFrom for decoded::ElGamalCiphertext { } /// The `ElGamalPubkey` type as a `Pod`. -#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct ElGamalPubkey(pub [u8; ELGAMAL_PUBKEY_LEN]); @@ -117,7 +118,7 @@ impl TryFrom for decoded::ElGamalPubkey { } /// The `DecryptHandle` type as a `Pod`. -#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct DecryptHandle(pub [u8; DECRYPT_HANDLE_LEN]); diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/grouped_elgamal.rs b/zk-token-sdk/src/zk_token_elgamal/pod/grouped_elgamal.rs index c7e820fcd04508..7d5ae944ecf1a9 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/grouped_elgamal.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/grouped_elgamal.rs @@ -8,9 +8,9 @@ use { zk_token_elgamal::pod::{ elgamal::{ElGamalCiphertext, DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN}, pedersen::{PedersenCommitment, PEDERSEN_COMMITMENT_LEN}, - Pod, Zeroable, }, }, + bytemuck::Zeroable, std::fmt, }; @@ -61,7 +61,7 @@ const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES: usize = PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN; /// The `GroupedElGamalCiphertext` type with two decryption handles as a `Pod` -#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct GroupedElGamalCiphertext2Handles(pub [u8; GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES]); @@ -95,7 +95,7 @@ impl TryFrom for GroupedElGamalCiphertext<2> { impl_extract!(TYPE = GroupedElGamalCiphertext2Handles); /// The `GroupedElGamalCiphertext` type with three decryption handles as a `Pod` -#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct GroupedElGamalCiphertext3Handles(pub [u8; GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES]); diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/instruction.rs b/zk-token-sdk/src/zk_token_elgamal/pod/instruction.rs index e29e3a500551ee..c87a0aec71dd87 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/instruction.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/instruction.rs @@ -1,11 +1,10 @@ use crate::zk_token_elgamal::pod::{ - GroupedElGamalCiphertext2Handles, GroupedElGamalCiphertext3Handles, Pod, PodU16, PodU64, - Zeroable, + GroupedElGamalCiphertext2Handles, GroupedElGamalCiphertext3Handles, PodU16, PodU64, }; #[cfg(not(target_os = "solana"))] use crate::{errors::ElGamalError, instruction::transfer as decoded}; -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct TransferAmountCiphertext(pub GroupedElGamalCiphertext3Handles); @@ -25,7 +24,7 @@ impl TryFrom for decoded::TransferAmountCiphertext { } } -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct FeeEncryption(pub GroupedElGamalCiphertext2Handles); @@ -45,7 +44,7 @@ impl TryFrom for decoded::FeeEncryption { } } -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct FeeParameters { /// Fee rate expressed as basis points of the transfer amount, i.e. increments of 0.01% diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/mod.rs b/zk-token-sdk/src/zk_token_elgamal/pod/mod.rs index 56ea70a6589532..d060213a37d6a8 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/mod.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/mod.rs @@ -36,7 +36,9 @@ pub enum ParseError { Invalid, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] +#[derive( + Clone, Copy, Debug, Default, PartialEq, Eq, bytemuck_derive::Pod, bytemuck_derive::Zeroable, +)] #[repr(transparent)] pub struct PodU16([u8; 2]); impl From for PodU16 { @@ -50,7 +52,9 @@ impl From for u16 { } } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] +#[derive( + Clone, Copy, Debug, Default, PartialEq, Eq, bytemuck_derive::Pod, bytemuck_derive::Zeroable, +)] #[repr(transparent)] pub struct PodU64([u8; 8]); impl From for PodU64 { @@ -64,7 +68,9 @@ impl From for u64 { } } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Pod, Zeroable)] +#[derive( + Clone, Copy, Debug, Default, PartialEq, Eq, bytemuck_derive::Pod, bytemuck_derive::Zeroable, +)] #[repr(transparent)] pub struct PodProofType(u8); impl From for PodProofType { @@ -80,7 +86,7 @@ impl TryFrom for ProofType { } } -#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct CompressedRistretto(pub [u8; 32]); diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/pedersen.rs b/zk-token-sdk/src/zk_token_elgamal/pod/pedersen.rs index d27f307f43df2c..d9d1d551b22d6e 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/pedersen.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/pedersen.rs @@ -1,23 +1,17 @@ //! Plain Old Data type for the Pedersen commitment scheme. +use {crate::RISTRETTO_POINT_LEN, std::fmt}; #[cfg(not(target_os = "solana"))] use { crate::{encryption::pedersen as decoded, errors::ElGamalError}, curve25519_dalek::ristretto::CompressedRistretto, }; -use { - crate::{ - zk_token_elgamal::pod::{Pod, Zeroable}, - RISTRETTO_POINT_LEN, - }, - std::fmt, -}; /// Byte length of a Pedersen commitment pub(crate) const PEDERSEN_COMMITMENT_LEN: usize = RISTRETTO_POINT_LEN; /// The `PedersenCommitment` type as a `Pod`. -#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct PedersenCommitment(pub [u8; PEDERSEN_COMMITMENT_LEN]); diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/range_proof.rs b/zk-token-sdk/src/zk_token_elgamal/pod/range_proof.rs index 4f134cb5eb7dd0..10746bef944c61 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/range_proof.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/range_proof.rs @@ -5,9 +5,9 @@ use crate::{ range_proof::{self as decoded, errors::RangeProofVerificationError}, UNIT_LEN, }; -use crate::{ - zk_token_elgamal::pod::{Pod, Zeroable}, - RISTRETTO_POINT_LEN, SCALAR_LEN, +use { + crate::{RISTRETTO_POINT_LEN, SCALAR_LEN}, + bytemuck::{Pod, Zeroable}, }; /// Byte length of a range proof excluding the inner-product proof component diff --git a/zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs b/zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs index f0f43e662a2e51..dc160f47f720b3 100644 --- a/zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs +++ b/zk-token-sdk/src/zk_token_elgamal/pod/sigma_proofs.rs @@ -12,7 +12,7 @@ use crate::sigma_proofs::{ pubkey_proof::PubkeyValidityProof as DecodedPubkeyValidityProof, zero_balance_proof::ZeroBalanceProof as DecodedZeroBalanceProof, }; -use crate::zk_token_elgamal::pod::{Pod, Zeroable}; +use bytemuck::{Pod, Zeroable}; /// Byte length of a ciphertext-commitment equality proof const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN: usize = 192; @@ -217,7 +217,7 @@ impl TryFrom for DecodedZeroBalanceProof { } /// The `FeeSigmaProof` type as a `Pod`. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(transparent)] pub struct FeeSigmaProof(pub [u8; FEE_SIGMA_PROOF_LEN]); @@ -238,7 +238,7 @@ impl TryFrom for DecodedFeeSigmaProof { } /// The `PubkeyValidityProof` type as a `Pod`. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(transparent)] pub struct PubkeyValidityProof(pub [u8; PUBKEY_VALIDITY_PROOF_LEN]); diff --git a/zk-token-sdk/src/zk_token_proof_state.rs b/zk-token-sdk/src/zk_token_proof_state.rs index d95aa4f11ec1c3..6d9644394ce197 100644 --- a/zk-token-sdk/src/zk_token_proof_state.rs +++ b/zk-token-sdk/src/zk_token_proof_state.rs @@ -53,7 +53,7 @@ impl ProofContextState { /// The `ProofContextState` without the proof context itself. This struct exists to facilitate the /// decoding of generic-independent fields in `ProofContextState`. -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +#[derive(Clone, Copy, Debug, PartialEq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct ProofContextStateMeta { /// The proof context authority that can close the account