diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md new file mode 100644 index 000000000..58ceed0cd --- /dev/null +++ b/API-CHANGELOG.md @@ -0,0 +1,199 @@ +# API Changelog + +This changelog is intended to list all updates to the [public API methods](https://xrpl.org/public-api-methods.html). + +For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior. + +The API version controls the API behavior you see. This includes what properties you see in responses, what parameters you're permitted to send in requests, and so on. You specify the API version in each of your requests. When a breaking change is introduced to the `rippled` API, a new version is released. To avoid breaking your code, you should set (or increase) your version when you're ready to upgrade. + +For a log of breaking changes, see the **API Version [number]** headings. In general, breaking changes are associated with a particular API Version number. For non-breaking changes, scroll to the **XRP Ledger version [x.y.z]** headings. Non-breaking changes are associated with a particular XRP Ledger (`rippled`) release. + +## API Version 1 + +This version is supported by all `rippled` versions. At time of writing, it is the default API version, used when no `api_version` is specified. When a new API version is introduced, the command line interface will default to the latest API version. The command line is intended for ad-hoc usage by humans, not programs or automated scripts. The command line is not meant for use in production code. + +### Idiosyncrasies + +#### V1 account_info response + +In [the response to the `account_info` command](https://xrpl.org/account_info.html#response-format), there is `account_data` - which is supposed to be an `AccountRoot` object - and `signer_lists` is returned in this object. However, the docs say that `signer_lists` should be at the root level of the reponse. + +It makes sense for `signer_lists` to be at the root level because signer lists are not part of the AccountRoot object. (First reported in [xrpl-dev-portal#938](https://github.com/XRPLF/xrpl-dev-portal/issues/938).) + +In `api_version: 2`, the `signer_lists` field [will be moved](#modifications-to-account_info-response-in-v2) to the root level of the account_info response. (https://github.com/XRPLF/rippled/pull/3770) + +#### server_info - network_id + +The `network_id` field was added in the `server_info` response in version 1.5.0 (2019), but it is not returned in [reporting mode](https://xrpl.org/rippled-server-modes.html#reporting-mode). + +## XRP Ledger server version 2.0.0 + +### Additions in 2.2 + +Additions are intended to be non-breaking (because they are purely additive). + +- `feature`: A non-admin mode that uses the same formatting as admin RPC, but hides potentially-sensitive data. + +### Additions in 2.0 + +Additions are intended to be non-breaking (because they are purely additive). + +- `server_definitions`: A new RPC that generates a `definitions.json`-like output that can be used in XRPL libraries. +- In `Payment` transactions, `DeliverMax` has been added. This is a replacement for the `Amount` field, which should not be used. Typically, the `delivered_amount` (in transaction metadata) should be used. To ease the transition, `DeliverMax` is present regardless of API version, since adding a field is non-breaking. +- API version 2 has been moved from beta to supported, meaning that it is generally available (regardless of the `beta_rpc_api` setting). + +## XRP Ledger server version 1.12.0 + +[Version 1.12.0](https://github.com/XRPLF/rippled/releases/tag/1.12.0) was released on Sep 6, 2023. + +### Additions in 1.12 + +Additions are intended to be non-breaking (because they are purely additive). + +- `server_info`: Added `ports`, an array which advertises the RPC and WebSocket ports. This information is also included in the `/crawl` endpoint (which calls `server_info` internally). `grpc` and `peer` ports are also included. (https://github.com/XRPLF/rippled/pull/4427) + - `ports` contains objects, each containing a `port` for the listening port (a number string), and a `protocol` array listing the supported protocols on that port. + - This allows crawlers to build a more detailed topology without needing to port-scan nodes. + - (For peers and other non-admin clients, the info about admin ports is excluded.) +- Clawback: The following additions are gated by the Clawback amendment (`featureClawback`). (https://github.com/XRPLF/rippled/pull/4553) + - Adds an [AccountRoot flag](https://xrpl.org/accountroot.html#accountroot-flags) called `lsfAllowTrustLineClawback` (https://github.com/XRPLF/rippled/pull/4617) + - Adds the corresponding `asfAllowTrustLineClawback` [AccountSet Flag](https://xrpl.org/accountset.html#accountset-flags) as well. + - Clawback is disabled by default, so if an issuer desires the ability to claw back funds, they must use an `AccountSet` transaction to set the AllowTrustLineClawback flag. They must do this before creating any trust lines, offers, escrows, payment channels, or checks. + - Adds the [Clawback transaction type](https://github.com/XRPLF/XRPL-Standards/blob/master/XLS-39d-clawback/README.md#331-clawback-transaction), containing these fields: + - `Account`: The issuer of the asset being clawed back. Must also be the sender of the transaction. + - `Amount`: The amount being clawed back, with the `Amount.issuer` being the token holder's address. +- Adds [AMM](https://github.com/XRPLF/XRPL-Standards/discussions/78) ([#4294](https://github.com/XRPLF/rippled/pull/4294), [#4626](https://github.com/XRPLF/rippled/pull/4626)) feature: + - Adds `amm_info` API to retrieve AMM information for a given tokens pair. + - Adds `AMMCreate` transaction type to create `AMM` instance. + - Adds `AMMDeposit` transaction type to deposit funds into `AMM` instance. + - Adds `AMMWithdraw` transaction type to withdraw funds from `AMM` instance. + - Adds `AMMVote` transaction type to vote for the trading fee of `AMM` instance. + - Adds `AMMBid` transaction type to bid for the Auction Slot of `AMM` instance. + - Adds `AMMDelete` transaction type to delete `AMM` instance. + - Adds `sfAMMID` to `AccountRoot` to indicate that the account is `AMM`'s account. `AMMID` is used to fetch `ltAMM`. + - Adds `lsfAMMNode` `TrustLine` flag to indicate that one side of the `TrustLine` is `AMM` account. + - Adds `tfLPToken`, `tfSingleAsset`, `tfTwoAsset`, `tfOneAssetLPToken`, `tfLimitLPToken`, `tfTwoAssetIfEmpty`, + `tfWithdrawAll`, `tfOneAssetWithdrawAll` which allow a trader to specify different fields combination + for `AMMDeposit` and `AMMWithdraw` transactions. + - Adds new transaction result codes: + - tecUNFUNDED_AMM: insufficient balance to fund AMM. The account does not have funds for liquidity provision. + - tecAMM_BALANCE: AMM has invalid balance. Calculated balances greater than the current pool balances. + - tecAMM_FAILED: AMM transaction failed. Fails due to a processing failure. + - tecAMM_INVALID_TOKENS: AMM invalid LP tokens. Invalid input values, format, or calculated values. + - tecAMM_EMPTY: AMM is in empty state. Transaction requires AMM in non-empty state (LP tokens > 0). + - tecAMM_NOT_EMPTY: AMM is not in empty state. Transaction requires AMM in empty state (LP tokens == 0). + - tecAMM_ACCOUNT: AMM account. Clawback of AMM account. + - tecINCOMPLETE: Some work was completed, but more submissions required to finish. AMMDelete partially deletes the trustlines. + +## XRP Ledger server version 1.11.0 + +[Version 1.11.0](https://github.com/XRPLF/rippled/releases/tag/1.11.0) was released on Jun 20, 2023. + +### Breaking changes in 1.11 + +- Added the ability to mark amendments as obsolete. For the `feature` admin API, there is a new possible value for the `vetoed` field. (https://github.com/XRPLF/rippled/pull/4291) + - The value of `vetoed` can now be `true`, `false`, or `"Obsolete"`. +- Removed the acceptance of seeds or public keys in place of account addresses. (https://github.com/XRPLF/rippled/pull/4404) + - This simplifies the API and encourages better security practices (i.e. seeds should never be sent over the network). +- For the `ledger_data` method, when all entries are filtered out, the `state` field of the response is now an empty list (in other words, an empty array, `[]`). (Previously, it would return `null`.) While this is technically a breaking change, the new behavior is consistent with the documentation, so this is considered only a bug fix. (https://github.com/XRPLF/rippled/pull/4398) +- If and when the `fixNFTokenRemint` amendment activates, there will be a new AccountRoot field, `FirstNFTSequence`. This field is set to the current account sequence when the account issues their first NFT. If an account has not issued any NFTs, then the field is not set. ([#4406](https://github.com/XRPLF/rippled/pull/4406)) + - There is a new account deletion restriction: an account can only be deleted if `FirstNFTSequence` + `MintedNFTokens` + `256` is less than the current ledger sequence. + - This is potentially a breaking change if clients have logic for determining whether an account can be deleted. +- NetworkID + - For sidechains and networks with a network ID greater than 1024, there is a new [transaction common field](https://xrpl.org/transaction-common-fields.html), `NetworkID`. (https://github.com/XRPLF/rippled/pull/4370) + - This field helps to prevent replay attacks and is now required for chains whose network ID is 1025 or higher. + - The field must be omitted for Mainnet, so there is no change for Mainnet users. + - There are three new local error codes: + - `telNETWORK_ID_MAKES_TX_NON_CANONICAL`: a `NetworkID` is present but the chain's network ID is less than 1025. Remove the field from the transaction, and try again. + - `telREQUIRES_NETWORK_ID`: a `NetworkID` is required, but is not present. Add the field to the transaction, and try again. + - `telWRONG_NETWORK`: a `NetworkID` is specified, but it is for a different network. Submit the transaction to a different server which is connected to the correct network. + +### Additions and bug fixes in 1.11 + +- Added `nftoken_id`, `nftoken_ids` and `offer_id` meta fields into NFT `tx` and `account_tx` responses. (https://github.com/XRPLF/rippled/pull/4447) +- Added an `account_flags` object to the `account_info` method response. (https://github.com/XRPLF/rippled/pull/4459) +- Added `NFTokenPages` to the `account_objects` RPC. (https://github.com/XRPLF/rippled/pull/4352) +- Fixed: `marker` returned from the `account_lines` command would not work on subsequent commands. (https://github.com/XRPLF/rippled/pull/4361) + +## XRP Ledger server version 1.10.0 + +[Version 1.10.0](https://github.com/XRPLF/rippled/releases/tag/1.10.0) +was released on Mar 14, 2023. + +### Breaking changes in 1.10 + +- If the `XRPFees` feature is enabled, the `fee_ref` field will be + removed from the [ledger subscription stream](https://xrpl.org/subscribe.html#ledger-stream), because it will no longer + have any meaning. + +## API Version 2 + +API version 2 is introduced in `rippled` version 2.0. Users can request it explicitly by specifying `"api_version" : 2`. + +#### Removed methods + +In API version 2, the following deprecated methods are no longer available: (https://github.com/XRPLF/rippled/pull/4759) + +- `tx_history` - Instead, use other methods such as `account_tx` or `ledger` with the `transactions` field set to `true`. +- `ledger_header` - Instead, use the `ledger` method. + +#### Modifications to JSON transaction element in V2 + +In API version 2, JSON elements for transaction output have been changed and made consistent for all methods which output transactions. (https://github.com/XRPLF/rippled/pull/4775) +This helps to unify the JSON serialization format of transactions. (https://github.com/XRPLF/clio/issues/722, https://github.com/XRPLF/rippled/issues/4727) + +- JSON transaction element is named `tx_json` +- Binary transaction element is named `tx_blob` +- JSON transaction metadata element is named `meta` +- Binary transaction metadata element is named `meta_blob` + +Additionally, these elements are now consistently available next to `tx_json` (i.e. sibling elements), where possible: + +- `hash` - Transaction ID. This data was stored inside transaction output in API version 1, but in API version 2 is a sibling element. +- `ledger_index` - Ledger index (only set on validated ledgers) +- `ledger_hash` - Ledger hash (only set on closed or validated ledgers) +- `close_time_iso` - Ledger close time expressed in ISO 8601 time format (only set on validated ledgers) +- `validated` - Bool element set to `true` if the transaction is in a validated ledger, otherwise `false` + +This change affects the following methods: + +- `tx` - Transaction data moved into element `tx_json` (was inline inside `result`) or, if binary output was requested, moved from `tx` to `tx_blob`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements +- `account_tx` - Renamed transaction element from `tx` to `tx_json`. Renamed binary transaction metadata element (if it was requested) from `meta` to `meta_blob`. Changed location of `hash` and added new elements +- `transaction_entry` - Renamed transaction metadata element from `metadata` to `meta`. Changed location of `hash` and added new elements +- `subscribe` - Renamed transaction element from `transaction` to `tx_json`. Changed location of `hash` and added new elements +- `sign`, `sign_for`, `submit` and `submit_multisigned` - Changed location of `hash` element. + +#### Modification to `Payment` transaction JSON schema + +When reading Payments, the `Amount` field should generally **not** be used. Instead, use [delivered_amount](https://xrpl.org/partial-payments.html#the-delivered_amount-field) to see the amount that the Payment delivered. To clarify its meaning, the `Amount` field is being renamed to `DeliverMax`. (https://github.com/XRPLF/rippled/pull/4733) + +- In `Payment` transaction type, JSON RPC field `Amount` is renamed to `DeliverMax`. To enable smooth client transition, `Amount` is still handled, as described below: (https://github.com/XRPLF/rippled/pull/4733) + - On JSON RPC input (e.g. `submit_multisigned` etc. methods), `Amount` is recognized as an alias to `DeliverMax` for both API version 1 and version 2 clients. + - On JSON RPC input, submitting both `Amount` and `DeliverMax` fields is allowed _only_ if they are identical; otherwise such input is rejected with `rpcINVALID_PARAMS` error. + - On JSON RPC output (e.g. `subscribe`, `account_tx` etc. methods), `DeliverMax` is present in both API version 1 and version 2. + - On JSON RPC output, `Amount` is only present in API version 1 and _not_ in version 2. + +#### Modifications to account_info response + +- `signer_lists` is returned in the root of the response. In API version 1, it was nested under `account_data`. (https://github.com/XRPLF/rippled/pull/3770) +- When using an invalid `signer_lists` value, the API now returns an "invalidParams" error. (https://github.com/XRPLF/rippled/pull/4585) + - (`signer_lists` must be a boolean. In API version 1, strings were accepted and may return a normal response - i.e. as if `signer_lists` were `true`.) + +#### Modifications to [account_tx](https://xrpl.org/account_tx.html#account_tx) response + +- Using `ledger_index_min`, `ledger_index_max`, and `ledger_index` returns `invalidParams` because if you use `ledger_index_min` or `ledger_index_max`, then it does not make sense to also specify `ledger_index`. In API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4571) + - The same applies for `ledger_index_min`, `ledger_index_max`, and `ledger_hash`. (https://github.com/XRPLF/rippled/issues/4545#issuecomment-1565065579) +- Using a `ledger_index_min` or `ledger_index_max` beyond the range of ledgers that the server has: + - returns `lgrIdxMalformed` in API version 2. Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/issues/4288) +- Attempting to use a non-boolean value (such as a string) for the `binary` or `forward` parameters returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4620) + +#### Modifications to [noripple_check](https://xrpl.org/noripple_check.html#noripple_check) response + +- Attempting to use a non-boolean value (such as a string) for the `transactions` parameter returns `invalidParams` (`rpcINVALID_PARAMS`). Previously, in API version 1, no error was returned. (https://github.com/XRPLF/rippled/pull/4620) + +# Unit tests for API changes + +The following information is useful to developers contributing to this project: + +The purpose of unit tests is to catch bugs and prevent regressions. In general, it often makes sense to create a test function when there is a breaking change to the API. For APIs that have changed in a new API version, the tests should be modified so that both the prior version and the new version are properly tested. + +To take one example: for `account_info` version 1, WebSocket and JSON-RPC behavior should be tested. The latest API version, i.e. API version 2, should be tested over WebSocket, JSON-RPC, and command line. diff --git a/src/ripple/app/misc/AmendmentTable.h b/src/ripple/app/misc/AmendmentTable.h index d7f7c8b26..fef13d50d 100644 --- a/src/ripple/app/misc/AmendmentTable.h +++ b/src/ripple/app/misc/AmendmentTable.h @@ -81,11 +81,11 @@ public: firstUnsupportedExpected() const = 0; virtual Json::Value - getJson() const = 0; + getJson(bool isAdmin) const = 0; /** Returns a Json::objectValue. */ virtual Json::Value - getJson(uint256 const& amendment) const = 0; + getJson(uint256 const& amendment, bool isAdmin) const = 0; /** Called when a new fully-validated ledger is accepted. */ void diff --git a/src/ripple/app/misc/impl/AmendmentTable.cpp b/src/ripple/app/misc/impl/AmendmentTable.cpp index 8f4f03219..8f9556e07 100644 --- a/src/ripple/app/misc/impl/AmendmentTable.cpp +++ b/src/ripple/app/misc/impl/AmendmentTable.cpp @@ -388,6 +388,7 @@ private: Json::Value& v, uint256 const& amendment, AmendmentState const& state, + bool isAdmin, std::lock_guard const& lock) const; void @@ -428,9 +429,9 @@ public: firstUnsupportedExpected() const override; Json::Value - getJson() const override; + getJson(bool isAdmin) const override; Json::Value - getJson(uint256 const&) const override; + getJson(uint256 const&, bool isAdmin) const override; bool needValidatedLedger(LedgerIndex seq) const override; @@ -906,13 +907,14 @@ AmendmentTableImpl::injectJson( Json::Value& v, const uint256& id, const AmendmentState& fs, + bool isAdmin, std::lock_guard const&) const { if (!fs.name.empty()) v[jss::name] = fs.name; v[jss::supported] = fs.supported; - if (!fs.enabled) + if (!fs.enabled && isAdmin) { if (fs.vote == AmendmentVote::obsolete) v[jss::vetoed] = "Obsolete"; @@ -921,7 +923,7 @@ AmendmentTableImpl::injectJson( } v[jss::enabled] = fs.enabled; - if (!fs.enabled && lastVote_) + if (!fs.enabled && lastVote_ && isAdmin) { auto const votesTotal = lastVote_->trustedValidations(); auto const votesNeeded = lastVote_->threshold(); @@ -936,7 +938,7 @@ AmendmentTableImpl::injectJson( } Json::Value -AmendmentTableImpl::getJson() const +AmendmentTableImpl::getJson(bool isAdmin) const { Json::Value ret(Json::objectValue); { @@ -947,6 +949,7 @@ AmendmentTableImpl::getJson() const ret[to_string(e.first)] = Json::objectValue, e.first, e.second, + isAdmin, lock); } } @@ -954,16 +957,19 @@ AmendmentTableImpl::getJson() const } Json::Value -AmendmentTableImpl::getJson(uint256 const& amendmentID) const +AmendmentTableImpl::getJson(uint256 const& amendmentID, bool isAdmin) const { Json::Value ret = Json::objectValue; - Json::Value& jAmendment = (ret[to_string(amendmentID)] = Json::objectValue); { std::lock_guard lock(mutex_); AmendmentState const* a = get(amendmentID, lock); if (a) - injectJson(jAmendment, amendmentID, *a, lock); + { + Json::Value& jAmendment = + (ret[to_string(amendmentID)] = Json::objectValue); + injectJson(jAmendment, amendmentID, *a, isAdmin, lock); + } } return ret; diff --git a/src/ripple/rpc/handlers/Feature1.cpp b/src/ripple/rpc/handlers/Feature1.cpp index 1858a062e..131a1e5d0 100644 --- a/src/ripple/rpc/handlers/Feature1.cpp +++ b/src/ripple/rpc/handlers/Feature1.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ doFeature(RPC::JsonContext& context) if (context.app.config().reporting()) return rpcError(rpcREPORTING_UNSUPPORTED); + bool const isAdmin = context.role == Role::ADMIN; // Get majority amendment status majorityAmendments_t majorities; @@ -47,7 +49,7 @@ doFeature(RPC::JsonContext& context) if (!context.params.isMember(jss::feature)) { - auto features = table.getJson(); + auto features = table.getJson(isAdmin); for (auto const& [h, t] : majorities) { @@ -69,13 +71,18 @@ doFeature(RPC::JsonContext& context) if (context.params.isMember(jss::vetoed)) { + if (!isAdmin) + return rpcError(rpcNO_PERMISSION); + if (context.params[jss::vetoed].asBool()) table.veto(feature); else table.unVeto(feature); } - Json::Value jvReply = table.getJson(feature); + Json::Value jvReply = table.getJson(feature, isAdmin); + if (!jvReply) + return rpcError(rpcBAD_FEATURE); auto m = majorities.find(feature); if (m != majorities.end()) diff --git a/src/ripple/rpc/handlers/ServerDefinitions.cpp b/src/ripple/rpc/handlers/ServerDefinitions.cpp index 81559b7f5..9cd799ba1 100644 --- a/src/ripple/rpc/handlers/ServerDefinitions.cpp +++ b/src/ripple/rpc/handlers/ServerDefinitions.cpp @@ -544,7 +544,8 @@ doServerDefinitions(RPC::JsonContext& context) if (auto const valLedger = context.ledgerMaster.getValidatedLedger()) majorities = getMajorityAmendments(*valLedger); auto& table = context.app.getAmendmentTable(); - auto features = table.getJson(); + bool const isAdmin = true; + auto features = table.getJson(isAdmin); for (auto const& [h, t] : majorities) features[to_string(h)][jss::majority] = t.time_since_epoch().count(); diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp index 4daf66d6e..862dcc055 100644 --- a/src/ripple/rpc/impl/Handler.cpp +++ b/src/ripple/rpc/impl/Handler.cpp @@ -106,6 +106,9 @@ Handler const handlerArray[]{ Role::USER, NO_CONDITION}, {"download_shard", byRef(&doDownloadShard), Role::ADMIN, NO_CONDITION}, + {"feature", byRef(&doFeature), Role::USER, NO_CONDITION}, + {"fee", byRef(&doFee), Role::USER, NEEDS_CURRENT_LEDGER}, + {"fetch_info", byRef(&doFetchInfo), Role::ADMIN, NO_CONDITION}, #ifdef RIPPLED_REPORTING {"gateway_balances", byRef(&doGatewayBalances), Role::ADMIN, NO_CONDITION}, #else @@ -116,9 +119,6 @@ Handler const handlerArray[]{ byRef(&doGetAggregatePrice), Role::USER, NO_CONDITION}, - {"feature", byRef(&doFeature), Role::ADMIN, NO_CONDITION}, - {"fee", byRef(&doFee), Role::USER, NEEDS_CURRENT_LEDGER}, - {"fetch_info", byRef(&doFetchInfo), Role::ADMIN, NO_CONDITION}, {"inject", byRef(&doInject), Role::ADMIN, NEEDS_CURRENT_LEDGER}, {"ledger_accept", byRef(&doLedgerAccept), diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 64e6a0386..238e05ba5 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -288,7 +288,7 @@ public: uint256 const unsupportedID = amendmentId(unsupported_[0]); { Json::Value const unsupp = - table->getJson(unsupportedID)[to_string(unsupportedID)]; + table->getJson(unsupportedID, true)[to_string(unsupportedID)]; BEAST_EXPECT(unsupp.size() == 0); } @@ -296,7 +296,7 @@ public: table->veto(unsupportedID); { Json::Value const unsupp = - table->getJson(unsupportedID)[to_string(unsupportedID)]; + table->getJson(unsupportedID, true)[to_string(unsupportedID)]; BEAST_EXPECT(unsupp[jss::vetoed].asBool()); } } @@ -638,6 +638,22 @@ public: BEAST_EXPECT(enabled.empty()); BEAST_EXPECT(majority.empty()); + uint256 const unsupportedID = amendmentId(unsupported_[0]); + { + Json::Value const unsupp = + table->getJson(unsupportedID, false)[to_string(unsupportedID)]; + BEAST_EXPECT(unsupp.size() == 0); + } + + table->veto(unsupportedID); + { + Json::Value const unsupp = + table->getJson(unsupportedID, false)[to_string(unsupportedID)]; + BEAST_EXPECT(!unsupp[jss::vetoed].asBool()); + } + + votes.emplace_back(testAmendment, validators.size()); + votes.emplace_back(testAmendment, validators.size()); doRound( diff --git a/src/test/rpc/Feature_test.cpp b/src/test/rpc/Feature_test.cpp index dcd95c8a9..fd63aee98 100644 --- a/src/test/rpc/Feature_test.cpp +++ b/src/test/rpc/Feature_test.cpp @@ -205,11 +205,94 @@ class Feature_test : public beast::unit_test::suite return cfg; })}; - auto jrr = env.rpc("feature")[jss::result]; - // The current HTTP/S ServerHandler returns an HTTP 403 error code here - // rather than a noPermission JSON error. The JSONRPCClient just eats - // that error and returns an null result. - BEAST_EXPECT(jrr.isNull()); + { + auto result = env.rpc("feature")[jss::result]; + BEAST_EXPECT(result.isMember(jss::features)); + // There should be at least 50 amendments. Don't do exact + // comparison to avoid maintenance as more amendments are added in + // the future. + BEAST_EXPECT(result[jss::features].size() >= 50); + for (auto it = result[jss::features].begin(); + it != result[jss::features].end(); + ++it) + { + uint256 id; + (void)id.parseHex(it.key().asString().c_str()); + if (!BEAST_EXPECT((*it).isMember(jss::name))) + return; + bool expectEnabled = + env.app().getAmendmentTable().isEnabled(id); + bool expectSupported = + env.app().getAmendmentTable().isSupported(id); + BEAST_EXPECTS( + (*it).isMember(jss::enabled) && + (*it)[jss::enabled].asBool() == expectEnabled, + (*it)[jss::name].asString() + " enabled"); + BEAST_EXPECTS( + (*it).isMember(jss::supported) && + (*it)[jss::supported].asBool() == expectSupported, + (*it)[jss::name].asString() + " supported"); + BEAST_EXPECT(!(*it).isMember(jss::vetoed)); + BEAST_EXPECT(!(*it).isMember(jss::majority)); + BEAST_EXPECT(!(*it).isMember(jss::count)); + BEAST_EXPECT(!(*it).isMember(jss::validations)); + BEAST_EXPECT(!(*it).isMember(jss::threshold)); + } + } + + { + Json::Value params; + // invalid feature + params[jss::feature] = + "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD" + "EF"; + auto const result = env.rpc( + "json", + "feature", + boost::lexical_cast(params))[jss::result]; + BEAST_EXPECTS( + result[jss::error] == "badFeature", result.toStyledString()); + BEAST_EXPECT( + result[jss::error_message] == "Feature unknown or invalid."); + } + + { + Json::Value params; + params[jss::feature] = + "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515" + "A7"; + // invalid param + params[jss::vetoed] = true; + auto const result = env.rpc( + "json", + "feature", + boost::lexical_cast(params))[jss::result]; + BEAST_EXPECTS( + result[jss::error] == "noPermission", + result[jss::error].asString()); + BEAST_EXPECT( + result[jss::error_message] == + "You don't have permission for this command."); + } + + { + std::string const feature = + "C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD" + "37"; + Json::Value params; + params[jss::feature] = feature; + auto const result = env.rpc( + "json", + "feature", + boost::lexical_cast(params))[jss::result]; + BEAST_EXPECT(result.isMember(feature)); + auto const amendmentResult = result[feature]; + BEAST_EXPECT(amendmentResult[jss::enabled].asBool() == false); + BEAST_EXPECT(amendmentResult[jss::supported].asBool() == true); + BEAST_EXPECT( + amendmentResult[jss::name].asString() == + "fixMasterKeyAsRegularKey"); + } } void