diff --git a/Cargo.lock b/Cargo.lock index 1172d51221d896..412c357395a298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,7 +244,7 @@ dependencies = [ "solana-inflation", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-log-collector", "solana-logger", "solana-measure", @@ -2717,6 +2717,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "five8" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75b8549488b4715defcb0d8a8a1c1c76a80661b5fa106b4ca0e7fce59d7d875" +dependencies = [ + "five8_core", +] + [[package]] name = "five8_const" version = "0.1.4" @@ -6516,8 +6525,6 @@ dependencies = [ [[package]] name = "solana-account" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f949fe4edaeaea78c844023bfc1c898e0b1f5a100f8a8d2d0f85d0a7b090258" dependencies = [ "bincode", "qualifier_attr", @@ -6557,7 +6564,7 @@ dependencies = [ "solana-fee-calculator", "solana-hash", "solana-instruction", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-nonce", "solana-program-option", "solana-program-pack", @@ -6596,8 +6603,6 @@ dependencies = [ [[package]] name = "solana-account-info" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c17d606a298a205fae325489fbed88ee6dc4463c111672172327e741c8905d" dependencies = [ "bincode", "serde", @@ -6754,8 +6759,6 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-interface" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1673f67efe870b64a65cb39e6194be5b26527691ce5922909939961a6e6b395" dependencies = [ "bincode", "bytemuck", @@ -6771,8 +6774,6 @@ dependencies = [ [[package]] name = "solana-atomic-u64" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52e52720efe60465b052b9e7445a01c17550666beec855cce66f44766697bc2" dependencies = [ "parking_lot 0.12.3", ] @@ -6989,8 +6990,6 @@ dependencies = [ [[package]] name = "solana-big-mod-exp" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75db7f2bbac3e62cfd139065d15bcda9e2428883ba61fc8d27ccb251081e7567" dependencies = [ "num-bigint 0.4.6", "num-traits", @@ -7000,8 +6999,6 @@ dependencies = [ [[package]] name = "solana-bincode" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" dependencies = [ "bincode", "serde", @@ -7011,8 +7008,6 @@ dependencies = [ [[package]] name = "solana-blake3-hasher" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0801e25a1b31a14494fc80882a036be0ffd290efc4c2d640bfcca120a4672" dependencies = [ "blake3", "solana-define-syscall", @@ -7042,8 +7037,6 @@ dependencies = [ [[package]] name = "solana-bn254" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4420f125118732833f36facf96a27e7b78314b2d642ba07fa9ffdacd8d79e243" dependencies = [ "ark-bn254", "ark-ec", @@ -7057,8 +7050,6 @@ dependencies = [ [[package]] name = "solana-borsh" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718333bcd0a1a7aed6655aa66bef8d7fb047944922b2d3a18f49cbc13e73d004" dependencies = [ "borsh 0.10.3", "borsh 1.5.7", @@ -7093,7 +7084,7 @@ dependencies = [ "solana-instruction", "solana-keccak-hasher", "solana-last-restart-slot", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", @@ -7133,7 +7124,7 @@ dependencies = [ "solana-bpf-loader-program", "solana-instruction", "solana-keypair", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-program-test", "solana-pubkey", "solana-sdk-ids", @@ -7345,7 +7336,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-keypair", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-loader-v4-program", "solana-logger", @@ -7383,6 +7374,7 @@ dependencies = [ "solana-transaction", "solana-transaction-error", "solana-transaction-status", + "solana-transaction-status-client-types", "solana-udp-client", "solana-version", "solana-vote-program", @@ -7448,6 +7440,7 @@ dependencies = [ "solana-transaction-context 2.3.0", "solana-transaction-error", "solana-transaction-status", + "solana-transaction-status-client-types", "solana-vote-program", "spl-memo", ] @@ -7493,6 +7486,7 @@ dependencies = [ "solana-tpu-client", "solana-transaction", "solana-transaction-error", + "solana-transaction-status-client-types", "solana-udp-client", "thiserror 2.0.12", "tokio", @@ -7538,8 +7532,6 @@ dependencies = [ [[package]] name = "solana-client-traits" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f0071874e629f29e0eb3dab8a863e98502ac7aba55b7e0df1803fc5cac72a7" dependencies = [ "solana-account", "solana-commitment-config", @@ -7559,8 +7551,6 @@ dependencies = [ [[package]] name = "solana-clock" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c2177a1b9fe8326004f1151a5acd124420b737811080b1035df31349e4d892" dependencies = [ "serde", "serde_derive", @@ -7572,8 +7562,6 @@ dependencies = [ [[package]] name = "solana-cluster-type" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ace9fea2daa28354d107ea879cff107181d85cd4e0f78a2bedb10e1a428c97e" dependencies = [ "serde", "serde_derive", @@ -7585,8 +7573,6 @@ dependencies = [ [[package]] name = "solana-commitment-config" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac49c4dde3edfa832de1697e9bcdb7c3b3f7cb7a1981b7c62526c8bb6700fb73" dependencies = [ "serde", "serde_derive", @@ -7607,6 +7593,7 @@ name = "solana-compute-budget-instruction" version = "2.3.0" dependencies = [ "agave-feature-set", + "assert_matches", "bincode", "criterion", "log", @@ -7634,8 +7621,6 @@ dependencies = [ [[package]] name = "solana-compute-budget-interface" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8432d2c4c22d0499aa06d62e4f7e333f81777b3d7c96050ae9e5cb71a8c3aee4" dependencies = [ "borsh 1.5.7", "serde", @@ -7780,7 +7765,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-logger", "solana-measure", "solana-message", @@ -7903,8 +7888,6 @@ dependencies = [ [[package]] name = "solana-cpi" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11" dependencies = [ "solana-account-info", "solana-define-syscall", @@ -7929,8 +7912,6 @@ dependencies = [ [[package]] name = "solana-decode-error" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a6a6383af236708048f8bd8d03db8ca4ff7baf4a48e5d580f4cce545925470" dependencies = [ "num-traits", ] @@ -7938,14 +7919,10 @@ dependencies = [ [[package]] name = "solana-define-syscall" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf784bb2cb3e02cac9801813c30187344228d2ae952534902108f6150573a33d" [[package]] name = "solana-derivation-path" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939756d798b25c5ec3cca10e06212bdca3b1443cb9bb740a38124f58b258737b" dependencies = [ "derivation-path", "qstring", @@ -8009,8 +7986,6 @@ dependencies = [ [[package]] name = "solana-ed25519-program" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0fc717048fdbe5d2ee7d673d73e6a30a094002f4a29ca7630ac01b6bddec04" dependencies = [ "bytemuck", "bytemuck_derive", @@ -8032,6 +8007,7 @@ dependencies = [ "solana-instruction", "solana-precompile-error", "solana-program-test", + "solana-sdk-ids", "solana-signer", "solana-transaction", "solana-transaction-error", @@ -8073,8 +8049,6 @@ dependencies = [ [[package]] name = "solana-epoch-info" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ef6f0b449290b0b9f32973eefd95af35b01c5c0c34c569f936c34c5b20d77b" dependencies = [ "serde", "serde_derive", @@ -8083,8 +8057,6 @@ dependencies = [ [[package]] name = "solana-epoch-rewards" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b575d3dd323b9ea10bb6fe89bf6bf93e249b215ba8ed7f68f1a3633f384db7" dependencies = [ "serde", "serde_derive", @@ -8099,8 +8071,6 @@ dependencies = [ [[package]] name = "solana-epoch-rewards-hasher" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c5fd2662ae7574810904585fd443545ed2b568dbd304b25a31e79ccc76e81b" dependencies = [ "siphasher 0.3.11", "solana-hash", @@ -8110,8 +8080,6 @@ dependencies = [ [[package]] name = "solana-epoch-schedule" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fce071fbddecc55d727b1d7ed16a629afe4f6e4c217bc8d00af3b785f6f67ed" dependencies = [ "serde", "serde_derive", @@ -8125,8 +8093,6 @@ dependencies = [ [[package]] name = "solana-example-mocks" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84461d56cbb8bb8d539347151e0525b53910102e4bced875d49d5139708e39d3" dependencies = [ "serde", "serde_derive", @@ -8190,8 +8156,6 @@ dependencies = [ [[package]] name = "solana-feature-gate-interface" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f5c5382b449e8e4e3016fb05e418c53d57782d8b5c30aa372fc265654b956d" dependencies = [ "bincode", "serde", @@ -8209,8 +8173,6 @@ dependencies = [ [[package]] name = "solana-feature-set" version = "2.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92f6c09cc41059c0e03ccbee7f5d4cc0a315d68ef0d59b67eb90246adfd8cc35" dependencies = [ "ahash 0.8.11", "lazy_static", @@ -8234,8 +8196,6 @@ dependencies = [ [[package]] name = "solana-fee-calculator" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89bc408da0fb3812bc3008189d148b4d3e08252c79ad810b245482a3f70cd8d" dependencies = [ "log", "serde", @@ -8247,8 +8207,6 @@ dependencies = [ [[package]] name = "solana-fee-structure" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f45f94a88efdb512805563181dfa1c85c60a21b6e6d602bf24a2ea88f9399d6e" dependencies = [ "serde", "serde_derive", @@ -8260,8 +8218,6 @@ dependencies = [ [[package]] name = "solana-file-download" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96156c84b006ce3f00b37fa852a7d93de13ede4c40a58269663c3e01907453e3" dependencies = [ "console", "indicatif", @@ -8272,8 +8228,6 @@ dependencies = [ [[package]] name = "solana-frozen-abi" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bdd72d99751bcd646dc6a32772bf181c7b49f5379e4a4c8946920f3082b0ec6" dependencies = [ "bs58", "bv", @@ -8292,8 +8246,6 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b83f88a126213cbcb57672c5e70ddb9791eff9b480e9f39fe9285fd2abca66fa" dependencies = [ "proc-macro2", "quote", @@ -8327,7 +8279,7 @@ dependencies = [ "solana-inflation", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-logger", "solana-native-token", "solana-poh-config", @@ -8349,8 +8301,6 @@ dependencies = [ [[package]] name = "solana-genesis-config" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968dabd2b92d57131473eddbd475339da530e14f54397386abf303de3a2595a2" dependencies = [ "bincode", "chrono", @@ -8499,8 +8449,6 @@ dependencies = [ [[package]] name = "solana-hard-forks" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c28371f878e2ead55611d8ba1b5fb879847156d04edea13693700ad1a28baf" dependencies = [ "serde", "serde_derive", @@ -8511,13 +8459,11 @@ dependencies = [ [[package]] name = "solana-hash" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7bcb14392900fe02e4e34e90234fbf0c673d4e327888410ba99fa2ba0f4e99" dependencies = [ "borsh 1.5.7", - "bs58", "bytemuck", "bytemuck_derive", + "five8", "js-sys", "serde", "serde_derive", @@ -8531,8 +8477,6 @@ dependencies = [ [[package]] name = "solana-inflation" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23eef6a09eb8e568ce6839573e4966850e85e9ce71e6ae1a6c930c1c43947de3" dependencies = [ "serde", "serde_derive", @@ -8543,8 +8487,6 @@ dependencies = [ [[package]] name = "solana-instruction" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce496a475e5062ba5de97215ab39d9c358f9c9df4bb7f3a45a1f1a8bd9065ed" dependencies = [ "bincode", "borsh 1.5.7", @@ -8563,8 +8505,6 @@ dependencies = [ [[package]] name = "solana-instructions-sysvar" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e85a6fad5c2d0c4f5b91d34b8ca47118fc593af706e523cdbedf846a954f57" dependencies = [ "bitflags 2.9.1", "solana-account-info", @@ -8580,8 +8520,6 @@ dependencies = [ [[package]] name = "solana-keccak-hasher" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7aeb957fbd42a451b99235df4942d96db7ef678e8d5061ef34c9b34cae12f79" dependencies = [ "sha3", "solana-define-syscall", @@ -8616,12 +8554,10 @@ dependencies = [ [[package]] name = "solana-keypair" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dbb7042c2e0c561afa07242b2099d55c57bd1b1da3b6476932197d84e15e3e4" dependencies = [ - "bs58", "ed25519-dalek", "ed25519-dalek-bip32", + "five8", "rand 0.7.3", "solana-derivation-path", "solana-pubkey", @@ -8635,8 +8571,6 @@ dependencies = [ [[package]] name = "solana-last-restart-slot" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6360ac2fdc72e7463565cd256eedcf10d7ef0c28a1249d261ec168c1b55cdd" dependencies = [ "serde", "serde_derive", @@ -8729,6 +8663,7 @@ dependencies = [ "solana-rayon-threadlimit", "solana-runtime", "solana-runtime-transaction", + "solana-sdk-ids", "solana-seed-derivable", "solana-sha256-hasher", "solana-shred-version", @@ -8768,22 +8703,6 @@ dependencies = [ [[package]] name = "solana-loader-v2-interface" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8ab08006dad78ae7cd30df8eea0539e207d08d91eaefb3e1d49a446e1c49654" -dependencies = [ - "serde", - "serde_bytes", - "serde_derive", - "solana-instruction", - "solana-pubkey", - "solana-sdk-ids", -] - -[[package]] -name = "solana-loader-v3-interface" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4be76cfa9afd84ca2f35ebc09f0da0f0092935ccdac0595d98447f259538c2" dependencies = [ "serde", "serde_bytes", @@ -8791,14 +8710,11 @@ dependencies = [ "solana-instruction", "solana-pubkey", "solana-sdk-ids", - "solana-system-interface", ] [[package]] name = "solana-loader-v3-interface" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f7162a05b8b0773156b443bccd674ea78bb9aa406325b467ea78c06c99a63a2" dependencies = [ "serde", "serde_bytes", @@ -8812,8 +8728,6 @@ dependencies = [ [[package]] name = "solana-loader-v4-interface" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "706a777242f1f39a83e2a96a2a6cb034cb41169c6ecbee2cf09cb873d9659e7e" dependencies = [ "serde", "serde_bytes", @@ -8836,7 +8750,7 @@ dependencies = [ "solana-bpf-loader-program", "solana-clock", "solana-instruction", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-log-collector", "solana-measure", @@ -8939,8 +8853,6 @@ dependencies = [ [[package]] name = "solana-logger" version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8e777ec1afd733939b532a42492d888ec7c88d8b4127a5d867eb45c6eb5cd5" dependencies = [ "env_logger", "lazy_static", @@ -8970,8 +8882,6 @@ dependencies = [ [[package]] name = "solana-message" version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6bf99c4570173710107a1f233f3bee226feea5fc817308707d4f7cb100a72d" dependencies = [ "bincode", "blake3", @@ -9013,8 +8923,6 @@ dependencies = [ [[package]] name = "solana-msg" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36a1a14399afaabc2781a1db09cb14ee4cc4ee5c7a5a3cfcc601811379a8092" dependencies = [ "solana-define-syscall", ] @@ -9022,8 +8930,6 @@ dependencies = [ [[package]] name = "solana-native-token" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307fb2f78060995979e9b4f68f833623565ed4e55d3725f100454ce78a99a1a3" [[package]] name = "solana-net-shaper" @@ -9068,8 +8974,6 @@ checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" [[package]] name = "solana-nonce" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703e22eb185537e06204a5bd9d509b948f0066f2d1d814a6f475dafb3ddf1325" dependencies = [ "serde", "serde_derive", @@ -9082,8 +8986,6 @@ dependencies = [ [[package]] name = "solana-nonce-account" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde971a20b8dbf60144d6a84439dda86b5466e00e2843091fe731083cda614da" dependencies = [ "solana-account", "solana-hash", @@ -9104,8 +9006,6 @@ dependencies = [ [[package]] name = "solana-offchain-message" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b526398ade5dea37f1f147ce55dae49aa017a5d7326606359b0445ca8d946581" dependencies = [ "num_enum", "solana-hash", @@ -9120,8 +9020,6 @@ dependencies = [ [[package]] name = "solana-packet" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004f2d2daf407b3ec1a1ca5ec34b3ccdfd6866dd2d3c7d0715004a96e4b6d127" dependencies = [ "bincode", "bitflags 2.9.1", @@ -9229,8 +9127,6 @@ dependencies = [ [[package]] name = "solana-poh-config" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d650c3b4b9060082ac6b0efbbb66865089c58405bfb45de449f3f2b91eccee75" dependencies = [ "serde", "serde_derive", @@ -9251,8 +9147,6 @@ dependencies = [ [[package]] name = "solana-precompile-error" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ff64daa2933c22982b323d88d0cdf693201ef56ac381ae16737fd5f579e07d6" dependencies = [ "num-traits", "solana-decode-error", @@ -9261,8 +9155,6 @@ dependencies = [ [[package]] name = "solana-precompiles" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a460ab805ec063802105b463ecb5eb02c3ffe469e67a967eea8a6e778e0bc06" dependencies = [ "lazy_static", "solana-ed25519-program", @@ -9278,8 +9170,6 @@ dependencies = [ [[package]] name = "solana-presigner" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a57a24e6a4125fc69510b6774cd93402b943191b6cddad05de7281491c90fe" dependencies = [ "solana-pubkey", "solana-signature", @@ -9289,8 +9179,6 @@ dependencies = [ [[package]] name = "solana-program" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "586469467e93ceb79048f8d8e3a619bf61d05396ee7de95cb40280301a589d05" dependencies = [ "bincode", "blake3", @@ -9335,7 +9223,7 @@ dependencies = [ "solana-keccak-hasher", "solana-last-restart-slot", "solana-loader-v2-interface", - "solana-loader-v3-interface 3.0.0", + "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-message", "solana-msg", @@ -9371,8 +9259,6 @@ dependencies = [ [[package]] name = "solana-program-entrypoint" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473ffe73c68d93e9f2aa726ad2985fe52760052709aaab188100a42c618060ec" dependencies = [ "solana-account-info", "solana-msg", @@ -9383,8 +9269,6 @@ dependencies = [ [[package]] name = "solana-program-error" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8ae2c1a8d0d4ae865882d5770a7ebca92bab9c685e43f0461682c6c05a35bfa" dependencies = [ "borsh 1.5.7", "num-traits", @@ -9399,24 +9283,17 @@ dependencies = [ [[package]] name = "solana-program-memory" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b0268f6c89825fb634a34bd0c3b8fdaeaecfc3728be1d622a8ee6dd577b60d4" dependencies = [ - "num-traits", "solana-define-syscall", ] [[package]] name = "solana-program-option" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc677a2e9bc616eda6dbdab834d463372b92848b2bfe4a1ed4e4b4adba3397d0" [[package]] name = "solana-program-pack" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b" dependencies = [ "solana-program-error", ] @@ -9498,7 +9375,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-keypair", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-log-collector", "solana-logger", "solana-message", @@ -9537,16 +9414,14 @@ dependencies = [ [[package]] name = "solana-pubkey" version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad77cf9f30b971a1eec48dde6a863dcac60ba005a34dfde23736afa5c7ac667" dependencies = [ "arbitrary", "borsh 0.10.3", "borsh 1.5.7", - "bs58", "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", + "five8", "five8_const", "getrandom 0.2.15", "js-sys", @@ -9626,8 +9501,6 @@ dependencies = [ [[package]] name = "solana-quic-definitions" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e606feac5110eb5d8afaa43ccaeea3ec49ccec36773387930b5ba545e745aea2" dependencies = [ "solana-keypair", ] @@ -9665,8 +9538,6 @@ dependencies = [ [[package]] name = "solana-rent" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1aea8fdea9de98ca6e8c2da5827707fb3842833521b528a713810ca685d2480" dependencies = [ "serde", "serde_derive", @@ -9680,8 +9551,6 @@ dependencies = [ [[package]] name = "solana-rent-collector" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c1e19f5d5108b0d824244425e43bc78bbb9476e2199e979b0230c9f632d3bf4" dependencies = [ "serde", "serde_derive", @@ -9699,8 +9568,6 @@ dependencies = [ [[package]] name = "solana-rent-debits" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f6f9113c6003492e74438d1288e30cffa8ccfdc2ef7b49b9e816d8034da18cd" dependencies = [ "solana-pubkey", "solana-reward-info", @@ -9709,8 +9576,6 @@ dependencies = [ [[package]] name = "solana-reserved-account-keys" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b293f4246626c0e0a991531f08848a713ada965612e99dc510963f04d12cae7" dependencies = [ "lazy_static", "solana-feature-set", @@ -9721,8 +9586,6 @@ dependencies = [ [[package]] name = "solana-reward-info" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18205b69139b1ae0ab8f6e11cdcb627328c0814422ad2482000fa2ca54ae4a2f" dependencies = [ "serde", "serde_derive", @@ -9873,6 +9736,7 @@ dependencies = [ "solana-program", "solana-pubkey", "solana-rpc-client-api", + "solana-sdk-ids", "solana-signature", "solana-signer", "solana-system-transaction", @@ -10077,7 +9941,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-lattice-hash", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-logger", "solana-measure", @@ -10180,8 +10044,6 @@ dependencies = [ [[package]] name = "solana-sanitize" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" [[package]] name = "solana-sbpf" @@ -10205,8 +10067,6 @@ dependencies = [ [[package]] name = "solana-sdk" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8af90d2ce445440e0548fa4a5f96fe8b265c22041a68c942012ffadd029667d" dependencies = [ "bincode", "bs58", @@ -10276,8 +10136,6 @@ dependencies = [ [[package]] name = "solana-sdk-ids" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" dependencies = [ "solana-pubkey", ] @@ -10285,8 +10143,6 @@ dependencies = [ [[package]] name = "solana-sdk-macro" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86280da8b99d03560f6ab5aca9de2e38805681df34e0bb8f238e69b29433b9df" dependencies = [ "bs58", "proc-macro2", @@ -10297,8 +10153,6 @@ dependencies = [ [[package]] name = "solana-secp256k1-program" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a1caa972414cc78122c32bdae65ac5fe89df7db598585a5cde19d16a20280a" dependencies = [ "bincode", "digest 0.10.7", @@ -10310,13 +10164,12 @@ dependencies = [ "solana-instruction", "solana-precompile-error", "solana-sdk-ids", + "solana-signature", ] [[package]] name = "solana-secp256k1-recover" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" dependencies = [ "libsecp256k1", "solana-define-syscall", @@ -10326,8 +10179,6 @@ dependencies = [ [[package]] name = "solana-secp256r1-program" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cda2aa1bbaceda14763c4f142a00b486f2f262cfd901bd0410649ad0404d5f7" dependencies = [ "bytemuck", "openssl", @@ -10346,8 +10197,6 @@ checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" [[package]] name = "solana-seed-derivable" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beb82b5adb266c6ea90e5cf3967235644848eac476c5a1f2f9283a143b7c97f" dependencies = [ "solana-derivation-path", ] @@ -10355,8 +10204,6 @@ dependencies = [ [[package]] name = "solana-seed-phrase" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15" dependencies = [ "hmac 0.12.1", "pbkdf2 0.11.0", @@ -10401,8 +10248,6 @@ dependencies = [ [[package]] name = "solana-serde" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1931484a408af466e14171556a47adaa215953c7f48b24e5f6b0282763818b04" dependencies = [ "serde", ] @@ -10410,8 +10255,6 @@ dependencies = [ [[package]] name = "solana-serde-varint" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc07d00200d82e6def2f7f7a45738e3406b17fe54a18adcf0defa16a97ccadb" dependencies = [ "serde", ] @@ -10419,8 +10262,6 @@ dependencies = [ [[package]] name = "solana-serialize-utils" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "817a284b63197d2b27afdba829c5ab34231da4a9b4e763466a003c40ca4f535e" dependencies = [ "solana-instruction", "solana-pubkey", @@ -10430,8 +10271,6 @@ dependencies = [ [[package]] name = "solana-sha256-hasher" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0037386961c0d633421f53560ad7c80675c0447cba4d1bb66d60974dd486c7ea" dependencies = [ "sha2 0.10.9", "solana-define-syscall", @@ -10441,8 +10280,6 @@ dependencies = [ [[package]] name = "solana-short-vec" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c54c66f19b9766a56fa0057d060de8378676cb64987533fa088861858fc5a69" dependencies = [ "serde", "solana-frozen-abi", @@ -10452,8 +10289,6 @@ dependencies = [ [[package]] name = "solana-shred-version" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afd3db0461089d1ad1a78d9ba3f15b563899ca2386351d38428faa5350c60a98" dependencies = [ "solana-hard-forks", "solana-hash", @@ -10463,11 +10298,9 @@ dependencies = [ [[package]] name = "solana-signature" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d251c8f3dc015f320b4161daac7f108156c837428e5a8cc61136d25beb11d6" dependencies = [ - "bs58", "ed25519-dalek", + "five8", "rand 0.8.5", "serde", "serde-big-array", @@ -10480,8 +10313,6 @@ dependencies = [ [[package]] name = "solana-signer" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" dependencies = [ "solana-pubkey", "solana-signature", @@ -10491,8 +10322,6 @@ dependencies = [ [[package]] name = "solana-slot-hashes" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8691982114513763e88d04094c9caa0376b867a29577939011331134c301ce" dependencies = [ "serde", "serde_derive", @@ -10504,8 +10333,6 @@ dependencies = [ [[package]] name = "solana-slot-history" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ccc1b2067ca22754d5283afb2b0126d61eae734fc616d23871b0943b0d935e" dependencies = [ "bv", "serde", @@ -10517,8 +10344,6 @@ dependencies = [ [[package]] name = "solana-stable-layout" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54" dependencies = [ "solana-instruction", "solana-pubkey", @@ -10691,6 +10516,7 @@ dependencies = [ "prost", "protobuf-src", "serde", + "serde_derive", "solana-account-decoder", "solana-hash", "solana-instruction", @@ -10702,6 +10528,7 @@ dependencies = [ "solana-transaction-context 2.3.0", "solana-transaction-error", "solana-transaction-status", + "test-case", "tonic-build", ] @@ -10760,7 +10587,6 @@ dependencies = [ "agave-feature-set", "agave-reserved-account-keys", "ahash 0.8.11", - "assert_matches", "bincode", "ed25519-dalek", "itertools 0.12.1", @@ -10790,7 +10616,7 @@ dependencies = [ "solana-instruction", "solana-instructions-sysvar", "solana-keypair", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-loader-v4-interface", "solana-loader-v4-program", "solana-log-collector", @@ -10944,8 +10770,6 @@ dependencies = [ [[package]] name = "solana-system-transaction" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd98a25e5bcba8b6be8bcbb7b84b24c2a6a8178d7fb0e3077a916855ceba91a" dependencies = [ "solana-hash", "solana-keypair", @@ -10959,8 +10783,6 @@ dependencies = [ [[package]] name = "solana-sysvar" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf6b44740d7f0c9f375d045c165bc0aab4a90658f92d6835aeb0649afaeaff9a" dependencies = [ "base64 0.22.1", "bincode", @@ -10998,8 +10820,6 @@ dependencies = [ [[package]] name = "solana-sysvar-id" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1" dependencies = [ "solana-pubkey", "solana-sdk-ids", @@ -11032,7 +10852,7 @@ dependencies = [ "solana-instruction", "solana-keypair", "solana-ledger", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-logger", "solana-message", "solana-native-token", @@ -11083,8 +10903,6 @@ dependencies = [ [[package]] name = "solana-time-utils" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af261afb0e8c39252a04d026e3ea9c405342b08c871a2ad8aa5448e068c784c" [[package]] name = "solana-timings" @@ -11251,8 +11069,6 @@ dependencies = [ [[package]] name = "solana-transaction" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abec848d081beb15a324c633cd0e0ab33033318063230389895cae503ec9b544" dependencies = [ "bincode", "serde", @@ -11354,14 +11170,13 @@ dependencies = [ [[package]] name = "solana-transaction-error" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" dependencies = [ "serde", "serde_derive", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-instruction", + "solana-pubkey", "solana-sanitize", ] @@ -11404,7 +11219,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-loader-v2-interface", - "solana-loader-v3-interface 5.0.0", + "solana-loader-v3-interface", "solana-message", "solana-program-option", "solana-pubkey", @@ -11439,12 +11254,15 @@ dependencies = [ "serde_json", "solana-account-decoder-client-types", "solana-commitment-config", + "solana-instruction", "solana-message", + "solana-pubkey", "solana-reward-info", "solana-signature", "solana-transaction", "solana-transaction-context 2.3.0", "solana-transaction-error", + "test-case", "thiserror 2.0.12", ] @@ -11593,8 +11411,6 @@ dependencies = [ [[package]] name = "solana-validator-exit" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bbf6d7a3c0b28dd5335c52c0e9eae49d0ae489a8f324917faf0ded65a812c1d" [[package]] name = "solana-version" @@ -11711,8 +11527,6 @@ dependencies = [ [[package]] name = "solana-vote-interface" version = "2.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f039b0788337bedc6c5450d2f237718f938defb5ce0e0ad8ef507e78dcd370" dependencies = [ "arbitrary", "bincode", @@ -11831,6 +11645,7 @@ dependencies = [ name = "solana-zk-elgamal-proof-program-tests" version = "2.3.0" dependencies = [ + "assert_matches", "bytemuck", "solana-account", "solana-compute-budget", @@ -11839,6 +11654,7 @@ dependencies = [ "solana-keypair", "solana-program-test", "solana-pubkey", + "solana-sdk-ids", "solana-signer", "solana-system-interface", "solana-transaction", @@ -14266,3 +14082,11 @@ dependencies = [ "cc", "pkg-config", ] + +[[patch.unused]] +name = "solana-package-metadata" +version = "2.2.1" + +[[patch.unused]] +name = "solana-package-metadata-macro" +version = "2.2.1" diff --git a/Cargo.toml b/Cargo.toml index 38545e6e07fd6b..513cc337785593 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -681,3 +681,103 @@ crossbeam-epoch = { git = "https://github.com/anza-xyz/crossbeam", rev = "fd279d # comments and the overrides in sync. solana-curve25519 = { path = "curves/curve25519" } solana-zk-sdk = { path = "zk-sdk" } + +# The following entries are auto-generated by /bin/bash +solana-account = { path = "/home/sol/src/solana-sdk/account" } +solana-account-info = { path = "/home/sol/src/solana-sdk/account-info" } +solana-address-lookup-table-interface = { path = "/home/sol/src/solana-sdk/address-lookup-table-interface" } +solana-atomic-u64 = { path = "/home/sol/src/solana-sdk/atomic-u64" } +solana-big-mod-exp = { path = "/home/sol/src/solana-sdk/big-mod-exp" } +solana-bincode = { path = "/home/sol/src/solana-sdk/bincode" } +solana-blake3-hasher = { path = "/home/sol/src/solana-sdk/blake3-hasher" } +solana-bn254 = { path = "/home/sol/src/solana-sdk/bn254" } +solana-borsh = { path = "/home/sol/src/solana-sdk/borsh" } +solana-client-traits = { path = "/home/sol/src/solana-sdk/client-traits" } +solana-clock = { path = "/home/sol/src/solana-sdk/clock" } +solana-cluster-type = { path = "/home/sol/src/solana-sdk/cluster-type" } +solana-commitment-config = { path = "/home/sol/src/solana-sdk/commitment-config" } +solana-compute-budget-interface = { path = "/home/sol/src/solana-sdk/compute-budget-interface" } +solana-cpi = { path = "/home/sol/src/solana-sdk/cpi" } +solana-decode-error = { path = "/home/sol/src/solana-sdk/decode-error" } +solana-define-syscall = { path = "/home/sol/src/solana-sdk/define-syscall" } +solana-derivation-path = { path = "/home/sol/src/solana-sdk/derivation-path" } +solana-ed25519-program = { path = "/home/sol/src/solana-sdk/ed25519-program" } +solana-epoch-info = { path = "/home/sol/src/solana-sdk/epoch-info" } +solana-epoch-rewards = { path = "/home/sol/src/solana-sdk/epoch-rewards" } +solana-epoch-rewards-hasher = { path = "/home/sol/src/solana-sdk/epoch-rewards-hasher" } +solana-epoch-schedule = { path = "/home/sol/src/solana-sdk/epoch-schedule" } +solana-example-mocks = { path = "/home/sol/src/solana-sdk/example-mocks" } +solana-feature-gate-interface = { path = "/home/sol/src/solana-sdk/feature-gate-interface" } +solana-feature-set = { path = "/home/sol/src/solana-sdk/feature-set" } +solana-fee-calculator = { path = "/home/sol/src/solana-sdk/fee-calculator" } +solana-fee-structure = { path = "/home/sol/src/solana-sdk/fee-structure" } +solana-file-download = { path = "/home/sol/src/solana-sdk/file-download" } +solana-frozen-abi = { path = "/home/sol/src/solana-sdk/frozen-abi" } +solana-frozen-abi-macro = { path = "/home/sol/src/solana-sdk/frozen-abi-macro" } +solana-genesis-config = { path = "/home/sol/src/solana-sdk/genesis-config" } +solana-hard-forks = { path = "/home/sol/src/solana-sdk/hard-forks" } +solana-hash = { path = "/home/sol/src/solana-sdk/hash" } +solana-inflation = { path = "/home/sol/src/solana-sdk/inflation" } +solana-instruction = { path = "/home/sol/src/solana-sdk/instruction" } +solana-instructions-sysvar = { path = "/home/sol/src/solana-sdk/instructions-sysvar" } +solana-keccak-hasher = { path = "/home/sol/src/solana-sdk/keccak-hasher" } +solana-keypair = { path = "/home/sol/src/solana-sdk/keypair" } +solana-last-restart-slot = { path = "/home/sol/src/solana-sdk/last-restart-slot" } +solana-loader-v2-interface = { path = "/home/sol/src/solana-sdk/loader-v2-interface" } +solana-loader-v3-interface = { path = "/home/sol/src/solana-sdk/loader-v3-interface" } +solana-loader-v4-interface = { path = "/home/sol/src/solana-sdk/loader-v4-interface" } +solana-logger = { path = "/home/sol/src/solana-sdk/logger" } +solana-message = { path = "/home/sol/src/solana-sdk/message" } +solana-msg = { path = "/home/sol/src/solana-sdk/msg" } +solana-native-token = { path = "/home/sol/src/solana-sdk/native-token" } +solana-nonce = { path = "/home/sol/src/solana-sdk/nonce" } +solana-nonce-account = { path = "/home/sol/src/solana-sdk/nonce-account" } +solana-offchain-message = { path = "/home/sol/src/solana-sdk/offchain-message" } +solana-package-metadata = { path = "/home/sol/src/solana-sdk/package-metadata" } +solana-package-metadata-macro = { path = "/home/sol/src/solana-sdk/package-metadata-macro" } +solana-packet = { path = "/home/sol/src/solana-sdk/packet" } +solana-poh-config = { path = "/home/sol/src/solana-sdk/poh-config" } +solana-precompile-error = { path = "/home/sol/src/solana-sdk/precompile-error" } +solana-precompiles = { path = "/home/sol/src/solana-sdk/precompiles" } +solana-presigner = { path = "/home/sol/src/solana-sdk/presigner" } +solana-program = { path = "/home/sol/src/solana-sdk/program" } +solana-program-entrypoint = { path = "/home/sol/src/solana-sdk/program-entrypoint" } +solana-program-error = { path = "/home/sol/src/solana-sdk/program-error" } +solana-program-memory = { path = "/home/sol/src/solana-sdk/program-memory" } +solana-program-option = { path = "/home/sol/src/solana-sdk/program-option" } +solana-program-pack = { path = "/home/sol/src/solana-sdk/program-pack" } +solana-pubkey = { path = "/home/sol/src/solana-sdk/pubkey" } +solana-quic-definitions = { path = "/home/sol/src/solana-sdk/quic-definitions" } +solana-rent = { path = "/home/sol/src/solana-sdk/rent" } +solana-rent-collector = { path = "/home/sol/src/solana-sdk/rent-collector" } +solana-rent-debits = { path = "/home/sol/src/solana-sdk/rent-debits" } +solana-reserved-account-keys = { path = "/home/sol/src/solana-sdk/reserved-account-keys" } +solana-reward-info = { path = "/home/sol/src/solana-sdk/reward-info" } +solana-sanitize = { path = "/home/sol/src/solana-sdk/sanitize" } +solana-sdk = { path = "/home/sol/src/solana-sdk/sdk" } +solana-sdk-ids = { path = "/home/sol/src/solana-sdk/sdk-ids" } +solana-sdk-macro = { path = "/home/sol/src/solana-sdk/sdk-macro" } +solana-secp256k1-program = { path = "/home/sol/src/solana-sdk/secp256k1-program" } +solana-secp256k1-recover = { path = "/home/sol/src/solana-sdk/secp256k1-recover" } +solana-secp256r1-program = { path = "/home/sol/src/solana-sdk/secp256r1-program" } +solana-seed-derivable = { path = "/home/sol/src/solana-sdk/seed-derivable" } +solana-seed-phrase = { path = "/home/sol/src/solana-sdk/seed-phrase" } +solana-serde = { path = "/home/sol/src/solana-sdk/serde" } +solana-serde-varint = { path = "/home/sol/src/solana-sdk/serde-varint" } +solana-serialize-utils = { path = "/home/sol/src/solana-sdk/serialize-utils" } +solana-sha256-hasher = { path = "/home/sol/src/solana-sdk/sha256-hasher" } +solana-short-vec = { path = "/home/sol/src/solana-sdk/short-vec" } +solana-shred-version = { path = "/home/sol/src/solana-sdk/shred-version" } +solana-signature = { path = "/home/sol/src/solana-sdk/signature" } +solana-signer = { path = "/home/sol/src/solana-sdk/signer" } +solana-slot-hashes = { path = "/home/sol/src/solana-sdk/slot-hashes" } +solana-slot-history = { path = "/home/sol/src/solana-sdk/slot-history" } +solana-stable-layout = { path = "/home/sol/src/solana-sdk/stable-layout" } +solana-system-transaction = { path = "/home/sol/src/solana-sdk/system-transaction" } +solana-sysvar = { path = "/home/sol/src/solana-sdk/sysvar" } +solana-sysvar-id = { path = "/home/sol/src/solana-sdk/sysvar-id" } +solana-time-utils = { path = "/home/sol/src/solana-sdk/time-utils" } +solana-transaction = { path = "/home/sol/src/solana-sdk/transaction" } +solana-transaction-error = { path = "/home/sol/src/solana-sdk/transaction-error" } +solana-validator-exit = { path = "/home/sol/src/solana-sdk/validator-exit" } +solana-vote-interface = { path = "/home/sol/src/solana-sdk/vote-interface" } diff --git a/cli-output/Cargo.toml b/cli-output/Cargo.toml index 3cc8cd9bc270eb..73db1a25220a9d 100644 --- a/cli-output/Cargo.toml +++ b/cli-output/Cargo.toml @@ -43,6 +43,7 @@ solana-sysvar = { workspace = true } solana-transaction = { workspace = true, features = ["verify"] } solana-transaction-error = { workspace = true } solana-transaction-status = { workspace = true } +solana-transaction-status-client-types = { workspace = true } solana-vote-program = { workspace = true } spl-memo = { workspace = true, features = ["no-entrypoint"] } diff --git a/cli-output/src/display.rs b/cli-output/src/display.rs index f0d64c3087dcab..dc2a913cced142 100644 --- a/cli-output/src/display.rs +++ b/cli-output/src/display.rs @@ -15,10 +15,11 @@ use { solana_pubkey::Pubkey, solana_signature::Signature, solana_transaction::versioned::{TransactionVersion, VersionedTransaction}, - solana_transaction_error::TransactionError, + solana_transaction_error::TransactionResult, solana_transaction_status::{ Rewards, UiReturnDataEncoding, UiTransactionReturnData, UiTransactionStatusMeta, }, + solana_transaction_status_client_types::UiTransactionResult, spl_memo::{id as spl_memo_id, v1::id as spl_memo_v1_id}, std::{collections::HashMap, fmt, io, time::Duration}, }; @@ -541,14 +542,14 @@ fn write_rewards( fn write_status( w: &mut W, - transaction_status: &Result<(), TransactionError>, + transaction_status: &UiTransactionResult<()>, prefix: &str, ) -> io::Result<()> { writeln!( w, "{}Status: {}", prefix, - match transaction_status { + match Into::>::into(transaction_status.clone()) { Ok(_) => "Ok".into(), Err(err) => err.to_string(), } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4ea6911f2f58cf..15799e83e38ae9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -85,6 +85,7 @@ solana-tpu-client = { workspace = true, features = ["default"] } solana-transaction = "=2.2.2" solana-transaction-error = "=2.2.1" solana-transaction-status = { workspace = true } +solana-transaction-status-client-types = { workspace = true } solana-udp-client = { workspace = true } solana-version = { workspace = true } solana-vote-program = { workspace = true } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index a7d5d5cbf8074f..b19945787a2ffd 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1768,7 +1768,7 @@ where match result { Err(err) => { let maybe_tx_err = err.get_transaction_error(); - if let Some(TransactionError::InstructionError(_, ix_error)) = maybe_tx_err { + if let Some(TransactionError::InstructionError { err: ix_error, .. }) = maybe_tx_err { if let Some(specific_error) = error_adapter(&ix_error) { return Err(specific_error.into()); } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index db8110923448cc..c08e5550d62d89 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -2155,7 +2155,7 @@ pub fn process_transaction_history( signature.verbose = Some(CliHistoryVerbose { slot: result.slot, block_time: result.block_time, - err: result.err, + err: result.err.map(Into::into), confirmation_status: result.confirmation_status, memo: result.memo, }); @@ -2209,7 +2209,7 @@ pub fn process_transaction_history( confirmation_status: result.confirmation_status, transaction, get_transaction_error, - err: result.err, + err: result.err.map(Into::into), }); } } diff --git a/cli/src/compute_budget.rs b/cli/src/compute_budget.rs index 3a19adfc846419..a03c848dfcd681 100644 --- a/cli/src/compute_budget.rs +++ b/cli/src/compute_budget.rs @@ -8,6 +8,7 @@ use { solana_rpc_client::rpc_client::RpcClient, solana_rpc_client_api::config::RpcSimulateTransactionConfig, solana_transaction::Transaction, + solana_transaction_status_client_types::UiTransactionError, }; /// Enum capturing the possible results of updating a message based on the @@ -56,7 +57,7 @@ fn simulate_for_compute_unit_limit_unchecked( .value; // Bail if the simulated transaction failed - if let Some(err) = simulate_result.err { + if let Some(UiTransactionError(err)) = simulate_result.err { return Err(err.into()); } diff --git a/cli/src/program.rs b/cli/src/program.rs index 89080edf0debe4..dc625d1c406038 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -2224,16 +2224,16 @@ fn close( config.send_transaction_config, ); if let Err(err) = result { - if let ClientErrorKind::TransactionError(TransactionError::InstructionError( - _, - InstructionError::InvalidInstructionData, - )) = err.kind() + if let ClientErrorKind::TransactionError(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + .. + }) = err.kind() { return Err("Closing a buffer account is not supported by the cluster".into()); - } else if let ClientErrorKind::TransactionError(TransactionError::InstructionError( - _, - InstructionError::InvalidArgument, - )) = err.kind() + } else if let ClientErrorKind::TransactionError(TransactionError::InstructionError { + err: InstructionError::InvalidArgument, + .. + }) = err.kind() { return Err("Closing a program account is not supported by the cluster".into()); } else { @@ -2465,10 +2465,10 @@ fn process_extend_program( config.send_transaction_config, ); if let Err(err) = result { - if let ClientErrorKind::TransactionError(TransactionError::InstructionError( - _, - InstructionError::InvalidInstructionData, - )) = err.kind() + if let ClientErrorKind::TransactionError(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + .. + }) = err.kind() { return Err("Extending a program is not supported by the cluster".into()); } else { @@ -2560,10 +2560,10 @@ fn process_migrate_program( config.send_transaction_config, ); if let Err(err) = result { - if let ClientErrorKind::TransactionError(TransactionError::InstructionError( - _, - InstructionError::InvalidInstructionData, - )) = err.kind() + if let ClientErrorKind::TransactionError(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + .. + }) = err.kind() { return Err("Migrating a program is not supported by the cluster".into()); } else { diff --git a/client/Cargo.toml b/client/Cargo.toml index 986a926fd87761..93535cc2ca9001 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -45,6 +45,7 @@ solana-time-utils = { workspace = true } solana-tpu-client = { workspace = true, features = ["default"] } solana-transaction = { workspace = true } solana-transaction-error = { workspace = true } +solana-transaction-status-client-types = { workspace = true } solana-udp-client = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/client/src/send_and_confirm_transactions_in_parallel.rs b/client/src/send_and_confirm_transactions_in_parallel.rs index 6d357180ef6770..98602f042ce5a0 100644 --- a/client/src/send_and_confirm_transactions_in_parallel.rs +++ b/client/src/send_and_confirm_transactions_in_parallel.rs @@ -21,6 +21,7 @@ use { solana_tpu_client::tpu_client::{Result, TpuSenderError}, solana_transaction::Transaction, solana_transaction_error::TransactionError, + solana_transaction_status_client_types::UiTransactionError, std::{ sync::{ atomic::{AtomicU64, AtomicUsize, Ordering}, @@ -289,7 +290,7 @@ async fn send_transaction_with_rpc_fallback( data: RpcResponseErrorData::SendTransactionPreflightFailure( RpcSimulateTransactionResult { - err: Some(TransactionError::BlockhashNotFound), + err: Some(UiTransactionError(TransactionError::BlockhashNotFound)), .. }, ), @@ -302,7 +303,7 @@ async fn send_transaction_with_rpc_fallback( data: RpcResponseErrorData::SendTransactionPreflightFailure( RpcSimulateTransactionResult { - err: Some(transaction_error), + err: Some(UiTransactionError(transaction_error)), .. }, ), diff --git a/compute-budget-instruction/Cargo.toml b/compute-budget-instruction/Cargo.toml index 779418adb2ec0e..d4163af7203549 100644 --- a/compute-budget-instruction/Cargo.toml +++ b/compute-budget-instruction/Cargo.toml @@ -29,6 +29,7 @@ crate-type = ["lib"] name = "solana_compute_budget_instruction" [dev-dependencies] +assert_matches = { workspace = true } bincode = { workspace = true } criterion = { workspace = true } rand = { workspace = true } diff --git a/compute-budget-instruction/src/compute_budget_instruction_details.rs b/compute-budget-instruction/src/compute_budget_instruction_details.rs index 23a0b4bd234759..3f6fe08391bf52 100644 --- a/compute-budget-instruction/src/compute_budget_instruction_details.rs +++ b/compute-budget-instruction/src/compute_budget_instruction_details.rs @@ -10,6 +10,7 @@ use { solana_compute_budget_interface::ComputeBudgetInstruction, solana_instruction::error::InstructionError, solana_pubkey::Pubkey, + solana_sdk_ids::compute_budget, solana_svm_transaction::instruction::SVMInstruction, solana_transaction_error::{TransactionError, TransactionResult as Result}, std::num::{NonZeroU32, Saturating}, @@ -103,20 +104,23 @@ impl ComputeBudgetInstructionDetails { feature_set: &FeatureSet, ) -> Result { // Sanitize requested heap size - let updated_heap_bytes = - if let Some((index, requested_heap_size)) = self.requested_heap_size { - if Self::sanitize_requested_heap_size(requested_heap_size) { - requested_heap_size - } else { - return Err(TransactionError::InstructionError( - index, - InstructionError::InvalidInstructionData, - )); - } + let updated_heap_bytes = if let Some((outer_instruction_index, requested_heap_size)) = + self.requested_heap_size + { + if Self::sanitize_requested_heap_size(requested_heap_size) { + requested_heap_size } else { - MIN_HEAP_FRAME_BYTES + return Err(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index, + responsible_program_address: Some(compute_budget::id()), + }); } - .min(MAX_HEAP_FRAME_BYTES); + } else { + MIN_HEAP_FRAME_BYTES + } + .min(MAX_HEAP_FRAME_BYTES); // Calculate compute unit limit let compute_unit_limit = self @@ -153,8 +157,12 @@ impl ComputeBudgetInstructionDetails { } fn process_instruction(&mut self, index: u8, instruction: &SVMInstruction) -> Result<()> { - let invalid_instruction_data_error = - TransactionError::InstructionError(index, InstructionError::InvalidInstructionData); + let invalid_instruction_data_error = TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: index, + responsible_program_address: Some(compute_budget::id()), + }; let duplicate_instruction_error = TransactionError::DuplicateInstruction(index); match try_from_slice_unchecked(instruction.data) { @@ -404,10 +412,12 @@ mod test { }) ); - let expected_heap_size_err = Err(TransactionError::InstructionError( - 3, - InstructionError::InvalidInstructionData, - )); + let expected_heap_size_err = Err(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 3, + responsible_program_address: Some(compute_budget::id()), + }); // invalid: requested_heap_size can't be zero let instruction_details = ComputeBudgetInstructionDetails { requested_compute_unit_limit: Some((1, 0)), diff --git a/compute-budget-instruction/src/instructions_processor.rs b/compute-budget-instruction/src/instructions_processor.rs index b3c00aed1bcc9a..c418c3a486cef6 100644 --- a/compute-budget-instruction/src/instructions_processor.rs +++ b/compute-budget-instruction/src/instructions_processor.rs @@ -28,6 +28,7 @@ mod tests { solana_keypair::Keypair, solana_message::Message, solana_pubkey::Pubkey, + solana_sdk_ids::compute_budget, solana_signer::Signer, solana_svm_transaction::svm_message::SVMMessage, solana_system_interface::instruction::transfer, @@ -161,30 +162,36 @@ mod tests { ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1), Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) + Err(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(compute_budget::id()), + }) ); test!( &[ ComputeBudgetInstruction::request_heap_frame(31 * 1024), Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) + Err(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(compute_budget::id()), + }) ); test!( &[ ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1), Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) + Err(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(compute_budget::id()), + }) ); test!( &[ @@ -219,10 +226,12 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), ComputeBudgetInstruction::request_heap_frame(1), ], - Err(TransactionError::InstructionError( - 3, - InstructionError::InvalidInstructionData, - )) + Err(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 3, + responsible_program_address: Some(compute_budget::id()), + }) ); test!( &[ diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index d466ac47b7e09a..aad570af8f2a3f 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -1560,17 +1560,20 @@ mod tests { (transaction.signatures[0], meta.status) }) .collect(); - let expected_tx_results = vec![ - (success_signature, Ok(())), + assert_eq!(actual_tx_results.len(), 2); + assert_eq!(actual_tx_results[0], (success_signature, Ok(()))); + assert_eq!( + actual_tx_results[1], ( ix_error_signature, - Err(TransactionError::InstructionError( - 0, - InstructionError::Custom(1), - )), + Err(TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ), - ]; - assert_eq!(actual_tx_results, expected_tx_results); + ); poh_recorder .read() diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index b9905b0d0936f8..531fb7b40ab3f4 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -4369,6 +4369,7 @@ pub(crate) mod tests { commitment::{BlockCommitment, VOTE_THRESHOLD_SIZE}, genesis_utils::{GenesisConfigInfo, ValidatorVoteKeypairs}, }, + solana_sdk_ids::system_program, solana_sha256_hasher::hash, solana_streamer::socket::SocketAddrSpace, solana_system_transaction as system_transaction, @@ -5347,17 +5348,23 @@ pub(crate) mod tests { (transaction.signatures[0], meta.status) }) .collect(); - let expected_tx_results = vec![ - (test_signatures_iter.next().unwrap(), Ok(())), + assert_eq!(actual_tx_results.len(), 2); + assert_eq!( + actual_tx_results[0], + (test_signatures_iter.next().unwrap(), Ok(())) + ); + assert_eq!( + actual_tx_results[1], ( test_signatures_iter.next().unwrap(), - Err(TransactionError::InstructionError( - 0, - InstructionError::Custom(1), - )), + Err(TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ), - ]; - assert_eq!(actual_tx_results, expected_tx_results); + ); assert!(test_signatures_iter.next().is_none()); } Blockstore::destroy(&ledger_path).unwrap(); diff --git a/core/tests/scheduler_cost_adjustment.rs b/core/tests/scheduler_cost_adjustment.rs index 4de0c335a8c6a1..46af490255acd2 100644 --- a/core/tests/scheduler_cost_adjustment.rs +++ b/core/tests/scheduler_cost_adjustment.rs @@ -15,7 +15,7 @@ use { solana_rent::Rent, solana_runtime::{bank::Bank, bank_forks::BankForks}, solana_runtime_transaction::runtime_transaction::RuntimeTransaction, - solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable, secp256k1_program}, + solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable, secp256k1_program, system_program}, solana_signer::Signer, solana_svm::transaction_processor::ExecutionRecordingConfig, solana_system_interface::instruction as system_instruction, @@ -229,19 +229,20 @@ fn test_builtin_ix_cost_adjustment_with_cu_limit_too_low() { // Cost model & Compute budget: reserve/allocate requested CU Limit `1` // VM Execution: consume `1` CU, then fail // Result: 0 adjustment - let expected = TestResult { - cost_adjustment: 0, - execution_status: Err(TransactionError::InstructionError( - 0, - InstructionError::ComputationalBudgetExceeded, - )), - }; assert_eq!( - expected, test_setup.execute_test_transaction(&[ test_setup.transfer_ix(), test_setup.set_cu_limit_ix(cu_limit), - ]) + ]), + TestResult { + cost_adjustment: 0, + execution_status: Err(TransactionError::InstructionError { + err: InstructionError::ComputationalBudgetExceeded, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()) + }), + }, ); } @@ -281,16 +282,17 @@ fn test_builtin_ix_cost_adjustment_with_memo_no_cu_limit() { // (3_000 + 200_000) = 203_000 CUs (note: less than memo_ix needs) // VM Execution: consume all allocated CUs, then fail // Result: no adjustment - let expected = TestResult { - cost_adjustment: 0, - execution_status: Err(TransactionError::InstructionError( - 1, - InstructionError::ProgramFailedToComplete, - )), - }; assert_eq!( - expected, - test_setup.execute_test_transaction(&[test_setup.transfer_ix(), memo_ix.clone()],) + test_setup.execute_test_transaction(&[test_setup.transfer_ix(), memo_ix.clone()],), + TestResult { + cost_adjustment: 0, + execution_status: Err(TransactionError::InstructionError { + err: InstructionError::ProgramFailedToComplete, + outer_instruction_index: 1, + inner_instruction_index: None, + responsible_program_address: Some(memo_ix.program_id), + }) + }, ); } diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 9b600c4346b617..8acb48c849beaa 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -125,6 +125,7 @@ solana-perf = { workspace = true, features = ["dev-context-only-utils"] } solana-program-option = { workspace = true } solana-program-pack = { workspace = true } solana-runtime = { workspace = true, features = ["dev-context-only-utils"] } +solana-sdk-ids = { workspace = true } solana-vote = { workspace = true, features = ["dev-context-only-utils"] } spl-generic-token = { workspace = true } spl-pod = { workspace = true } diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index 79a90e983eeed7..d2aac27a60b76e 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -2363,6 +2363,7 @@ pub mod tests { SchedulingContext, }, }, + solana_sdk_ids::system_program, solana_seed_derivable::SeedDerivable, solana_signer::Signer, solana_svm::transaction_processor::ExecutionRecordingConfig, @@ -3995,10 +3996,12 @@ pub mod tests { assert_eq!(bank.get_balance(&pubkey), 1_000); assert_eq!( bank.transfer(10_001, &mint_keypair, &pubkey), - Err(TransactionError::InstructionError( - 0, - SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); assert_eq!( bank.transfer(10_001, &mint_keypair, &pubkey), diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 4376c14d3cbce6..135cd0f1d3888d 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -181,6 +181,12 @@ pub struct SerializedAccountMetadata { pub vm_owner_addr: u64, } +#[derive(Debug, PartialEq)] +pub struct FirstErrorAttribution { + pub inner_instruction_index: Option, + pub responsible_program_address: Pubkey, +} + /// Main pipeline from runtime to program execution. pub struct InvokeContext<'a> { /// Information about the currently executing transaction. @@ -202,6 +208,7 @@ pub struct InvokeContext<'a> { pub timings: ExecuteDetailsTimings, pub syscall_context: Vec>, traces: Vec>, + first_error_attribution: Option, } impl<'a> InvokeContext<'a> { @@ -226,6 +233,7 @@ impl<'a> InvokeContext<'a> { timings: ExecuteDetailsTimings::default(), syscall_context: Vec::new(), traces: Vec::new(), + first_error_attribution: None, } } @@ -493,6 +501,7 @@ impl<'a> InvokeContext<'a> { self.environment_config .epoch_stake_callback .process_precompile(program_id, instruction_data, instruction_datas) + .inspect_err(|_| self.blame_program_for_error(program_id)) .map_err(InstructionError::from) .and(self.pop()) } @@ -593,6 +602,11 @@ impl<'a> InvokeContext<'a> { } } }; + + if result.is_err() { + self.blame_program_for_error(&program_id); + } + let post_remaining_units = self.get_remaining(); *compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units); @@ -612,6 +626,25 @@ impl<'a> InvokeContext<'a> { self.log_collector.clone() } + /// If no other program has yet been blamed for causing an error in this invocation, blame this + /// one and the inner instruction that it belongs to. + pub fn blame_program_for_error(&mut self, responsible_program_address: &Pubkey) { + if self.first_error_attribution.is_some() { + return; + } + self.first_error_attribution = Some(FirstErrorAttribution { + inner_instruction_index: self + .transaction_context + .get_current_inner_instruction_index(), + responsible_program_address: *responsible_program_address, + }); + } + + /// Get details about where to attribute the error this invocation first encountered + pub fn get_first_error_attribution(&self) -> &Option { + &self.first_error_attribution + } + /// Consume compute units pub fn consume_checked(&self, amount: u64) -> Result<(), Box> { let mut compute_meter = self.compute_meter.borrow_mut(); @@ -1359,4 +1392,36 @@ mod tests { resize_delta ); } + + #[test] + fn test_attribute_first_error() { + with_mock_invoke_context!(invoke_context, transaction_context, vec![]); + invoke_context.transaction_context.push().unwrap(); // First outer instruction + invoke_context.transaction_context.push().unwrap(); // First inner instruction + let program_id = Pubkey::new_unique(); + invoke_context.blame_program_for_error(&program_id); + assert_eq!( + invoke_context.first_error_attribution, + Some(FirstErrorAttribution { + inner_instruction_index: Some(0), + responsible_program_address: program_id, + }), + ); + } + + #[test] + fn test_attribute_first_error_can_only_set_once() { + with_mock_invoke_context!(invoke_context, transaction_context, vec![]); + let program_id = Pubkey::new_unique(); + let some_other_program_id = Pubkey::new_unique(); + invoke_context.blame_program_for_error(&program_id); + invoke_context.blame_program_for_error(&some_other_program_id); + assert_eq!( + invoke_context.first_error_attribution, + Some(FirstErrorAttribution { + inner_instruction_index: None, + responsible_program_address: program_id, + }), + ); + } } diff --git a/program-test/tests/panic.rs b/program-test/tests/panic.rs index a565c03742a3fe..dfd6ebd8252ec4 100644 --- a/program-test/tests/panic.rs +++ b/program-test/tests/panic.rs @@ -36,6 +36,11 @@ async fn panic_test() { .await .unwrap_err() .unwrap(), - TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete) + TransactionError::InstructionError { + outer_instruction_index: 0, + err: InstructionError::ProgramFailedToComplete, + inner_instruction_index: None, + responsible_program_address: Some(program_id), + }, ); } diff --git a/program-test/tests/warp.rs b/program-test/tests/warp.rs index 4a8ecbe10d0700..4d5d5964021268 100644 --- a/program-test/tests/warp.rs +++ b/program-test/tests/warp.rs @@ -84,7 +84,12 @@ async fn clock_sysvar_updated_from_warp() { .await .unwrap_err() .unwrap(), - TransactionError::InstructionError(0, InstructionError::Custom(WRONG_SLOT_ERROR)) + TransactionError::InstructionError { + err: InstructionError::Custom(WRONG_SLOT_ERROR), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(program_id) + }, ); // Warp to success! diff --git a/programs/bpf-loader-tests/tests/common.rs b/programs/bpf-loader-tests/tests/common.rs index e4c2a6c3d69237..d2ff2bf1af9165 100644 --- a/programs/bpf-loader-tests/tests/common.rs +++ b/programs/bpf-loader-tests/tests/common.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] use { + assert_matches::assert_matches, solana_account::{state_traits::StateMut, AccountSharedData}, solana_instruction::{error::InstructionError, Instruction}, solana_keypair::Keypair, @@ -41,14 +42,19 @@ pub async fn assert_ix_error( recent_blockhash, ); - assert_eq!( + assert_matches!( client .process_transaction(transaction) .await .unwrap_err() .unwrap(), - TransactionError::InstructionError(0, expected_err), - "{assertion_failed_msg}", + TransactionError::InstructionError { + err: actual_err, + inner_instruction_index: Some(_) | None, + outer_instruction_index: 0, + responsible_program_address: Some(_) | None, + } if actual_err == expected_err, + "{assertion_failed_msg}" ); } diff --git a/programs/bpf-loader-tests/tests/extend_program_ix.rs b/programs/bpf-loader-tests/tests/extend_program_ix.rs index 54363a92a15710..d69f0210574856 100644 --- a/programs/bpf-loader-tests/tests/extend_program_ix.rs +++ b/programs/bpf-loader-tests/tests/extend_program_ix.rs @@ -9,7 +9,7 @@ use { }, solana_program_test::*, solana_pubkey::Pubkey, - solana_sdk_ids::bpf_loader_upgradeable::id, + solana_sdk_ids::bpf_loader_upgradeable::{self, id}, solana_signer::Signer, solana_system_interface::{ error::SystemError, instruction as system_instruction, program as system_program, @@ -165,7 +165,12 @@ async fn test_failed_extend_twice_in_same_slot() { .await .unwrap_err() .unwrap(), - TransactionError::InstructionError(0, InstructionError::InvalidArgument) + TransactionError::InstructionError { + err: InstructionError::InvalidArgument, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(p) + } if p == bpf_loader_upgradeable::id() ); } @@ -224,7 +229,13 @@ async fn test_failed_extend_upgrade_authority_did_not_sign() { .await .unwrap_err() .unwrap(), - TransactionError::InstructionError(0, InstructionError::IncorrectAuthority) + TransactionError::InstructionError { + err: + InstructionError::IncorrectAuthority, + outer_instruction_index: 0, + inner_instruction_index: None, + responsible_program_address: Some(p) + } if p == id() ); let mut ix = extend_program_checked( @@ -247,7 +258,13 @@ async fn test_failed_extend_upgrade_authority_did_not_sign() { .await .unwrap_err() .unwrap(), - TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature) + TransactionError::InstructionError { + err: + InstructionError::MissingRequiredSignature, + outer_instruction_index: 0, + inner_instruction_index: None, + responsible_program_address: Some(p) + } if p == id() ); } diff --git a/programs/ed25519-tests/Cargo.toml b/programs/ed25519-tests/Cargo.toml index 930ca7424ddbbc..17b6d18b4524ba 100644 --- a/programs/ed25519-tests/Cargo.toml +++ b/programs/ed25519-tests/Cargo.toml @@ -16,6 +16,7 @@ solana-ed25519-program = { workspace = true } solana-instruction = { workspace = true } solana-precompile-error = { workspace = true } solana-program-test = { workspace = true } +solana-sdk-ids = { workspace = true } solana-signer = { workspace = true } solana-transaction = { workspace = true } solana-transaction-error = { workspace = true } diff --git a/programs/ed25519-tests/tests/process_transaction.rs b/programs/ed25519-tests/tests/process_transaction.rs index acc790db02d67f..ffbee877a382e3 100644 --- a/programs/ed25519-tests/tests/process_transaction.rs +++ b/programs/ed25519-tests/tests/process_transaction.rs @@ -65,8 +65,13 @@ async fn test_failure() { assert_matches!( client.process_transaction(transaction).await, Err(BanksClientError::TransactionError( - TransactionError::InstructionError(0, InstructionError::Custom(3)) - )) + TransactionError::InstructionError { + err: InstructionError::Custom(3), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(p), + } + )) if p == solana_sdk_ids::ed25519_program::id() ); // this assert is for documenting the matched error code above assert_eq!(3, PrecompileError::InvalidDataOffsets as u32); diff --git a/programs/stake-tests/tests/test_move_stake_and_lamports.rs b/programs/stake-tests/tests/test_move_stake_and_lamports.rs index c432b5097495ad..d0b72d5159fc5a 100644 --- a/programs/stake-tests/tests/test_move_stake_and_lamports.rs +++ b/programs/stake-tests/tests/test_move_stake_and_lamports.rs @@ -267,7 +267,7 @@ async fn process_instruction( Err(e) => { // banks client error -> transaction error -> instruction error -> program error match e.unwrap() { - TransactionError::InstructionError(_, e) => Err(e.try_into().unwrap()), + TransactionError::InstructionError { err, .. } => Err(err.try_into().unwrap()), TransactionError::InsufficientFundsForRent { .. } => { Err(ProgramError::InsufficientFunds) } diff --git a/programs/zk-elgamal-proof-tests/Cargo.toml b/programs/zk-elgamal-proof-tests/Cargo.toml index 2b0f682c3689d0..66c9164eecaae9 100644 --- a/programs/zk-elgamal-proof-tests/Cargo.toml +++ b/programs/zk-elgamal-proof-tests/Cargo.toml @@ -8,6 +8,7 @@ license = { workspace = true } edition = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } bytemuck = { workspace = true } solana-account = { workspace = true } solana-compute-budget = { workspace = true } @@ -16,6 +17,7 @@ solana-instruction = { workspace = true } solana-keypair = { workspace = true } solana-program-test = { workspace = true } solana-pubkey = { workspace = true } +solana-sdk-ids = { workspace = true } solana-signer = { workspace = true } solana-system-interface = { workspace = true } solana-transaction = { workspace = true } diff --git a/programs/zk-elgamal-proof-tests/tests/process_transaction.rs b/programs/zk-elgamal-proof-tests/tests/process_transaction.rs index adfa96c68c410d..4b4b5c4e4540ec 100644 --- a/programs/zk-elgamal-proof-tests/tests/process_transaction.rs +++ b/programs/zk-elgamal-proof-tests/tests/process_transaction.rs @@ -1,4 +1,5 @@ use { + assert_matches::assert_matches, bytemuck::{bytes_of, Pod}, solana_account::Account, solana_instruction::error::InstructionError, @@ -761,9 +762,14 @@ async fn test_verify_proof_without_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) + TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ); // try to verify a valid proof, but with a wrong proof type @@ -785,9 +791,14 @@ async fn test_verify_proof_without_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) + TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ); } @@ -816,9 +827,14 @@ async fn test_verify_proof_without_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) + TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ) } @@ -870,9 +886,14 @@ async fn test_verify_proof_with_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(1, InstructionError::InvalidInstructionData) + TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ); // try to create proof context state with incorrect account data length @@ -897,9 +918,14 @@ async fn test_verify_proof_with_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(1, InstructionError::InvalidAccountData) + TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ); // try to create proof context state with insufficient rent @@ -957,9 +983,14 @@ async fn test_verify_proof_with_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(1, InstructionError::InvalidInstructionData) + TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ); } @@ -996,9 +1027,14 @@ async fn test_verify_proof_with_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized) + TransactionError::InstructionError { + err: InstructionError::AccountAlreadyInitialized, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ); // self-owned context state account @@ -1099,9 +1135,14 @@ async fn test_verify_proof_from_account_with_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(1, InstructionError::InvalidInstructionData) + TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ); // successfully create a proof context state @@ -1144,9 +1185,14 @@ async fn test_verify_proof_from_account_with_context( .await .unwrap_err() .unwrap(); - assert_eq!( + assert_matches!( err, - TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized) + TransactionError::InstructionError { + err: InstructionError::AccountAlreadyInitialized, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(p) + } if p == solana_sdk_ids::zk_elgamal_proof_program::id() ); // self-owned context state account @@ -1247,7 +1293,12 @@ async fn test_close_context_state( .unwrap(); assert_eq!( err, - TransactionError::InstructionError(0, InstructionError::InvalidAccountOwner) + TransactionError::InstructionError { + err: InstructionError::InvalidAccountOwner, + outer_instruction_index: 0, + inner_instruction_index: None, + responsible_program_address: Some(zk_elgamal_proof_program::id()), + } ); // successfully close proof context state @@ -1349,7 +1400,12 @@ async fn test_close_context_state( .unwrap(); assert_eq!( err, - TransactionError::InstructionError(2, InstructionError::InvalidInstructionData) + TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 2, + responsible_program_address: Some(zk_elgamal_proof_program::id()), + } ); // close self-owned proof context accounts diff --git a/rpc-client-api/src/client_error.rs b/rpc-client-api/src/client_error.rs index e8a7aca9a2a1e2..6509d936a032bb 100644 --- a/rpc-client-api/src/client_error.rs +++ b/rpc-client-api/src/client_error.rs @@ -38,7 +38,7 @@ impl ErrorKind { }, ), .. - }) => Some(tx_err.clone()), + }) => Some(tx_err.clone().into()), Self::TransactionError(tx_err) => Some(tx_err.clone()), _ => None, } diff --git a/rpc-client-types/src/request.rs b/rpc-client-types/src/request.rs index e66797bff62bc1..d0469e3b02e5bb 100644 --- a/rpc-client-types/src/request.rs +++ b/rpc-client-types/src/request.rs @@ -169,6 +169,7 @@ impl RpcRequest { } #[derive(Debug)] +#[allow(clippy::large_enum_variant)] pub enum RpcResponseErrorData { Empty, SendTransactionPreflightFailure(RpcSimulateTransactionResult), diff --git a/rpc-client-types/src/response.rs b/rpc-client-types/src/response.rs index 05764cbafa77ce..f0ef306b856c2c 100644 --- a/rpc-client-types/src/response.rs +++ b/rpc-client-types/src/response.rs @@ -4,10 +4,10 @@ use { solana_clock::{Epoch, Slot, UnixTimestamp}, solana_fee_calculator::{FeeCalculator, FeeRateGovernor}, solana_inflation::Inflation, - solana_transaction_error::{TransactionError, TransactionResult as Result}, + solana_transaction_error::TransactionResult as Result, solana_transaction_status_client_types::{ ConfirmedTransactionStatusWithSignature, TransactionConfirmationStatus, UiConfirmedBlock, - UiInnerInstructions, UiTransactionReturnData, + UiInnerInstructions, UiTransactionError, UiTransactionReturnData, }, std::{collections::HashMap, fmt, net::SocketAddr, str::FromStr}, thiserror::Error, @@ -240,14 +240,14 @@ pub enum RpcSignatureResult { #[serde(rename_all = "camelCase")] pub struct RpcLogsResponse { pub signature: String, // Signature as base58 string - pub err: Option, + pub err: Option, pub logs: Vec, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ProcessedSignatureResult { - pub err: Option, + pub err: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -397,7 +397,7 @@ pub struct RpcSignatureConfirmation { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct RpcSimulateTransactionResult { - pub err: Option, + pub err: Option, pub logs: Option>, pub accounts: Option>>, pub units_consumed: Option, @@ -451,7 +451,7 @@ pub struct RpcTokenAccountBalance { pub struct RpcConfirmedTransactionStatusWithSignature { pub signature: String, pub slot: Slot, - pub err: Option, + pub err: Option, pub memo: Option, pub block_time: Option, pub confirmation_status: Option, @@ -506,7 +506,7 @@ impl From for RpcConfirmedTransactionSt Self { signature: signature.to_string(), slot, - err, + err: err.map(Into::into), memo, block_time, confirmation_status: None, diff --git a/rpc-client/Cargo.toml b/rpc-client/Cargo.toml index f781a0fa670a9f..1e8c95358ebbe9 100644 --- a/rpc-client/Cargo.toml +++ b/rpc-client/Cargo.toml @@ -35,6 +35,7 @@ solana-instruction = { workspace = true } solana-message = { workspace = true } solana-pubkey = { workspace = true } solana-rpc-client-api = { workspace = true } +solana-sdk-ids = { workspace = true } solana-signature = { workspace = true } solana-transaction = { workspace = true, features = ["bincode"] } solana-transaction-error = { workspace = true } diff --git a/rpc-client/src/mock_sender.rs b/rpc-client/src/mock_sender.rs index e0d17b6e27e74e..7856fcf33d8fb4 100644 --- a/rpc-client/src/mock_sender.rs +++ b/rpc-client/src/mock_sender.rs @@ -27,7 +27,7 @@ use { }, solana_signature::Signature, solana_transaction::{versioned::TransactionVersion, Transaction}, - solana_transaction_error::{TransactionError, TransactionResult}, + solana_transaction_error::TransactionError, solana_transaction_status_client_types::{ option_serializer::OptionSerializer, EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction, @@ -170,22 +170,23 @@ impl RpcSender for MockSender { transaction_count: Some(123), })?, "getSignatureStatuses" => { - let status: TransactionResult<()> = if self.url == "account_in_use" { - Err(TransactionError::AccountInUse) + let err = if self.url == "account_in_use" { + Some(TransactionError::AccountInUse) } else if self.url == "instruction_error" { - Err(TransactionError::InstructionError( - 0, - InstructionError::UninitializedAccount, - )) + Some(TransactionError::InstructionError { + err: InstructionError::UninitializedAccount, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(Pubkey::from_str_const("s1gnatuerX3Qzstatuz8mV4j2pR9Wf5yB6kLh")), + }) } else { - Ok(()) + None }; let status = if self.url == "sig_not_found" { None } else { - let err = status.clone().err(); Some(TransactionStatus { - status, + status: err.clone().map_or(Ok(()), Err), slot: 1, confirmations: None, err, @@ -234,7 +235,7 @@ impl RpcSender for MockSender { }), meta: Some(UiTransactionStatusMeta { err: None, - status: Ok(()), + status: Ok(()).into(), fee: 0, pre_balances: vec![499999999999999950, 50, 1], post_balances: vec![499999999999999950, 50, 1], diff --git a/rpc-client/src/rpc_client.rs b/rpc-client/src/rpc_client.rs index bf42e2fb06ad6c..1da1bc4da8003c 100644 --- a/rpc-client/src/rpc_client.rs +++ b/rpc-client/src/rpc_client.rs @@ -3954,10 +3954,12 @@ mod tests { let result = rpc_client.send_and_confirm_transaction(&tx); assert_matches!( result.unwrap_err().kind(), - ErrorKind::TransactionError(TransactionError::InstructionError( - 0, - InstructionError::UninitializedAccount - )) + ErrorKind::TransactionError(TransactionError::InstructionError { + err: InstructionError::UninitializedAccount, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(_), + }) ); let rpc_client = RpcClient::new_mock("sig_not_found".to_string()); diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index a946161c2bcf77..0ee8afc2e858d2 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -3919,7 +3919,7 @@ pub mod rpc_full { return Err(RpcCustomError::SendTransactionPreflightFailure { message: format!("Transaction simulation failed: {err}"), result: RpcSimulateTransactionResult { - err: Some(err), + err: Some(err.into()), logs: Some(logs), accounts: None, units_consumed: Some(units_consumed), @@ -4064,7 +4064,7 @@ pub mod rpc_full { Ok(new_response( bank, RpcSimulateTransactionResult { - err: result.err(), + err: result.map_err(Into::into).err(), logs: Some(logs), accounts, units_consumed: Some(units_consumed), @@ -6654,15 +6654,20 @@ pub mod tests { confirmed_block_signatures[1] ); let res = io.handle_request_sync(&req, meta.clone()); - let expected_res: transaction::Result<()> = Err(TransactionError::InstructionError( - 0, - InstructionError::Custom(1), - )); let json: Value = serde_json::from_str(&res.unwrap()).unwrap(); let result: Option = serde_json::from_value(json["result"]["value"][0].clone()) .expect("actual response deserialization"); - assert_eq!(expected_res, result.as_ref().unwrap().status); + assert_eq!( + result.as_ref().unwrap().status, + Err(TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: None, + } + .into()), + ); // disable rpc-tx-history, but attempt historical query meta.config.enable_rpc_transaction_history = false; @@ -7231,23 +7236,31 @@ pub mod tests { if let EncodedTransaction::Json(transaction) = transaction { if transaction.signatures[0] == confirmed_block_signatures[0].to_string() { let meta = meta.unwrap(); - assert_eq!(meta.status, Ok(())); + assert_eq!(meta.status, Ok(()).into()); assert_eq!(meta.err, None); } else if transaction.signatures[0] == confirmed_block_signatures[1].to_string() { let meta = meta.unwrap(); assert_eq!( meta.err, - Some(TransactionError::InstructionError( - 0, - InstructionError::Custom(1) - )) + Some( + TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + } + .into() + ), ); assert_eq!( meta.status, - Err(TransactionError::InstructionError( - 0, - InstructionError::Custom(1) - )) + Err(TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }) + .into(), ); } else { assert_eq!(meta, None); @@ -7277,23 +7290,31 @@ pub mod tests { deserialize(&bs58::decode(&transaction).into_vec().unwrap()).unwrap(); if decoded_transaction.signatures[0] == confirmed_block_signatures[0] { let meta = meta.unwrap(); - assert_eq!(meta.status, Ok(())); + assert_eq!(meta.status, Ok(()).into()); assert_eq!(meta.err, None); } else if decoded_transaction.signatures[0] == confirmed_block_signatures[1] { let meta = meta.unwrap(); assert_eq!( meta.err, - Some(TransactionError::InstructionError( - 0, - InstructionError::Custom(1) - )) + Some( + TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + } + .into() + ), ); assert_eq!( meta.status, - Err(TransactionError::InstructionError( - 0, - InstructionError::Custom(1) - )) + Err(TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }) + .into(), ); } else { assert_eq!(meta, None); diff --git a/rpc/src/rpc_subscription_tracker.rs b/rpc/src/rpc_subscription_tracker.rs index a011c9c1f33d75..b66d4374ca9640 100644 --- a/rpc/src/rpc_subscription_tracker.rs +++ b/rpc/src/rpc_subscription_tracker.rs @@ -288,6 +288,21 @@ impl SubscriptionControl { }) } + #[cfg(test)] + pub fn block_subscribed(&self, pubkey: Option<&Pubkey>) -> bool { + self.0.subscriptions.iter().any(|item| { + if let SubscriptionParams::Block(params) = item.key() { + let subscribed_pubkey = match ¶ms.kind { + &BlockSubscriptionKind::All => None, + BlockSubscriptionKind::MentionsAccountOrProgram(pubkey) => Some(pubkey), + }; + subscribed_pubkey == pubkey + } else { + false + } + }) + } + #[cfg(test)] pub fn logs_subscribed(&self, pubkey: Option<&Pubkey>) -> bool { self.0.subscriptions.iter().any(|item| { diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index 1c716e05d58e65..dc68f76aeea116 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -402,7 +402,9 @@ fn filter_signature_result( ) -> (Option, Slot) { ( result.map(|result| { - RpcSignatureResult::ProcessedSignature(ProcessedSignatureResult { err: result.err() }) + RpcSignatureResult::ProcessedSignature(ProcessedSignatureResult { + err: result.map_err(Into::into).err(), + }) }), last_notified_slot, ) @@ -446,7 +448,7 @@ fn filter_logs_results( ) -> (impl Iterator, Slot) { let responses = logs.into_iter().flatten().map(|log| RpcLogsResponse { signature: log.signature.to_string(), - err: log.result.err(), + err: log.result.map_err(Into::into).err(), logs: log.log_messages, }); (responses, last_notified_slot) @@ -2635,6 +2637,98 @@ pub(crate) mod tests { .signature_subscribed(&unprocessed_tx.signatures[0])); } + #[test] + #[serial] + fn test_signature_subscribe_error_tx() { + fn make_signature_error(subscription_id: u64) -> serde_json::Value { + json!({ + "jsonrpc": "2.0", + "method": "signatureNotification", + "params": { + "result": { + "context": { + "slot": 0 + }, + "value": { + "err": { + "InstructionError": [0, { "Custom": 1 }, "11111111111111111111111111111111", null], + }, + } + }, + "subscription": subscription_id + } + }) + } + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100); + let bank = Bank::new_for_tests(&genesis_config); + let blockhash = bank.last_blockhash(); + let bank_forks = BankForks::new_rw_arc(bank); + + let alice = Keypair::new(); + + let exit = Arc::new(AtomicBool::new(false)); + let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); + let max_complete_rewards_slot = Arc::new(AtomicU64::default()); + let optimistically_confirmed_bank = + OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); + let subscriptions = Arc::new(RpcSubscriptions::new_for_tests( + exit, + max_complete_transaction_status_slot, + max_complete_rewards_slot, + bank_forks.clone(), + Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())), + optimistically_confirmed_bank, + )); + + let sub_config = RpcSignatureSubscribeConfig { + commitment: Some(CommitmentConfig::processed()), + enable_received_notification: None, + }; + + let bad_tx = system_transaction::create_account( + &mint_keypair, + &alice, + blockhash, + 101, // Insufficient balance; will yield `TransactionError::Custom(1)`. + 0, + &system_program::id(), + ); + + let (rpc, mut receiver) = rpc_pubsub_service::test_connection(&subscriptions); + let sub_id = rpc + .signature_subscribe(bad_tx.signatures[0].to_string(), Some(sub_config.clone())) + .unwrap(); + assert!(subscriptions + .control + .signature_subscribed(&bad_tx.signatures[0])); + + assert!(bank_forks + .read() + .unwrap() + .get(0) + .unwrap() + .process_transaction_with_metadata(bad_tx.clone()) + .is_ok()); + + subscriptions.notify_subscribers(CommitmentSlots::new_from_slot(0)); + + let expected_response_all = make_signature_error(u64::from(sub_id)); + let response_all = receiver.recv(); + assert_eq!( + expected_response_all, + serde_json::from_str::(&response_all).unwrap(), + ); + + assert!(!subscriptions + .control + .signature_subscribed(&bad_tx.signatures[0])); + } + #[test] #[serial] fn test_check_slot_subscribe() { @@ -2918,32 +3012,32 @@ pub(crate) mod tests { assert!(!subscriptions.control.account_subscribed(&alice.pubkey())); } - fn make_logs_result(signature: &str, subscription_id: u64) -> serde_json::Value { - json!({ - "jsonrpc": "2.0", - "method": "logsNotification", - "params": { - "result": { - "context": { - "slot": 0 - }, - "value": { - "signature": signature, - "err": null, - "logs": [ - "Program 11111111111111111111111111111111 invoke [1]", - "Program 11111111111111111111111111111111 success" - ] - } - }, - "subscription": subscription_id - } - }) - } - #[test] #[serial] - fn test_logs_subscribe() { + fn test_logs_subscribe_success_tx() { + fn make_logs_result(signature: &str, subscription_id: u64) -> serde_json::Value { + json!({ + "jsonrpc": "2.0", + "method": "logsNotification", + "params": { + "result": { + "context": { + "slot": 0 + }, + "value": { + "signature": signature, + "err": null, + "logs": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success" + ] + } + }, + "subscription": subscription_id + } + }) + } + let GenesisConfigInfo { genesis_config, mint_keypair, @@ -3029,6 +3123,122 @@ pub(crate) mod tests { assert!(!subscriptions.control.logs_subscribed(Some(&alice.pubkey()))); } + #[test] + #[serial] + fn test_logs_subscribe_error_tx() { + fn make_logs_error(signature: &str, subscription_id: u64) -> serde_json::Value { + json!({ + "jsonrpc": "2.0", + "method": "logsNotification", + "params": { + "result": { + "context": { + "slot": 0 + }, + "value": { + "signature": signature, + "err": { + "InstructionError": [0, { "Custom": 1 }, "11111111111111111111111111111111", null], + }, + "logs": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Transfer: insufficient lamports 100, need 1000000000", + "Program 11111111111111111111111111111111 failed: custom program error: 0x1" + ] + } + }, + "subscription": subscription_id + } + }) + } + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(100); + let bank = Bank::new_for_tests(&genesis_config); + let blockhash = bank.last_blockhash(); + let bank_forks = BankForks::new_rw_arc(bank); + + let alice = Keypair::new(); + + let exit = Arc::new(AtomicBool::new(false)); + let max_complete_transaction_status_slot = Arc::new(AtomicU64::default()); + let max_complete_rewards_slot = Arc::new(AtomicU64::default()); + let optimistically_confirmed_bank = + OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks); + let subscriptions = Arc::new(RpcSubscriptions::new_for_tests( + exit, + max_complete_transaction_status_slot, + max_complete_rewards_slot, + bank_forks.clone(), + Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())), + optimistically_confirmed_bank, + )); + + let sub_config = RpcTransactionLogsConfig { + commitment: Some(CommitmentConfig::processed()), + }; + + let (rpc_all, mut receiver_all) = rpc_pubsub_service::test_connection(&subscriptions); + let sub_id_for_all = rpc_all + .logs_subscribe(RpcTransactionLogsFilter::All, Some(sub_config.clone())) + .unwrap(); + assert!(subscriptions.control.logs_subscribed(None)); + + let (rpc_alice, mut receiver_alice) = rpc_pubsub_service::test_connection(&subscriptions); + let sub_id_for_alice = rpc_alice + .logs_subscribe( + RpcTransactionLogsFilter::Mentions(vec![alice.pubkey().to_string()]), + Some(sub_config), + ) + .unwrap(); + assert!(subscriptions.control.logs_subscribed(Some(&alice.pubkey()))); + rpc_alice.block_until_processed(&subscriptions); + + let bad_tx = system_transaction::create_account( + &mint_keypair, + &alice, + blockhash, + 101, // Insufficient balance; will yield `TransactionError::Custom(1)`. + 0, + &system_program::id(), + ); + + assert!(bank_forks + .read() + .unwrap() + .get(0) + .unwrap() + .process_transaction_with_metadata(bad_tx.clone()) + .is_ok()); + + subscriptions.notify_subscribers(CommitmentSlots::new_from_slot(0)); + + let expected_response_all = + make_logs_error(&bad_tx.signatures[0].to_string(), u64::from(sub_id_for_all)); + let response_all = receiver_all.recv(); + assert_eq!( + expected_response_all, + serde_json::from_str::(&response_all).unwrap(), + ); + let expected_response_alice = make_logs_error( + &bad_tx.signatures[0].to_string(), + u64::from(sub_id_for_alice), + ); + let response_alice = receiver_alice.recv(); + assert_eq!( + expected_response_alice, + serde_json::from_str::(&response_alice).unwrap(), + ); + + rpc_all.logs_unsubscribe(sub_id_for_all).unwrap(); + assert!(!subscriptions.control.logs_subscribed(None)); + rpc_alice.logs_unsubscribe(sub_id_for_alice).unwrap(); + assert!(!subscriptions.control.logs_subscribed(Some(&alice.pubkey()))); + } + #[test] fn test_total_subscriptions() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100); diff --git a/runtime/src/account_saver.rs b/runtime/src/account_saver.rs index ca364ae9aa714f..016acbe43f5f88 100644 --- a/runtime/src/account_saver.rs +++ b/runtime/src/account_saver.rs @@ -361,10 +361,12 @@ mod tests { let txs = vec![tx]; let processing_results = vec![new_executed_processing_result( - Err(TransactionError::InstructionError( - 1, - InstructionError::InvalidArgument, - )), + Err(TransactionError::InstructionError { + err: InstructionError::InvalidArgument, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), loaded, )]; let max_collected_accounts = max_number_of_accounts_to_collect(&txs, &processing_results); @@ -456,10 +458,12 @@ mod tests { let txs = vec![tx]; let processing_results = vec![new_executed_processing_result( - Err(TransactionError::InstructionError( - 1, - InstructionError::InvalidArgument, - )), + Err(TransactionError::InstructionError { + err: InstructionError::InvalidArgument, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), loaded, )]; let max_collected_accounts = max_number_of_accounts_to_collect(&txs, &processing_results); @@ -564,10 +568,12 @@ mod tests { let txs = vec![tx]; let processing_results = vec![new_executed_processing_result( - Err(TransactionError::InstructionError( - 1, - InstructionError::InvalidArgument, - )), + Err(TransactionError::InstructionError { + err: InstructionError::InvalidArgument, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), loaded, )]; let max_collected_accounts = max_number_of_accounts_to_collect(&txs, &processing_results); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 6297e4b1c41174..600811ac2a4848 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -244,7 +244,7 @@ struct RentMetrics { pub type BankStatusCache = StatusCache>; #[cfg_attr( feature = "frozen-abi", - frozen_abi(digest = "5dfDCRGWPV7thfoZtLpTJAV8cC93vQUXgTm6BnrfeUsN") + frozen_abi(digest = "5UmYzdMvTDkFBKsqddQ43mSikgEA6s2bTvRZUq78YPQ2") )] pub type BankSlotDelta = SlotDelta>; diff --git a/runtime/src/bank/partitioned_epoch_rewards/mod.rs b/runtime/src/bank/partitioned_epoch_rewards/mod.rs index 1692099271e8f3..ca9f8525a55918 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/mod.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/mod.rs @@ -858,7 +858,12 @@ mod tests { // actions like StakeInstruction::Withdraw assert_eq!( stake_result, - Err(InstructionError(0, StakeError::EpochRewardsActive.into())) + Err(InstructionError { + err: StakeError::EpochRewardsActive.into(), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(solana_sdk_ids::stake::id()), + }), ); } else { // When the bank is outside of reward interval, the withdraw diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 8087e4b2f8ea78..5a40dc93d81233 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -84,7 +84,8 @@ use { solana_rent_collector::RENT_EXEMPT_RENT_EPOCH, solana_reward_info::RewardType, solana_sdk_ids::{ - bpf_loader, bpf_loader_upgradeable, incinerator, native_loader, secp256k1_program, + bpf_loader, bpf_loader_upgradeable, compute_budget, incinerator, native_loader, + secp256k1_program, }, solana_sha256_hasher::hash, solana_signature::Signature, @@ -1077,9 +1078,14 @@ fn test_rent_exempt_executable_account() { transfer_lamports, genesis_config.hash(), ); - assert_matches!( + assert_eq!( bank.process_transaction(&tx), - Err(TransactionError::InstructionError(0, _)) + Err(TransactionError::InstructionError { + err: InstructionError::AccountNotRentExempt, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); assert_eq!(bank.get_balance(&account_pubkey), account_balance); } @@ -2461,7 +2467,12 @@ fn test_one_tx_two_out_atomic_fail() { let tx = Transaction::new(&[&mint_keypair], message, genesis_config.hash()); assert_eq!( bank.process_transaction(&tx).unwrap_err(), - TransactionError::InstructionError(1, SystemError::ResultWithNegativeLamports.into()) + TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None, + }, ); assert_eq!(bank.get_balance(&mint_keypair.pubkey()), amount); assert_eq!(bank.get_balance(&key1), 0); @@ -2507,10 +2518,12 @@ fn test_detect_failed_duplicate_transactions() { assert_eq!( bank.process_transaction(&tx), - Err(TransactionError::InstructionError( - 0, - SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); // The lamports didn't move, but the from address paid the transaction fee. @@ -2551,10 +2564,12 @@ fn test_insufficient_funds() { assert_eq!(bank.get_balance(&pubkey), amount); assert_eq!( bank.transfer((mint_amount - amount) + 1, &mint_keypair, &pubkey), - Err(TransactionError::InstructionError( - 0, - SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); // transaction_count returns the count of all committed transactions since // bank_transaction_count_fix was activated, regardless of success @@ -2576,10 +2591,12 @@ fn test_executed_transaction_count_post_bank_transaction_count_fix() { bank.transfer(amount, &mint_keypair, &pubkey).unwrap(); assert_eq!( bank.transfer((mint_amount - amount) + 1, &mint_keypair, &pubkey), - Err(TransactionError::InstructionError( - 0, - SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); // With bank_transaction_count_fix, transaction_count should include both the successful and @@ -2597,10 +2614,12 @@ fn test_executed_transaction_count_post_bank_transaction_count_fix() { assert_eq!( bank2.transfer((mint_amount - amount) + 2, &mint_keypair, &pubkey), - Err(TransactionError::InstructionError( - 0, - SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); // The transaction_count inherited from parent bank is 3: 2 from the parent bank and 1 at this bank2 @@ -4557,10 +4576,12 @@ fn test_is_delta_with_no_committables() { // so is_delta should be true assert_eq!( bank.transfer(10_001, &mint_keypair, &solana_pubkey::new_rand()), - Err(TransactionError::InstructionError( - 0, - SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); assert!(bank.is_delta.load(Relaxed)); @@ -4824,10 +4845,12 @@ fn test_add_builtin() { let (bank, _bank_forks) = bank.wrap_with_bank_forks_for_tests(); assert_eq!( bank.process_transaction(&transaction), - Err(TransactionError::InstructionError( - 1, - InstructionError::Custom(42) - )) + Err(TransactionError::InstructionError { + err: InstructionError::Custom(42), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), ); } @@ -4882,10 +4905,12 @@ fn test_add_duplicate_static_program() { assert_eq!(vote_loader_account.data(), new_vote_loader_account.data()); assert_eq!( bank.process_transaction(&transaction), - Err(TransactionError::InstructionError( - 1, - InstructionError::Custom(42) - )) + Err(TransactionError::InstructionError { + err: InstructionError::Custom(42), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), ); } @@ -5228,11 +5253,15 @@ fn test_assign_from_nonce_account_fail() { let message = Message::new(&[ix], Some(&nonce.pubkey())); let tx = Transaction::new(&[&nonce], message, blockhash); - let expect = Err(TransactionError::InstructionError( - 0, - InstructionError::ModifiedProgramId, - )); - assert_eq!(bank.process_transaction(&tx), expect); + assert_eq!( + bank.process_transaction(&tx), + Err(TransactionError::InstructionError { + err: InstructionError::ModifiedProgramId, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), + ); } #[test] @@ -5372,10 +5401,12 @@ fn test_nonce_transaction() { ); assert_eq!( bank.process_transaction(&nonce_tx), - Err(TransactionError::InstructionError( - 1, - solana_system_interface::error::SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), ); /* Check fee charged and nonce has advanced */ let mut recent_message = nonce_tx.message.clone(); @@ -5499,10 +5530,12 @@ fn test_nonce_transaction_with_tx_wide_caps() { ); assert_eq!( bank.process_transaction(&nonce_tx), - Err(TransactionError::InstructionError( - 1, - solana_system_interface::error::SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), ); /* Check fee charged and nonce has advanced */ let mut recent_message = nonce_tx.message.clone(); @@ -5628,10 +5661,12 @@ fn test_nonce_payer() { debug!("{:?}", nonce_tx); assert_eq!( bank.process_transaction(&nonce_tx), - Err(TransactionError::InstructionError( - 1, - solana_system_interface::error::SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), ); /* Check fee charged and nonce has advanced */ let mut recent_message = nonce_tx.message; @@ -5695,10 +5730,12 @@ fn test_nonce_payer_tx_wide_cap() { assert_eq!( bank.process_transaction(&nonce_tx), - Err(TransactionError::InstructionError( - 1, - solana_system_interface::error::SystemError::ResultWithNegativeLamports.into(), - )) + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: Some(system_program::id()), + }), ); /* Check fee charged and nonce has advanced */ let mut recent_message = nonce_tx.message; @@ -6020,10 +6057,12 @@ fn test_pre_post_transaction_balances() { // This is an InstructionError - fees charged assert_eq!( commit_results[2].as_ref().unwrap().status, - Err(TransactionError::InstructionError( - 0, - InstructionError::Custom(1), - )), + Err(TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); assert_eq!( transaction_balances_set.pre_balances[2], @@ -7280,10 +7319,12 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { ); assert_eq!( bank.process_transaction(&transaction), - Err(TransactionError::InstructionError( - 0, - InstructionError::UnsupportedProgramId - )), + Err(TransactionError::InstructionError { + err: InstructionError::UnsupportedProgramId, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); { let program_cache = bank.transaction_processor.program_cache.read().unwrap(); @@ -7304,10 +7345,12 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { let transaction = Transaction::new(&[&binding], message, bank.last_blockhash()); assert_eq!( bank.process_transaction(&transaction), - Err(TransactionError::InstructionError( - 0, - InstructionError::UnsupportedProgramId, - )), + Err(TransactionError::InstructionError { + err: InstructionError::UnsupportedProgramId, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); { let program_cache = bank.transaction_processor.program_cache.read().unwrap(); @@ -7455,11 +7498,16 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized), bank_client .send_and_confirm_message(&[&mint_keypair, &upgrade_authority_keypair], message) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::AccountAlreadyInitialized, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: None + }, ); // Test initialized ProgramData account @@ -7479,14 +7527,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::Custom(0)), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::Custom(0), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Test deploy no authority @@ -7513,11 +7566,16 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(0, InstructionError::NotEnoughAccountKeys), bank_client .send_and_confirm_message(&[&mint_keypair], message) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::NotEnoughAccountKeys, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: None + }, ); // Test deploy authority not a signer @@ -7545,11 +7603,16 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), bank_client .send_and_confirm_message(&[&mint_keypair], message) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::MissingRequiredSignature, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: None + }, ); // Test invalid Buffer account state @@ -7570,14 +7633,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::InvalidAccountData), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::InvalidAccountData, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Test program account not rent exempt @@ -7598,14 +7666,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::ExecutableAccountNotRentExempt), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::ExecutableAccountNotRentExempt, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Test program account not rent exempt because data is larger than needed @@ -7631,14 +7704,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { ); let message = Message::new(&instructions, Some(&mint_keypair.pubkey())); assert_eq!( - TransactionError::InstructionError(1, InstructionError::ExecutableAccountNotRentExempt), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::ExecutableAccountNotRentExempt, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Test program account too small @@ -7664,14 +7742,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { ); let message = Message::new(&instructions, Some(&mint_keypair.pubkey())); assert_eq!( - TransactionError::InstructionError(1, InstructionError::AccountDataTooSmall), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::AccountDataTooSmall, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Test Insufficient payer funds (need more funds to cover the @@ -7701,14 +7784,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::Custom(1)), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); bank.store_account( &mint_keypair.pubkey(), @@ -7733,14 +7821,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::AccountDataTooSmall), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::AccountDataTooSmall, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Test max_data_len too large @@ -7767,14 +7860,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::InvalidArgument), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::InvalidArgument, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Test not the system account @@ -7834,14 +7932,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::InvalidAccountData), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::InvalidAccountData, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Test small buffer account @@ -7878,14 +7981,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::InvalidAccountData), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::InvalidAccountData, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Mismatched buffer and program authority @@ -7921,14 +8029,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::IncorrectAuthority), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::IncorrectAuthority, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); // Deploy buffer with mismatched None authority @@ -7964,14 +8077,19 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Some(&mint_keypair.pubkey()), ); assert_eq!( - TransactionError::InstructionError(1, InstructionError::IncorrectAuthority), bank_client .send_and_confirm_message( &[&mint_keypair, &program_keypair, &upgrade_authority_keypair], message ) .unwrap_err() - .unwrap() + .unwrap(), + TransactionError::InstructionError { + err: InstructionError::IncorrectAuthority, + inner_instruction_index: None, + outer_instruction_index: 1, + responsible_program_address: None + }, ); } @@ -8393,10 +8511,12 @@ fn test_program_is_native_loader() { ); assert_eq!( bank.process_transaction(&tx), - Err(TransactionError::InstructionError( - 0, - InstructionError::UnsupportedProgramId - )) + Err(TransactionError::InstructionError { + err: InstructionError::UnsupportedProgramId, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(native_loader::id()), + }), ); } @@ -8433,10 +8553,12 @@ fn test_invoke_non_program_account_owned_by_a_builtin() { ); assert_eq!( bank.process_transaction(&tx), - Err(TransactionError::InstructionError( - 0, - InstructionError::UnsupportedProgramId - )) + Err(TransactionError::InstructionError { + err: InstructionError::UnsupportedProgramId, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); } @@ -9808,10 +9930,12 @@ fn test_transfer_sysvar() { let tx = system_transaction::transfer(&mint_keypair, &blockhash_sysvar, 10, blockhash); assert_eq!( bank.process_transaction(&tx), - Err(TransactionError::InstructionError( - 0, - InstructionError::ReadonlyLamportChange - )) + Err(TransactionError::InstructionError { + err: InstructionError::ReadonlyLamportChange, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); assert_eq!( bank.get_account(&sysvar::clock::id()).unwrap().lamports(), @@ -9827,10 +9951,12 @@ fn test_transfer_sysvar() { let tx = Transaction::new(&[&mint_keypair], message, blockhash); assert_eq!( bank.process_transaction(&tx), - Err(TransactionError::InstructionError( - 0, - InstructionError::ReadonlyDataModified - )) + Err(TransactionError::InstructionError { + err: InstructionError::ReadonlyDataModified, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); } @@ -10148,10 +10274,12 @@ fn test_failed_compute_request_instruction() { assert_eq!( results[0], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData - )) + Err(TransactionError::InstructionError { + err: InstructionError::InvalidInstructionData, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(compute_budget::id()), + }), ); assert_eq!(results[1], Ok(())); // two transfers and the mock program @@ -10575,7 +10703,12 @@ fn test_an_empty_instruction_without_program() { let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); assert_eq!( bank.process_transaction(&tx).unwrap_err(), - TransactionError::InstructionError(0, InstructionError::UnsupportedProgramId), + TransactionError::InstructionError { + err: InstructionError::UnsupportedProgramId, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(native_loader::id()), + }, ); } @@ -11130,7 +11263,12 @@ fn test_invalid_rent_state_changes_fee_payer() { let result = bank.process_transaction(&tx); assert_eq!( result.unwrap_err(), - TransactionError::InstructionError(0, InstructionError::Custom(1)) + TransactionError::InstructionError { + err: InstructionError::Custom(1), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: None + }, ); assert_ne!( fee_payer_balance, @@ -12002,6 +12140,8 @@ fn test_cap_accounts_data_allocations_per_transaction() { const NUM_MAX_SIZE_ALLOCATIONS_PER_TRANSACTION: usize = MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION as usize / MAX_PERMITTED_DATA_LENGTH as usize; + const NUM_MAX_SIZE_ALLOCATIONS_PER_TRANSACTION_AS_U8: u8 = + NUM_MAX_SIZE_ALLOCATIONS_PER_TRANSACTION as u8; let (genesis_config, mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); @@ -12033,10 +12173,12 @@ fn test_cap_accounts_data_allocations_per_transaction() { assert_eq!(accounts_data_size_before, accounts_data_size_after); assert_eq!( result, - Err(TransactionError::InstructionError( - NUM_MAX_SIZE_ALLOCATIONS_PER_TRANSACTION as u8, - solana_instruction::error::InstructionError::MaxAccountsDataAllocationsExceeded, - )), + Err(TransactionError::InstructionError { + err: InstructionError::MaxAccountsDataAllocationsExceeded, + inner_instruction_index: None, + outer_instruction_index: NUM_MAX_SIZE_ALLOCATIONS_PER_TRANSACTION_AS_U8, + responsible_program_address: Some(system_program::id()), + }), ); } @@ -12325,10 +12467,12 @@ fn test_feature_activation_loaded_programs_cache_preparation_phase() { let result_with_feature_enabled = bank.process_transaction(&transaction); assert_eq!( result_with_feature_enabled, - Err(TransactionError::InstructionError( - 0, - InstructionError::UnsupportedProgramId - )) + Err(TransactionError::InstructionError { + err: InstructionError::UnsupportedProgramId, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), ); } @@ -12748,7 +12892,12 @@ fn test_system_instruction_unsigned_transaction() { .send_and_confirm_instruction(&mallory_keypair, malicious_instruction) .unwrap_err() .unwrap(), - TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature) + TransactionError::InstructionError { + err: InstructionError::MissingRequiredSignature, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: None + }, ); assert_eq!( bank_client.get_balance(&alice_pubkey).unwrap(), @@ -13258,10 +13407,12 @@ fn test_filter_program_errors_and_collect_fee_details() { }, ))), new_executed_processing_result( - Err(TransactionError::InstructionError( - 0, - SystemError::ResultWithNegativeLamports.into(), - )), + Err(TransactionError::InstructionError { + err: SystemError::ResultWithNegativeLamports.into(), + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(system_program::id()), + }), fee_details, ), new_executed_processing_result(Ok(()), fee_details), @@ -13528,7 +13679,12 @@ fn test_loader_v3_to_v4_migration() { let error = bank.process_transaction(&transaction).unwrap_err(); assert_eq!( error, - TransactionError::InstructionError(0, InstructionError::InvalidArgument) + TransactionError::InstructionError { + err: InstructionError::InvalidArgument, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: None, + }, ); let bank = new_bank_from_parent_with_bank_forks( @@ -13629,35 +13785,37 @@ fn test_loader_v3_to_v4_migration() { bank.last_blockhash(), ); let error = bank.process_transaction(&transaction).unwrap_err(); - assert_eq!(error, TransactionError::InstructionError(0, expected_error)); + assert_eq!( + error, + TransactionError::InstructionError { + err: expected_error, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(bpf_loader_upgradeable::id()), + }, + ); } - for (mut programdata_account, transaction, expected_execution_result) in [ + for (mut programdata_account, transaction, expected_error) in [ ( closed_programdata_account, finalized_migration_transaction.clone(), - Err(TransactionError::InstructionError( - 0, - InstructionError::UnsupportedProgramId, - )), + Some(InstructionError::UnsupportedProgramId), ), ( uninitialized_programdata_account, finalized_migration_transaction.clone(), - Err(TransactionError::InstructionError( - 0, - InstructionError::UnsupportedProgramId, - )), + Some(InstructionError::UnsupportedProgramId), ), ( finalized_programdata_account, finalized_migration_transaction, - Ok(()), + None, ), ( upgradeable_programdata_account, upgradeable_migration_transaction, - Ok(()), + None, ), ] { let bank = new_bank_from_parent_with_bank_forks( @@ -13691,7 +13849,19 @@ fn test_loader_v3_to_v4_migration() { let binding = mint_keypair.insecure_clone(); let transaction = Transaction::new(&[&binding], message, bank.last_blockhash()); let execution_result = bank.process_transaction(&transaction); - assert_eq!(execution_result, expected_execution_result); + if let Some(expected_err) = expected_error { + assert_eq!( + execution_result, + Err(TransactionError::InstructionError { + err: expected_err, + inner_instruction_index: None, + outer_instruction_index: 0, + responsible_program_address: Some(program_keypair.pubkey()), + }), + ); + } else { + assert_matches!(execution_result, Ok(())); + } } } diff --git a/storage-proto/Cargo.toml b/storage-proto/Cargo.toml index acecf3aebf37d5..f365212b9a738a 100644 --- a/storage-proto/Cargo.toml +++ b/storage-proto/Cargo.toml @@ -14,6 +14,7 @@ bincode = { workspace = true } bs58 = { workspace = true } prost = { workspace = true } serde = { workspace = true } +serde_derive = { workspace = true } solana-account-decoder = { workspace = true } solana-hash = { workspace = true } solana-instruction = { workspace = true } @@ -28,6 +29,7 @@ solana-transaction-status = { workspace = true } [dev-dependencies] enum-iterator = { workspace = true } +test-case = { workspace = true } [lib] crate-type = ["lib"] diff --git a/storage-proto/proto/transaction_by_addr.proto b/storage-proto/proto/transaction_by_addr.proto index 5748b05655edba..f1172be614676a 100644 --- a/storage-proto/proto/transaction_by_addr.proto +++ b/storage-proto/proto/transaction_by_addr.proto @@ -70,6 +70,7 @@ message InstructionError { uint32 index = 1; InstructionErrorType error = 2; CustomError custom = 3; + optional bytes responsible_program_address_bytes = 4; } message TransactionDetails { diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index cd9372c8dc58ca..45412140836748 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -1,5 +1,5 @@ use { - crate::{StoredExtendedRewards, StoredTransactionStatusMeta}, + crate::{StoredExtendedRewards, StoredTransactionError, StoredTransactionStatusMeta}, solana_account_decoder::parse_token::{real_number_string_trimmed, UiTokenAmount}, solana_hash::{Hash, HASH_BYTES}, solana_instruction::error::InstructionError, @@ -286,6 +286,20 @@ impl From for VersionedTransaction { } } +impl From for generated::TransactionError { + fn from(value: TransactionError) -> Self { + let stored_error = Into::::into(value).0; + Self { err: stored_error } + } +} + +impl From for TransactionError { + fn from(value: generated::TransactionError) -> Self { + let stored_error = StoredTransactionError(value.err); + stored_error.into() + } +} + impl From for generated::Message { fn from(message: LegacyMessage) -> Self { Self { @@ -409,12 +423,7 @@ impl From for generated::TransactionStatusMeta { compute_units_consumed, cost_units, } = value; - let err = match status { - Ok(()) => None, - Err(err) => Some(generated::TransactionError { - err: bincode::serialize(&err).expect("transaction error to serialize to bytes"), - }), - }; + let err = status.err().map(Into::into); let inner_instructions_none = inner_instructions.is_none(); let inner_instructions = inner_instructions .unwrap_or_default() @@ -503,9 +512,9 @@ impl TryFrom for TransactionStatusMeta { compute_units_consumed, cost_units, } = value; - let status = match &err { + let status = match err { None => Ok(()), - Some(tx_error) => Err(bincode::deserialize(&tx_error.err)?), + Some(tx_error) => Err(tx_error.into()), }; let inner_instructions = if inner_instructions_none { None @@ -728,11 +737,28 @@ impl TryFrom for TransactionError { fn try_from(transaction_error: tx_by_addr::TransactionError) -> Result { if transaction_error.transaction_error == 8 { if let Some(instruction_error) = transaction_error.instruction_error { + // Decode the index. + // + // Index values are 32-bit integers of the form: + // TTTTTTTT IIIIIIII xxxxxxxx xxxxxxxx + // + // * T - The top level index of the instruction that errored + // * I - The inner index of the instruction that errored; 0 if None, 1-indexed otherwise + let [outer_instruction_index, maybe_inner_instruction_index, _unused1, _unused2] = + instruction_error.index.to_le_bytes(); + let inner_instruction_index = maybe_inner_instruction_index.checked_sub(1); + let responsible_program_address = instruction_error + .responsible_program_address_bytes + .map(Pubkey::try_from) + .transpose() + .expect("Failed to parse `responsible_program_address_bytes` as a Pubkey"); if let Some(custom) = instruction_error.custom { - return Ok(TransactionError::InstructionError( - instruction_error.index as u8, - InstructionError::Custom(custom.custom), - )); + return Ok(TransactionError::InstructionError { + err: InstructionError::Custom(custom.custom), + inner_instruction_index, + outer_instruction_index, + responsible_program_address, + }); } let ie = match instruction_error.error { @@ -792,10 +818,12 @@ impl TryFrom for TransactionError { _ => return Err("Invalid InstructionError"), }; - return Ok(TransactionError::InstructionError( - instruction_error.index as u8, - ie, - )); + return Ok(TransactionError::InstructionError { + err: ie, + inner_instruction_index, + outer_instruction_index, + responsible_program_address, + }); } } @@ -909,7 +937,7 @@ impl From for tx_by_addr::TransactionError { TransactionError::ClusterMaintenance => { tx_by_addr::TransactionErrorType::ClusterMaintenance } - TransactionError::InstructionError(_, _) => { + TransactionError::InstructionError { .. } => { tx_by_addr::TransactionErrorType::InstructionError } TransactionError::AccountBorrowOutstanding => { @@ -983,179 +1011,188 @@ impl From for tx_by_addr::TransactionError { } } as i32, instruction_error: match transaction_error { - TransactionError::InstructionError(index, ref instruction_error) => { - Some(tx_by_addr::InstructionError { - index: index as u32, - error: match instruction_error { - InstructionError::GenericError => { - tx_by_addr::InstructionErrorType::GenericError - } - InstructionError::InvalidArgument => { - tx_by_addr::InstructionErrorType::InvalidArgument - } - InstructionError::InvalidInstructionData => { - tx_by_addr::InstructionErrorType::InvalidInstructionData - } - InstructionError::InvalidAccountData => { - tx_by_addr::InstructionErrorType::InvalidAccountData - } - InstructionError::AccountDataTooSmall => { - tx_by_addr::InstructionErrorType::AccountDataTooSmall - } - InstructionError::InsufficientFunds => { - tx_by_addr::InstructionErrorType::InsufficientFunds - } - InstructionError::IncorrectProgramId => { - tx_by_addr::InstructionErrorType::IncorrectProgramId - } - InstructionError::MissingRequiredSignature => { - tx_by_addr::InstructionErrorType::MissingRequiredSignature - } - InstructionError::AccountAlreadyInitialized => { - tx_by_addr::InstructionErrorType::AccountAlreadyInitialized - } - InstructionError::UninitializedAccount => { - tx_by_addr::InstructionErrorType::UninitializedAccount - } - InstructionError::UnbalancedInstruction => { - tx_by_addr::InstructionErrorType::UnbalancedInstruction - } - InstructionError::ModifiedProgramId => { - tx_by_addr::InstructionErrorType::ModifiedProgramId - } - InstructionError::ExternalAccountLamportSpend => { - tx_by_addr::InstructionErrorType::ExternalAccountLamportSpend - } - InstructionError::ExternalAccountDataModified => { - tx_by_addr::InstructionErrorType::ExternalAccountDataModified - } - InstructionError::ReadonlyLamportChange => { - tx_by_addr::InstructionErrorType::ReadonlyLamportChange - } - InstructionError::ReadonlyDataModified => { - tx_by_addr::InstructionErrorType::ReadonlyDataModified - } - InstructionError::DuplicateAccountIndex => { - tx_by_addr::InstructionErrorType::DuplicateAccountIndex - } - InstructionError::ExecutableModified => { - tx_by_addr::InstructionErrorType::ExecutableModified - } - InstructionError::RentEpochModified => { - tx_by_addr::InstructionErrorType::RentEpochModified - } - InstructionError::NotEnoughAccountKeys => { - tx_by_addr::InstructionErrorType::NotEnoughAccountKeys - } - InstructionError::AccountDataSizeChanged => { - tx_by_addr::InstructionErrorType::AccountDataSizeChanged - } - InstructionError::AccountNotExecutable => { - tx_by_addr::InstructionErrorType::AccountNotExecutable - } - InstructionError::AccountBorrowFailed => { - tx_by_addr::InstructionErrorType::AccountBorrowFailed - } - InstructionError::AccountBorrowOutstanding => { - tx_by_addr::InstructionErrorType::AccountBorrowOutstanding - } - InstructionError::DuplicateAccountOutOfSync => { - tx_by_addr::InstructionErrorType::DuplicateAccountOutOfSync - } - InstructionError::Custom(_) => tx_by_addr::InstructionErrorType::Custom, - InstructionError::InvalidError => { - tx_by_addr::InstructionErrorType::InvalidError - } - InstructionError::ExecutableDataModified => { - tx_by_addr::InstructionErrorType::ExecutableDataModified - } - InstructionError::ExecutableLamportChange => { - tx_by_addr::InstructionErrorType::ExecutableLamportChange - } - InstructionError::ExecutableAccountNotRentExempt => { - tx_by_addr::InstructionErrorType::ExecutableAccountNotRentExempt - } - InstructionError::UnsupportedProgramId => { - tx_by_addr::InstructionErrorType::UnsupportedProgramId - } - InstructionError::CallDepth => { - tx_by_addr::InstructionErrorType::CallDepth - } - InstructionError::MissingAccount => { - tx_by_addr::InstructionErrorType::MissingAccount - } - InstructionError::ReentrancyNotAllowed => { - tx_by_addr::InstructionErrorType::ReentrancyNotAllowed - } - InstructionError::MaxSeedLengthExceeded => { - tx_by_addr::InstructionErrorType::MaxSeedLengthExceeded - } - InstructionError::InvalidSeeds => { - tx_by_addr::InstructionErrorType::InvalidSeeds - } - InstructionError::InvalidRealloc => { - tx_by_addr::InstructionErrorType::InvalidRealloc - } - InstructionError::ComputationalBudgetExceeded => { - tx_by_addr::InstructionErrorType::ComputationalBudgetExceeded - } - InstructionError::PrivilegeEscalation => { - tx_by_addr::InstructionErrorType::PrivilegeEscalation - } - InstructionError::ProgramEnvironmentSetupFailure => { - tx_by_addr::InstructionErrorType::ProgramEnvironmentSetupFailure - } - InstructionError::ProgramFailedToComplete => { - tx_by_addr::InstructionErrorType::ProgramFailedToComplete - } - InstructionError::ProgramFailedToCompile => { - tx_by_addr::InstructionErrorType::ProgramFailedToCompile - } - InstructionError::Immutable => { - tx_by_addr::InstructionErrorType::Immutable - } - InstructionError::IncorrectAuthority => { - tx_by_addr::InstructionErrorType::IncorrectAuthority - } - InstructionError::BorshIoError(_) => { - tx_by_addr::InstructionErrorType::BorshIoError - } - InstructionError::AccountNotRentExempt => { - tx_by_addr::InstructionErrorType::AccountNotRentExempt - } - InstructionError::InvalidAccountOwner => { - tx_by_addr::InstructionErrorType::InvalidAccountOwner - } - InstructionError::ArithmeticOverflow => { - tx_by_addr::InstructionErrorType::ArithmeticOverflow - } - InstructionError::UnsupportedSysvar => { - tx_by_addr::InstructionErrorType::UnsupportedSysvar - } - InstructionError::IllegalOwner => { - tx_by_addr::InstructionErrorType::IllegalOwner - } - InstructionError::MaxAccountsDataAllocationsExceeded => { - tx_by_addr::InstructionErrorType::MaxAccountsDataAllocationsExceeded - } - InstructionError::MaxAccountsExceeded => { - tx_by_addr::InstructionErrorType::MaxAccountsExceeded - } - InstructionError::MaxInstructionTraceLengthExceeded => { - tx_by_addr::InstructionErrorType::MaxInstructionTraceLengthExceeded - } - InstructionError::BuiltinProgramsMustConsumeComputeUnits => { - tx_by_addr::InstructionErrorType::BuiltinProgramsMustConsumeComputeUnits - } - } as i32, - custom: match instruction_error { - InstructionError::Custom(custom) => { - Some(tx_by_addr::CustomError { custom: *custom }) - } - _ => None, - }, - }) - } + TransactionError::InstructionError { + err: ref instruction_error, + inner_instruction_index, + outer_instruction_index, + responsible_program_address, + } => Some(tx_by_addr::InstructionError { + index: u32::from_le_bytes([ + outer_instruction_index, + inner_instruction_index + .map(|i| i.checked_add(1).unwrap()) + .unwrap_or(0), + 0, // unused + 0, // unused + ]), + error: match instruction_error { + InstructionError::GenericError => { + tx_by_addr::InstructionErrorType::GenericError + } + InstructionError::InvalidArgument => { + tx_by_addr::InstructionErrorType::InvalidArgument + } + InstructionError::InvalidInstructionData => { + tx_by_addr::InstructionErrorType::InvalidInstructionData + } + InstructionError::InvalidAccountData => { + tx_by_addr::InstructionErrorType::InvalidAccountData + } + InstructionError::AccountDataTooSmall => { + tx_by_addr::InstructionErrorType::AccountDataTooSmall + } + InstructionError::InsufficientFunds => { + tx_by_addr::InstructionErrorType::InsufficientFunds + } + InstructionError::IncorrectProgramId => { + tx_by_addr::InstructionErrorType::IncorrectProgramId + } + InstructionError::MissingRequiredSignature => { + tx_by_addr::InstructionErrorType::MissingRequiredSignature + } + InstructionError::AccountAlreadyInitialized => { + tx_by_addr::InstructionErrorType::AccountAlreadyInitialized + } + InstructionError::UninitializedAccount => { + tx_by_addr::InstructionErrorType::UninitializedAccount + } + InstructionError::UnbalancedInstruction => { + tx_by_addr::InstructionErrorType::UnbalancedInstruction + } + InstructionError::ModifiedProgramId => { + tx_by_addr::InstructionErrorType::ModifiedProgramId + } + InstructionError::ExternalAccountLamportSpend => { + tx_by_addr::InstructionErrorType::ExternalAccountLamportSpend + } + InstructionError::ExternalAccountDataModified => { + tx_by_addr::InstructionErrorType::ExternalAccountDataModified + } + InstructionError::ReadonlyLamportChange => { + tx_by_addr::InstructionErrorType::ReadonlyLamportChange + } + InstructionError::ReadonlyDataModified => { + tx_by_addr::InstructionErrorType::ReadonlyDataModified + } + InstructionError::DuplicateAccountIndex => { + tx_by_addr::InstructionErrorType::DuplicateAccountIndex + } + InstructionError::ExecutableModified => { + tx_by_addr::InstructionErrorType::ExecutableModified + } + InstructionError::RentEpochModified => { + tx_by_addr::InstructionErrorType::RentEpochModified + } + InstructionError::NotEnoughAccountKeys => { + tx_by_addr::InstructionErrorType::NotEnoughAccountKeys + } + InstructionError::AccountDataSizeChanged => { + tx_by_addr::InstructionErrorType::AccountDataSizeChanged + } + InstructionError::AccountNotExecutable => { + tx_by_addr::InstructionErrorType::AccountNotExecutable + } + InstructionError::AccountBorrowFailed => { + tx_by_addr::InstructionErrorType::AccountBorrowFailed + } + InstructionError::AccountBorrowOutstanding => { + tx_by_addr::InstructionErrorType::AccountBorrowOutstanding + } + InstructionError::DuplicateAccountOutOfSync => { + tx_by_addr::InstructionErrorType::DuplicateAccountOutOfSync + } + InstructionError::Custom(_) => tx_by_addr::InstructionErrorType::Custom, + InstructionError::InvalidError => { + tx_by_addr::InstructionErrorType::InvalidError + } + InstructionError::ExecutableDataModified => { + tx_by_addr::InstructionErrorType::ExecutableDataModified + } + InstructionError::ExecutableLamportChange => { + tx_by_addr::InstructionErrorType::ExecutableLamportChange + } + InstructionError::ExecutableAccountNotRentExempt => { + tx_by_addr::InstructionErrorType::ExecutableAccountNotRentExempt + } + InstructionError::UnsupportedProgramId => { + tx_by_addr::InstructionErrorType::UnsupportedProgramId + } + InstructionError::CallDepth => tx_by_addr::InstructionErrorType::CallDepth, + InstructionError::MissingAccount => { + tx_by_addr::InstructionErrorType::MissingAccount + } + InstructionError::ReentrancyNotAllowed => { + tx_by_addr::InstructionErrorType::ReentrancyNotAllowed + } + InstructionError::MaxSeedLengthExceeded => { + tx_by_addr::InstructionErrorType::MaxSeedLengthExceeded + } + InstructionError::InvalidSeeds => { + tx_by_addr::InstructionErrorType::InvalidSeeds + } + InstructionError::InvalidRealloc => { + tx_by_addr::InstructionErrorType::InvalidRealloc + } + InstructionError::ComputationalBudgetExceeded => { + tx_by_addr::InstructionErrorType::ComputationalBudgetExceeded + } + InstructionError::PrivilegeEscalation => { + tx_by_addr::InstructionErrorType::PrivilegeEscalation + } + InstructionError::ProgramEnvironmentSetupFailure => { + tx_by_addr::InstructionErrorType::ProgramEnvironmentSetupFailure + } + InstructionError::ProgramFailedToComplete => { + tx_by_addr::InstructionErrorType::ProgramFailedToComplete + } + InstructionError::ProgramFailedToCompile => { + tx_by_addr::InstructionErrorType::ProgramFailedToCompile + } + InstructionError::Immutable => tx_by_addr::InstructionErrorType::Immutable, + InstructionError::IncorrectAuthority => { + tx_by_addr::InstructionErrorType::IncorrectAuthority + } + InstructionError::BorshIoError(_) => { + tx_by_addr::InstructionErrorType::BorshIoError + } + InstructionError::AccountNotRentExempt => { + tx_by_addr::InstructionErrorType::AccountNotRentExempt + } + InstructionError::InvalidAccountOwner => { + tx_by_addr::InstructionErrorType::InvalidAccountOwner + } + InstructionError::ArithmeticOverflow => { + tx_by_addr::InstructionErrorType::ArithmeticOverflow + } + InstructionError::UnsupportedSysvar => { + tx_by_addr::InstructionErrorType::UnsupportedSysvar + } + InstructionError::IllegalOwner => { + tx_by_addr::InstructionErrorType::IllegalOwner + } + InstructionError::MaxAccountsDataAllocationsExceeded => { + tx_by_addr::InstructionErrorType::MaxAccountsDataAllocationsExceeded + } + InstructionError::MaxAccountsExceeded => { + tx_by_addr::InstructionErrorType::MaxAccountsExceeded + } + InstructionError::MaxInstructionTraceLengthExceeded => { + tx_by_addr::InstructionErrorType::MaxInstructionTraceLengthExceeded + } + InstructionError::BuiltinProgramsMustConsumeComputeUnits => { + tx_by_addr::InstructionErrorType::BuiltinProgramsMustConsumeComputeUnits + } + } as i32, + custom: match instruction_error { + InstructionError::Custom(custom) => { + Some(tx_by_addr::CustomError { custom: *custom }) + } + _ => None, + }, + responsible_program_address_bytes: responsible_program_address + .map(|p| p.to_bytes()) + .map(Into::into), + }), _ => None, }, transaction_details: match transaction_error { @@ -1266,7 +1303,7 @@ impl From for EntrySummary { #[cfg(test)] mod test { - use {super::*, enum_iterator::all}; + use {super::*, bincode, enum_iterator::all, serde_derive::Deserialize, test_case::test_case}; #[test] fn test_reward_type_encode() { @@ -1477,8 +1514,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::AccountAlreadyInitialized); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::AccountAlreadyInitialized, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1486,8 +1527,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::AccountBorrowFailed); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::AccountBorrowFailed, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1495,8 +1540,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::AccountBorrowOutstanding); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::AccountBorrowOutstanding, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1504,8 +1553,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::AccountDataSizeChanged); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::AccountDataSizeChanged, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1513,8 +1566,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::AccountDataTooSmall); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::AccountDataTooSmall, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1522,8 +1579,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::AccountNotExecutable); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::AccountNotExecutable, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1531,7 +1592,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = TransactionError::InstructionError(10, InstructionError::CallDepth); + let transaction_error = TransactionError::InstructionError { + err: InstructionError::CallDepth, + inner_instruction_index: Some(42), + outer_instruction_index: 10, + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1539,8 +1605,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ComputationalBudgetExceeded); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ComputationalBudgetExceeded, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1548,8 +1618,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::DuplicateAccountIndex); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::DuplicateAccountIndex, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1557,8 +1631,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::DuplicateAccountOutOfSync); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::DuplicateAccountOutOfSync, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1566,10 +1644,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = TransactionError::InstructionError( - 10, - InstructionError::ExecutableAccountNotRentExempt, - ); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ExecutableAccountNotRentExempt, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1577,8 +1657,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ExecutableDataModified); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ExecutableDataModified, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1586,8 +1670,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ExecutableLamportChange); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ExecutableLamportChange, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1595,8 +1683,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ExecutableModified); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ExecutableModified, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1604,8 +1696,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ExternalAccountDataModified); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ExternalAccountDataModified, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1613,8 +1709,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ExternalAccountLamportSpend); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ExternalAccountLamportSpend, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1622,8 +1722,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::GenericError); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::GenericError, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1631,7 +1735,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = TransactionError::InstructionError(10, InstructionError::Immutable); + let transaction_error = TransactionError::InstructionError { + err: InstructionError::Immutable, + inner_instruction_index: Some(42), + outer_instruction_index: 10, + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1639,8 +1748,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::IncorrectAuthority); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::IncorrectAuthority, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1648,8 +1761,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::IncorrectProgramId); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::IncorrectProgramId, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1657,8 +1774,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::InsufficientFunds); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::InsufficientFunds, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1666,8 +1787,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::InvalidAccountData); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::InvalidAccountData, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1675,8 +1800,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::InvalidArgument); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::InvalidArgument, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1684,8 +1813,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::InvalidError); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::InvalidError, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1693,8 +1826,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::InvalidInstructionData); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::InvalidInstructionData, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1702,8 +1839,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::InvalidRealloc); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::InvalidRealloc, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1711,8 +1852,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::InvalidSeeds); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::InvalidSeeds, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1720,8 +1865,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::MaxSeedLengthExceeded); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::MaxSeedLengthExceeded, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1729,8 +1878,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::MissingAccount); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::MissingAccount, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1738,8 +1891,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::MissingRequiredSignature); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::MissingRequiredSignature, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1747,8 +1904,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ModifiedProgramId); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ModifiedProgramId, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1756,8 +1917,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::NotEnoughAccountKeys); + let transaction_error = TransactionError::InstructionError { + err: InstructionError::NotEnoughAccountKeys, + inner_instruction_index: None, + outer_instruction_index: 10, + responsible_program_address: None, + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1765,8 +1930,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::PrivilegeEscalation); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::PrivilegeEscalation, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1774,10 +1943,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = TransactionError::InstructionError( - 10, - InstructionError::ProgramEnvironmentSetupFailure, - ); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ProgramEnvironmentSetupFailure, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1785,8 +1956,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ProgramFailedToCompile); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ProgramFailedToCompile, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1794,8 +1969,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ProgramFailedToComplete); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ProgramFailedToComplete, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1803,8 +1982,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ReadonlyDataModified); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ReadonlyDataModified, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1812,8 +1995,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ReadonlyLamportChange); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ReadonlyLamportChange, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1821,8 +2008,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::ReentrancyNotAllowed); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::ReentrancyNotAllowed, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1830,8 +2021,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::RentEpochModified); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::RentEpochModified, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1839,8 +2034,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::UnbalancedInstruction); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::UnbalancedInstruction, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1848,8 +2047,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::UninitializedAccount); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::UninitializedAccount, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1857,8 +2060,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::UnsupportedProgramId); + let transaction_error = TransactionError::InstructionError { + outer_instruction_index: 10, + err: InstructionError::UnsupportedProgramId, + inner_instruction_index: Some(41), + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1866,8 +2073,12 @@ mod test { tx_by_addr_transaction_error.try_into().unwrap() ); - let transaction_error = - TransactionError::InstructionError(10, InstructionError::Custom(10)); + let transaction_error = TransactionError::InstructionError { + err: InstructionError::Custom(10), + inner_instruction_index: Some(42), + outer_instruction_index: 10, + responsible_program_address: Some(Pubkey::new_unique()), + }; let tx_by_addr_transaction_error: tx_by_addr::TransactionError = transaction_error.clone().into(); assert_eq!( @@ -1902,7 +2113,7 @@ mod test { #[test] fn test_error_enums() { - let ix_index = 1; + let ix_index: u32 = 1; let custom_error = 42; for error in all::() { match error { @@ -1931,6 +2142,9 @@ mod test { index: ix_index, error: ix_error as i32, custom: None, + responsible_program_address_bytes: Some( + Pubkey::new_unique().as_array().into(), + ), }), transaction_details: None, }; @@ -1948,6 +2162,9 @@ mod test { custom: Some(tx_by_addr::CustomError { custom: custom_error, }), + responsible_program_address_bytes: Some( + Pubkey::new_unique().as_array().into(), + ), }), transaction_details: None, }; @@ -1972,4 +2189,220 @@ mod test { } } } + + #[test_case(TransactionError::InsufficientFundsForFee; "Typical")] + #[test_case(TransactionError::InstructionError{ + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: Some(41), + outer_instruction_index: 42, + responsible_program_address: Some(Pubkey::new_unique()), + }; "Special case (InstructionError)")] + fn test_serialize_transaction_error_to_generated_transaction_error_round_trip( + err: TransactionError, + ) { + let serialized: generated::TransactionError = err.clone().into(); + let deserialized: TransactionError = serialized.into(); + assert_eq!(deserialized, err); + } + + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + /* Missing data that was introduced in Agave 2.3 */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: None, + }; + "Legacy error with only outer instruction index and error" + )] + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + 0, /* Responsible program address - None */ + 0, /* Inner instruction index - None */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: None, + }; + "Modern error with only outer instruction index and error" + )] + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + /* Responsible program address - Some(4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw) */ + 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 0, /* Inner instruction index - None */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: Some(Pubkey::from_str_const("4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw")), + }; + "Modern error with outer instruction index, error, and responsible program address" + )] + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + 0, /* Responsible program address - None */ + 1, 41, /* Inner instruction index - Some(41) */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: Some(41), + outer_instruction_index: 42, + responsible_program_address: None, + }; + "Modern error with outer instruction index, error, and inner instruction index" + )] + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + /* Responsible program address - Some(4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw) */ + 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 1, 41, /* Inner instruction index - Some(41) */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: Some(41), + outer_instruction_index: 42, + responsible_program_address: Some(Pubkey::from_str_const("4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw")), + }; + "Modern error with everything" + )] + fn test_deserialize_transaction_error_instruction_error_forward_compatibility( + stored_transaction_error_bytes: Vec, + expected_transaction_error: TransactionError, + ) { + let legacy_stored_transaction_error = generated::TransactionError { + err: stored_transaction_error_bytes, + }; + let deserialized: TransactionError = legacy_stored_transaction_error.into(); + assert_eq!(deserialized, expected_transaction_error,); + } + + #[test] + /// This test exists to ensure that if we store data in blockstore in the modern format that it + /// can still be read by legacy software. The legacy software should simply ignore the new data + /// that comes after what it believes to be the EOF. + fn test_deserialize_transaction_error_instruction_error_backward_compatibility() { + #[derive(Debug, Deserialize, PartialEq)] + enum LegacyTransactionError { + Placeholder0, + Placeholder1, + Placeholder2, + Placeholder3, + Placeholder4, + Placeholder5, + Placeholder6, + Placeholder7, + InstructionError(u8, InstructionError), + } + let modern_stored_transaction_error_bytes = vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + 0, 0, /* Extra stuff the legacy enum does not expect */ + ]; + let deserialized: LegacyTransactionError = + bincode::deserialize(&modern_stored_transaction_error_bytes) + .expect("Failed to deserialize modern byte format to legacy transaction error"); + assert_eq!( + deserialized, + LegacyTransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef)), + ); + } + + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + 0, /* Responsible program address - None */ + 0, /* Inner instruction index - None */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: None, + }; + "Outer instruction index and error" + )] + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + /* Responsible program address - Some(4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw) */ + 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 0, /* Inner instruction index - None */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: Some(Pubkey::from_str_const("4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw")), + }; + "Outer instruction index, error, and responsible program address" + )] + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + 0, /* Responsible program address - None */ + 1, 41, /* Inner instruction index - Some(41) */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: Some(41), + outer_instruction_index: 42, + responsible_program_address: None, + }; + "Outer instruction index, error, and inner instruction index" + )] + #[test_case( + vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, 239, 190, 173, 222, /* InstructionError::Custom(0xdeadbeef) */ + /* Responsible program address - Some(4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw) */ + 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 1, 41, /* Inner instruction index - Some(41) */ + ], + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: Some(41), + outer_instruction_index: 42, + responsible_program_address: Some(Pubkey::from_str_const("4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw")), + }; + "Error with everything" + )] + fn test_serialize_transaction_error_instruction_error_backward_compatibility( + expected_stored_transaction_error_bytes: Vec, + transaction_error: TransactionError, + ) { + let stored_transaction_error: StoredTransactionError = transaction_error.into(); + assert_eq!( + stored_transaction_error.0, + expected_stored_transaction_error_bytes, + ); + } } diff --git a/storage-proto/src/lib.rs b/storage-proto/src/lib.rs index b2f22a25b2ff72..a920118e7032af 100644 --- a/storage-proto/src/lib.rs +++ b/storage-proto/src/lib.rs @@ -7,11 +7,11 @@ use { solana_message::v0::LoadedAddresses, solana_serde::default_on_eof, solana_transaction_context::TransactionReturnData, - solana_transaction_error::TransactionResult as Result, + solana_transaction_error::{TransactionError, TransactionResult as Result}, solana_transaction_status::{ InnerInstructions, Reward, RewardType, TransactionStatusMeta, TransactionTokenBalance, }, - std::str::FromStr, + std::{io::Cursor, str::FromStr}, }; pub mod convert; @@ -109,6 +109,64 @@ impl From for StoredTokenAmount { } } +struct StoredTransactionError(Vec); + +impl From for TransactionError { + fn from(value: StoredTransactionError) -> Self { + let bytes = value.0; + match &bytes.as_slice() { + [8, 0, 0, 0, ..] => { + let mut cursor = Cursor::new(&bytes); + cursor.set_position(4); // Skip past the u32 that indicates the variant index + + // Order is important here; this is the order in which `TransactionError` fields are + // serialized into storage, so forever must be the order in which they're read out. + let outer_instruction_index = bincode::deserialize_from(&mut cursor).unwrap(); + let err = bincode::deserialize_from(&mut cursor).unwrap(); + let responsible_program_address = + // If we've reached the end of the buffer, this will materialize as `None`. + // This exists for backward compatibility with old stored data. + bincode::deserialize_from(&mut cursor).ok().flatten(); + let inner_instruction_index = + // If we've reached the end of the buffer, this will materialize as `None` + // This exists for backward compatibility with old stored data. + bincode::deserialize_from(&mut cursor).ok().flatten(); + + TransactionError::InstructionError { + err, + inner_instruction_index, + outer_instruction_index, + responsible_program_address, + } + } + _ => bincode::deserialize::(&bytes) + .expect("transaction error to deserialize from bytes"), + } + } +} + +impl From for StoredTransactionError { + fn from(value: TransactionError) -> Self { + let bytes = match value { + TransactionError::InstructionError { + err, + inner_instruction_index, + outer_instruction_index, + responsible_program_address, + } => bincode::serialize(&( + 8_u32, /* Variant index of `TransactionError::InstructionError` */ + outer_instruction_index, + err, + responsible_program_address, + inner_instruction_index, + )), + err => bincode::serialize(&err), + } + .expect("transaction error to serialize to bytes"); + StoredTransactionError(bytes) + } +} + #[derive(Serialize, Deserialize)] pub struct StoredTransactionTokenBalance { pub account_index: u8, @@ -265,3 +323,48 @@ impl TryFrom for StoredTransactionStatusMeta { }) } } + +#[cfg(test)] +mod tests { + use { + crate::StoredTransactionError, solana_instruction::error::InstructionError, + solana_pubkey::Pubkey, solana_transaction_error::TransactionError, test_case::test_case, + }; + + #[test_case(TransactionError::InsufficientFundsForFee; "Typical")] + #[test_case(TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: Some(41), + outer_instruction_index: 42, + responsible_program_address: Some(Pubkey::new_unique()), + }; "Special case (InstructionError)")] + fn test_serialize_transaction_error_to_stored_transaction_error_round_trip( + err: TransactionError, + ) { + let serialized: StoredTransactionError = err.clone().into(); + let deserialized: TransactionError = serialized.into(); + assert_eq!(deserialized, err); + } + + #[test] + fn test_deserialize_stored_transaction_error_instruction_error_from_legacy_data() { + let legacy_stored_transaction = StoredTransactionError(vec![ + 8, 0, 0, 0, /* Eighth enum variant - `InstructionError` */ + 42, /* Outer instruction index */ + 25, 0, 0, 0, /* InstructionError::Custom */ + /* 0xdeadbeef */ + 239, 190, 173, 222, + /* Missing data that was introduced in Agave 2.3 */ + ]); + let deserialized: TransactionError = legacy_stored_transaction.into(); + assert_eq!( + deserialized, + TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: None + } + ); + } +} diff --git a/svm/Cargo.toml b/svm/Cargo.toml index 72ca6cb92026c8..d6d0c41c3ca95b 100644 --- a/svm/Cargo.toml +++ b/svm/Cargo.toml @@ -54,7 +54,7 @@ solana-system-interface = { workspace = true } solana-sysvar-id = { workspace = true } solana-timings = { workspace = true } solana-transaction-context = { workspace = true } -solana-transaction-error = { workspace = true } +solana-transaction-error = { workspace = true, features = ["serde"] } solana-type-overrides = { workspace = true } spl-generic-token = { workspace = true } thiserror = { workspace = true } @@ -66,7 +66,6 @@ name = "solana_svm" [dev-dependencies] agave-feature-set = { workspace = true } agave-reserved-account-keys = { workspace = true } -assert_matches = { workspace = true } bincode = { workspace = true } ed25519-dalek = { workspace = true } libsecp256k1 = { workspace = true } diff --git a/svm/src/message_processor.rs b/svm/src/message_processor.rs index 978aa735ce3f75..84c01f7128df9d 100644 --- a/svm/src/message_processor.rs +++ b/svm/src/message_processor.rs @@ -1,4 +1,5 @@ use { + log::debug, solana_measure::measure_us, solana_program_runtime::invoke_context::InvokeContext, solana_svm_transaction::svm_message::SVMMessage, @@ -20,7 +21,7 @@ pub(crate) fn process_message( accumulated_consumed_units: &mut u64, ) -> Result<(), TransactionError> { debug_assert_eq!(program_indices.len(), message.num_instructions()); - for (top_level_instruction_index, ((program_id, instruction), program_indices)) in message + for (outer_instruction_index, ((program_id, instruction), program_indices)) in message .program_instructions_iter() .zip(program_indices.iter()) .enumerate() @@ -90,7 +91,28 @@ pub(crate) fn process_message( .total_us += process_instruction_us; result.map_err(|err| { - TransactionError::InstructionError(top_level_instruction_index as u8, err) + let error_attribution = invoke_context.get_first_error_attribution(); + let (inner_instruction_index, responsible_program_address) = match error_attribution { + Some(attr) => ( + attr.inner_instruction_index.map(|i| i as u8), + Some(attr.responsible_program_address), + ), + None => { + debug!( + "FIXME: It should be impossible to have encountered an `Err` here without \ + the `TransactionContext` having accumulated information about the program \ + that threw its first-seen error. Error was: {:?}", + err, + ); + (None, None) + } + }; + TransactionError::InstructionError { + err, + inner_instruction_index, + outer_instruction_index: outer_instruction_index as u8, + responsible_program_address, + } })?; } Ok(()) @@ -320,10 +342,12 @@ mod tests { ); assert_eq!( result, - Err(TransactionError::InstructionError( - 0, - InstructionError::ReadonlyLamportChange - )) + Err(TransactionError::InstructionError { + outer_instruction_index: 0, + err: InstructionError::ReadonlyLamportChange, + inner_instruction_index: None, + responsible_program_address: Some(mock_system_program_id), + }), ); let message = new_sanitized_message(Message::new_with_compiled_instructions( @@ -364,10 +388,12 @@ mod tests { ); assert_eq!( result, - Err(TransactionError::InstructionError( - 0, - InstructionError::ReadonlyDataModified - )) + Err(TransactionError::InstructionError { + outer_instruction_index: 0, + err: InstructionError::ReadonlyDataModified, + inner_instruction_index: None, + responsible_program_address: Some(mock_system_program_id), + }), ); } @@ -500,10 +526,12 @@ mod tests { ); assert_eq!( result, - Err(TransactionError::InstructionError( - 0, - InstructionError::AccountBorrowFailed - )) + Err(TransactionError::InstructionError { + outer_instruction_index: 0, + err: InstructionError::AccountBorrowFailed, + inner_instruction_index: None, + responsible_program_address: Some(mock_program_id), + }), ); // Try to borrow mut the same account in a safe way @@ -702,10 +730,12 @@ mod tests { assert_eq!( result, - Err(TransactionError::InstructionError( - 3, - InstructionError::Custom(0xbabb1e) - )) + Err(TransactionError::InstructionError { + outer_instruction_index: 3, + err: InstructionError::Custom(0xbabb1e), + inner_instruction_index: None, + responsible_program_address: Some(mock_program_id), + }), ); assert_eq!(transaction_context.get_instruction_trace_length(), 4); } diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index 6e7c4229c74800..46d6b2d1da6116 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -1104,6 +1104,7 @@ mod tests { rollback_accounts::RollbackAccounts, }, agave_reserved_account_keys::ReservedAccountKeys, + itertools::Itertools, solana_account::{create_account_shared_data_for_test, WritableAccount}, solana_clock::Clock, solana_compute_budget_interface::ComputeBudgetInstruction, @@ -1111,10 +1112,12 @@ mod tests { solana_fee_calculator::FeeCalculator, solana_fee_structure::FeeDetails, solana_hash::Hash, + solana_instruction::{AccountMeta, Instruction}, solana_keypair::Keypair, solana_message::{LegacyMessage, Message, MessageHeader, SanitizedMessage}, solana_nonce as nonce, solana_program_runtime::{ + declare_process_instruction, execution_budget::{ SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionBudget, }, @@ -1123,12 +1126,15 @@ mod tests { solana_rent::Rent, solana_rent_collector::{RentCollector, RENT_EXEMPT_RENT_EPOCH}, solana_rent_debits::RentDebits, + solana_sdk::instruction::InstructionError, solana_sdk_ids::{bpf_loader, system_program, sysvar}, solana_signature::Signature, solana_svm_callback::{AccountState, InvokeContextCallback}, solana_transaction::{sanitized::SanitizedTransaction, Transaction}, solana_transaction_context::TransactionContext, solana_transaction_error::{TransactionError, TransactionError::DuplicateInstruction}, + std::convert::TryInto, + std::sync::LazyLock, test_case::test_case, }; @@ -1327,6 +1333,243 @@ mod tests { ); } + #[derive(PartialEq)] + enum ThrowLocation { + // Throw before calling nested programs via CPI. + LeadingEdge, + // Throw after calling nested programs via CPI. + TrailingEdge, + } + + #[test_case(0, ThrowLocation::LeadingEdge, 0, None; "Throw in program 0 before CPI")] + #[test_case(0, ThrowLocation::TrailingEdge, 0, None; "Throw in program 0 after CPI")] + #[test_case(1, ThrowLocation::LeadingEdge, 0, Some(0); "Throw in program 1 before CPI")] + #[test_case(1, ThrowLocation::TrailingEdge, 0, Some(0); "Throw in program 1 after CPI")] + #[test_case(2, ThrowLocation::LeadingEdge, 0, Some(1); "Throw in program 2 before CPI")] + #[test_case(2, ThrowLocation::TrailingEdge, 0, Some(1); "Throw in program 2 after CPI")] + #[test_case(3, ThrowLocation::LeadingEdge, 0, Some(2); "Throw in program 3 before CPI")] + #[test_case(3, ThrowLocation::TrailingEdge, 0, Some(2); "Throw in program 3 after CPI")] + #[test_case(4, ThrowLocation::LeadingEdge, 0, Some(3); "Throw in program 4 before CPI")] + #[test_case(4, ThrowLocation::TrailingEdge, 0, Some(3); "Throw in program 4 after CPI")] + #[test_case(5, ThrowLocation::LeadingEdge, 0, Some(4); "Throw in program 5 before CPI")] + #[test_case(5, ThrowLocation::TrailingEdge, 0, Some(4); "Throw in program 5 after CPI")] + #[test_case(6, ThrowLocation::LeadingEdge, 0, Some(5); "Throw in program 6 before CPI")] + #[test_case(6, ThrowLocation::TrailingEdge, 0, Some(5); "Throw in program 6 after CPI")] + #[test_case(7, ThrowLocation::LeadingEdge, 1, None; "Throw in program 7 before CPI")] + #[test_case(7, ThrowLocation::TrailingEdge, 1, None; "Throw in program 7 after CPI")] + #[test_case(8, ThrowLocation::LeadingEdge, 1, Some(0); "Throw in program 8 before CPI")] + #[test_case(8, ThrowLocation::TrailingEdge, 1, Some(0); "Throw in program 8 after CPI")] + #[test_case(9, ThrowLocation::LeadingEdge, 1, Some(1); "Throw in program 9 before CPI")] + #[test_case(9, ThrowLocation::TrailingEdge, 1, Some(1); "Throw in program 9 after CPI")] + #[test_case(10, ThrowLocation::LeadingEdge, 1, Some(2); "Throw in program 10 before CPI")] + #[test_case(10, ThrowLocation::TrailingEdge, 1, Some(2); "Throw in program 10 after CPI")] + #[test_case(11, ThrowLocation::LeadingEdge, 1, Some(3); "Throw in program 11 before CPI")] + #[test_case(11, ThrowLocation::TrailingEdge, 1, Some(3); "Throw in program 11 after CPI")] + #[test_case(12, ThrowLocation::LeadingEdge, 1, Some(4); "Throw in program 12 before CPI")] + #[test_case(12, ThrowLocation::TrailingEdge, 1, Some(4); "Throw in program 12 after CPI")] + #[test_case(13, ThrowLocation::LeadingEdge, 1, Some(5); "Throw in program 13 before CPI")] + #[test_case(13, ThrowLocation::TrailingEdge, 1, Some(5); "Throw in program 13 after CPI")] + fn test_instruction_error_carries_responsible_program_account_index( + account_index_of_program_that_should_throw_exception: u8, + throw_location: ThrowLocation, + expected_outer_instruction_index: u8, + expected_inner_instruction_index: Option, + ) { + static PROGRAM_ADDRESSES: LazyLock<[Pubkey; 14]> = + LazyLock::new(|| std::array::from_fn(|_| Pubkey::new_unique())); + // This mock program takes in a list of programs and two bytes of data: + // (1) the index of the program that should throw an error + // (2) the index of the program being called + // + // The programs get executed - two at each CPI call depth - like this: + // + // Program 0 + // -- Program 1 + // -- Program 2 + // ---- Program 3 + // ---- Program 4 + // ------ Program 5 + // ------ Program 6 + // Program 7 + // -- Program 8 + // -- Program 9 + // ---- Program 10 + // ---- Program 11 + // ------ Program 12 + // ------ Program 13 + declare_process_instruction!(MockBuiltin, 1 /* cu_to_consume */, |invoke_context| { + let transaction_context = invoke_context.transaction_context.clone(); + let instruction_context = transaction_context.get_current_instruction_context()?; + + let stack_height: u8 = invoke_context.get_stack_height().try_into().unwrap(); + let instruction_data = instruction_context.get_instruction_data(); + let top_level_instruction_index = instruction_data[0] as usize; + let index_of_program_that_must_throw_exception = instruction_data[1] as usize; + let current_program_index = instruction_data[2] as usize; + let throw_location = match instruction_data[3] { + 0 => ThrowLocation::LeadingEdge, + 1 => ThrowLocation::TrailingEdge, + _ => panic!("Unrecognized value for `throw_location` in instruction data"), + }; + let this_program_should_throw = + current_program_index == index_of_program_that_must_throw_exception; + + // Ensure that the program the instruction data claims to be running is the program that + // is actually running. + let actual_program_address = *instruction_context + .get_last_program_key(&transaction_context) + .unwrap(); + assert_eq!( + actual_program_address, + PROGRAM_ADDRESSES[current_program_index], + "The address of the program at index {} that the instruction data claims to be running ({}) is not the program that is actually running ({})", + current_program_index, + PROGRAM_ADDRESSES[current_program_index], + actual_program_address, + ); + + if this_program_should_throw && throw_location == ThrowLocation::LeadingEdge { + return Err(InstructionError::Custom(0xdeadbeef)); + } + + let mut last_result = Ok(()); + if stack_height < 4 && current_program_index % 2 == top_level_instruction_index % 2 { + // Every odd program does CPIs unless it has reached the maximum CPI stack depth. + for ii in 1..3 { + let next_program_index = current_program_index.checked_add(ii).unwrap(); + let last_program_index = next_program_index.next_multiple_of(7); + let next_program_address = PROGRAM_ADDRESSES[next_program_index]; + let accounts = (next_program_index..last_program_index) + .map(|index| AccountMeta::new_readonly(PROGRAM_ADDRESSES[index], false)) + .collect_vec(); + last_result = invoke_context.native_invoke( + Instruction::new_with_bytes( + next_program_address, + &[ + instruction_data[0], + instruction_data[1], + next_program_index as u8, + instruction_data[3], + ], + accounts, + ) + .into(), + &[], + ); + if last_result.is_err() { + return last_result; + } + } + } + + if this_program_should_throw && throw_location == ThrowLocation::TrailingEdge { + return Err(InstructionError::Custom(0xdeadbeef)); + } + + return last_result; + }); + + // ======================================================= + // BEGIN: Create a transaction that calls the mock program + // ======================================================= + const FIRST_INSTRUCTION_PROGRAM_INDEX: usize = 0; + const SECOND_INSTRUCTION_PROGRAM_INDEX: usize = 7; + let first_accounts = (FIRST_INSTRUCTION_PROGRAM_INDEX..SECOND_INSTRUCTION_PROGRAM_INDEX) + .map(|index| AccountMeta::new_readonly(PROGRAM_ADDRESSES[index], false)) + .collect_vec(); + let second_accounts = (SECOND_INSTRUCTION_PROGRAM_INDEX..PROGRAM_ADDRESSES.len()) + .map(|index| AccountMeta::new_readonly(PROGRAM_ADDRESSES[index], false)) + .collect_vec(); + let throw_location_data = match throw_location { + ThrowLocation::LeadingEdge => 0, + ThrowLocation::TrailingEdge => 1, + }; + let message: Message = Message::new( + &[ + Instruction::new_with_bytes( + PROGRAM_ADDRESSES[FIRST_INSTRUCTION_PROGRAM_INDEX], + &[ + 0, /* top-level index of this instruction */ + account_index_of_program_that_should_throw_exception, + FIRST_INSTRUCTION_PROGRAM_INDEX as u8, /* index of program being called */ + throw_location_data, + ], + first_accounts, + ), + Instruction::new_with_bytes( + PROGRAM_ADDRESSES[SECOND_INSTRUCTION_PROGRAM_INDEX], + &[ + 1, /* top-level index of this instruction */ + account_index_of_program_that_should_throw_exception, + SECOND_INSTRUCTION_PROGRAM_INDEX as u8, /* index of program being called */ + throw_location_data, + ], + second_accounts, + ), + ], + None, + ); + let sanitized_message = new_unchecked_sanitized_message(message); + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + for index in 0..PROGRAM_ADDRESSES.len() { + program_cache_for_tx_batch.replenish( + PROGRAM_ADDRESSES[index], + Arc::new(ProgramCacheEntry::new_builtin(0, 0, MockBuiltin::vm)), + ); + } + let batch_processor = TransactionBatchProcessor::::default(); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + let mut mock_program_account = AccountSharedData::new(1, 0, &native_loader::id()); + mock_program_account.set_executable(true); + let loaded_transaction = LoadedTransaction { + accounts: (0..PROGRAM_ADDRESSES.len()) + .map(|index| (PROGRAM_ADDRESSES[index], mock_program_account.clone())) + .collect_vec(), + program_indices: vec![ + vec![FIRST_INSTRUCTION_PROGRAM_INDEX as u16], + vec![SECOND_INSTRUCTION_PROGRAM_INDEX as u16], + ], + fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget: SVMTransactionExecutionBudget::default(), + rent: 0, + rent_debits: RentDebits::default(), + loaded_accounts_data_size: 32, + }; + // ======================================================= + // END: Create a transaction that calls the mock program + // ======================================================= + + let result = batch_processor.execute_loaded_transaction( + &MockBankCallback::default(), + &sanitized_transaction, + loaded_transaction, + &mut ExecuteTimings::default(), + &mut TransactionErrorMetrics::default(), + &mut program_cache_for_tx_batch, + &TransactionProcessingEnvironment::default(), + &TransactionProcessingConfig::default(), + ); + + let status = result.execution_details.status; + assert_eq!( + status.err(), + Some(TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: expected_inner_instruction_index, + outer_instruction_index: expected_outer_instruction_index, + responsible_program_address: Some( + PROGRAM_ADDRESSES + [account_index_of_program_that_should_throw_exception as usize] + ), + }) + ) + } + #[test] fn test_execute_loaded_transaction_recordings() { // Setting all the arguments correctly is too burdensome for testing diff --git a/transaction-context/src/lib.rs b/transaction-context/src/lib.rs index 8d4196e04ff9c2..f6daeb1af7d3fe 100644 --- a/transaction-context/src/lib.rs +++ b/transaction-context/src/lib.rs @@ -377,6 +377,37 @@ impl TransactionContext { .ok_or(InstructionError::CallDepth) } + /// Returns the index of this instruction in a 'stack' of instructions. + /// + /// Useful for displaying instruction index paths (ie. for the second CPI of the third top level + /// instruction, displaying 3.2). + /// + /// # Example + /// + /// Observe how the index resets every time a new top-level instruction is called. + /// + /// * #1 Program A (no index) + /// * #1.1 CPI to Program B (index 0) + /// * #1.2 CPI to Program C (index 1) + /// * #1.3 CPI to Program D (index 2) + /// * #1.4 CPI to Program E (index 3) + /// * #1.5 CPI to Program F (index 4) + /// * #2 Program G (no index) + /// * #3 Program H (no index) + /// * #3.1 Program I (index 0) + pub fn get_current_inner_instruction_index(&self) -> Option { + match self.instruction_stack.as_slice() { + [trace_index_of_nearest_outer_instruction, .., index_of_current_inner_instruction] => { + Some( + index_of_current_inner_instruction + .saturating_sub(*trace_index_of_nearest_outer_instruction) + .saturating_sub(1), + ) + } + _ => None, + } + } + /// Pushes the next InstructionContext #[cfg(not(target_os = "solana"))] pub fn push(&mut self) -> Result<(), InstructionError> { @@ -1337,4 +1368,31 @@ mod tests { ); assert_eq!(build_transaction_context(account).push(), Ok(()),); } + + #[test] + fn test_get_current_inner_instruction_index_none_at_bottom_of_stack() { + let mut context = TransactionContext::new(vec![], Rent::default(), 1, 2); + context.push().unwrap(); // First outer instruction. + assert_eq!(context.get_current_inner_instruction_index(), None); + context.pop().unwrap(); + context.push().unwrap(); // Second outer instruction + assert_eq!(context.get_current_inner_instruction_index(), None); + } + + #[test] + fn test_get_current_inner_instruction_index() { + let mut context = TransactionContext::new(vec![], Rent::default(), 3, 5); + context.push().unwrap(); // First outer instruction + context.push().unwrap(); // First inner instruction (stack level 2) + assert_eq!(context.get_current_inner_instruction_index(), Some(0)); + context.push().unwrap(); // Second inner instruction (stack level 3) + assert_eq!(context.get_current_inner_instruction_index(), Some(1)); + context.pop().unwrap(); + context.push().unwrap(); // Third inner instruction (stack level 3) + assert_eq!(context.get_current_inner_instruction_index(), Some(2)); + context.pop().unwrap(); + context.pop().unwrap(); + context.push().unwrap(); // Fourth inner instruction (stack level 2) + assert_eq!(context.get_current_inner_instruction_index(), Some(3)); + } } diff --git a/transaction-status-client-types/Cargo.toml b/transaction-status-client-types/Cargo.toml index 7c1fec2a45b2b9..d0883ec1632ea1 100644 --- a/transaction-status-client-types/Cargo.toml +++ b/transaction-status-client-types/Cargo.toml @@ -18,7 +18,9 @@ serde_derive = { workspace = true } serde_json = { workspace = true } solana-account-decoder-client-types = { workspace = true } solana-commitment-config = { workspace = true } +solana-instruction = { workspace = true } solana-message = { workspace = true } +solana-pubkey = { workspace = true } solana-reward-info = { workspace = true, features = ["serde"] } solana-signature = { workspace = true, default-features = false } solana-transaction = { workspace = true, features = ["serde"] } @@ -26,5 +28,8 @@ solana-transaction-context = { workspace = true } solana-transaction-error = { workspace = true, features = ["serde"] } thiserror = { workspace = true } +[dev-dependencies] +test-case = { workspace = true } + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/transaction-status-client-types/src/lib.rs b/transaction-status-client-types/src/lib.rs index 6036f9c8fc37df..cb819762361dca 100644 --- a/transaction-status-client-types/src/lib.rs +++ b/transaction-status-client-types/src/lib.rs @@ -3,20 +3,28 @@ use { crate::option_serializer::OptionSerializer, base64::{prelude::BASE64_STANDARD, Engine}, core::fmt, + serde::{ + de::{self, Deserialize as DeserializeTrait}, + ser::{Serialize as SerializeTrait, SerializeTupleVariant}, + Deserializer, + }, serde_derive::{Deserialize, Serialize}, serde_json::Value, solana_account_decoder_client_types::token::UiTokenAmount, solana_commitment_config::CommitmentConfig, + solana_instruction::error::InstructionError, solana_message::{ compiled_instruction::CompiledInstruction, v0::{LoadedAddresses, MessageAddressTableLookup}, MessageHeader, }, + solana_pubkey::Pubkey, solana_reward_info::RewardType, solana_signature::Signature, solana_transaction::versioned::{TransactionVersion, VersionedTransaction}, solana_transaction_context::TransactionReturnData, solana_transaction_error::{TransactionError, TransactionResult}, + std::ops::Deref, thiserror::Error, }; pub mod option_serializer; @@ -226,12 +234,122 @@ impl From<&MessageAddressTableLookup> for UiAddressTableLookup { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UiTransactionError(pub TransactionError); + +impl Deref for UiTransactionError { + type Target = TransactionError; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for UiTransactionError { + fn from(value: TransactionError) -> Self { + UiTransactionError(value) + } +} + +impl From for TransactionError { + fn from(value: UiTransactionError) -> Self { + value.0 + } +} + +impl SerializeTrait for UiTransactionError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match &self.0 { + TransactionError::InstructionError { + err, + inner_instruction_index, + outer_instruction_index, + responsible_program_address, + } => { + let mut state = serializer.serialize_tuple_variant( + "TransactionError", + 8, + "InstructionError", + 4, + )?; + state.serialize_field(outer_instruction_index)?; + state.serialize_field(err)?; + state.serialize_field(&responsible_program_address.map(|p| p.to_string()))?; + state.serialize_field(inner_instruction_index)?; + state.end() + } + err => TransactionError::serialize(err, serializer), + } + } +} + +impl<'de> DeserializeTrait<'de> for UiTransactionError { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = serde_json::Value::deserialize(deserializer)?; + if let Some(obj) = value.as_object() { + if let Some(arr) = obj.get("InstructionError").and_then(|v| v.as_array()) { + let outer_instruction_index: u8 = arr[0] + .as_u64() + .expect("Expected the first element to be the `outer_instruction_index`") + as u8; + let err = InstructionError::deserialize(&arr[1]) + .expect("Expected the second element to deserialize as an `InstructionError`"); + let responsible_program_address = arr.get(2).and_then(|v| { + v.as_str().map(|s| { + s.parse::() + .expect("Expected the third element to parse as a `Pubkey`") + }) + }); + let inner_instruction_index: Option = + arr.get(3).and_then(|v| v.as_u64().map(|i| i as u8)); + return Ok(UiTransactionError(TransactionError::InstructionError { + err, + inner_instruction_index, + outer_instruction_index, + responsible_program_address, + })); + } + } + let err = TransactionError::deserialize(value).map_err(de::Error::custom)?; + Ok(UiTransactionError(err)) + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct UiTransactionResult(Result); + +impl Deref for UiTransactionResult { + type Target = Result; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for UiTransactionResult { + fn from(value: TransactionResult) -> Self { + UiTransactionResult(value.map_err(Into::into)) + } +} + +impl From> for TransactionResult { + fn from(value: UiTransactionResult) -> Self { + value.0.map_err(Into::into) + } +} + /// A duplicate representation of TransactionStatusMeta with `err` field #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UiTransactionStatusMeta { - pub err: Option, - pub status: TransactionResult<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302 + pub err: Option, + pub status: UiTransactionResult<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302 pub fee: u64, pub pre_balances: Vec, pub post_balances: Vec, @@ -285,8 +403,8 @@ pub struct UiTransactionStatusMeta { impl From for UiTransactionStatusMeta { fn from(meta: TransactionStatusMeta) -> Self { Self { - err: meta.status.clone().err(), - status: meta.status, + err: meta.status.clone().map_err(Into::into).err(), + status: meta.status.into(), fee: meta.fee, pre_balances: meta.pre_balances, post_balances: meta.post_balances, @@ -660,7 +778,12 @@ impl TransactionStatus { #[cfg(test)] mod test { - use {super::*, serde_json::json}; + use { + super::*, + serde_json::{from_value, json, to_value}, + solana_transaction_error::TransactionError, + test_case::test_case, + }; #[test] fn test_decode_invalid_transaction() { @@ -812,4 +935,84 @@ mod test { }"; test_serde::(json_input, expected_json_output); } + + #[test_case(TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: Some(41), + outer_instruction_index: 42, + responsible_program_address: Some(Pubkey::from_str_const("11111111111111111111111111111111")), + }, json!({ + "InstructionError": [42, { "Custom": 0xdeadbeef_u32 }, "11111111111111111111111111111111", 41], + }); "Custom `InstructionError`")] + #[test_case(TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: None, + }, json!({ + "InstructionError": [42, { "Custom": 0xdeadbeef_u32 }, null, null], + }); "Custom `InstructionError` with only `outer_instruction_index` and `err`")] + #[test_case(TransactionError::InsufficientFundsForRent { + account_index: 42, + }, json!({ + "InsufficientFundsForRent": { "account_index": 42 }, + }); "Pass-through struct error")] + #[test_case(TransactionError::InsufficientFundsForFee, json!("InsufficientFundsForFee"); "Pass-through TransactionError")] + fn test_serialize_ui_transaction_error( + transaction_error: TransactionError, + expected_serialization: Value, + ) { + let actual_serialization = to_value(UiTransactionError(transaction_error)) + .expect("Failed to serialize `UiTransactionError"); + assert_eq!(actual_serialization, expected_serialization); + } + + #[test_case(TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: Some(41), + outer_instruction_index: 42, + responsible_program_address: Some(Pubkey::from_str_const("11111111111111111111111111111111")), + }, json!({ + "InstructionError": [42, { "Custom": 0xdeadbeef_u32 }, "11111111111111111111111111111111", 41], + }); "Custom `InstructionError`")] + #[test_case(TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: None, + }, json!({ + "InstructionError": [42, { "Custom": 0xdeadbeef_u32 }, null, null], + }); "Custom `InstructionError` with only `outer_instruction_index` and `err`")] + #[test_case(TransactionError::InstructionError { + err: InstructionError::Custom(0xdeadbeef), + inner_instruction_index: None, + outer_instruction_index: 42, + responsible_program_address: None, + }, json!({ + "InstructionError": [42, { "Custom": 0xdeadbeef_u32, /* Legacy instruction errors are serialized with only two elements in the tuple */ }], + }); "Custom `InstructionError` with only two elements")] + #[test_case(TransactionError::InsufficientFundsForRent { + account_index: 42, + }, json!({ + "InsufficientFundsForRent": { "account_index": 42 }, + }); "Pass-through struct error")] + #[test_case(TransactionError::InsufficientFundsForFee, json!("InsufficientFundsForFee"); "Pass-through TransactionError")] + fn test_deserialize_ui_transaction_error( + expected_transaction_error: TransactionError, + serialized_value: Value, + ) { + let UiTransactionError(actual_transaction_error) = + from_value::(serialized_value) + .expect("Failed to deserialize `UiTransactionError"); + assert_eq!(actual_transaction_error, expected_transaction_error); + } + + #[test] + #[should_panic(expected = "Expected the third element to parse as a `Pubkey`")] + fn test_deserialize_ui_transaction_error_bad_address() { + from_value::(json!({ + "InstructionError": [0, "InsufficientFunds", "nOtApUbKeY1111111111111111111111", null], + })) + .expect("Failed to deserialize `UiTransactionError"); + } } diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 84e8a75fbefabb..4324f268be50bc 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -162,8 +162,8 @@ fn build_simple_ui_transaction_status_meta( show_rewards: bool, ) -> UiTransactionStatusMeta { UiTransactionStatusMeta { - err: meta.status.clone().err(), - status: meta.status, + err: meta.status.clone().map_err(Into::into).err(), + status: meta.status.into(), fee: meta.fee, pre_balances: meta.pre_balances, post_balances: meta.post_balances, @@ -196,8 +196,8 @@ fn parse_ui_transaction_status_meta( ) -> UiTransactionStatusMeta { let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses)); UiTransactionStatusMeta { - err: meta.status.clone().err(), - status: meta.status, + err: meta.status.clone().map_err(Into::into).err(), + status: meta.status.into(), fee: meta.fee, pre_balances: meta.pre_balances, post_balances: meta.post_balances,