diff --git a/src/backend/Pg.cpp b/src/backend/Pg.cpp index e68c5775..e8d6e463 100644 --- a/src/backend/Pg.cpp +++ b/src/backend/Pg.cpp @@ -33,13 +33,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -848,112 +848,6 @@ CREATE TABLE IF NOT EXISTS keys ( PRIMARY KEY(ledger_seq, key) ); --- account_tx() RPC helper. From the rippled reporting process, only the --- parameters without defaults are required. For the parameters with --- defaults, validation should be done by rippled, such as: --- _in_account_id should be a valid xrp base58 address. --- _in_forward either true or false according to the published api --- _in_limit should be validated and not simply passed through from --- client. --- --- For _in_ledger_index_min and _in_ledger_index_max, if passed in the --- request, verify that their type is int and pass through as is. --- For _ledger_hash, verify and convert from hex length 32 bytes and --- prepend with \x (\\x C++). --- --- For _in_ledger_index, if the input type is integer, then pass through --- as is. If the type is string and contents = validated, then do not --- set _in_ledger_index. Instead set _in_invalidated to TRUE. --- --- There is no need for rippled to do any type of lookup on max/min --- ledger range, lookup of hash, or the like. This functions does those --- things, including error responses if bad input. Only the above must --- be done to set the correct search range. --- --- If a marker is present in the request, verify the members 'ledger' --- and 'seq' are integers and they correspond to _in_marker_seq --- _in_marker_index. --- To reiterate: --- JSON input field 'ledger' corresponds to _in_marker_seq --- JSON input field 'seq' corresponds to _in_marker_index -CREATE OR REPLACE FUNCTION account_tx( - _in_account_id bytea, - _in_limit bigint, - _in_marker_seq bigint DEFAULT NULL::bigint, - _in_marker_index bigint DEFAULT NULL::bigint) -RETURNS jsonb -AS $$ -DECLARE - _min bigint; - _max bigint; - _marker bool; - _between_min bigint; - _between_max bigint; - _sql text; - _cursor refcursor; - _result jsonb; - _record record; - _tally bigint := 0; - _ret_marker jsonb; - _transactions jsonb[] := '{}'; -BEGIN - _min := min_ledger(); - _max := max_ledger(); - IF _in_marker_seq IS NOT NULL OR _in_marker_index IS NOT NULL THEN - _marker := TRUE; - IF _in_marker_seq IS NULL OR _in_marker_index IS NULL THEN - -- The rippled implementation returns no transaction results - -- if either of these values are missing. - _between_min := 0; - _between_max := 0; - ELSE - _between_min := _min; - _between_max := _in_marker_seq; - END IF; - ELSE - _marker := FALSE; - _between_min := _min; - _between_max := _max; - END IF; - - - _sql := format('SELECT hash, ledger_seq, transaction_index FROM account_transactions WHERE account = $1 - AND ledger_seq BETWEEN $2 AND $3 ORDER BY ledger_seq DESC, transaction_index DESC'); - - OPEN _cursor FOR EXECUTE _sql USING _in_account_id, _between_min, _between_max; - LOOP - FETCH _cursor INTO _record; - IF _record IS NULL THEN EXIT; END IF; - IF _marker IS TRUE THEN - IF _in_marker_seq = _record.ledger_seq THEN - IF _in_marker_index < _record.transaction_index THEN - CONTINUE; - END IF; - END IF; - _marker := FALSE; - END IF; - _tally := _tally + 1; - IF _tally > _in_limit THEN - _ret_marker := jsonb_build_object( - 'ledger_sequence', _record.ledger_seq, - 'transaction_index', _record.transaction_index); - EXIT; - END IF; - - -- Is the transaction index in the tx object? - _transactions := _transactions || jsonb_build_object('hash',_record.hash); - END LOOP; - CLOSE _cursor; - - _result := jsonb_build_object('ledger_index_min', _min, - 'ledger_index_max', _max, - 'transactions', _transactions); - IF _ret_marker IS NOT NULL THEN - _result := _result || jsonb_build_object('cursor', _ret_marker); - END IF; - RETURN _result; -END; -$$ LANGUAGE plpgsql; -- Avoid inadvertent administrative tampering with committed data. CREATE OR REPLACE RULE ledgers_update_protect AS ON UPDATE TO @@ -1249,6 +1143,125 @@ $$ LANGUAGE plpgsql; // , R"(Full idempotent text of schema version n)" }; +static constexpr char* accountTxSchema = R"( + +-- account_tx() RPC helper. From the rippled reporting process, only the +-- parameters without defaults are required. For the parameters with +-- defaults, validation should be done by rippled, such as: +-- _in_account_id should be a valid xrp base58 address. +-- _in_forward either true or false according to the published api +-- _in_limit should be validated and not simply passed through from +-- client. +-- +-- For _in_ledger_index_min and _in_ledger_index_max, if passed in the +-- request, verify that their type is int and pass through as is. +-- For _ledger_hash, verify and convert from hex length 32 bytes and +-- prepend with \x (\\x C++). +-- +-- For _in_ledger_index, if the input type is integer, then pass through +-- as is. If the type is string and contents = validated, then do not +-- set _in_ledger_index. Instead set _in_invalidated to TRUE. +-- +-- There is no need for rippled to do any type of lookup on max/min +-- ledger range, lookup of hash, or the like. This functions does those +-- things, including error responses if bad input. Only the above must +-- be done to set the correct search range. +-- +-- If a marker is present in the request, verify the members 'ledger' +-- and 'seq' are integers and they correspond to _in_marker_seq +-- _in_marker_index. +-- To reiterate: +-- JSON input field 'ledger' corresponds to _in_marker_seq +-- JSON input field 'seq' corresponds to _in_marker_index +CREATE OR REPLACE FUNCTION account_tx( + _in_account_id bytea, + _in_limit bigint, + _in_forward bool, + _in_marker_seq bigint DEFAULT NULL::bigint, + _in_marker_index bigint DEFAULT NULL::bigint) +RETURNS jsonb +AS $$ +DECLARE + _min bigint; + _max bigint; + _marker bool; + _between_min bigint; + _between_max bigint; + _sql text; + _cursor refcursor; + _result jsonb; + _record record; + _tally bigint := 0; + _ret_marker jsonb; + _transactions jsonb[] := '{}'; + _sort_order text := (SELECT CASE WHEN _in_forward IS TRUE THEN + 'ASC' ELSE 'DESC' END); +BEGIN + _min := min_ledger(); + _max := max_ledger(); + IF _in_marker_seq IS NOT NULL OR _in_marker_index IS NOT NULL THEN + _marker := TRUE; + IF _in_marker_seq IS NULL OR _in_marker_index IS NULL THEN + -- The rippled implementation returns no transaction results + -- if either of these values are missing. + _between_min := 0; + _between_max := 0; + ELSE + _between_min := _min; + _between_max := _in_marker_seq; + END IF; + ELSE + _marker := FALSE; + _between_min := _min; + _between_max := _max; + END IF; + + + _sql := format('SELECT hash, ledger_seq, transaction_index FROM account_transactions WHERE account = $1 + AND ledger_seq BETWEEN $2 AND $3 ORDER BY ledger_seq %s, transaction_index %s'); + + OPEN _cursor FOR EXECUTE _sql USING _in_account_id, _between_min, _between_max; + LOOP + FETCH _cursor INTO _record; + IF _record IS NULL THEN EXIT; END IF; + IF _marker IS TRUE THEN + IF _in_marker_seq = _record.ledger_seq THEN + IF _in_forward IS TRUE THEN + IF _in_marker_index > _record.transaction_index THEN + CONTINUE; + END IF; + ELSE + IF _in_marker_index < _record.transaction_index THEN + CONTINUE; + END IF; + END IF; + END IF; + _marker := FALSE; + END IF; + _tally := _tally + 1; + IF _tally > _in_limit THEN + _ret_marker := jsonb_build_object( + 'ledger_sequence', _record.ledger_seq, + 'transaction_index', _record.transaction_index); + EXIT; + END IF; + + -- Is the transaction index in the tx object? + _transactions := _transactions || jsonb_build_object('hash',_record.hash); + END LOOP; + CLOSE _cursor; + + _result := jsonb_build_object('ledger_index_min', _min, + 'ledger_index_max', _max, + 'transactions', _transactions); + IF _ret_marker IS NOT NULL THEN + _result := _result || jsonb_build_object('cursor', _ret_marker); + END IF; + RETURN _result; +END; +$$ LANGUAGE plpgsql; + +)"; std::array upgrade_schemata = { // upgrade from version 0: "There is no upgrade path from version 0. Instead, install " @@ -1314,6 +1327,18 @@ applySchema( } } +void +initAccountTx(std::shared_ptr const& pool) +{ + auto res = PgQuery(pool)(accountTxSchema); + if (!res) + { + std::stringstream ss; + ss << "Error initializing account_tx stored procedure"; + throw std::runtime_error(ss.str()); + } +} + void initSchema(std::shared_ptr const& pool) { diff --git a/src/backend/Pg.h b/src/backend/Pg.h index 24881b37..11586bd4 100644 --- a/src/backend/Pg.h +++ b/src/backend/Pg.h @@ -539,6 +539,8 @@ make_PgPool(boost::json::object const& pgConfig); */ void initSchema(std::shared_ptr const& pool); +void +initAccountTx(std::shared_ptr const& pool); // Load the ledger info for the specified ledger/s from the database // @param whichLedger specifies the ledger to load via ledger sequence, ledger diff --git a/src/backend/PostgresBackend.cpp b/src/backend/PostgresBackend.cpp index 66da5e41..af0e2dfd 100644 --- a/src/backend/PostgresBackend.cpp +++ b/src/backend/PostgresBackend.cpp @@ -541,17 +541,19 @@ PostgresBackend::fetchAccountTransactions( char const*& command = dbParams.first; std::vector>& values = dbParams.second; command = - "SELECT account_tx($1::bytea, $2::bigint, " - "$3::bigint, $4::bigint)"; - values.resize(4); + "SELECT account_tx($1::bytea, $2::bigint, $3::bool" + "$4::bigint, $5::bigint)"; + values.resize(5); values[0] = "\\x" + strHex(account); values[1] = std::to_string(limit); + values[2] = std::to_string(forward); + if (cursor) { - values[2] = std::to_string(cursor->ledgerSequence); - values[3] = std::to_string(cursor->transactionIndex); + values[3] = std::to_string(cursor->ledgerSequence); + values[4] = std::to_string(cursor->transactionIndex); } for (size_t i = 0; i < values.size(); ++i) { @@ -607,6 +609,7 @@ PostgresBackend::open(bool readOnly) { if (!readOnly) initSchema(pgPool_); + initAccountTx(pgPool_); } void