diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index 95f665e58..5d87c7b14 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -3275,6 +3275,10 @@
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index f0a2286c5..73ce4306e 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -3765,6 +3765,9 @@
ripple\rpc
+
+ ripple\rpc\tests
+
ripple\rpc\tests
diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h
index 337586b6e..2b10530bc 100644
--- a/src/ripple/protocol/JsonFields.h
+++ b/src/ripple/protocol/JsonFields.h
@@ -344,6 +344,7 @@ JSS ( severity ); // in: LogLevel
JSS ( signature ); // out: NetworkOPs
JSS ( signing_key ); // out: NetworkOPs
JSS ( signer_list ); // in: AccountObjects
+JSS ( signer_lists ); // in/out: AccountInfo
JSS ( snapshot ); // in: Subscribe
JSS ( source_account ); // in: PathRequest, RipplePathFind
JSS ( source_amount ); // in: PathRequest, RipplePathFind
diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp
index b69fa1fff..726fbfc21 100644
--- a/src/ripple/rpc/handlers/AccountInfo.cpp
+++ b/src/ripple/rpc/handlers/AccountInfo.cpp
@@ -34,11 +34,13 @@
namespace ripple {
// {
-// account: ,
-// strict:
-// if true, only allow public keys and addresses. false, default.
+// account: ,
+// strict: // optional (default false)
+// // if true only allow public keys and addresses.
// ledger_hash :
// ledger_index :
+// signer_lists : // optional (default false)
+// // if true return SignerList(s).
// }
// TODO(tom): what is that "default"?
@@ -72,6 +74,22 @@ Json::Value doAccountInfo (RPC::Context& context)
{
RPC::injectSLE(jvAccepted, *sleAccepted);
result[jss::account_data] = jvAccepted;
+
+ // Return SignerList(s) if that is requested.
+ if (params.isMember (jss::signer_lists) &&
+ params[jss::signer_lists].asBool ())
+ {
+ // We put the SignerList in an array because of an anticipated
+ // future when we support multiple signer lists on one account.
+ auto& jvSignerList = result[jss::account_data][jss::signer_lists];
+ jvSignerList = Json::arrayValue;
+
+ // This code will need to be revisited if in the future we support
+ // multiple SignerLists on one account.
+ auto const sleSigners = ledger->read (keylet::signers (accountID));
+ if (sleSigners)
+ jvSignerList.append (sleSigners->getJson (0));
+ }
}
else
{
diff --git a/src/ripple/rpc/tests/AccountInfo_test.cpp b/src/ripple/rpc/tests/AccountInfo_test.cpp
new file mode 100644
index 000000000..8555f7202
--- /dev/null
+++ b/src/ripple/rpc/tests/AccountInfo_test.cpp
@@ -0,0 +1,165 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2016 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include // jss:: definitions
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+class AccountInfo_test : public beast::unit_test::suite
+{
+public:
+
+ void testErrors()
+ {
+ using namespace jtx;
+ Env env(*this);
+ {
+ // account_info with no account.
+ auto const info = env.rpc ("json", "account_info", "{ }");
+ expect (info[jss::result][jss::error_message] ==
+ "Missing field 'account'.");
+ }
+ {
+ // account_info with a malformed account sting.
+ auto const info = env.rpc ("json", "account_info", "{\"account\": "
+ "\"n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV\"}");
+ expect (info[jss::result][jss::error_message] ==
+ "Disallowed seed.");
+ }
+ {
+ // account_info with an account that's not in the ledger.
+ Account const bogie {"bogie"};
+ auto const info = env.rpc ("json", "account_info",
+ std::string ("{ ") + "\"account\": \"" + bogie.human() + "\"}");
+ expect (info[jss::result][jss::error_message] ==
+ "Account not found.");
+ }
+ }
+
+ // Test the "signer_lists" argument in account_info.
+ void testSignerLists()
+ {
+ using namespace jtx;
+ Env env(*this, features(featureMultiSign));
+ Account const alice {"alice"};
+ env.fund(XRP(1000), alice);
+
+ auto const withoutSigners = std::string ("{ ") +
+ "\"account\": \"" + alice.human() + "\"}";
+
+ auto const withSigners = std::string ("{ ") +
+ "\"account\": \"" + alice.human() + "\", " +
+ "\"signer_lists\": true }";
+
+ // Alice has no SignerList yet.
+ {
+ // account_info without the "signer_lists" argument.
+ auto const info = env.rpc ("json", "account_info", withoutSigners);
+ expect (! info[jss::result][jss::account_data].
+ isMember (jss::signer_lists));
+ }
+ {
+ // account_info with the "signer_lists" argument.
+ auto const info = env.rpc ("json", "account_info", withSigners);
+ auto const& data = info[jss::result][jss::account_data];
+ expect (data.isMember (jss::signer_lists));
+ auto const& signerLists = data[jss::signer_lists];
+ expect (signerLists.isArray());
+ expect (signerLists.size() == 0);
+ }
+
+ // Give alice a SignerList.
+ Account const bogie {"bogie"};
+
+ Json::Value const smallSigners = signers(alice, 2, { { bogie, 3 } });
+ env(smallSigners);
+ {
+ // account_info without the "signer_lists" argument.
+ auto const info = env.rpc ("json", "account_info", withoutSigners);
+ expect (! info[jss::result][jss::account_data].
+ isMember (jss::signer_lists));
+ }
+ {
+ // account_info with the "signer_lists" argument.
+ auto const info = env.rpc ("json", "account_info", withSigners);
+ auto const& data = info[jss::result][jss::account_data];
+ expect (data.isMember (jss::signer_lists));
+ auto const& signerLists = data[jss::signer_lists];
+ expect (signerLists.isArray());
+ expect (signerLists.size() == 1);
+ auto const& signers = signerLists[0u];
+ expect (signers.isObject());
+ expect (signers[sfSignerQuorum.jsonName] == 2);
+ auto const& signerEntries = signers[sfSignerEntries.jsonName];
+ expect (signerEntries.size() == 1);
+ auto const& entry0 = signerEntries[0u][sfSignerEntry.jsonName];
+ expect (entry0[sfSignerWeight.jsonName] == 3);
+ }
+
+ // Give alice a big signer list
+ Account const demon {"demon"};
+ Account const ghost {"ghost"};
+ Account const haunt {"haunt"};
+ Account const jinni {"jinni"};
+ Account const phase {"phase"};
+ Account const shade {"shade"};
+ Account const spook {"spook"};
+
+ Json::Value const bigSigners = signers(alice, 4, {
+ {bogie, 1}, {demon, 1}, {ghost, 1}, {haunt, 1},
+ {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}, });
+ env(bigSigners);
+ {
+ // account_info with the "signer_lists" argument.
+ auto const info = env.rpc ("json", "account_info", withSigners);
+ auto const& data = info[jss::result][jss::account_data];
+ expect (data.isMember (jss::signer_lists));
+ auto const& signerLists = data[jss::signer_lists];
+ expect (signerLists.isArray());
+ expect (signerLists.size() == 1);
+ auto const& signers = signerLists[0u];
+ expect (signers.isObject());
+ expect (signers[sfSignerQuorum.jsonName] == 4);
+ auto const& signerEntries = signers[sfSignerEntries.jsonName];
+ expect (signerEntries.size() == 8);
+ for (unsigned i = 0u; i < 8; ++i)
+ {
+ auto const& entry = signerEntries[i][sfSignerEntry.jsonName];
+ expect (entry.size() == 2);
+ expect (entry.isMember(sfAccount.jsonName));
+ expect (entry[sfSignerWeight.jsonName] == 1);
+ }
+ }
+ }
+
+ void run()
+ {
+ testErrors();
+ testSignerLists();
+ }
+};
+
+BEAST_DEFINE_TESTSUITE(AccountInfo,app,ripple);
+
+}
+}
+
diff --git a/src/ripple/unity/rpcx.cpp b/src/ripple/unity/rpcx.cpp
index 8a7cc2248..9b124e5a6 100644
--- a/src/ripple/unity/rpcx.cpp
+++ b/src/ripple/unity/rpcx.cpp
@@ -99,6 +99,7 @@
#include
#include
+#include
#include
#include
#include