Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core-database-postgres): add nonce column #2844

Merged
merged 19 commits into from Aug 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4ec0d7f
feat(core-database-postgres): add transaction nonce to the database
vasild Jul 26, 2019
6f418c6
chore(core-database-postgres): execute the nonce migration
vasild Jul 26, 2019
f9df8ee
Merge remote-tracking branch 'ArkEcosystem/core/2.6' into add-nonce-t…
vasild Jul 29, 2019
2180b87
chore(core-database-postgres): add explicit error message for NULL nonce
vasild Jul 29, 2019
0a28f6f
fix(core-database-postgres): use proper formatting syntax for SQL RAISE
vasild Jul 29, 2019
f5fc1a5
chore(core-database-postgres): add nonce to db transaction model
vasild Jul 30, 2019
894ea46
Merge remote-tracking branch 'ArkEcosystem/core/2.6' into add-nonce-t…
vasild Jul 30, 2019
ec67b75
fix(core-database-postgres): handle unset nonce in models/transaction.ts
vasild Jul 30, 2019
151a3a6
chore(core-database-postgres): reword error messages
vasild Jul 30, 2019
109e402
feat(core-database-postgres): set nonce for v1 transactions
vasild Aug 1, 2019
410a566
Merge remote-tracking branch 'ArkEcosystem/core/2.6' into add-nonce-t…
vasild Aug 1, 2019
7f6b8bd
Merge branch '2.6' into add-nonce-to-db
spkjp Aug 1, 2019
284ccf6
Merge remote-tracking branch 'ArkEcosystem/core/2.6' into add-nonce-t…
vasild Aug 2, 2019
28b9bb6
fix: first nonce from a given sender should be 1, not 0
vasild Aug 2, 2019
178beb1
Merge remote-tracking branch 'ArkEcosystem/core/2.6' into add-nonce-t…
vasild Aug 2, 2019
aaf548f
fix integration test to generate a transaction with the correct nonce
vasild Aug 2, 2019
8e37c0a
Merge remote-tracking branch 'ArkEcosystem/core/2.6' into add-nonce-t…
vasild Aug 6, 2019
e6c569f
fix integration test (resign) to generate a transaction with the corr…
vasild Aug 6, 2019
12baf99
fix: codacy glob
spkjp Aug 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions .codacy.yml
Expand Up @@ -6,6 +6,6 @@ exclude_paths:
- 'scripts/**/*'
- 'upgrade/**/*'
- 'vagrant/**/*'
- '*.sh'
- '*.sql'
- '*.md'
- '**.sql'
- '**.sh'
- '**.md'
Expand Up @@ -65,6 +65,7 @@ describe("Delegate resignation handler bootstrap", () => {
.withNetwork("unitnet")
.withPassphrase(sender.passphrase)
.withTimestamp(optionsDefault.timestamp)
.withNonce(Utils.BigNumber.make(1)) // this will generate a transaction with nonce=2
.createOne();

const blockResignation = delegate.forge([resignation], {
Expand Down
Expand Up @@ -101,6 +101,7 @@ describe("Vote handler bootstrap", () => {
.withNetwork("unitnet")
.withPassphrase(sender.passphrase)
.withTimestamp(optionsDefault.timestamp + 100)
.withNonce(Utils.BigNumber.make(1)) // this will generate a transaction with nonce=2
.createOne();
const blockUnvote = delegate.forge([unvote], {
timestamp: 12345789,
Expand Down
@@ -0,0 +1,150 @@
ALTER TABLE ${schema~}.transactions DROP CONSTRAINT IF EXISTS "transactions_nonce";

DROP FUNCTION IF EXISTS ${schema~}.check_transaction_nonce(
version_arg ${schema~}.transactions.version%TYPE,
id_arg ${schema~}.transactions.id%TYPE,
sender_public_key_arg ${schema~}.transactions.sender_public_key%TYPE,
nonce_arg ${schema~}.transactions.nonce%TYPE,
block_id_arg ${schema~}.blocks.id%TYPE,
sequence_arg ${schema~}.transactions.sequence%TYPE
);

DROP FUNCTION IF EXISTS ${schema~}.set_nonces();

ALTER TABLE ${schema~}.transactions DROP COLUMN IF EXISTS nonce;
ALTER TABLE ${schema~}.transactions ADD COLUMN nonce BIGINT;

CREATE UNIQUE INDEX IF NOT EXISTS "transactions_sender_nonce" ON
${schema~}.transactions ("sender_public_key", "nonce");

CREATE FUNCTION ${schema~}.set_nonces() RETURNS VOID
AS
$$
DECLARE
current_row RECORD;
i ${schema~}.transactions.nonce%TYPE;
previous_sender_public_key ${schema~}.transactions.sender_public_key%TYPE := '';
BEGIN
FOR current_row IN
SELECT
${schema~}.transactions.sender_public_key,
${schema~}.transactions.id
FROM
${schema~}.transactions,
${schema~}.blocks
WHERE
${schema~}.transactions.block_id = blocks.id
ORDER BY
${schema~}.transactions.sender_public_key,
${schema~}.blocks.height,
${schema~}.transactions.sequence
LOOP
IF current_row.sender_public_key != previous_sender_public_key THEN
i := 1;
END IF;
UPDATE ${schema~}.transactions SET nonce = i WHERE id = current_row.id;
previous_sender_public_key := current_row.sender_public_key;
i := i + 1;
END LOOP;
END;
$$
LANGUAGE PLPGSQL
VOLATILE;

SELECT ${schema~}.set_nonces();

DROP FUNCTION ${schema~}.set_nonces();

ALTER TABLE ${schema~}.transactions ALTER COLUMN nonce SET NOT NULL;

CREATE FUNCTION ${schema~}.set_row_nonce() RETURNS TRIGGER
AS
$$
BEGIN
SELECT COUNT(*) + 1 INTO NEW.nonce
FROM ${schema~}.transactions
WHERE sender_public_key = NEW.sender_public_key;

RETURN NEW;
END;
$$
LANGUAGE PLPGSQL
VOLATILE;

CREATE TRIGGER transactions_set_nonce
BEFORE INSERT
ON ${schema~}.transactions
FOR EACH ROW
WHEN (NEW.version = 1)
EXECUTE PROCEDURE ${schema~}.set_row_nonce();

CREATE FUNCTION check_transaction_nonce(
version_arg ${schema~}.transactions.version%TYPE,
id_arg ${schema~}.transactions.id%TYPE,
sender_public_key_arg ${schema~}.transactions.sender_public_key%TYPE,
nonce_arg ${schema~}.transactions.nonce%TYPE,
block_id_arg ${schema~}.blocks.id%TYPE,
sequence_arg ${schema~}.transactions.sequence%TYPE
) RETURNS BOOLEAN
AS
$$
DECLARE
other_tx_with_same_nonce ${schema~}.transactions.id%TYPE;
current_tx_block_height ${schema~}.blocks.height%TYPE;
previous_tx_block_height ${schema~}.blocks.height%TYPE;
previous_tx_sequence ${schema~}.transactions.sequence%TYPE;
error_message_prefix TEXT;
BEGIN
IF nonce_arg IS NULL THEN
RAISE 'Invalid transaction: version=%, but nonce is NULL (id="%")', version_arg, id_arg;
END IF;

error_message_prefix := 'Invalid transaction (id="' || id_arg || '", sender_public_key="' ||
sender_public_key_arg || '", nonce=' || nonce_arg || ')';

IF nonce_arg < 0 THEN
RAISE '%: negative nonce', error_message_prefix;
END IF;

-- First transaction from this sender.
IF nonce_arg = 1 THEN
RETURN TRUE;
END IF;

-- Check that a transaction from the same sender with nonce = nonce_arg - 1 exists
-- and is ordered earlier in the blockchain.

SELECT height INTO current_tx_block_height FROM ${schema~}.blocks WHERE id = block_id_arg;

SELECT
${schema~}.blocks.height, ${schema~}.transactions.sequence INTO
previous_tx_block_height, previous_tx_sequence
FROM ${schema~}.transactions, ${schema~}.blocks
WHERE
${schema~}.transactions.sender_public_key = sender_public_key_arg AND
${schema~}.transactions.nonce = nonce_arg - 1 AND
${schema~}.transactions.block_id = ${schema~}.blocks.id;

IF NOT FOUND THEN
RAISE '%: the previous transaction from the same sender does not exist (with nonce=%)',
error_message_prefix, nonce_arg - 1;
END IF;

IF previous_tx_block_height > current_tx_block_height THEN
RAISE '%: the previous transaction from the same sender (with nonce=%) is in a higher block (% > %)',
error_message_prefix, nonce_arg - 1, previous_tx_block_height, current_tx_block_height;
END IF;

IF previous_tx_block_height = current_tx_block_height AND previous_tx_sequence >= sequence_arg THEN
RAISE '%: the previous transaction from the same sender (with nonce=%) is in the same block (height=%) but with a bigger sequence (% >= %)',
error_message_prefix, nonce_arg - 1, previous_tx_block_height, previous_tx_sequence, sequence_arg;
END IF;

RETURN TRUE;
END;
$$
LANGUAGE PLPGSQL
VOLATILE;

ALTER TABLE ${schema~}.transactions ADD CONSTRAINT "transactions_nonce"
CHECK (${schema~}.check_transaction_nonce(version, id, sender_public_key, nonce, block_id, sequence));
1 change: 1 addition & 0 deletions packages/core-database-postgres/src/migrations/index.ts
Expand Up @@ -18,4 +18,5 @@ export const migrations = [
loadQueryFile(__dirname, "./20190626000000-enforce-chained-blocks.sql"),
loadQueryFile(__dirname, "./20190718000000-check_previous_block-add-schema.sql"),
loadQueryFile(__dirname, "./20190803000000-add-type_group-column-to-transactions-table.sql"),
loadQueryFile(__dirname, "./20190806000000-add-nonce-column-to-transactions-table.sql"),
];
10 changes: 10 additions & 0 deletions packages/core-database-postgres/src/models/transaction.ts
Expand Up @@ -85,6 +85,16 @@ export class Transaction extends Model {
},
supportedOperators: [Database.SearchOperator.OP_CONTAINS],
},
{
name: "nonce",
init: col => col.value !== undefined ? Utils.BigNumber.make(col.value).toFixed() : undefined,
supportedOperators: [
Database.SearchOperator.OP_LTE,
Database.SearchOperator.OP_GTE,
Database.SearchOperator.OP_EQ,
],
def: undefined,
},
];

public getTable(): string {
Expand Down