Skip to content

Commit c85ec93

Browse files
ortyomkalebdron
authored andcommitted
CompareAndSetAccountDetail command
Signed-off-by: artyom-yurin <artem_yrin@mail.ru>
1 parent c2a814e commit c85ec93

23 files changed

+753
-3
lines changed

docs/source/api/commands.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,3 +834,63 @@ Possible Stateful Validation Errors
834834

835835
.. [#f1] https://www.ietf.org/rfc/rfc1035.txt
836836
.. [#f2] https://www.ietf.org/rfc/rfc1123.txt
837+
838+
Compare and Set Account Detail
839+
------------------------------
840+
841+
Purpose
842+
^^^^^^^
843+
844+
Purpose of compare and set account detail command is to set key-value information for a given account if the old value matches the value passed.
845+
846+
Schema
847+
^^^^^^
848+
849+
.. code-block:: proto
850+
851+
message CompareAndSetAccountDetail{
852+
string account_id = 1;
853+
string key = 2;
854+
string value = 3;
855+
oneof opt_old_value {
856+
string old_value = 4;
857+
}
858+
}
859+
860+
.. note::
861+
Pay attention, that old_value field is optional.
862+
This is due to the fact that the key-value pair might not exist.
863+
864+
Structure
865+
^^^^^^^^^
866+
867+
.. csv-table::
868+
:header: "Field", "Description", "Constraint", "Example"
869+
:widths: 15, 30, 20, 15
870+
871+
"Account ID", "id of the account to which the key-value information was set. If key-value pair doesnot exist , then it will be created", "an existing account", "artyom@soramitsu"
872+
"Key", "key of information being set", "`[A-Za-z0-9_]{1,64}`", "Name"
873+
"Value", "new value for the corresponding key", "length of value ≤ 4096", "Artyom"
874+
"Old value", "current value for the corresponding key", "length of value ≤ 4096", "Artem"
875+
876+
Validation
877+
^^^^^^^^^^
878+
879+
Three cases:
880+
881+
Case 1. When transaction creator wants to set account detail to his/her account and he or she has permission GetMyAccountDetail / GetAllAccountsDetail / GetDomainAccountDetail
882+
883+
Case 2. When transaction creator wants to set account detail to another account and he or she has permissions SetAccountDetail and GetAllAccountsDetail / GetDomainAccountDetail
884+
885+
Case 3. SetAccountDetail permission was granted to transaction creator and he or she has permission GetAllAccountsDetail / GetDomainAccountDetail
886+
887+
Possible Stateful Validation Errors
888+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
889+
890+
.. csv-table::
891+
:header: "Code", "Error Name", "Description", "How to solve"
892+
893+
"1", "Could not compare and set account detail", "Internal error happened", "Try again or contact developers"
894+
"2", "No such permissions", "Command's creator does not have permission to set and read account detail for this account", "Grant the necessary permission"
895+
"3", "No such account", "Cannot find account to set account detail to", "Make sure account id is correct"
896+
"4", "No match values", "Old values do not match", "Make sure old value is correct"

irohad/ametsuchi/command_executor.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace shared_model {
2929
class SetQuorum;
3030
class SubtractAssetQuantity;
3131
class TransferAsset;
32+
class CompareAndSetAccountDetail;
3233
} // namespace interface
3334
} // namespace shared_model
3435

@@ -119,6 +120,10 @@ namespace iroha {
119120

120121
virtual CommandResult operator()(
121122
const shared_model::interface::TransferAsset &command) = 0;
123+
124+
virtual CommandResult operator()(
125+
const shared_model::interface::CompareAndSetAccountDetail
126+
&command) = 0;
122127
};
123128
} // namespace ametsuchi
124129
} // namespace iroha

irohad/ametsuchi/impl/postgres_command_executor.cpp

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "interfaces/commands/add_peer.hpp"
1515
#include "interfaces/commands/add_signatory.hpp"
1616
#include "interfaces/commands/append_role.hpp"
17+
#include "interfaces/commands/compare_and_set_account_detail.hpp"
1718
#include "interfaces/commands/create_account.hpp"
1819
#include "interfaces/commands/create_asset.hpp"
1920
#include "interfaces/commands/create_domain.hpp"
@@ -266,6 +267,71 @@ namespace {
266267
return query;
267268
}
268269

270+
shared_model::interface::types::DomainIdType getDomainFromName(
271+
const shared_model::interface::types::AccountIdType &account_id) {
272+
// TODO 03.10.18 andrei: IR-1728 Move getDomainFromName to shared_model
273+
std::vector<std::string> res;
274+
boost::split(res, account_id, boost::is_any_of("@"));
275+
return res.at(1);
276+
}
277+
278+
/**
279+
* Generate an SQL subquery which checks if creator has corresponding
280+
* permissions for target account
281+
* It verifies individual, domain, and global permissions, and returns true if
282+
* any of listed permissions is present
283+
*/
284+
auto hasQueryPermission(
285+
const shared_model::interface::types::AccountIdType &creator,
286+
const shared_model::interface::types::AccountIdType &target_account,
287+
shared_model::interface::permissions::Role indiv_permission_id,
288+
shared_model::interface::permissions::Role all_permission_id,
289+
shared_model::interface::permissions::Role domain_permission_id,
290+
const shared_model::interface::types::DomainIdType &creator_domain,
291+
const shared_model::interface::types::DomainIdType
292+
&target_account_domain) {
293+
const auto bits = shared_model::interface::RolePermissionSet::size();
294+
const auto perm_str =
295+
shared_model::interface::RolePermissionSet({indiv_permission_id})
296+
.toBitstring();
297+
const auto all_perm_str =
298+
shared_model::interface::RolePermissionSet({all_permission_id})
299+
.toBitstring();
300+
const auto domain_perm_str =
301+
shared_model::interface::RolePermissionSet({domain_permission_id})
302+
.toBitstring();
303+
304+
boost::format cmd(R"(
305+
has_indiv_perm AS (
306+
SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
307+
& '%3%') = '%3%' FROM role_has_permissions AS rp
308+
JOIN account_has_roles AS ar on ar.role_id = rp.role_id
309+
WHERE ar.account_id = %2%
310+
),
311+
has_all_perm AS (
312+
SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
313+
& '%4%') = '%4%' FROM role_has_permissions AS rp
314+
JOIN account_has_roles AS ar on ar.role_id = rp.role_id
315+
WHERE ar.account_id = %2%
316+
),
317+
has_domain_perm AS (
318+
SELECT (COALESCE(bit_or(rp.permission), '0'::bit(%1%))
319+
& '%5%') = '%5%' FROM role_has_permissions AS rp
320+
JOIN account_has_roles AS ar on ar.role_id = rp.role_id
321+
WHERE ar.account_id = %2%
322+
),
323+
has_query_perm AS (
324+
SELECT (%2% = %6% AND (SELECT * FROM has_indiv_perm))
325+
OR (SELECT * FROM has_all_perm)
326+
OR (%7% = %8% AND (SELECT * FROM has_domain_perm)) AS perm
327+
)
328+
)");
329+
330+
return (cmd % bits % creator % perm_str % all_perm_str % domain_perm_str
331+
% target_account % creator_domain % target_account_domain)
332+
.str();
333+
}
334+
269335
std::string checkAccountDomainRoleOrGlobalRolePermission(
270336
shared_model::interface::permissions::Role global_permission,
271337
shared_model::interface::permissions::Role domain_permission,
@@ -797,6 +863,51 @@ namespace iroha {
797863
ELSE 1
798864
END AS result)";
799865

866+
const std::string PostgresCommandExecutor::compareAndSetAccountDetailBase =
867+
R"(PREPARE %s (text, text, text, text, text, text, text) AS
868+
WITH %s
869+
old_value AS
870+
(
871+
SELECT *
872+
FROM account
873+
WHERE
874+
account_id = $2
875+
AND CASE
876+
WHEN data ? $1 AND data->$1 ?$3
877+
THEN
878+
CASE
879+
WHEN $5 IS NOT NULL THEN data->$1->$3 = $5::jsonb
880+
ELSE FALSE
881+
END
882+
ELSE TRUE
883+
END
884+
),
885+
inserted AS
886+
(
887+
UPDATE account
888+
SET data = jsonb_set(
889+
CASE
890+
WHEN data ? $1 THEN data
891+
ELSE jsonb_set(data, array[$1], '{}')
892+
END,
893+
array[$1, $3], $4::jsonb
894+
)
895+
WHERE
896+
EXISTS (SELECT * FROM old_value)
897+
AND account_id = $2
898+
%s
899+
RETURNING (1)
900+
)
901+
SELECT CASE
902+
WHEN EXISTS (SELECT * FROM inserted) THEN 0
903+
WHEN NOT EXISTS
904+
(SELECT * FROM account WHERE account_id=$2) THEN 3
905+
WHEN NOT EXISTS (SELECT * FROM old_value) THEN 4
906+
%s
907+
ELSE 1
908+
END
909+
AS result)";
910+
800911
std::string CommandError::toString() const {
801912
return (boost::format("%s: %d with extra info '%s'") % command_name
802913
% error_code % error_extra)
@@ -1228,6 +1339,43 @@ namespace iroha {
12281339
sql_, cmd.str(), "TransferAsset", std::move(str_args));
12291340
}
12301341

1342+
CommandResult PostgresCommandExecutor::operator()(
1343+
const shared_model::interface::CompareAndSetAccountDetail &command) {
1344+
auto &account_id = command.accountId();
1345+
auto &key = command.key();
1346+
auto &value = command.value();
1347+
auto &old_value = command.oldValue();
1348+
1349+
auto cmd = boost::format(
1350+
"EXECUTE %1% ('%2%', '%3%', '%4%', '%5%', %6%, '%7%', '%8%')");
1351+
1352+
appendCommandName("compareAndSetAccountDetail", cmd, do_validation_);
1353+
1354+
std::string new_json_value = "\"" + value + "\"";
1355+
std::string expected_json_value = "NULL";
1356+
1357+
if (old_value) {
1358+
expected_json_value = "'\"" + old_value.get() + "\"'";
1359+
}
1360+
1361+
cmd = (cmd % creator_account_id_ % account_id % key % new_json_value
1362+
% expected_json_value % getDomainFromName(creator_account_id_)
1363+
% getDomainFromName(account_id));
1364+
1365+
auto str_args = [&account_id, &key, &new_json_value, &old_value] {
1366+
return getQueryArgsStringBuilder()
1367+
.append("account_id", account_id)
1368+
.append("key", key)
1369+
.append("value", new_json_value)
1370+
.append("old_value",
1371+
old_value ? "\"" + old_value.get() + "\"" : "NULL")
1372+
.finalize();
1373+
};
1374+
1375+
return executeQuery(
1376+
sql_, cmd.str(), "compareAndSetAccountDetail", std::move(str_args));
1377+
}
1378+
12311379
void PostgresCommandExecutor::prepareStatements(soci::session &sql) {
12321380
std::vector<PreparedStatement> statements;
12331381

@@ -1598,6 +1746,45 @@ namespace iroha {
15981746
R"( AND (SELECT * FROM has_perm))",
15991747
R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
16001748

1749+
statements.push_back(
1750+
{"compareAndSetAccountDetail",
1751+
compareAndSetAccountDetailBase,
1752+
{(boost::format(R"(
1753+
has_role_perm AS (%s),
1754+
has_grantable_perm AS (%s),
1755+
%s,
1756+
has_perm AS (SELECT CASE
1757+
WHEN (SELECT * FROM has_query_perm) THEN
1758+
CASE
1759+
WHEN (SELECT * FROM has_grantable_perm)
1760+
THEN true
1761+
WHEN ($1 = $2) THEN true
1762+
WHEN (SELECT * FROM has_role_perm)
1763+
THEN true
1764+
ELSE false END
1765+
ELSE false END
1766+
),
1767+
)")
1768+
% checkAccountRolePermission(
1769+
shared_model::interface::permissions::Role::kSetDetail, "$1")
1770+
% checkAccountGrantablePermission(
1771+
shared_model::interface::permissions::Grantable::
1772+
kSetMyAccountDetail,
1773+
"$1",
1774+
"$2")
1775+
% hasQueryPermission(
1776+
"$1",
1777+
"$2",
1778+
shared_model::interface::permissions::Role::kGetMyAccDetail,
1779+
shared_model::interface::permissions::Role::kGetAllAccDetail,
1780+
shared_model::interface::permissions::Role::
1781+
kGetDomainAccDetail,
1782+
"$6",
1783+
"$7"))
1784+
.str(),
1785+
R"( AND (SELECT * FROM has_perm))",
1786+
R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
1787+
16011788
for (const auto &st : statements) {
16021789
prepareStatement(sql, st);
16031790
}

irohad/ametsuchi/impl/postgres_command_executor.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ namespace iroha {
8383
CommandResult operator()(
8484
const shared_model::interface::TransferAsset &command) override;
8585

86+
CommandResult operator()(
87+
const shared_model::interface::CompareAndSetAccountDetail &command)
88+
override;
89+
8690
static void prepareStatements(soci::session &sql);
8791

8892
private:
@@ -111,6 +115,7 @@ namespace iroha {
111115
static const std::string setQuorumBase;
112116
static const std::string subtractAssetQuantityBase;
113117
static const std::string transferAssetBase;
118+
static const std::string compareAndSetAccountDetailBase;
114119
};
115120
} // namespace ametsuchi
116121
} // namespace iroha

shared_model/backend/protobuf/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_library(shared_model_proto_backend
3131
commands/impl/proto_set_quorum.cpp
3232
commands/impl/proto_subtract_asset_quantity.cpp
3333
commands/impl/proto_transfer_asset.cpp
34+
commands/impl/proto_compare_and_set_account_detail.cpp
3435
queries/impl/proto_query.cpp
3536
queries/impl/proto_get_account.cpp
3637
queries/impl/proto_get_account_asset_transactions.cpp

shared_model/backend/protobuf/commands/impl/proto_command.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "backend/protobuf/commands/proto_add_peer.hpp"
1010
#include "backend/protobuf/commands/proto_add_signatory.hpp"
1111
#include "backend/protobuf/commands/proto_append_role.hpp"
12+
#include "backend/protobuf/commands/proto_compare_and_set_account_detail.hpp"
1213
#include "backend/protobuf/commands/proto_create_account.hpp"
1314
#include "backend/protobuf/commands/proto_create_asset.hpp"
1415
#include "backend/protobuf/commands/proto_create_domain.hpp"
@@ -43,7 +44,8 @@ namespace {
4344
shared_model::proto::SetQuorum,
4445
shared_model::proto::SubtractAssetQuantity,
4546
shared_model::proto::TransferAsset,
46-
shared_model::proto::RemovePeer>;
47+
shared_model::proto::RemovePeer,
48+
shared_model::proto::CompareAndSetAccountDetail>;
4749

4850
/// list of types in proto variant
4951
using ProtoCommandListType = ProtoCommandVariantType::types;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright Soramitsu Co., Ltd. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include "backend/protobuf/commands/proto_compare_and_set_account_detail.hpp"
7+
8+
namespace shared_model {
9+
namespace proto {
10+
11+
CompareAndSetAccountDetail::CompareAndSetAccountDetail(
12+
iroha::protocol::Command &command)
13+
: compare_and_set_account_detail_{
14+
command.compare_and_set_account_detail()} {}
15+
16+
const interface::types::AccountIdType &
17+
CompareAndSetAccountDetail::accountId() const {
18+
return compare_and_set_account_detail_.account_id();
19+
}
20+
21+
const interface::types::AccountDetailKeyType &
22+
CompareAndSetAccountDetail::key() const {
23+
return compare_and_set_account_detail_.key();
24+
}
25+
26+
const interface::types::AccountDetailValueType &
27+
CompareAndSetAccountDetail::value() const {
28+
return compare_and_set_account_detail_.value();
29+
}
30+
31+
const boost::optional<interface::types::AccountDetailValueType>
32+
CompareAndSetAccountDetail::oldValue() const {
33+
if (compare_and_set_account_detail_.opt_old_value_case()
34+
== iroha::protocol::CompareAndSetAccountDetail::
35+
OPT_OLD_VALUE_NOT_SET) {
36+
return boost::none;
37+
}
38+
return compare_and_set_account_detail_.old_value();
39+
}
40+
41+
} // namespace proto
42+
} // namespace shared_model

0 commit comments

Comments
 (0)