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