Line data Source code
1 : /**
2 : * Copyright Soramitsu Co., Ltd. All Rights Reserved.
3 : * SPDX-License-Identifier: Apache-2.0
4 : */
5 :
6 : #include "ametsuchi/impl/postgres_command_executor.hpp"
7 :
8 : #include <soci/postgresql/soci-postgresql.h>
9 : #include <boost/algorithm/string.hpp>
10 : #include <boost/format.hpp>
11 : #include "ametsuchi/impl/soci_utils.hpp"
12 : #include "cryptography/public_key.hpp"
13 : #include "interfaces/commands/add_asset_quantity.hpp"
14 : #include "interfaces/commands/add_peer.hpp"
15 : #include "interfaces/commands/add_signatory.hpp"
16 : #include "interfaces/commands/append_role.hpp"
17 : #include "interfaces/commands/create_account.hpp"
18 : #include "interfaces/commands/create_asset.hpp"
19 : #include "interfaces/commands/create_domain.hpp"
20 : #include "interfaces/commands/create_role.hpp"
21 : #include "interfaces/commands/detach_role.hpp"
22 : #include "interfaces/commands/grant_permission.hpp"
23 : #include "interfaces/commands/remove_signatory.hpp"
24 : #include "interfaces/commands/revoke_permission.hpp"
25 : #include "interfaces/commands/set_account_detail.hpp"
26 : #include "interfaces/commands/set_quorum.hpp"
27 : #include "interfaces/commands/subtract_asset_quantity.hpp"
28 : #include "interfaces/commands/transfer_asset.hpp"
29 : #include "interfaces/common_objects/types.hpp"
30 : #include "interfaces/permission_to_string.hpp"
31 : #include "utils/string_builder.hpp"
32 :
33 : namespace {
34 : struct PreparedStatement {
35 : std::string command_name;
36 : std::string command_base;
37 : std::vector<std::string> permission_checks;
38 :
39 : static const std::string validationPrefix;
40 : static const std::string noValidationPrefix;
41 : };
42 :
43 : const std::string PreparedStatement::validationPrefix = "WithValidation";
44 : const std::string PreparedStatement::noValidationPrefix = "WithOutValidation";
45 :
46 : // Transforms prepared statement into two strings:
47 : // 1. SQL query with validation
48 : // 2. SQL query without validation
49 : std::pair<std::string, std::string> compileStatement(
50 : const PreparedStatement &statement) {
51 : // Create query with validation
52 47712 : auto with_validation = boost::format(statement.command_base)
53 47712 : % (statement.command_name + PreparedStatement::validationPrefix);
54 :
55 : // append all necessary checks to the query
56 199794 : for (const auto &check : statement.permission_checks) {
57 152082 : with_validation = with_validation % check;
58 : }
59 :
60 : // Create query without validation
61 47712 : auto without_validation = boost::format(statement.command_base)
62 47712 : % (statement.command_name + PreparedStatement::noValidationPrefix);
63 :
64 : // since checks are not needed, append empty strings to their place
65 199794 : for (size_t i = 0; i < statement.permission_checks.size(); i++) {
66 152082 : without_validation = without_validation % "";
67 152082 : }
68 :
69 47712 : return {with_validation.str(), without_validation.str()};
70 47712 : }
71 :
72 : void prepareStatement(soci::session &sql,
73 : const PreparedStatement &statement) {
74 47712 : auto queries = compileStatement(statement);
75 :
76 47712 : sql << queries.first;
77 47712 : sql << queries.second;
78 47712 : }
79 :
80 : template <typename QueryArgsCallable>
81 : iroha::expected::Error<iroha::ametsuchi::CommandError> makeCommandError(
82 : std::string &&command_name,
83 : const iroha::ametsuchi::CommandError::ErrorCodeType code,
84 : QueryArgsCallable &&query_args) noexcept {
85 15 : return iroha::expected::makeError(iroha::ametsuchi::CommandError{
86 15 : std::move(command_name), code, query_args()});
87 0 : }
88 :
89 : /// mapping between pairs of SQL error substrings and related fake error
90 : /// codes, which are indices in this collection
91 : const std::vector<std::tuple<std::string, std::string>> kSqlToFakeErrorCode =
92 410 : {std::make_tuple("Key (account_id)=", "is not present in table"),
93 41 : std::make_tuple("Key (permittee_account_id)", "is not present in table"),
94 41 : std::make_tuple("Key (role_id)=", "is not present in table"),
95 41 : std::make_tuple("Key (domain_id)=", "is not present in table"),
96 41 : std::make_tuple("Key (asset_id)=", "already exists"),
97 41 : std::make_tuple("Key (domain_id)=", "already exists"),
98 41 : std::make_tuple("Key (role_id)=", "already exists"),
99 41 : std::make_tuple("Key (account_id, public_key)=", "already exists"),
100 41 : std::make_tuple("Key (account_id)=", "already exists"),
101 41 : std::make_tuple("Key (default_role)=", "is not present in table")};
102 :
103 : /// mapping between command name, fake error code and related real error code
104 : const std::map<std::string, std::map<int, int>> kCmdNameToErrorCode{
105 41 : std::make_pair(
106 : "AddSignatory",
107 41 : std::map<int, int>{std::make_pair(0, 3), std::make_pair(7, 4)}),
108 41 : std::make_pair(
109 : "AppendRole",
110 41 : std::map<int, int>{std::make_pair(0, 3), std::make_pair(2, 4)}),
111 41 : std::make_pair(
112 : "DetachRole",
113 41 : std::map<int, int>{std::make_pair(0, 3), std::make_pair(2, 5)}),
114 41 : std::make_pair("RemoveSignatory",
115 41 : std::map<int, int>{std::make_pair(0, 3)}),
116 41 : std::make_pair("SetAccountDetail",
117 41 : std::map<int, int>{std::make_pair(0, 3)}),
118 41 : std::make_pair("SetQuorum", std::map<int, int>{std::make_pair(0, 3)}),
119 41 : std::make_pair("GrantPermission",
120 41 : std::map<int, int>{std::make_pair(1, 3)}),
121 41 : std::make_pair("RevokePermission",
122 41 : std::map<int, int>{std::make_pair(1, 3)}),
123 41 : std::make_pair(
124 : "CreateAccount",
125 41 : std::map<int, int>{std::make_pair(3, 3), std::make_pair(8, 4)}),
126 41 : std::make_pair(
127 : "CreateAsset",
128 41 : std::map<int, int>{std::make_pair(3, 3), std::make_pair(4, 4)}),
129 41 : std::make_pair(
130 : "CreateDomain",
131 41 : std::map<int, int>{std::make_pair(5, 3), std::make_pair(9, 4)}),
132 41 : std::make_pair("CreateRole", std::map<int, int>{std::make_pair(6, 3)}),
133 41 : std::make_pair("AddSignatory", std::map<int, int>{std::make_pair(7, 4)})};
134 :
135 : /**
136 : * Get a real error code based on the fake one and a command name
137 : * @param fake_error_code - inner error code to be translated into the user's
138 : * one
139 : * @param command_name of the failed command
140 : * @return real error code
141 : */
142 : boost::optional<iroha::ametsuchi::CommandError::ErrorCodeType>
143 : getRealErrorCode(size_t fake_error_code, const std::string &command_name) {
144 18 : auto fake_to_real_code = kCmdNameToErrorCode.find(command_name);
145 18 : if (fake_to_real_code == kCmdNameToErrorCode.end()) {
146 0 : return {};
147 : }
148 :
149 18 : auto real_code = fake_to_real_code->second.find(fake_error_code);
150 18 : if (real_code == fake_to_real_code->second.end()) {
151 0 : return {};
152 : }
153 :
154 18 : return real_code->second;
155 18 : }
156 :
157 : // TODO [IR-1830] Akvinikym 31.10.18: make benchmarks to compare exception
158 : // parsing vs nested queries
159 : /**
160 : * Get an error code from the text SQL error
161 : * @tparam QueryArgsCallable - type of callable to get query arguments
162 : * @param command_name - name of the failed command
163 : * @param error - string error, which SQL gave out
164 : * @param query_args - callable to get a string representation of query
165 : * arguments
166 : * @return command_error structure
167 : */
168 : template <typename QueryArgsCallable>
169 : iroha::ametsuchi::CommandResult getCommandError(
170 : std::string &&command_name,
171 : const std::string &error,
172 : QueryArgsCallable &&query_args) noexcept {
173 5 : std::string key, to_be_presented;
174 : bool errors_matched;
175 :
176 : // go through mapping of SQL errors and get index of the current error - it
177 : // is "fake" error code
178 38 : for (size_t fakeErrorCode = 0; fakeErrorCode < kSqlToFakeErrorCode.size();
179 33 : ++fakeErrorCode) {
180 38 : std::tie(key, to_be_presented) = kSqlToFakeErrorCode[fakeErrorCode];
181 38 : errors_matched = error.find(key) != std::string::npos
182 38 : and error.find(to_be_presented) != std::string::npos;
183 38 : if (errors_matched) {
184 5 : if (auto real_error_code =
185 5 : getRealErrorCode(fakeErrorCode, command_name)) {
186 5 : return makeCommandError(std::move(command_name),
187 5 : *real_error_code,
188 5 : std::forward<QueryArgsCallable>(query_args));
189 : }
190 0 : break;
191 : }
192 33 : }
193 : // parsing is not successful, return the general error
194 1 : return makeCommandError(std::move(command_name),
195 : 1,
196 1 : std::forward<QueryArgsCallable>(query_args));
197 5 : }
198 :
199 : /**
200 : * Executes sql query
201 : * Assumes that statement query returns 0 in case of success
202 : * or error code in case of failure
203 : * @tparam QueryArgsCallable - type of callable to get query arguments
204 : * @param sql - connection on which to execute statement
205 : * @param cmd - sql query to be executed
206 : * @param command_name - which command executes a query
207 : * @param query_args - callable to get a string representation of query
208 : * arguments
209 : * @return CommandResult with command name and error message
210 : */
211 : template <typename QueryArgsCallable>
212 : iroha::ametsuchi::CommandResult executeQuery(
213 : soci::session &sql,
214 : const std::string &cmd,
215 : std::string command_name,
216 : QueryArgsCallable &&query_args) noexcept {
217 : uint32_t result;
218 : try {
219 1618 : sql << cmd, soci::into(result);
220 1616 : if (result != 0) {
221 15 : return makeCommandError(std::move(command_name),
222 15 : result,
223 15 : std::forward<QueryArgsCallable>(query_args));
224 : }
225 1614 : return {};
226 5 : } catch (const std::exception &e) {
227 5 : return getCommandError(std::move(command_name),
228 5 : e.what(),
229 5 : std::forward<QueryArgsCallable>(query_args));
230 5 : }
231 1618 : }
232 :
233 : std::string checkAccountRolePermission(
234 : shared_model::interface::permissions::Role permission,
235 : const shared_model::interface::types::AccountIdType &account_id) {
236 : const auto perm_str =
237 50694 : shared_model::interface::RolePermissionSet({permission}).toBitstring();
238 50694 : const auto bits = shared_model::interface::RolePermissionSet::size();
239 50694 : std::string query = (boost::format(R"(
240 : SELECT COALESCE(bit_or(rp.permission), '0'::bit(%1%))
241 : & '%2%' = '%2%' FROM role_has_permissions AS rp
242 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
243 : WHERE ar.account_id = %3%)")
244 50694 : % bits % perm_str % account_id)
245 50694 : .str();
246 50694 : return query;
247 50694 : }
248 :
249 : std::string checkAccountGrantablePermission(
250 : shared_model::interface::permissions::Grantable permission,
251 : const shared_model::interface::types::AccountIdType &creator_id,
252 : const shared_model::interface::types::AccountIdType &account_id) {
253 : const auto perm_str =
254 14910 : shared_model::interface::GrantablePermissionSet({permission})
255 14910 : .toBitstring();
256 14910 : const auto bits = shared_model::interface::GrantablePermissionSet::size();
257 14910 : std::string query = (boost::format(R"(
258 : SELECT COALESCE(bit_or(permission), '0'::bit(%1%))
259 : & '%2%' = '%2%' FROM account_has_grantable_permissions
260 : WHERE account_id = %4% AND
261 : permittee_account_id = %3%
262 14910 : )") % bits % perm_str
263 14910 : % creator_id % account_id)
264 14910 : .str();
265 14910 : return query;
266 14910 : }
267 :
268 : std::string checkAccountDomainRoleOrGlobalRolePermission(
269 : shared_model::interface::permissions::Role global_permission,
270 : shared_model::interface::permissions::Role domain_permission,
271 : const shared_model::interface::types::AccountIdType &creator_id,
272 : const shared_model::interface::types::AssetIdType
273 : &id_with_target_domain) {
274 5964 : std::string query = (boost::format(R"(WITH
275 : has_global_role_perm AS (%1%),
276 : has_domain_role_perm AS (%2%)
277 : SELECT CASE
278 : WHEN (SELECT * FROM has_global_role_perm) THEN true
279 : WHEN ((split_part(%3%, '@', 2) = split_part(%4%, '#', 2))) THEN
280 : CASE
281 : WHEN (SELECT * FROM has_domain_role_perm) THEN true
282 : ELSE false
283 : END
284 : ELSE false END
285 5964 : )") % checkAccountRolePermission(global_permission, creator_id)
286 5964 : % checkAccountRolePermission(domain_permission,
287 5964 : creator_id)
288 5964 : % creator_id % id_with_target_domain)
289 5964 : .str();
290 5964 : return query;
291 5964 : }
292 :
293 : std::string checkAccountHasRoleOrGrantablePerm(
294 : shared_model::interface::permissions::Role role,
295 : shared_model::interface::permissions::Grantable grantable,
296 : const shared_model::interface::types::AccountIdType &creator_id,
297 : const shared_model::interface::types::AccountIdType &account_id) {
298 8946 : return (boost::format(R"(WITH
299 : has_role_perm AS (%s),
300 : has_grantable_perm AS (%s)
301 : SELECT CASE
302 : WHEN (SELECT * FROM has_grantable_perm) THEN true
303 : WHEN (%s = %s) THEN
304 : CASE
305 : WHEN (SELECT * FROM has_role_perm) THEN true
306 : ELSE false
307 : END
308 : ELSE false END
309 : )")
310 8946 : % checkAccountRolePermission(role, creator_id)
311 8946 : % checkAccountGrantablePermission(grantable, creator_id, account_id)
312 8946 : % creator_id % account_id)
313 8946 : .str();
314 0 : }
315 :
316 : template <typename Format>
317 : void appendCommandName(const std::string &name,
318 : Format &cmd,
319 : bool do_validation) {
320 7038 : auto command_name = name
321 7038 : + (do_validation ? PreparedStatement::validationPrefix
322 : : PreparedStatement::noValidationPrefix);
323 7038 : cmd % command_name;
324 7038 : }
325 :
326 : /**
327 : * Get a pretty string builder initialized for query arguments append
328 : * @return string builder
329 : */
330 : shared_model::detail::PrettyStringBuilder getQueryArgsStringBuilder() {
331 100 : return shared_model::detail::PrettyStringBuilder().init("Query arguments");
332 0 : }
333 : } // namespace
334 :
335 : namespace iroha {
336 : namespace ametsuchi {
337 : // TODO [IR-1830] Akvinikym 31.10.18: make benchmarks to compare exception
338 : // parsing vs nested queries
339 : const std::string PostgresCommandExecutor::addAssetQuantityBase = R"(
340 : PREPARE %s (text, text, int, text) AS
341 : WITH has_account AS (SELECT account_id FROM account
342 : WHERE account_id = $1 LIMIT 1),
343 : has_asset AS (SELECT asset_id FROM asset
344 : WHERE asset_id = $2 AND
345 : precision >= $3 LIMIT 1),
346 : %s
347 : amount AS (SELECT amount FROM account_has_asset
348 : WHERE asset_id = $2 AND
349 : account_id = $1 LIMIT 1),
350 : new_value AS (SELECT $4::decimal +
351 : (SELECT
352 : CASE WHEN EXISTS
353 : (SELECT amount FROM amount LIMIT 1) THEN
354 : (SELECT amount FROM amount LIMIT 1)
355 : ELSE 0::decimal
356 : END) AS value
357 : ),
358 : inserted AS
359 : (
360 : INSERT INTO account_has_asset(account_id, asset_id, amount)
361 : (
362 : SELECT $1, $2, value FROM new_value
363 : WHERE EXISTS (SELECT * FROM has_account LIMIT 1) AND
364 : EXISTS (SELECT * FROM has_asset LIMIT 1) AND
365 : EXISTS (SELECT value FROM new_value
366 : WHERE value < 2::decimal ^ (256 - $3)
367 : LIMIT 1)
368 : %s
369 : )
370 : ON CONFLICT (account_id, asset_id) DO UPDATE
371 : SET amount = EXCLUDED.amount
372 : RETURNING (1)
373 : )
374 : SELECT CASE
375 : WHEN EXISTS (SELECT * FROM inserted LIMIT 1) THEN 0
376 : %s
377 : WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 3
378 : WHEN NOT EXISTS (SELECT value FROM new_value
379 : WHERE value < 2::decimal ^ (256 - $3)
380 : LIMIT 1) THEN 4
381 : ELSE 1
382 : END AS result;)";
383 :
384 : const std::string PostgresCommandExecutor::addPeerBase = R"(
385 : PREPARE %s (text, text, text) AS
386 : WITH
387 : %s
388 : inserted AS (
389 : INSERT INTO peer(public_key, address)
390 : (
391 : SELECT $2, $3
392 : %s
393 : ) RETURNING (1)
394 : )
395 : SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
396 : %s
397 : ELSE 1 END AS result)";
398 :
399 : const std::string PostgresCommandExecutor::addSignatoryBase = R"(
400 : PREPARE %s (text, text, text) AS
401 : WITH %s
402 : insert_signatory AS
403 : (
404 : INSERT INTO signatory(public_key)
405 : (SELECT $3 %s) ON CONFLICT DO NOTHING RETURNING (1)
406 : ),
407 : has_signatory AS (SELECT * FROM signatory WHERE public_key = $3),
408 : insert_account_signatory AS
409 : (
410 : INSERT INTO account_has_signatory(account_id, public_key)
411 : (
412 : SELECT $2, $3 WHERE (EXISTS
413 : (SELECT * FROM insert_signatory) OR
414 : EXISTS (SELECT * FROM has_signatory))
415 : %s
416 : )
417 : RETURNING (1)
418 : )
419 : SELECT CASE
420 : WHEN EXISTS (SELECT * FROM insert_account_signatory) THEN 0
421 : %s
422 : ELSE 1
423 : END AS RESULT;)";
424 :
425 : const std::string PostgresCommandExecutor::appendRoleBase = R"(
426 : PREPARE %s (text, text, text) AS
427 : WITH %s
428 : role_exists AS (SELECT * FROM role WHERE role_id = $3),
429 : inserted AS (
430 : INSERT INTO account_has_roles(account_id, role_id)
431 : (
432 : SELECT $2, $3 %s) RETURNING (1)
433 : )
434 : SELECT CASE
435 : WHEN EXISTS (SELECT * FROM inserted) THEN 0
436 : WHEN NOT EXISTS (SELECT * FROM role_exists) THEN 4
437 : %s
438 : ELSE 1
439 : END AS result)";
440 :
441 : const std::string PostgresCommandExecutor::createAccountBase = R"(
442 : PREPARE %s (text, text, text, text) AS
443 : WITH get_domain_default_role AS (SELECT default_role FROM domain
444 : WHERE domain_id = $3),
445 : %s
446 : insert_signatory AS
447 : (
448 : INSERT INTO signatory(public_key)
449 : (
450 : SELECT $4 WHERE EXISTS
451 : (SELECT * FROM get_domain_default_role)
452 : ) ON CONFLICT DO NOTHING RETURNING (1)
453 : ),
454 : has_signatory AS (SELECT * FROM signatory WHERE public_key = $4),
455 : insert_account AS
456 : (
457 : INSERT INTO account(account_id, domain_id, quorum, data)
458 : (
459 : SELECT $2, $3, 1, '{}' WHERE (EXISTS
460 : (SELECT * FROM insert_signatory) OR EXISTS
461 : (SELECT * FROM has_signatory)
462 : ) AND EXISTS (SELECT * FROM get_domain_default_role)
463 : %s
464 : ) RETURNING (1)
465 : ),
466 : insert_account_signatory AS
467 : (
468 : INSERT INTO account_has_signatory(account_id, public_key)
469 : (
470 : SELECT $2, $4 WHERE
471 : EXISTS (SELECT * FROM insert_account)
472 : )
473 : RETURNING (1)
474 : ),
475 : insert_account_role AS
476 : (
477 : INSERT INTO account_has_roles(account_id, role_id)
478 : (
479 : SELECT $2, default_role FROM get_domain_default_role
480 : WHERE EXISTS (SELECT * FROM get_domain_default_role)
481 : AND EXISTS (SELECT * FROM insert_account_signatory)
482 : ) RETURNING (1)
483 : )
484 : SELECT CASE
485 : WHEN EXISTS (SELECT * FROM insert_account_role) THEN 0
486 : %s
487 : WHEN NOT EXISTS (SELECT * FROM get_domain_default_role) THEN 3
488 : ELSE 1
489 : END AS result)";
490 :
491 : const std::string PostgresCommandExecutor::createAssetBase = R"(
492 : PREPARE %s (text, text, text, int) AS
493 : WITH %s
494 : inserted AS
495 : (
496 : INSERT INTO asset(asset_id, domain_id, precision, data)
497 : (
498 : SELECT $2, $3, $4, NULL
499 : %s
500 : ) RETURNING (1)
501 : )
502 : SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
503 : %s
504 : ELSE 1 END AS result)";
505 :
506 : const std::string PostgresCommandExecutor::createDomainBase = R"(
507 : PREPARE %s (text, text, text) AS
508 : WITH %s
509 : inserted AS
510 : (
511 : INSERT INTO domain(domain_id, default_role)
512 : (
513 : SELECT $2, $3
514 : %s
515 : ) RETURNING (1)
516 : )
517 : SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
518 : %s
519 : ELSE 1 END AS result)";
520 :
521 : const std::string PostgresCommandExecutor::createRoleBase = R"(
522 : PREPARE %s (text, text, bit) AS
523 : WITH %s
524 : insert_role AS (INSERT INTO role(role_id)
525 : (SELECT $2
526 : %s) RETURNING (1)),
527 : insert_role_permissions AS
528 : (
529 : INSERT INTO role_has_permissions(role_id, permission)
530 : (
531 : SELECT $2, $3 WHERE EXISTS
532 : (SELECT * FROM insert_role)
533 : ) RETURNING (1)
534 : )
535 : SELECT CASE
536 : WHEN EXISTS (SELECT * FROM insert_role_permissions) THEN 0
537 : %s
538 : WHEN EXISTS (SELECT * FROM role WHERE role_id = $2) THEN 2
539 : ELSE 1
540 : END AS result)";
541 :
542 : const std::string PostgresCommandExecutor::detachRoleBase = R"(
543 : PREPARE %s (text, text, text) AS
544 : WITH %s
545 : deleted AS
546 : (
547 : DELETE FROM account_has_roles
548 : WHERE account_id=$2
549 : AND role_id=$3
550 : %s
551 : RETURNING (1)
552 : )
553 : SELECT CASE WHEN EXISTS (SELECT * FROM deleted) THEN 0
554 : WHEN NOT EXISTS (SELECT * FROM account
555 : WHERE account_id = $2) THEN 3
556 : WHEN NOT EXISTS (SELECT * FROM role
557 : WHERE role_id = $3) THEN 5
558 : WHEN NOT EXISTS (SELECT * FROM account_has_roles
559 : WHERE account_id=$2 AND role_id=$3) THEN 4
560 : %s
561 : ELSE 1 END AS result)";
562 :
563 : const std::string PostgresCommandExecutor::grantPermissionBase = R"(
564 : PREPARE %s (text, text, bit, bit) AS
565 : WITH %s
566 : inserted AS (
567 : INSERT INTO account_has_grantable_permissions AS
568 : has_perm(permittee_account_id, account_id, permission)
569 : (SELECT $2, $1, $3 %s) ON CONFLICT
570 : (permittee_account_id, account_id)
571 : DO UPDATE SET permission=(SELECT has_perm.permission | $3
572 : WHERE (has_perm.permission & $3) <> $3)
573 : RETURNING (1)
574 : )
575 : SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
576 : %s
577 : ELSE 1 END AS result)";
578 :
579 : const std::string PostgresCommandExecutor::removeSignatoryBase = R"(
580 : PREPARE %s (text, text, text) AS
581 : WITH
582 : %s
583 : delete_account_signatory AS (DELETE FROM account_has_signatory
584 : WHERE account_id = $2
585 : AND public_key = $3
586 : %s
587 : RETURNING (1)),
588 : delete_signatory AS
589 : (
590 : DELETE FROM signatory WHERE public_key = $3 AND
591 : NOT EXISTS (SELECT 1 FROM account_has_signatory
592 : WHERE public_key = $3)
593 : AND NOT EXISTS (SELECT 1 FROM peer WHERE public_key = $3)
594 : RETURNING (1)
595 : )
596 : SELECT CASE
597 : WHEN EXISTS (SELECT * FROM delete_account_signatory) THEN
598 : CASE
599 : WHEN EXISTS (SELECT * FROM delete_signatory) THEN 0
600 : WHEN EXISTS (SELECT 1 FROM account_has_signatory
601 : WHERE public_key = $3) THEN 0
602 : WHEN EXISTS (SELECT 1 FROM peer
603 : WHERE public_key = $3) THEN 0
604 : ELSE 1
605 : END
606 : %s
607 : ELSE 1
608 : END AS result)";
609 :
610 : const std::string PostgresCommandExecutor::revokePermissionBase = R"(
611 : PREPARE %s (text, text, bit, bit) AS
612 : WITH %s
613 : inserted AS (
614 : UPDATE account_has_grantable_permissions as has_perm
615 : SET permission=(SELECT has_perm.permission & $4
616 : WHERE has_perm.permission & $3 = $3 AND
617 : has_perm.permittee_account_id=$2 AND
618 : has_perm.account_id=$1) WHERE
619 : permittee_account_id=$2 AND
620 : account_id=$1 %s
621 : RETURNING (1)
622 : )
623 : SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
624 : %s
625 : ELSE 1 END AS result)";
626 :
627 : const std::string PostgresCommandExecutor::setAccountDetailBase = R"(
628 : PREPARE %s (text, text, text[], text[], text, text) AS
629 : WITH %s
630 : inserted AS
631 : (
632 : UPDATE account SET data = jsonb_set(
633 : CASE WHEN data ?$1 THEN data ELSE
634 : jsonb_set(data, $3, $6::jsonb) END,
635 : $4, $5::jsonb) WHERE account_id=$2 %s
636 : RETURNING (1)
637 : )
638 : SELECT CASE WHEN EXISTS (SELECT * FROM inserted) THEN 0
639 : %s
640 : WHEN NOT EXISTS
641 : (SELECT * FROM account WHERE account_id=$2) THEN 3
642 : ELSE 1 END AS result)";
643 :
644 : const std::string PostgresCommandExecutor::setQuorumBase = R"(
645 : PREPARE %s (text, text, int) AS
646 : WITH
647 : %s
648 : %s
649 : updated AS (
650 : UPDATE account SET quorum=$3
651 : WHERE account_id=$2
652 : %s
653 : RETURNING (1)
654 : )
655 : SELECT CASE WHEN EXISTS (SELECT * FROM updated) THEN 0
656 : %s
657 : ELSE 1
658 : END AS result)";
659 :
660 : const std::string PostgresCommandExecutor::subtractAssetQuantityBase = R"(
661 : PREPARE %s (text, text, int, text) AS
662 : WITH %s
663 : has_account AS (SELECT account_id FROM account
664 : WHERE account_id = $1 LIMIT 1),
665 : has_asset AS (SELECT asset_id FROM asset
666 : WHERE asset_id = $2
667 : AND precision >= $3 LIMIT 1),
668 : amount AS (SELECT amount FROM account_has_asset
669 : WHERE asset_id = $2
670 : AND account_id = $1 LIMIT 1),
671 : new_value AS (SELECT
672 : (SELECT
673 : CASE WHEN EXISTS
674 : (SELECT amount FROM amount LIMIT 1)
675 : THEN (SELECT amount FROM amount LIMIT 1)
676 : ELSE 0::decimal
677 : END) - $4::decimal AS value
678 : ),
679 : inserted AS
680 : (
681 : INSERT INTO account_has_asset(account_id, asset_id, amount)
682 : (
683 : SELECT $1, $2, value FROM new_value
684 : WHERE EXISTS (SELECT * FROM has_account LIMIT 1) AND
685 : EXISTS (SELECT * FROM has_asset LIMIT 1) AND
686 : EXISTS (SELECT value FROM new_value WHERE value >= 0 LIMIT 1)
687 : %s
688 : )
689 : ON CONFLICT (account_id, asset_id)
690 : DO UPDATE SET amount = EXCLUDED.amount
691 : RETURNING (1)
692 : )
693 : SELECT CASE
694 : WHEN EXISTS (SELECT * FROM inserted LIMIT 1) THEN 0
695 : %s
696 : WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 3
697 : WHEN NOT EXISTS
698 : (SELECT value FROM new_value WHERE value >= 0 LIMIT 1) THEN 4
699 : ELSE 1
700 : END AS result)";
701 :
702 : const std::string PostgresCommandExecutor::transferAssetBase = R"(
703 : PREPARE %s (text, text, text, text, int, text) AS
704 : WITH
705 : %s
706 : has_src_account AS (SELECT account_id FROM account
707 : WHERE account_id = $2 LIMIT 1),
708 : has_dest_account AS (SELECT account_id FROM account
709 : WHERE account_id = $3
710 : LIMIT 1),
711 : has_asset AS (SELECT asset_id FROM asset
712 : WHERE asset_id = $4 AND
713 : precision >= $5 LIMIT 1),
714 : src_amount AS (SELECT amount FROM account_has_asset
715 : WHERE asset_id = $4 AND
716 : account_id = $2 LIMIT 1),
717 : dest_amount AS (SELECT amount FROM account_has_asset
718 : WHERE asset_id = $4 AND
719 : account_id = $3 LIMIT 1),
720 : new_src_value AS (SELECT
721 : (SELECT
722 : CASE WHEN EXISTS
723 : (SELECT amount FROM src_amount LIMIT 1)
724 : THEN
725 : (SELECT amount FROM src_amount LIMIT 1)
726 : ELSE 0::decimal
727 : END) - $6::decimal AS value
728 : ),
729 : new_dest_value AS (SELECT
730 : (SELECT $6::decimal +
731 : CASE WHEN EXISTS
732 : (SELECT amount FROM dest_amount LIMIT 1)
733 : THEN
734 : (SELECT amount FROM dest_amount LIMIT 1)
735 : ELSE 0::decimal
736 : END) AS value
737 : ),
738 : insert_src AS
739 : (
740 : INSERT INTO account_has_asset(account_id, asset_id, amount)
741 : (
742 : SELECT $2, $4, value
743 : FROM new_src_value
744 : WHERE EXISTS (SELECT * FROM has_src_account LIMIT 1) AND
745 : EXISTS (SELECT * FROM has_dest_account LIMIT 1) AND
746 : EXISTS (SELECT * FROM has_asset LIMIT 1) AND
747 : EXISTS (SELECT value FROM new_src_value
748 : WHERE value >= 0 LIMIT 1) %s
749 : )
750 : ON CONFLICT (account_id, asset_id)
751 : DO UPDATE SET amount = EXCLUDED.amount
752 : RETURNING (1)
753 : ),
754 : insert_dest AS
755 : (
756 : INSERT INTO account_has_asset(account_id, asset_id, amount)
757 : (
758 : SELECT $3, $4, value
759 : FROM new_dest_value
760 : WHERE EXISTS (SELECT * FROM insert_src) AND
761 : EXISTS (SELECT * FROM has_src_account LIMIT 1) AND
762 : EXISTS (SELECT * FROM has_dest_account LIMIT 1) AND
763 : EXISTS (SELECT * FROM has_asset LIMIT 1) AND
764 : EXISTS (SELECT value FROM new_dest_value
765 : WHERE value < 2::decimal ^ (256 - $5)
766 : LIMIT 1) %s
767 : )
768 : ON CONFLICT (account_id, asset_id)
769 : DO UPDATE SET amount = EXCLUDED.amount
770 : RETURNING (1)
771 : )
772 : SELECT CASE
773 : WHEN EXISTS (SELECT * FROM insert_dest LIMIT 1) THEN 0
774 : %s
775 : WHEN NOT EXISTS (SELECT * FROM has_dest_account LIMIT 1) THEN 4
776 : WHEN NOT EXISTS (SELECT * FROM has_src_account LIMIT 1) THEN 3
777 : WHEN NOT EXISTS (SELECT * FROM has_asset LIMIT 1) THEN 5
778 : WHEN NOT EXISTS (SELECT value FROM new_src_value
779 : WHERE value >= 0 LIMIT 1) THEN 6
780 : WHEN NOT EXISTS (SELECT value FROM new_dest_value
781 : WHERE value < 2::decimal ^ (256 - $5)
782 : LIMIT 1) THEN 7
783 : ELSE 1
784 : END AS result)";
785 :
786 : std::string CommandError::toString() const {
787 0 : return (boost::format("%s: %d with extra info '%s'") % command_name
788 0 : % error_code % error_extra)
789 0 : .str();
790 0 : }
791 :
792 : PostgresCommandExecutor::PostgresCommandExecutor(
793 : soci::session &sql,
794 : std::shared_ptr<shared_model::interface::PermissionToString>
795 : perm_converter)
796 1409 : : sql_(sql),
797 1409 : do_validation_(true),
798 1409 : perm_converter_{std::move(perm_converter)} {}
799 :
800 : void PostgresCommandExecutor::setCreatorAccountId(
801 : const shared_model::interface::types::AccountIdType
802 : &creator_account_id) {
803 2340 : creator_account_id_ = creator_account_id;
804 2340 : }
805 :
806 : void PostgresCommandExecutor::doValidation(bool do_validation) {
807 2340 : do_validation_ = do_validation;
808 2340 : }
809 :
810 : CommandResult PostgresCommandExecutor::operator()(
811 : const shared_model::interface::AddAssetQuantity &command) {
812 156 : auto &account_id = creator_account_id_;
813 156 : auto &asset_id = command.assetId();
814 156 : auto amount = command.amount().toStringRepr();
815 156 : int precision = command.amount().precision();
816 :
817 : // 14.09.2018 nickaleks: IR-1707 move common logic to separate function
818 156 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', %4%, '%5%')");
819 :
820 156 : appendCommandName("addAssetQuantity", cmd, do_validation_);
821 :
822 156 : cmd = (cmd % account_id % asset_id % precision % amount);
823 :
824 : auto str_args = [&account_id, &asset_id, &amount, precision] {
825 7 : return getQueryArgsStringBuilder()
826 7 : .append("account_id", account_id)
827 7 : .append("asset_id", asset_id)
828 7 : .append("amount", amount)
829 7 : .append("precision", std::to_string(precision))
830 7 : .finalize();
831 0 : };
832 :
833 156 : return executeQuery(
834 156 : sql_, cmd.str(), "AddAssetQuantity", std::move(str_args));
835 156 : }
836 :
837 : CommandResult PostgresCommandExecutor::operator()(
838 : const shared_model::interface::AddPeer &command) {
839 517 : auto &peer = command.peer();
840 :
841 517 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
842 :
843 517 : appendCommandName("addPeer", cmd, do_validation_);
844 :
845 517 : cmd = (cmd % creator_account_id_ % peer.pubkey().hex() % peer.address());
846 :
847 : auto str_args = [&peer] {
848 1 : return getQueryArgsStringBuilder()
849 1 : .append("peer", peer.toString())
850 1 : .finalize();
851 0 : };
852 :
853 517 : return executeQuery(sql_, cmd.str(), "AddPeer", std::move(str_args));
854 517 : }
855 :
856 : CommandResult PostgresCommandExecutor::operator()(
857 : const shared_model::interface::AddSignatory &command) {
858 114 : auto &account_id = command.accountId();
859 114 : auto pubkey = command.pubkey().hex();
860 114 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
861 :
862 114 : appendCommandName("addSignatory", cmd, do_validation_);
863 :
864 114 : cmd = (cmd % creator_account_id_ % account_id % pubkey);
865 :
866 : auto str_args = [&account_id, &pubkey] {
867 5 : return getQueryArgsStringBuilder()
868 5 : .append("account_id", account_id)
869 5 : .append("pubkey", pubkey)
870 5 : .finalize();
871 0 : };
872 :
873 114 : return executeQuery(sql_, cmd.str(), "AddSignatory", std::move(str_args));
874 114 : }
875 :
876 : CommandResult PostgresCommandExecutor::operator()(
877 : const shared_model::interface::AppendRole &command) {
878 946 : auto &account_id = command.accountId();
879 946 : auto &role_name = command.roleName();
880 946 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
881 :
882 946 : appendCommandName("appendRole", cmd, do_validation_);
883 :
884 946 : cmd = (cmd % creator_account_id_ % account_id % role_name);
885 :
886 : auto str_args = [&account_id, &role_name] {
887 4 : return getQueryArgsStringBuilder()
888 4 : .append("account_id", account_id)
889 4 : .append("role_name", role_name)
890 4 : .finalize();
891 0 : };
892 :
893 946 : return executeQuery(sql_, cmd.str(), "AppendRole", std::move(str_args));
894 946 : }
895 :
896 : CommandResult PostgresCommandExecutor::operator()(
897 : const shared_model::interface::CreateAccount &command) {
898 1174 : auto &account_name = command.accountName();
899 1174 : auto &domain_id = command.domainId();
900 1174 : auto &pubkey = command.pubkey().hex();
901 : shared_model::interface::types::AccountIdType account_id =
902 1174 : account_name + "@" + domain_id;
903 :
904 1174 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', '%5%')");
905 :
906 1174 : appendCommandName("createAccount", cmd, do_validation_);
907 :
908 1174 : cmd = (cmd % creator_account_id_ % account_id % domain_id % pubkey);
909 :
910 : auto str_args = [&account_id, &domain_id, &pubkey] {
911 7 : return getQueryArgsStringBuilder()
912 7 : .append("account_id", account_id)
913 7 : .append("domain_id", domain_id)
914 7 : .append("pubkey", pubkey)
915 7 : .finalize();
916 0 : };
917 :
918 1174 : return executeQuery(
919 1174 : sql_, cmd.str(), "CreateAccount", std::move(str_args));
920 1174 : }
921 :
922 : CommandResult PostgresCommandExecutor::operator()(
923 : const shared_model::interface::CreateAsset &command) {
924 601 : auto &domain_id = command.domainId();
925 601 : auto asset_id = command.assetName() + "#" + domain_id;
926 601 : int precision = command.precision();
927 601 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', %5%)");
928 :
929 601 : appendCommandName("createAsset", cmd, do_validation_);
930 :
931 601 : cmd = (cmd % creator_account_id_ % asset_id % domain_id % precision);
932 :
933 : auto str_args = [&domain_id, &asset_id, precision] {
934 7 : return getQueryArgsStringBuilder()
935 7 : .append("domain_id", domain_id)
936 7 : .append("asset_id", asset_id)
937 7 : .append("precision", std::to_string(precision))
938 7 : .finalize();
939 0 : };
940 :
941 601 : return executeQuery(sql_, cmd.str(), "CreateAsset", std::move(str_args));
942 601 : }
943 :
944 : CommandResult PostgresCommandExecutor::operator()(
945 : const shared_model::interface::CreateDomain &command) {
946 782 : auto &domain_id = command.domainId();
947 782 : auto &default_role = command.userDefaultRole();
948 782 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
949 :
950 782 : appendCommandName("createDomain", cmd, do_validation_);
951 :
952 782 : cmd = (cmd % creator_account_id_ % domain_id % default_role);
953 :
954 : auto str_args = [&domain_id, &default_role] {
955 7 : return getQueryArgsStringBuilder()
956 7 : .append("domain_id", domain_id)
957 7 : .append("default_role", default_role)
958 7 : .finalize();
959 0 : };
960 :
961 782 : return executeQuery(sql_, cmd.str(), "CreateDomain", std::move(str_args));
962 782 : }
963 :
964 : CommandResult PostgresCommandExecutor::operator()(
965 : const shared_model::interface::CreateRole &command) {
966 1618 : auto &role_id = command.roleName();
967 1618 : auto &permissions = command.rolePermissions();
968 1618 : auto perm_str = permissions.toBitstring();
969 1618 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
970 :
971 1618 : appendCommandName("createRole", cmd, do_validation_);
972 :
973 1618 : cmd = (cmd % creator_account_id_ % role_id % perm_str);
974 :
975 : auto str_args = [&role_id, &perm_str] {
976 : // TODO [IR-1889] Akvinikym 21.11.18: integrate
977 : // PermissionSet::toString() instead of bit string, when it is created
978 4 : return getQueryArgsStringBuilder()
979 4 : .append("role_id", role_id)
980 4 : .append("perm_str", perm_str)
981 4 : .finalize();
982 0 : };
983 :
984 1618 : return executeQuery(sql_, cmd.str(), "CreateRole", std::move(str_args));
985 1618 : }
986 :
987 : CommandResult PostgresCommandExecutor::operator()(
988 : const shared_model::interface::DetachRole &command) {
989 829 : auto &account_id = command.accountId();
990 829 : auto &role_name = command.roleName();
991 829 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
992 :
993 829 : appendCommandName("detachRole", cmd, do_validation_);
994 :
995 829 : cmd = (cmd % creator_account_id_ % account_id % role_name);
996 :
997 : auto str_args = [&account_id, &role_name] {
998 4 : return getQueryArgsStringBuilder()
999 4 : .append("account_id", account_id)
1000 4 : .append("role_name", role_name)
1001 4 : .finalize();
1002 0 : };
1003 :
1004 829 : return executeQuery(sql_, cmd.str(), "DetachRole", std::move(str_args));
1005 829 : }
1006 :
1007 : CommandResult PostgresCommandExecutor::operator()(
1008 : const shared_model::interface::GrantPermission &command) {
1009 37 : auto &permittee_account_id = command.accountId();
1010 37 : auto permission = command.permissionName();
1011 37 : auto perm = shared_model::interface::RolePermissionSet(
1012 37 : {shared_model::interface::permissions::permissionFor(
1013 37 : command.permissionName())})
1014 37 : .toBitstring();
1015 : const auto perm_str =
1016 37 : shared_model::interface::GrantablePermissionSet({permission})
1017 37 : .toBitstring();
1018 37 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', '%5%')");
1019 :
1020 37 : appendCommandName("grantPermission", cmd, do_validation_);
1021 :
1022 37 : cmd =
1023 37 : (cmd % creator_account_id_ % permittee_account_id % perm_str % perm);
1024 :
1025 : auto str_args = [&creator_account_id = creator_account_id_,
1026 37 : &permittee_account_id,
1027 37 : permission = perm_converter_->toString(permission)] {
1028 9 : return getQueryArgsStringBuilder()
1029 9 : .append("creator_account_id_", creator_account_id)
1030 9 : .append("permittee_account_id", permittee_account_id)
1031 9 : .append("permission", permission)
1032 9 : .finalize();
1033 0 : };
1034 :
1035 37 : return executeQuery(
1036 37 : sql_, cmd.str(), "GrantPermission", std::move(str_args));
1037 37 : }
1038 :
1039 : CommandResult PostgresCommandExecutor::operator()(
1040 : const shared_model::interface::RemoveSignatory &command) {
1041 19 : auto &account_id = command.accountId();
1042 19 : auto &pubkey = command.pubkey().hex();
1043 19 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%')");
1044 :
1045 19 : appendCommandName("removeSignatory", cmd, do_validation_);
1046 :
1047 19 : cmd = (cmd % creator_account_id_ % account_id % pubkey);
1048 :
1049 : auto str_args = [&account_id, &pubkey] {
1050 9 : return getQueryArgsStringBuilder()
1051 9 : .append("account_id", account_id)
1052 9 : .append("pubkey", pubkey)
1053 9 : .finalize();
1054 0 : };
1055 :
1056 19 : return executeQuery(
1057 19 : sql_, cmd.str(), "RemoveSignatory", std::move(str_args));
1058 19 : }
1059 :
1060 : CommandResult PostgresCommandExecutor::operator()(
1061 : const shared_model::interface::RevokePermission &command) {
1062 12 : auto &permittee_account_id = command.accountId();
1063 12 : auto permission = command.permissionName();
1064 : const auto without_perm_str =
1065 12 : shared_model::interface::GrantablePermissionSet()
1066 12 : .set()
1067 12 : .unset(permission)
1068 12 : .toBitstring();
1069 12 : const auto perms = shared_model::interface::GrantablePermissionSet()
1070 12 : .set(permission)
1071 12 : .toBitstring();
1072 :
1073 12 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', '%5%')");
1074 :
1075 12 : appendCommandName("revokePermission", cmd, do_validation_);
1076 :
1077 12 : cmd = (cmd % creator_account_id_ % permittee_account_id % perms
1078 12 : % without_perm_str);
1079 :
1080 : auto str_args = [&creator_account_id = creator_account_id_,
1081 12 : &permittee_account_id,
1082 12 : permission = perm_converter_->toString(permission)] {
1083 4 : return getQueryArgsStringBuilder()
1084 4 : .append("creator_account_id_", creator_account_id)
1085 4 : .append("permittee_account_id", permittee_account_id)
1086 4 : .append("permission", permission)
1087 4 : .finalize();
1088 0 : };
1089 :
1090 12 : return executeQuery(
1091 12 : sql_, cmd.str(), "RevokePermission", std::move(str_args));
1092 12 : }
1093 :
1094 : CommandResult PostgresCommandExecutor::operator()(
1095 : const shared_model::interface::SetAccountDetail &command) {
1096 99 : auto &account_id = command.accountId();
1097 99 : auto &key = command.key();
1098 99 : auto &value = command.value();
1099 99 : if (creator_account_id_.empty()) {
1100 : // When creator is not known, it is genesis block
1101 0 : creator_account_id_ = "genesis";
1102 0 : }
1103 99 : std::string json = "{" + creator_account_id_ + "}";
1104 99 : std::string empty_json = "{}";
1105 99 : std::string filled_json = "{" + creator_account_id_ + ", " + key + "}";
1106 99 : std::string val = "\"" + value + "\"";
1107 :
1108 99 : auto cmd = boost::format(
1109 : "EXECUTE %1% ('%2%', '%3%', '%4%', '%5%', '%6%', '%7%')");
1110 :
1111 99 : appendCommandName("setAccountDetail", cmd, do_validation_);
1112 :
1113 99 : cmd = (cmd % creator_account_id_ % account_id % json % filled_json % val
1114 99 : % empty_json);
1115 :
1116 : auto str_args = [&account_id, &key, &value] {
1117 5 : return getQueryArgsStringBuilder()
1118 5 : .append("account_id", account_id)
1119 5 : .append("key", key)
1120 5 : .append("value", value)
1121 5 : .finalize();
1122 0 : };
1123 :
1124 99 : return executeQuery(
1125 99 : sql_, cmd.str(), "SetAccountDetail", std::move(str_args));
1126 99 : }
1127 :
1128 : CommandResult PostgresCommandExecutor::operator()(
1129 : const shared_model::interface::SetQuorum &command) {
1130 18 : auto &account_id = command.accountId();
1131 18 : int quorum = command.newQuorum();
1132 18 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', %4%)");
1133 :
1134 18 : appendCommandName("setQuorum", cmd, do_validation_);
1135 :
1136 18 : cmd = (cmd % creator_account_id_ % account_id % quorum);
1137 :
1138 : auto str_args = [&account_id, quorum] {
1139 4 : return getQueryArgsStringBuilder()
1140 4 : .append("account_id", account_id)
1141 4 : .append("quorum", std::to_string(quorum))
1142 4 : .finalize();
1143 0 : };
1144 :
1145 18 : return executeQuery(sql_, cmd.str(), "SetQuorum", std::move(str_args));
1146 18 : }
1147 :
1148 : CommandResult PostgresCommandExecutor::operator()(
1149 : const shared_model::interface::SubtractAssetQuantity &command) {
1150 11 : auto &asset_id = command.assetId();
1151 11 : auto amount = command.amount().toStringRepr();
1152 11 : uint32_t precision = command.amount().precision();
1153 11 : auto cmd = boost::format("EXECUTE %1% ('%2%', '%3%', %4%, '%5%')");
1154 :
1155 11 : appendCommandName("subtractAssetQuantity", cmd, do_validation_);
1156 :
1157 11 : cmd = (cmd % creator_account_id_ % asset_id % precision % amount);
1158 :
1159 : auto str_args = [&creator_account_id = creator_account_id_,
1160 11 : &asset_id,
1161 : &amount,
1162 11 : precision] {
1163 8 : return getQueryArgsStringBuilder()
1164 8 : .append("creator_account_id", creator_account_id)
1165 8 : .append("asset_id", asset_id)
1166 8 : .append("amount", amount)
1167 8 : .append("precision", std::to_string(precision))
1168 8 : .finalize();
1169 0 : };
1170 :
1171 11 : return executeQuery(
1172 11 : sql_, cmd.str(), "SubtractAssetQuantity", std::move(str_args));
1173 11 : }
1174 :
1175 : CommandResult PostgresCommandExecutor::operator()(
1176 : const shared_model::interface::TransferAsset &command) {
1177 105 : auto &src_account_id = command.srcAccountId();
1178 105 : auto &dest_account_id = command.destAccountId();
1179 105 : auto &asset_id = command.assetId();
1180 105 : auto amount = command.amount().toStringRepr();
1181 105 : uint32_t precision = command.amount().precision();
1182 : auto cmd =
1183 105 : boost::format("EXECUTE %1% ('%2%', '%3%', '%4%', '%5%', %6%, '%7%')");
1184 :
1185 105 : appendCommandName("transferAsset", cmd, do_validation_);
1186 :
1187 105 : cmd = (cmd % creator_account_id_ % src_account_id % dest_account_id
1188 105 : % asset_id % precision % amount);
1189 :
1190 : auto str_args =
1191 : [&src_account_id, &dest_account_id, &asset_id, &amount, precision] {
1192 15 : return getQueryArgsStringBuilder()
1193 15 : .append("src_account_id", src_account_id)
1194 15 : .append("dest_account_id", dest_account_id)
1195 15 : .append("asset_id", asset_id)
1196 15 : .append("amount", amount)
1197 15 : .append("precision", std::to_string(precision))
1198 15 : .finalize();
1199 0 : };
1200 :
1201 105 : return executeQuery(
1202 105 : sql_, cmd.str(), "TransferAsset", std::move(str_args));
1203 105 : }
1204 :
1205 : void PostgresCommandExecutor::prepareStatements(soci::session &sql) {
1206 2982 : std::vector<PreparedStatement> statements;
1207 :
1208 8946 : statements.push_back(
1209 2982 : {"addAssetQuantity",
1210 2982 : addAssetQuantityBase,
1211 2982 : {(boost::format(R"(has_perm AS (%s),)")
1212 2982 : % checkAccountDomainRoleOrGlobalRolePermission(
1213 : shared_model::interface::permissions::Role::kAddAssetQty,
1214 : shared_model::interface::permissions::Role::
1215 : kAddDomainAssetQty,
1216 2982 : "$1",
1217 2982 : "$2"))
1218 2982 : .str(),
1219 2982 : "AND (SELECT * from has_perm)",
1220 2982 : "WHEN NOT (SELECT * from has_perm) THEN 2"}});
1221 :
1222 8946 : statements.push_back(
1223 2982 : {"addPeer",
1224 2982 : addPeerBase,
1225 2982 : {(boost::format(R"(has_perm AS (%s),)")
1226 2982 : % checkAccountRolePermission(
1227 2982 : shared_model::interface::permissions::Role::kAddPeer, "$1"))
1228 2982 : .str(),
1229 2982 : "WHERE (SELECT * FROM has_perm)",
1230 2982 : "WHEN NOT (SELECT * from has_perm) THEN 2"}});
1231 :
1232 11928 : statements.push_back(
1233 2982 : {"addSignatory",
1234 2982 : addSignatoryBase,
1235 2982 : {(boost::format(R"(
1236 : has_perm AS (%s),)")
1237 2982 : % checkAccountHasRoleOrGrantablePerm(
1238 : shared_model::interface::permissions::Role::kAddSignatory,
1239 : shared_model::interface::permissions::Grantable::
1240 : kAddMySignatory,
1241 2982 : "$1",
1242 2982 : "$2"))
1243 2982 : .str(),
1244 2982 : " WHERE (SELECT * FROM has_perm)",
1245 2982 : " AND (SELECT * FROM has_perm)",
1246 2982 : "WHEN NOT (SELECT * from has_perm) THEN 2"}});
1247 :
1248 2982 : const auto bits = shared_model::interface::RolePermissionSet::size();
1249 2982 : const auto grantable_bits =
1250 2982 : shared_model::interface::GrantablePermissionSet::size();
1251 :
1252 8946 : statements.push_back(
1253 2982 : {"appendRole",
1254 2982 : appendRoleBase,
1255 2982 : {(boost::format(R"(
1256 : has_perm AS (%1%),
1257 : role_permissions AS (
1258 : SELECT permission FROM role_has_permissions
1259 : WHERE role_id = $3
1260 : ),
1261 : account_roles AS (
1262 : SELECT role_id FROM account_has_roles WHERE account_id = $1
1263 : ),
1264 : account_has_role_permissions AS (
1265 : SELECT COALESCE(bit_or(rp.permission), '0'::bit(%2%)) &
1266 : (SELECT * FROM role_permissions) =
1267 : (SELECT * FROM role_permissions)
1268 : FROM role_has_permissions AS rp
1269 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
1270 : WHERE ar.account_id = $1
1271 : ),)")
1272 2982 : % checkAccountRolePermission(
1273 : shared_model::interface::permissions::Role::kAppendRole,
1274 2982 : "$1")
1275 2982 : % bits)
1276 2982 : .str(),
1277 2982 : R"( WHERE
1278 : EXISTS (SELECT * FROM account_roles) AND
1279 : (SELECT * FROM account_has_role_permissions)
1280 : AND (SELECT * FROM has_perm))",
1281 2982 : R"(
1282 : WHEN NOT EXISTS (SELECT * FROM account_roles) THEN 2
1283 : WHEN NOT (SELECT * FROM account_has_role_permissions) THEN 2
1284 : WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
1285 :
1286 8946 : statements.push_back(
1287 2982 : {"createAccount",
1288 2982 : createAccountBase,
1289 2982 : {(boost::format(R"(
1290 : domain_role_permissions_bits AS (
1291 : SELECT COALESCE(bit_or(rhp.permission), '0'::bit(%1%)) AS bits
1292 : FROM role_has_permissions AS rhp
1293 : WHERE rhp.role_id = (SELECT * FROM get_domain_default_role)),
1294 : account_permissions AS (
1295 : SELECT COALESCE(bit_or(rhp.permission), '0'::bit(%1%)) AS perm
1296 : FROM role_has_permissions AS rhp
1297 : JOIN account_has_roles AS ar ON ar.role_id = rhp.role_id
1298 : WHERE ar.account_id = $1
1299 : ),
1300 : creator_has_enough_permissions AS (
1301 : SELECT ap.perm & dpb.bits = dpb.bits
1302 : FROM account_permissions AS ap, domain_role_permissions_bits AS dpb
1303 : ),
1304 : has_perm AS (%2%),
1305 2982 : )") % bits
1306 2982 : % checkAccountRolePermission(
1307 : shared_model::interface::permissions::Role::kCreateAccount,
1308 2982 : "$1"))
1309 2982 : .str(),
1310 2982 : R"(AND (SELECT * FROM has_perm)
1311 : AND (SELECT * FROM creator_has_enough_permissions))",
1312 2982 : R"(WHEN NOT (SELECT * FROM has_perm) THEN 2
1313 : WHEN NOT (SELECT * FROM creator_has_enough_permissions) THEN 2)"}});
1314 :
1315 8946 : statements.push_back(
1316 2982 : {"createAsset",
1317 2982 : createAssetBase,
1318 2982 : {(boost::format(R"(
1319 : has_perm AS (%s),)")
1320 2982 : % checkAccountRolePermission(
1321 : shared_model::interface::permissions::Role::kCreateAsset,
1322 2982 : "$1"))
1323 2982 : .str(),
1324 2982 : R"(WHERE (SELECT * FROM has_perm))",
1325 2982 : R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
1326 :
1327 8946 : statements.push_back(
1328 2982 : {"createDomain",
1329 2982 : createDomainBase,
1330 2982 : {(boost::format(R"(
1331 : has_perm AS (%s),)")
1332 2982 : % checkAccountRolePermission(
1333 : shared_model::interface::permissions::Role::kCreateDomain,
1334 2982 : "$1"))
1335 2982 : .str(),
1336 2982 : R"(WHERE (SELECT * FROM has_perm))",
1337 2982 : R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
1338 :
1339 8946 : statements.push_back(
1340 2982 : {"createRole",
1341 2982 : createRoleBase,
1342 2982 : {(boost::format(R"(
1343 : account_has_role_permissions AS (
1344 : SELECT COALESCE(bit_or(rp.permission), '0'::bit(%s)) &
1345 : $3 = $3
1346 : FROM role_has_permissions AS rp
1347 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
1348 : WHERE ar.account_id = $1),
1349 : has_perm AS (%s),)")
1350 2982 : % bits
1351 2982 : % checkAccountRolePermission(
1352 : shared_model::interface::permissions::Role::kCreateRole,
1353 2982 : "$1"))
1354 2982 : .str(),
1355 2982 : R"(WHERE (SELECT * FROM account_has_role_permissions)
1356 : AND (SELECT * FROM has_perm))",
1357 2982 : R"(WHEN NOT (SELECT * FROM
1358 : account_has_role_permissions) THEN 2
1359 : WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
1360 :
1361 8946 : statements.push_back(
1362 2982 : {"detachRole",
1363 2982 : detachRoleBase,
1364 2982 : {(boost::format(R"(
1365 : has_perm AS (%s),)")
1366 2982 : % checkAccountRolePermission(
1367 : shared_model::interface::permissions::Role::kDetachRole,
1368 2982 : "$1"))
1369 2982 : .str(),
1370 2982 : R"(AND (SELECT * FROM has_perm))",
1371 2982 : R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
1372 :
1373 8946 : statements.push_back({"grantPermission",
1374 2982 : grantPermissionBase,
1375 2982 : {(boost::format(R"(
1376 : has_perm AS (SELECT COALESCE(bit_or(rp.permission), '0'::bit(%1%))
1377 : & $4 = $4 FROM role_has_permissions AS rp
1378 : JOIN account_has_roles AS ar on ar.role_id = rp.role_id
1379 : WHERE ar.account_id = $1),)")
1380 2982 : % bits)
1381 2982 : .str(),
1382 2982 : R"( WHERE (SELECT * FROM has_perm))",
1383 2982 : R"(WHEN NOT (SELECT * FROM has_perm) THEN 2)"}});
1384 :
1385 8946 : statements.push_back(
1386 2982 : {"removeSignatory",
1387 2982 : removeSignatoryBase,
1388 2982 : {(boost::format(R"(
1389 : has_perm AS (%s),
1390 : get_account AS (
1391 : SELECT quorum FROM account WHERE account_id = $2 LIMIT 1
1392 : ),
1393 : get_signatories AS (
1394 : SELECT public_key FROM account_has_signatory
1395 : WHERE account_id = $2
1396 : ),
1397 : get_signatory AS (
1398 : SELECT * FROM get_signatories
1399 : WHERE public_key = $3
1400 : ),
1401 : check_account_signatories AS (
1402 : SELECT quorum FROM get_account
1403 : WHERE quorum < (SELECT COUNT(*) FROM get_signatories)
1404 : ),
1405 : )")
1406 2982 : % checkAccountHasRoleOrGrantablePerm(
1407 : shared_model::interface::permissions::Role::kRemoveSignatory,
1408 : shared_model::interface::permissions::Grantable::
1409 : kRemoveMySignatory,
1410 2982 : "$1",
1411 2982 : "$2"))
1412 2982 : .str(),
1413 2982 : R"(
1414 : AND (SELECT * FROM has_perm)
1415 : AND EXISTS (SELECT * FROM get_account)
1416 : AND EXISTS (SELECT * FROM get_signatories)
1417 : AND EXISTS (SELECT * FROM check_account_signatories)
1418 : )",
1419 2982 : R"(
1420 : WHEN NOT EXISTS (SELECT * FROM get_account) THEN 3
1421 : WHEN NOT (SELECT * FROM has_perm) THEN 2
1422 : WHEN NOT EXISTS (SELECT * FROM get_signatory) THEN 4
1423 : WHEN NOT EXISTS (SELECT * FROM check_account_signatories) THEN 5
1424 : )"}});
1425 :
1426 8946 : statements.push_back({"revokePermission",
1427 2982 : revokePermissionBase,
1428 2982 : {(boost::format(R"(
1429 : has_perm AS (SELECT COALESCE(bit_or(permission), '0'::bit(%1%))
1430 : & $3 = $3 FROM account_has_grantable_permissions
1431 : WHERE account_id = $1 AND
1432 : permittee_account_id = $2),)")
1433 2982 : % grantable_bits)
1434 2982 : .str(),
1435 2982 : R"( AND (SELECT * FROM has_perm))",
1436 2982 : R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
1437 :
1438 8946 : statements.push_back(
1439 2982 : {"setAccountDetail",
1440 2982 : setAccountDetailBase,
1441 2982 : {(boost::format(R"(
1442 : has_role_perm AS (%s),
1443 : has_grantable_perm AS (%s),
1444 : has_perm AS (SELECT CASE
1445 : WHEN (SELECT * FROM has_grantable_perm) THEN true
1446 : WHEN ($1 = $2) THEN true
1447 : WHEN (SELECT * FROM has_role_perm) THEN true
1448 : ELSE false END
1449 : ),
1450 : )")
1451 2982 : % checkAccountRolePermission(
1452 2982 : shared_model::interface::permissions::Role::kSetDetail, "$1")
1453 2982 : % checkAccountGrantablePermission(
1454 : shared_model::interface::permissions::Grantable::
1455 : kSetMyAccountDetail,
1456 2982 : "$1",
1457 2982 : "$2"))
1458 2982 : .str(),
1459 2982 : R"( AND (SELECT * FROM has_perm))",
1460 2982 : R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
1461 :
1462 11928 : statements.push_back(
1463 2982 : {"setQuorum",
1464 2982 : setQuorumBase,
1465 2982 : {R"( get_signatories AS (
1466 : SELECT public_key FROM account_has_signatory
1467 : WHERE account_id = $2
1468 : ),
1469 : check_account_signatories AS (
1470 : SELECT 1 FROM account
1471 : WHERE $3 <= (SELECT COUNT(*) FROM get_signatories)
1472 : AND account_id = $2
1473 : ),)",
1474 2982 : (boost::format(R"(
1475 : has_perm AS (%s),)")
1476 2982 : % checkAccountHasRoleOrGrantablePerm(
1477 : shared_model::interface::permissions::Role::kSetQuorum,
1478 : shared_model::interface::permissions::Grantable::
1479 : kSetMyQuorum,
1480 2982 : "$1",
1481 2982 : "$2"))
1482 2982 : .str(),
1483 2982 : R"(AND EXISTS
1484 : (SELECT * FROM get_signatories)
1485 : AND EXISTS (SELECT * FROM check_account_signatories)
1486 : AND (SELECT * FROM has_perm))",
1487 2982 : R"(
1488 : WHEN NOT (SELECT * FROM has_perm) THEN 2
1489 : WHEN NOT EXISTS (SELECT * FROM get_signatories) THEN 4
1490 : WHEN NOT EXISTS (SELECT * FROM check_account_signatories) THEN 5
1491 : )"}});
1492 :
1493 8946 : statements.push_back({"subtractAssetQuantity",
1494 2982 : subtractAssetQuantityBase,
1495 2982 : {(boost::format(R"(
1496 : has_perm AS (%s),)")
1497 2982 : % checkAccountDomainRoleOrGlobalRolePermission(
1498 : shared_model::interface::permissions::Role::
1499 : kSubtractAssetQty,
1500 : shared_model::interface::permissions::Role::
1501 : kSubtractDomainAssetQty,
1502 2982 : "$1",
1503 2982 : "$2"))
1504 2982 : .str(),
1505 2982 : R"( AND (SELECT * FROM has_perm))",
1506 2982 : R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
1507 :
1508 11928 : statements.push_back(
1509 2982 : {"transferAsset",
1510 2982 : transferAssetBase,
1511 2982 : {(boost::format(R"(
1512 : has_role_perm AS (%s),
1513 : has_grantable_perm AS (%s),
1514 : dest_can_receive AS (%s),
1515 : has_perm AS (SELECT
1516 : CASE WHEN (SELECT * FROM dest_can_receive) THEN
1517 : CASE WHEN NOT ($1 = $2) THEN
1518 : CASE WHEN (SELECT * FROM has_grantable_perm)
1519 : THEN true
1520 : ELSE false END
1521 : ELSE
1522 : CASE WHEN (SELECT * FROM has_role_perm)
1523 : THEN true
1524 : ELSE false END
1525 : END
1526 : ELSE false END
1527 : ),
1528 : )")
1529 2982 : % checkAccountRolePermission(
1530 2982 : shared_model::interface::permissions::Role::kTransfer, "$1")
1531 2982 : % checkAccountGrantablePermission(
1532 : shared_model::interface::permissions::Grantable::
1533 : kTransferMyAssets,
1534 2982 : "$1",
1535 2982 : "$2")
1536 2982 : % checkAccountRolePermission(
1537 2982 : shared_model::interface::permissions::Role::kReceive, "$3"))
1538 2982 : .str(),
1539 2982 : R"( AND (SELECT * FROM has_perm))",
1540 2982 : R"( AND (SELECT * FROM has_perm))",
1541 2982 : R"( WHEN NOT (SELECT * FROM has_perm) THEN 2 )"}});
1542 :
1543 50694 : for (const auto &st : statements) {
1544 47712 : prepareStatement(sql, st);
1545 : }
1546 2982 : };
1547 :
1548 : } // namespace ametsuchi
1549 : } // namespace iroha
|