From d61e69d85bb35ea6632013fc8f71d9dfbf53cd5b Mon Sep 17 00:00:00 2001 From: Siddharth Suresh Date: Wed, 18 Oct 2023 14:50:51 -0700 Subject: [PATCH] Add upgrade command line helper --- Cargo.lock | 279 +++++++++++++++++- docs/software/commands.md | 26 ++ src/main/CommandHandler.cpp | 44 +++ src/main/CommandHandler.h | 1 + src/main/CommandLine.cpp | 139 +++++++++ src/main/SettingsUpgradeUtils.cpp | 212 +++++++++++++ src/main/SettingsUpgradeUtils.h | 20 ++ src/main/dumpxdr.cpp | 107 ++++--- src/main/dumpxdr.h | 6 + src/rust/Cargo.toml | 8 + src/rust/src/contract.rs | 2 +- src/rust/src/host-dep-tree-curr.txt | 9 +- .../test/InvokeHostFunctionTests.cpp | 193 ++++++++++++ 13 files changed, 987 insertions(+), 59 deletions(-) create mode 100644 src/main/SettingsUpgradeUtils.cpp create mode 100644 src/main/SettingsUpgradeUtils.h diff --git a/Cargo.lock b/Cargo.lock index 98baa244e3..e7a6249e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "arbitrary" version = "1.3.2" @@ -74,6 +89,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + [[package]] name = "base64ct" version = "1.6.0" @@ -119,12 +140,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + [[package]] name = "const-oid" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cpufeatures" version = "0.2.8" @@ -224,6 +264,41 @@ dependencies = [ "syn", ] +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "der" version = "0.7.6" @@ -234,6 +309,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "serde", +] + [[package]] name = "derive_arbitrary" version = "1.3.2" @@ -326,6 +410,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "ethnum" version = "1.3.2" @@ -354,6 +444,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -423,11 +519,20 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hmac" @@ -438,6 +543,35 @@ dependencies = [ "digest", ] +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -455,7 +589,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", ] [[package]] @@ -667,7 +813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.3", ] [[package]] @@ -902,6 +1048,35 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64 0.21.4", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.2", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.10.7" @@ -970,6 +1145,21 @@ dependencies = [ "syn", ] +[[package]] +name = "soroban-env-common" +version = "0.0.17" +source = "git+https://github.com/stellar/rs-soroban-env?rev=66a3c504387da5bb5bb0b1cc4b0beb22c74fb252#66a3c504387da5bb5bb0b1cc4b0beb22c74fb252" +dependencies = [ + "crate-git-revision", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros 0.0.17", + "static_assertions", + "stellar-xdr 0.0.17", +] + [[package]] name = "soroban-env-common" version = "20.0.0-rc2" @@ -982,7 +1172,7 @@ dependencies = [ "soroban-env-macros 20.0.0-rc2 (git+https://github.com/stellar/rs-soroban-env?rev=2674d867d7c6aa4212abab05ff30e5804ff1db90)", "soroban-wasmi", "static_assertions", - "stellar-xdr", + "stellar-xdr 20.0.0-rc1", ] [[package]] @@ -997,7 +1187,7 @@ dependencies = [ "soroban-env-macros 20.0.0-rc2 (git+https://github.com/stellar/rs-soroban-env?rev=4f4a7f3d614ef65bba17ba6f56d4f378c2890a86)", "soroban-wasmi", "static_assertions", - "stellar-xdr", + "stellar-xdr 20.0.0-rc1", "tracy-client", ] @@ -1050,6 +1240,20 @@ dependencies = [ "tracy-client", ] +[[package]] +name = "soroban-env-macros" +version = "0.0.17" +source = "git+https://github.com/stellar/rs-soroban-env?rev=66a3c504387da5bb5bb0b1cc4b0beb22c74fb252#66a3c504387da5bb5bb0b1cc4b0beb22c74fb252" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr 0.0.17", + "syn", +] + [[package]] name = "soroban-env-macros" version = "20.0.0-rc2" @@ -1060,7 +1264,7 @@ dependencies = [ "quote", "serde", "serde_json", - "stellar-xdr", + "stellar-xdr 20.0.0-rc1", "syn", ] @@ -1074,7 +1278,7 @@ dependencies = [ "quote", "serde", "serde_json", - "stellar-xdr", + "stellar-xdr 20.0.0-rc1", "syn", ] @@ -1133,12 +1337,13 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "stellar-core" version = "0.1.0" dependencies = [ - "base64", + "base64 0.13.1", "cargo-lock", "cxx", "log", "rand", "rustc-simple-version", + "soroban-env-common 0.0.17", "soroban-env-host 20.0.0-rc2 (git+https://github.com/stellar/rs-soroban-env?rev=2674d867d7c6aa4212abab05ff30e5804ff1db90)", "soroban-env-host 20.0.0-rc2 (git+https://github.com/stellar/rs-soroban-env?rev=4f4a7f3d614ef65bba17ba6f56d4f378c2890a86)", "soroban-synth-wasm", @@ -1155,16 +1360,33 @@ dependencies = [ "thiserror", ] +[[package]] +name = "stellar-xdr" +version = "0.0.17" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=84d4d01f739b4f2c3f445f41bc7464a5621ba58c#84d4d01f739b4f2c3f445f41bc7464a5621ba58c" +dependencies = [ + "crate-git-revision", + "hex", + "serde", + "serde_with", +] + [[package]] name = "stellar-xdr" version = "20.0.0-rc1" source = "git+https://github.com/stellar/rs-stellar-xdr?rev=9c97e4fa909a0b6455547a4f4a95800696b2a69a#9c97e4fa909a0b6455547a4f4a95800696b2a69a" dependencies = [ - "base64", + "base64 0.13.1", "crate-git-revision", "hex", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.5.0" @@ -1212,6 +1434,34 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a79d09ac6b08c1ab3906a2f7cc2e81a0e27c7ae89c63812df75e52bef0751e07" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c65469ed6b3a4809d987a41eb1dc918e9bc1d92211cbad7ae82931846f7451" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1254,7 +1504,7 @@ version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ - "indexmap", + "indexmap 1.9.3", "serde", "serde_spanned", "toml_datetime", @@ -1488,7 +1738,7 @@ version = "0.106.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d014e33793cab91655fa6349b0bc974984de106b2e0f6b0dfe6f6594b260624d" dependencies = [ - "indexmap", + "indexmap 1.9.3", "url", ] @@ -1532,6 +1782,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-core" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6041b3f84485c21b57acdc0fee4f4f0c93f426053dc05fa5d6fc262537bbff" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.48.1" diff --git a/docs/software/commands.md b/docs/software/commands.md index a4b76892d5..0fe3f4c1ce 100644 --- a/docs/software/commands.md +++ b/docs/software/commands.md @@ -80,6 +80,25 @@ Command options can only by placed after command. * **fuzz **: Run a single fuzz input and exit. * **gen-fuzz **: Generate a random fuzzer input file. * **gen-seed**: Generate and print a random public/private key and then exit. +* **get-settings-upgrade-txs **: Generates the three transactions needed to propose + a Soroban Settings upgrade from scratch, as will as the XDR `ConfigUpgradeSetKey` to submit to the `upgrades` endpoint. The results will be dumped to standard output. is the key that will be used as the source account on the transactions. + is the current sequence number of the Stellar account corresponding to . + + Option (required) **--xdr** takes a base64 encoded XDR serialized `ConfigUpgradeSet`. + Example: + `stellar-core get-settings-upgrade-txs GAUQW73V52I2WLIPKCKYXZBHIYFTECS7UPSG4OSVUHNDXEZJJWFXZG56 73014444032 "Standalone Network ; February 2017" --xdr AAAAAQAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= --signtxs`.
+ + Option **--signtxs** will prompt for a secret key and sign the TransactionEnvelopes.
+ + Output format by line - + 1. Base64 upload tx envelope XDR + 2. Hex tx ID for the upload tx. + 3. Base 64 create tx envelope XDR + 4. Hex tx ID for the create tx. + 5. Base64 invoke tx envelope XDR + 6. Hex tx ID for the invoke tx. + 7. Base64 ConfigUpgradeSetKey XDR. + * **help**: Print the available command line options and then exit.. * **http-command ** Send an [HTTP command](#http-commands) to an already running local instance of stellar-core and then exit. For example: @@ -316,6 +335,13 @@ format. ConfigUpgradeSet will be used to update the existing network ConfigSettingEntry that exists at the corresponding CONFIG_SETTING LedgerKey. +* **dumpproposedsettings** + `dumpproposedsettings?blob=Base64`
+ blob is a base64 encoded XDR serialized `ConfigUpgradeSetKey`. + This command outputs the `ConfigUpgradeSet` (if it's valid and it exists) in a readable format + that corresponds to the `ConfigUpgradeSetKey` passed in. This can be used by validators + to verify Soroban Settings upgrades before voting on them. + * **surveytopology** `surveytopology?duration=DURATION&node=NODE_ID`
Starts a survey that will request peer connectivity information from nodes diff --git a/src/main/CommandHandler.cpp b/src/main/CommandHandler.cpp index c9eb8164c0..7d66788ea2 100644 --- a/src/main/CommandHandler.cpp +++ b/src/main/CommandHandler.cpp @@ -115,6 +115,7 @@ CommandHandler::CommandHandler(Application& app) : mApp(app) addRoute("tx", &CommandHandler::tx); addRoute("getledgerentry", &CommandHandler::getLedgerEntry); addRoute("upgrades", &CommandHandler::upgrades); + addRoute("dumpproposedsettings", &CommandHandler::dumpProposedSettings); addRoute("self-check", &CommandHandler::selfCheck); #ifdef BUILD_TESTS @@ -606,6 +607,49 @@ CommandHandler::upgrades(std::string const& params, std::string& retStr) } } +void +CommandHandler::dumpProposedSettings(std::string const& params, + std::string& retStr) +{ + ZoneScoped; + std::map retMap; + http::server::server::parseParams(params, retMap); + auto blob = retMap["blob"]; + if (!blob.empty()) + { + auto lhhe = mApp.getLedgerManager().getLastClosedLedgerHeader(); + if (protocolVersionIsBefore(lhhe.header.ledgerVersion, + SOROBAN_PROTOCOL_VERSION)) + { + retStr = "The dumpProposedSettings command is not allowed pre-v20"; + return; + } + + std::vector buffer; + decoder::decode_b64(blob, buffer); + ConfigUpgradeSetKey key; + xdr::xdr_from_opaque(buffer, key); + LedgerTxn ltx(mApp.getLedgerTxnRoot()); + + auto ptr = ConfigUpgradeSetFrame::makeFromKey(ltx, key); + + if (!ptr || ptr->isValidForApply() != Upgrades::UpgradeValidity::VALID) + { + retStr = "configUpgradeSet is missing or invalid"; + return; + } + + retStr = xdr_to_string(ptr->toXDR(), "ConfigUpgradeSet"); + } + else + { + throw std::invalid_argument( + "Must specify a ConfigUpgradeSetKey blob: " + "dumpproposedsettings?blob="); + } +} + void CommandHandler::selfCheck(std::string const&, std::string& retStr) { diff --git a/src/main/CommandHandler.h b/src/main/CommandHandler.h index 9275acf130..38cccb5bf1 100644 --- a/src/main/CommandHandler.h +++ b/src/main/CommandHandler.h @@ -57,6 +57,7 @@ class CommandHandler void getLedgerEntry(std::string const& params, std::string& retStr); void unban(std::string const& params, std::string& retStr); void upgrades(std::string const& params, std::string& retStr); + void dumpProposedSettings(std::string const& params, std::string& retStr); void surveyTopology(std::string const&, std::string& retStr); void stopSurvey(std::string const&, std::string& retStr); void getSurveyResult(std::string const&, std::string& retStr); diff --git a/src/main/CommandLine.cpp b/src/main/CommandLine.cpp index 70c78f0696..36f8d2086e 100644 --- a/src/main/CommandLine.cpp +++ b/src/main/CommandLine.cpp @@ -2,6 +2,11 @@ // under the Apache License, Version 2.0. See the COPYING file at the root // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 +// clang-format off +// This needs to be included first +#include "rust/RustVecXdrMarshal.h" +// clang-format on + #include "main/CommandLine.h" #include "bucket/BucketManager.h" #include "catchup/CatchupConfiguration.h" @@ -18,15 +23,20 @@ #include "main/Diagnostics.h" #include "main/ErrorMessages.h" #include "main/PersistentState.h" +#include "main/SettingsUpgradeUtils.h" #include "main/StellarCoreVersion.h" #include "main/dumpxdr.h" #include "overlay/OverlayManager.h" #include "rust/RustBridge.h" #include "scp/QuorumSetUtils.h" +#include "transactions/TransactionUtils.h" #include "util/Logging.h" #include "util/types.h" #include "work/WorkScheduler.h" +#include +#include + #ifdef BUILD_TESTS #include "test/Fuzzer.h" #include "test/fuzz.h" @@ -1225,6 +1235,131 @@ runUpgradeDB(CommandLineArgs const& args) }); } +int +getSettingsUpgradeTransactions(CommandLineArgs const& args) +{ + std::string netId; + + int64_t seqNum; + std::string upgradeFile; + + bool signTxs = false; + + auto signTxnOption = + clara::Opt{signTxs}["--signtxs"]("sign all transactions"); + + auto netIdOption = clara::Opt(netId, "NETWORK-PASSPHRASE")["--netid"]( + "network ID used for signing") + .required(); + + auto netIdParser = ParserWithValidation{ + netIdOption, required(netId, "NETWORK-PASSPHRASE")}; + + std::string id; + + std::string base64Xdr; + auto base64Option = clara::Opt{base64Xdr, "XDR-BASE64"}["--xdr"]( + "ConfigUpgradeSet in base64") + .required(); + + auto base64Parser = + ParserWithValidation{base64Option, required(base64Xdr, "XDR-BASE64")}; + + ParserWithValidation seqNumParser{ + clara::Arg(seqNum, "SequenceNumber").required(), + [&] { return seqNum >= 0 ? "" : "SequenceNumber must be >= 0"; }}; + + return runWithHelp( + args, + {requiredArgParser(id, "PublicKey"), seqNumParser, + requiredArgParser(netId, "NetworkPassphrase"), base64Parser, + signTxnOption}, + [&] { + ConfigUpgradeSet upgradeSet; + std::vector binBlob; + decoder::decode_b64(base64Xdr, binBlob); + xdr::xdr_from_opaque(binBlob, upgradeSet); + + PublicKey pk = KeyUtils::fromStrKey(id); + + std::vector txsToSign; + + auto uploadRes = getUploadTx(pk, seqNum + 1); + txsToSign.emplace_back(uploadRes.first); + auto const& contractCodeLedgerKey = uploadRes.second; + + auto createRes = + getCreateTx(pk, contractCodeLedgerKey, netId, seqNum + 2); + txsToSign.emplace_back(std::get<0>(createRes)); + auto const& contractSourceRefLedgerKey = std::get<1>(createRes); + auto const& contractID = std::get<2>(createRes); + + auto invokeRes = getInvokeTx(pk, contractCodeLedgerKey, + contractSourceRefLedgerKey, contractID, + upgradeSet, seqNum + 3); + txsToSign.emplace_back(invokeRes.first); + auto const& upgradeSetKey = invokeRes.second; + + if (signTxs) + { + signtxns(txsToSign, netId, true, false, true); + } + else + { + TransactionSignaturePayload payload; + payload.networkId = sha256(netId); + payload.taggedTransaction.type(ENVELOPE_TYPE_TX); + + auto tx1 = + decoder::encode_b64(xdr::xdr_to_opaque(txsToSign.at(0))); + auto payload1 = payload; + payload1.taggedTransaction.tx() = txsToSign.at(0).v1().tx; + + auto tx2 = + decoder::encode_b64(xdr::xdr_to_opaque(txsToSign.at(1))); + auto payload2 = payload; + payload2.taggedTransaction.tx() = txsToSign.at(1).v1().tx; + + auto tx3 = + decoder::encode_b64(xdr::xdr_to_opaque(txsToSign.at(2))); + auto payload3 = payload; + payload3.taggedTransaction.tx() = txsToSign.at(2).v1().tx; + + std::cerr + << "Unsigned TransactionEnvelope to upload upgrade WASM " + << std::endl; + std::cout << tx1 << std::endl; + std::cout << binToHex(xdr::xdr_to_opaque( + sha256(xdr::xdr_to_opaque(payload1)))) + << std::endl; + + std::cerr << "Unsigned TransactionEnvelope to create upgrade " + "contract " + << std::endl; + + std::cout << tx2 << std::endl; + std::cout << binToHex(xdr::xdr_to_opaque( + sha256(xdr::xdr_to_opaque(payload2)))) + << std::endl; + + std::cerr + << "Unsigned TransactionEnvelope to invoke contract with " + "upgrade bytes " + << std::endl; + std::cout << tx3 << std::endl; + std::cout << binToHex(xdr::xdr_to_opaque( + sha256(xdr::xdr_to_opaque(payload3)))) + << std::endl; + } + + std::cerr << "ConfigUpgradeSetKey "; + std::cout << decoder::encode_b64(xdr::xdr_to_opaque(upgradeSetKey)) + << std::endl; + + return 0; + }); +} + int runNewHist(CommandLineArgs const& args) { @@ -1685,6 +1820,10 @@ handleCommandLine(int argc, char* const* argv) runSignTransaction}, {"upgrade-db", "upgrade database schema to current version", runUpgradeDB}, + {"get-settings-upgrade-txs", + "returns all transactions that need to be submitted to do a settings " + "upgrade", + getSettingsUpgradeTransactions}, #ifdef BUILD_TESTS {"load-xdr", "load an XDR bucket file, for testing", runLoadXDR}, {"rebuild-ledger-from-buckets", diff --git a/src/main/SettingsUpgradeUtils.cpp b/src/main/SettingsUpgradeUtils.cpp new file mode 100644 index 0000000000..a6e37b7a2f --- /dev/null +++ b/src/main/SettingsUpgradeUtils.cpp @@ -0,0 +1,212 @@ +#include "main/SettingsUpgradeUtils.h" +#include "crypto/SHA.h" +#include "rust/RustBridge.h" +#include "transactions/TransactionUtils.h" +#include + +namespace stellar +{ + +std::pair +getUploadTx(PublicKey const& publicKey, SequenceNumber seqNum) +{ + TransactionEnvelope txEnv; + txEnv.type(ENVELOPE_TYPE_TX); + + auto& tx = txEnv.v1().tx; + tx.sourceAccount = toMuxedAccount(publicKey); + tx.fee = 3'000'000; + tx.seqNum = seqNum; + + Preconditions cond; + cond.type(PRECOND_NONE); + tx.cond = cond; + + Memo memo; + memo.type(MEMO_NONE); + tx.memo = memo; + + Operation uploadOp; + uploadOp.body.type(INVOKE_HOST_FUNCTION); + auto& uploadHF = uploadOp.body.invokeHostFunctionOp().hostFunction; + uploadHF.type(HOST_FUNCTION_TYPE_UPLOAD_CONTRACT_WASM); + + auto const writeByteWasm = rust_bridge::get_write_bytes(); + uploadHF.wasm().assign(writeByteWasm.data.begin(), + writeByteWasm.data.end()); + + tx.operations.emplace_back(uploadOp); + + LedgerKey contractCodeLedgerKey; + contractCodeLedgerKey.type(CONTRACT_CODE); + contractCodeLedgerKey.contractCode().hash = sha256(uploadHF.wasm()); + + SorobanResources uploadResources; + uploadResources.footprint.readWrite = {contractCodeLedgerKey}; + uploadResources.instructions = 1'833'940; + uploadResources.readBytes = 50; + uploadResources.writeBytes = 2000; + + tx.ext.v(1); + tx.ext.sorobanData().resources = uploadResources; + tx.ext.sorobanData().resourceFee = 2'000'000; + + return {txEnv, contractCodeLedgerKey}; +} + +std::tuple +getCreateTx(PublicKey const& publicKey, LedgerKey const& contractCodeLedgerKey, + std::string const& networkPassphrase, SequenceNumber seqNum) +{ + TransactionEnvelope txEnv; + txEnv.type(ENVELOPE_TYPE_TX); + + auto& tx = txEnv.v1().tx; + tx.sourceAccount = toMuxedAccount(publicKey); + tx.fee = 2'000'000; + tx.seqNum = seqNum; + + Preconditions cond; + cond.type(PRECOND_NONE); + tx.cond = cond; + + Memo memo; + memo.type(MEMO_NONE); + tx.memo = memo; + + ContractIDPreimage idPreimage(CONTRACT_ID_PREIMAGE_FROM_ADDRESS); + idPreimage.fromAddress().address.type(SC_ADDRESS_TYPE_ACCOUNT); + idPreimage.fromAddress().address.accountId().ed25519() = + publicKey.ed25519(); + idPreimage.fromAddress().salt = autocheck::generator()(5); + + HashIDPreimage fullPreImage; + fullPreImage.type(ENVELOPE_TYPE_CONTRACT_ID); + fullPreImage.contractID().contractIDPreimage = idPreimage; + fullPreImage.contractID().networkID = sha256(networkPassphrase); + + auto contractID = xdrSha256(fullPreImage); + + Operation createOp; + createOp.body.type(INVOKE_HOST_FUNCTION); + auto& createHF = createOp.body.invokeHostFunctionOp().hostFunction; + createHF.type(HOST_FUNCTION_TYPE_CREATE_CONTRACT); + auto& createContractArgs = createHF.createContract(); + createContractArgs.contractIDPreimage = idPreimage; + createContractArgs.executable.type(CONTRACT_EXECUTABLE_WASM); + createContractArgs.executable.wasm_hash() = + contractCodeLedgerKey.contractCode().hash; + + SorobanAuthorizationEntry auth; + auth.credentials.type(SOROBAN_CREDENTIALS_SOURCE_ACCOUNT); + auth.rootInvocation.function.type( + SOROBAN_AUTHORIZED_FUNCTION_TYPE_CREATE_CONTRACT_HOST_FN); + auth.rootInvocation.function.createContractHostFn().contractIDPreimage = + idPreimage; + auth.rootInvocation.function.createContractHostFn().executable.type( + CONTRACT_EXECUTABLE_WASM); + auth.rootInvocation.function.createContractHostFn().executable.wasm_hash() = + contractCodeLedgerKey.contractCode().hash; + createOp.body.invokeHostFunctionOp().auth = {auth}; + + tx.operations.emplace_back(createOp); + + // Build footprint + SCVal scContractSourceRefKey(SCValType::SCV_LEDGER_KEY_CONTRACT_INSTANCE); + + LedgerKey contractSourceRefLedgerKey; + contractSourceRefLedgerKey.type(CONTRACT_DATA); + contractSourceRefLedgerKey.contractData().contract.type( + SC_ADDRESS_TYPE_CONTRACT); + contractSourceRefLedgerKey.contractData().contract.contractId() = + contractID; + contractSourceRefLedgerKey.contractData().key = scContractSourceRefKey; + contractSourceRefLedgerKey.contractData().durability = + ContractDataDurability::PERSISTENT; + + SorobanResources uploadResources; + uploadResources.footprint.readOnly = {contractCodeLedgerKey}; + uploadResources.footprint.readWrite = {contractSourceRefLedgerKey}; + uploadResources.instructions = 150'000; + uploadResources.readBytes = 2000; + uploadResources.writeBytes = 120; + + tx.ext.v(1); + tx.ext.sorobanData().resources = uploadResources; + tx.ext.sorobanData().resourceFee = 1'000'000; + + return {txEnv, contractSourceRefLedgerKey, contractID}; +} + +std::pair +getInvokeTx(PublicKey const& publicKey, LedgerKey const& contractCodeLedgerKey, + LedgerKey const& contractSourceRefLedgerKey, Hash const& contractID, + ConfigUpgradeSet const& upgradeSet, SequenceNumber seqNum) +{ + + TransactionEnvelope txEnv; + txEnv.type(ENVELOPE_TYPE_TX); + + auto& tx = txEnv.v1().tx; + tx.sourceAccount = toMuxedAccount(publicKey); + tx.fee = 1'000'000; + tx.seqNum = seqNum; + + Preconditions cond; + cond.type(PRECOND_NONE); + tx.cond = cond; + + Memo memo; + memo.type(MEMO_NONE); + tx.memo = memo; + + Operation invokeOp; + invokeOp.body.type(INVOKE_HOST_FUNCTION); + auto& invokeHF = invokeOp.body.invokeHostFunctionOp().hostFunction; + invokeHF.type(HOST_FUNCTION_TYPE_INVOKE_CONTRACT); + + SCAddress addr(SC_ADDRESS_TYPE_CONTRACT); + addr.contractId() = contractID; + invokeHF.invokeContract().contractAddress = addr; + + const std::string& functionNameStr = "write"; + SCSymbol functionName; + functionName.assign(functionNameStr.begin(), functionNameStr.end()); + invokeHF.invokeContract().functionName = functionName; + + auto upgradeSetBytes(xdr::xdr_to_opaque(upgradeSet)); + SCVal b(SCV_BYTES); + b.bytes() = upgradeSetBytes; + invokeHF.invokeContract().args.emplace_back(b); + + tx.operations.emplace_back(invokeOp); + + LedgerKey upgrade(CONTRACT_DATA); + upgrade.contractData().durability = TEMPORARY; + upgrade.contractData().contract = addr; + + SCVal upgradeHashBytes(SCV_BYTES); + auto upgradeHash = sha256(upgradeSetBytes); + upgradeHashBytes.bytes() = xdr::xdr_to_opaque(upgradeHash); + upgrade.contractData().key = upgradeHashBytes; + + SorobanResources invokeResources; + invokeResources.footprint.readOnly = {contractSourceRefLedgerKey, + contractCodeLedgerKey}; + invokeResources.footprint.readWrite = {upgrade}; + invokeResources.instructions = 2'000'000; + invokeResources.readBytes = 3000; + invokeResources.writeBytes = 2000; + + tx.ext.v(1); + tx.ext.sorobanData().resources = invokeResources; + tx.ext.sorobanData().resourceFee = 500'000; + + ConfigUpgradeSetKey key; + key.contentHash = upgradeHash; + key.contractID = contractID; + + return {txEnv, key}; +} + +} \ No newline at end of file diff --git a/src/main/SettingsUpgradeUtils.h b/src/main/SettingsUpgradeUtils.h new file mode 100644 index 0000000000..c7c3ee3ea7 --- /dev/null +++ b/src/main/SettingsUpgradeUtils.h @@ -0,0 +1,20 @@ +#include "xdr/Stellar-ledger-entries.h" +#include "xdr/Stellar-ledger.h" +#include + +namespace stellar +{ + +std::pair +getUploadTx(PublicKey const& publicKey, SequenceNumber seqNum); + +std::tuple +getCreateTx(PublicKey const& publicKey, LedgerKey const& contractCodeLedgerKey, + std::string const& networkPassphrase, SequenceNumber seqNum); + +std::pair +getInvokeTx(PublicKey const& publicKey, LedgerKey const& contractCodeLedgerKey, + LedgerKey const& contractSourceRefLedgerKey, Hash const& contractID, + ConfigUpgradeSet const& upgradeSet, SequenceNumber seqNum); + +} \ No newline at end of file diff --git a/src/main/dumpxdr.cpp b/src/main/dumpxdr.cpp index cb763b9488..9d4eba5494 100644 --- a/src/main/dumpxdr.cpp +++ b/src/main/dumpxdr.cpp @@ -110,7 +110,7 @@ dumpXdrStream(std::string const& filename, bool compact) xdr_strerror(errno)); \ } while (0) -static void +void readFile(const std::string& filename, bool base64, std::function)> proc) { @@ -373,6 +373,64 @@ readSecret(const std::string& prompt, bool force_tty) #endif } +void +signtxns(std::vector& txEnvs, std::string netId, + bool base64, bool txn_stdin, bool dump_hex_txid) +{ + SecretKey sk(SecretKey::fromStrKeySeed(readSecret( + fmt::format(FMT_STRING("Secret key seed [network id: '{}']: "), netId), + txn_stdin))); + + for (auto& txEnv : txEnvs) + { + auto& signatures = txbridge::getSignatures(txEnv); + if (signatures.size() == signatures.max_size()) + throw std::runtime_error("Envelope already contains " + "maximum number of signatures"); + + TransactionSignaturePayload payload; + payload.networkId = sha256(netId); + switch (txEnv.type()) + { + case ENVELOPE_TYPE_TX_V0: + payload.taggedTransaction.type(ENVELOPE_TYPE_TX); + // TransactionV0 and Transaction always have the same + // signatures so there is no reason to check versions here, + // just always convert to Transaction + payload.taggedTransaction.tx() = + txbridge::convertForV13(txEnv).v1().tx; + break; + case ENVELOPE_TYPE_TX: + payload.taggedTransaction.type(ENVELOPE_TYPE_TX); + payload.taggedTransaction.tx() = txEnv.v1().tx; + break; + case ENVELOPE_TYPE_TX_FEE_BUMP: + payload.taggedTransaction.type(ENVELOPE_TYPE_TX_FEE_BUMP); + payload.taggedTransaction.feeBump() = txEnv.feeBump().tx; + break; + default: + abort(); + } + + auto payloadHash = sha256(xdr::xdr_to_opaque(payload)); + + signatures.emplace_back( + SignatureUtils::getHint(sk.getPublicKey().ed25519()), + sk.sign(payloadHash)); + + auto out = xdr::xdr_to_opaque(txEnv); + if (base64) + std::cout << decoder::encode_b64(out) << std::endl; + else + std::cout.write(reinterpret_cast(out.data()), out.size()); + + if (dump_hex_txid) + { + std::cout << binToHex(xdr::xdr_to_opaque(payloadHash)) << std::endl; + } + } +} + void signtxn(std::string const& filename, std::string netId, bool base64) { @@ -394,50 +452,11 @@ signtxn(std::string const& filename, std::string netId, bool base64) "Refusing to write binary transaction to terminal"); readFile(filename, base64, [&](xdr::opaque_vec<> d) { - TransactionEnvelope txenv; - xdr::xdr_from_opaque(d, txenv); - auto& signatures = txbridge::getSignatures(txenv); - if (signatures.size() == signatures.max_size()) - throw std::runtime_error("Envelope already contains " - "maximum number of signatures"); - - SecretKey sk(SecretKey::fromStrKeySeed(readSecret( - fmt::format(FMT_STRING("Secret key seed [network id: '{}']: "), - netId), - txn_stdin))); - TransactionSignaturePayload payload; - payload.networkId = sha256(netId); - switch (txenv.type()) - { - case ENVELOPE_TYPE_TX_V0: - payload.taggedTransaction.type(ENVELOPE_TYPE_TX); - // TransactionV0 and Transaction always have the same - // signatures so there is no reason to check versions here, - // just always convert to Transaction - payload.taggedTransaction.tx() = - txbridge::convertForV13(txenv).v1().tx; - break; - case ENVELOPE_TYPE_TX: - payload.taggedTransaction.type(ENVELOPE_TYPE_TX); - payload.taggedTransaction.tx() = txenv.v1().tx; - break; - case ENVELOPE_TYPE_TX_FEE_BUMP: - payload.taggedTransaction.type(ENVELOPE_TYPE_TX_FEE_BUMP); - payload.taggedTransaction.feeBump() = txenv.feeBump().tx; - break; - default: - abort(); - } + TransactionEnvelope txEnv; + xdr::xdr_from_opaque(d, txEnv); - signatures.emplace_back( - SignatureUtils::getHint(sk.getPublicKey().ed25519()), - sk.sign(sha256(xdr::xdr_to_opaque(payload)))); - - auto out = xdr::xdr_to_opaque(txenv); - if (base64) - cout << decoder::encode_b64(out) << std::endl; - else - cout.write(reinterpret_cast(out.data()), out.size()); + std::vector txEnvs = {txEnv}; + signtxns(txEnvs, netId, base64, txn_stdin, false); }); } catch (const std::exception& e) diff --git a/src/main/dumpxdr.h b/src/main/dumpxdr.h index d8f5da484f..f0fdac40a3 100644 --- a/src/main/dumpxdr.h +++ b/src/main/dumpxdr.h @@ -5,12 +5,18 @@ // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 #include "overlay/StellarXDR.h" +#include +#include namespace stellar { void dumpXdrStream(std::string const& filename, bool json); void printXdr(std::string const& filename, std::string const& filetype, bool base64, bool compact, bool rawMode); +void signtxns(std::vector& txenvs, std::string netId, + bool base64, bool txn_stdin, bool dump_hex_txid); void signtxn(std::string const& filename, std::string netId, bool base64); void priv2pub(); +void readFile(const std::string& filename, bool base64, + std::function)> proc); } diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 66a40dad3c..f931e3e6a1 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -57,6 +57,14 @@ git = "https://github.com/stellar/rs-soroban-env" package = "soroban-env-host" rev = "2674d867d7c6aa4212abab05ff30e5804ff1db90" +[dependencies.soroban-env-common-prev] +optional = true +version = "0.0.17" +git = "https://github.com/stellar/rs-soroban-env" +package = "soroban-env-common" +rev = "66a3c504387da5bb5bb0b1cc4b0beb22c74fb252" +features = ["serde"] + [dependencies.soroban-test-wasms] git = "https://github.com/stellar/rs-soroban-env" rev = "4f4a7f3d614ef65bba17ba6f56d4f378c2890a86" diff --git a/src/rust/src/contract.rs b/src/rust/src/contract.rs index 5180919c69..ab34b546f4 100644 --- a/src/rust/src/contract.rs +++ b/src/rust/src/contract.rs @@ -128,7 +128,7 @@ impl AsRef<[u8]> for CxxBuf { // FIXME: plumb this through from the limit xdrpp uses. // Currently they are just two same-valued constants. -const MARSHALLING_STACK_LIMIT: u32 = 1000; +pub(crate) const MARSHALLING_STACK_LIMIT: u32 = 1000; #[derive(Debug)] pub(crate) enum CoreHostError { diff --git a/src/rust/src/host-dep-tree-curr.txt b/src/rust/src/host-dep-tree-curr.txt index b409792e2b..9c171fc08b 100644 --- a/src/rust/src/host-dep-tree-curr.txt +++ b/src/rust/src/host-dep-tree-curr.txt @@ -90,13 +90,14 @@ soroban-env-host 20.0.0-rc2 git+https://github.com/stellar/rs-soroban-env?rev=4f │ ├── tracy-client 0.15.2 checksum:434ecabbda9f67eeea1eab44d52f4a20538afa3e2c2770f2efc161142b25b608 │ ├── stellar-xdr 20.0.0-rc1 git+https://github.com/stellar/rs-stellar-xdr?rev=9c97e4fa909a0b6455547a4f4a95800696b2a69a#9c97e4fa909a0b6455547a4f4a95800696b2a69a │ │ ├── hex 0.4.3 checksum:7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70 +│ │ │ └── serde 1.0.164 checksum:9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d +│ │ │ └── serde_derive 1.0.164 checksum:d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68 +│ │ │ ├── syn 2.0.18 checksum:32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e +│ │ │ ├── quote 1.0.28 checksum:1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488 +│ │ │ └── proc-macro2 1.0.60 checksum:dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406 │ │ ├── crate-git-revision 0.0.6 checksum:c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98 │ │ │ ├── serde_json 1.0.97 checksum:bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a │ │ │ │ ├── serde 1.0.164 checksum:9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d -│ │ │ │ │ └── serde_derive 1.0.164 checksum:d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68 -│ │ │ │ │ ├── syn 2.0.18 checksum:32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e -│ │ │ │ │ ├── quote 1.0.28 checksum:1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488 -│ │ │ │ │ └── proc-macro2 1.0.60 checksum:dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406 │ │ │ │ ├── ryu 1.0.13 checksum:f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041 │ │ │ │ └── itoa 1.0.6 checksum:453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6 │ │ │ ├── serde_derive 1.0.164 checksum:d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68 diff --git a/src/transactions/test/InvokeHostFunctionTests.cpp b/src/transactions/test/InvokeHostFunctionTests.cpp index 541f91714c..c7871369d3 100644 --- a/src/transactions/test/InvokeHostFunctionTests.cpp +++ b/src/transactions/test/InvokeHostFunctionTests.cpp @@ -17,6 +17,7 @@ #include "lib/catch.hpp" #include "main/Application.h" #include "main/CommandHandler.h" +#include "main/SettingsUpgradeUtils.h" #include "rust/RustBridge.h" #include "test/TestAccount.h" #include "test/TestUtils.h" @@ -2168,3 +2169,195 @@ TEST_CASE("temp entry eviction", "[tx][soroban]") } } } + +/* +This test uses the same utils (SettingsUpgradeUtils.h) as the +get-settings-upgrade-txs command to make sure the transactions have the proper +resources set. +*/ +TEST_CASE("settings upgrade command line utils", "[tx][soroban][upgrades]") +{ + VirtualClock clock; + auto cfg = getTestConfig(); + cfg.ENABLE_SOROBAN_DIAGNOSTIC_EVENTS = true; + cfg.EXPERIMENTAL_BUCKETLIST_DB = false; + auto app = createTestApplication(clock, cfg); + auto root = TestAccount::createRoot(*app); + auto& lm = app->getLedgerManager(); + + const int64_t startingBalance = + app->getLedgerManager().getLastMinBalance(50); + + auto a1 = root.create("A", startingBalance); + + std::vector txsToSign; + + auto uploadRes = + getUploadTx(a1.getPublicKey(), a1.getLastSequenceNumber() + 1); + txsToSign.emplace_back(uploadRes.first); + auto const& contractCodeLedgerKey = uploadRes.second; + + auto createRes = + getCreateTx(a1.getPublicKey(), contractCodeLedgerKey, + cfg.NETWORK_PASSPHRASE, a1.getLastSequenceNumber() + 2); + txsToSign.emplace_back(std::get<0>(createRes)); + auto const& contractSourceRefLedgerKey = std::get<1>(createRes); + auto const& contractID = std::get<2>(createRes); + + xdr::xvector updatedEntries; + for (uint32_t i = 0; + i < static_cast(CONFIG_SETTING_BUCKETLIST_SIZE_WINDOW); ++i) + { + LedgerTxn ltx(app->getLedgerTxnRoot()); + auto entry = + ltx.load(configSettingKey(static_cast(i))); + if (entry.current().data.configSetting().configSettingID() == + CONFIG_SETTING_CONTRACT_LEDGER_COST_V0) + { + entry.current() + .data.configSetting() + .contractLedgerCost() + .feeRead1KB = 1234; + } + updatedEntries.emplace_back(entry.current().data.configSetting()); + } + + ConfigUpgradeSet upgradeSet; + upgradeSet.updatedEntry = updatedEntries; + + auto invokeRes = getInvokeTx(a1.getPublicKey(), contractCodeLedgerKey, + contractSourceRefLedgerKey, contractID, + upgradeSet, a1.getLastSequenceNumber() + 3); + txsToSign.emplace_back(invokeRes.first); + auto const& upgradeSetKey = invokeRes.second; + + for (auto& txEnv : txsToSign) + { + txEnv.v1().signatures.emplace_back(SignatureUtils::sign( + a1.getSecretKey(), + sha256(xdr::xdr_to_opaque(app->getNetworkID(), ENVELOPE_TYPE_TX, + txEnv.v1().tx)))); + + auto const& tx = TransactionFrameBase::makeTransactionFromWire( + app->getNetworkID(), txEnv); + LedgerTxn ltx(app->getLedgerTxnRoot()); + TransactionMetaFrame txm(ltx.loadHeader().current().ledgerVersion); + REQUIRE(tx->checkValid(*app, ltx, 0, 0, 0)); + REQUIRE(tx->apply(*app, ltx, txm)); + ltx.commit(); + } + + auto& commandHandler = app->getCommandHandler(); + + std::string command = "mode=set&configupgradesetkey="; + command += decoder::encode_b64(xdr::xdr_to_opaque(upgradeSetKey)); + command += "&upgradetime=2000-07-21T22:04:00Z"; + + std::string ret; + commandHandler.upgrades(command, ret); + REQUIRE(ret == ""); + + auto checkCost = [&](int64 feeRead) { + auto costKey = configSettingKey( + ConfigSettingID::CONFIG_SETTING_CONTRACT_LEDGER_COST_V0); + LedgerTxn ltx(app->getLedgerTxnRoot()); + auto costEntry = ltx.load(costKey); + REQUIRE(costEntry.current() + .data.configSetting() + .contractLedgerCost() + .feeRead1KB == feeRead); + }; + + SECTION("success") + { + // trigger upgrade + auto ledgerUpgrade = LedgerUpgrade{LEDGER_UPGRADE_CONFIG}; + ledgerUpgrade.newConfig() = upgradeSetKey; + + auto const& lcl = lm.getLastClosedLedgerHeader(); + auto txSet = TxSetFrame::makeEmpty(lcl); + auto lastCloseTime = lcl.header.scpValue.closeTime; + + app->getHerder().externalizeValue( + txSet, lcl.header.ledgerSeq + 1, lastCloseTime, + {LedgerTestUtils::toUpgradeType(ledgerUpgrade)}); + + checkCost(1234); + } + + auto const& lcl = lm.getLastClosedLedgerHeader(); + + // The only readWrite key in the invoke op is the one that writes the + // ConfigUpgradeSet xdr + auto proposalKey = invokeRes.first.v1() + .tx.ext.sorobanData() + .resources.footprint.readWrite.at(0); + + SECTION("entry expired") + { + { + LedgerTxn ltx(app->getLedgerTxnRoot()); + auto ttl = ltx.load(getTTLKey(proposalKey)); + // Expire the entry on the next ledger + ttl.current().data.ttl().liveUntilLedgerSeq = lcl.header.ledgerSeq; + ltx.commit(); + } + + // trigger upgrade + auto ledgerUpgrade = LedgerUpgrade{LEDGER_UPGRADE_CONFIG}; + ledgerUpgrade.newConfig() = upgradeSetKey; + + auto txSet = TxSetFrame::makeEmpty(lcl); + auto lastCloseTime = lcl.header.scpValue.closeTime; + + app->getHerder().externalizeValue( + txSet, lcl.header.ledgerSeq + 1, lastCloseTime, + {LedgerTestUtils::toUpgradeType(ledgerUpgrade)}); + + // No upgrade due to expired entry + checkCost(1000); + } + + auto updateBytes = [&](SCVal const& bytes) { + { + LedgerTxn ltx(app->getLedgerTxnRoot()); + auto entry = ltx.load(proposalKey); + entry.current().data.contractData().val = bytes; + ltx.commit(); + } + + // trigger upgrade + auto ledgerUpgrade = LedgerUpgrade{LEDGER_UPGRADE_CONFIG}; + ledgerUpgrade.newConfig() = upgradeSetKey; + + auto txSet = TxSetFrame::makeEmpty(lcl); + auto lastCloseTime = lcl.header.scpValue.closeTime; + + app->getHerder().externalizeValue( + txSet, lcl.header.ledgerSeq + 1, lastCloseTime, + {LedgerTestUtils::toUpgradeType(ledgerUpgrade)}); + + // No upgrade due to tampered entry + checkCost(1000); + }; + + SECTION("Invalid XDR") + { + SCVal b(SCV_BYTES); + updateBytes(b); + } + + SECTION("Valid XDR but hash mismatch") + { + ConfigSettingEntry costSetting(CONFIG_SETTING_CONTRACT_LEDGER_COST_V0); + costSetting.contractLedgerCost().feeRead1KB = 1234; + + ConfigUpgradeSet upgradeSet; + upgradeSet.updatedEntry.emplace_back(costSetting); + + auto upgradeSetBytes(xdr::xdr_to_opaque(upgradeSet)); + SCVal b(SCV_BYTES); + b.bytes() = upgradeSetBytes; + updateBytes(b); + } +}