From 3aa72b69548ca2a43bc54ba32c2574680f1162f5 Mon Sep 17 00:00:00 2001 From: Hugo Trentesaux <hugo@trentesaux.fr> Date: Wed, 22 Nov 2023 18:21:57 +0100 Subject: [PATCH] add components from giant squid --- db/migrations/1700653099748-Data.js | 61 --- db/migrations/1700673585666-Data.js | 149 ++++++++ package-lock.json | 19 + package.json | 3 +- schema.graphql | 131 ++++++- src/giant-squid.ts | 126 +++++++ src/main.ts | 418 ++++++++++----------- src/model/generated/_counterLevel.ts | 5 + src/model/generated/_extrinsicSignature.ts | 49 +++ src/model/generated/_itemType.ts | 5 + src/model/generated/block.model.ts | 60 ++- src/model/generated/call.model.ts | 46 ++- src/model/generated/event.model.ts | 50 +++ src/model/generated/extrinsic.model.ts | 56 +++ src/model/generated/index.ts | 10 +- src/model/generated/itemsCounter.model.ts | 25 ++ src/processor.ts | 33 +- 17 files changed, 941 insertions(+), 305 deletions(-) delete mode 100644 db/migrations/1700653099748-Data.js create mode 100644 db/migrations/1700673585666-Data.js create mode 100644 src/giant-squid.ts create mode 100644 src/model/generated/_counterLevel.ts create mode 100644 src/model/generated/_extrinsicSignature.ts create mode 100644 src/model/generated/_itemType.ts create mode 100644 src/model/generated/event.model.ts create mode 100644 src/model/generated/extrinsic.model.ts create mode 100644 src/model/generated/itemsCounter.model.ts diff --git a/db/migrations/1700653099748-Data.js b/db/migrations/1700653099748-Data.js deleted file mode 100644 index f7e7cbe..0000000 --- a/db/migrations/1700653099748-Data.js +++ /dev/null @@ -1,61 +0,0 @@ -module.exports = class Data1700653099748 { - name = 'Data1700653099748' - - async up(db) { - await db.query(`CREATE TABLE "transfer" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "amount" numeric NOT NULL, "from_id" character varying, "to_id" character varying, CONSTRAINT "PK_fd9ddbdd49a17afcbe014401295" PRIMARY KEY ("id"))`) - await db.query(`CREATE INDEX "IDX_d6624eacc30144ea97915fe846" ON "transfer" ("block_number") `) - await db.query(`CREATE INDEX "IDX_70ff8b624c3118ac3a4862d22c" ON "transfer" ("timestamp") `) - await db.query(`CREATE INDEX "IDX_76bdfed1a7eb27c6d8ecbb7349" ON "transfer" ("from_id") `) - await db.query(`CREATE INDEX "IDX_0751309c66e97eac9ef1149362" ON "transfer" ("to_id") `) - await db.query(`CREATE INDEX "IDX_f4007436c1b546ede08a4fd7ab" ON "transfer" ("amount") `) - await db.query(`CREATE TABLE "account" ("id" character varying NOT NULL, CONSTRAINT "PK_54115ee388cdb6d86bb4bf5b2ea" PRIMARY KEY ("id"))`) - await db.query(`CREATE TABLE "cert" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "issuer_id" character varying, "receiver_id" character varying, CONSTRAINT "PK_6a0ce80cc860598b4f16c00998c" PRIMARY KEY ("id"))`) - await db.query(`CREATE INDEX "IDX_a0541d4207bde7970641de0641" ON "cert" ("block_number") `) - await db.query(`CREATE INDEX "IDX_70592e488b2e75cd8a2fa79826" ON "cert" ("issuer_id") `) - await db.query(`CREATE INDEX "IDX_262e29ab91c8ebc727cc518f2f" ON "cert" ("receiver_id") `) - await db.query(`CREATE TABLE "identity" ("id" character varying NOT NULL, "index" integer NOT NULL, "name" text, "account_id" character varying, CONSTRAINT "REL_bafa9e6c71c3f69cef6602a809" UNIQUE ("account_id"), CONSTRAINT "PK_ff16a44186b286d5e626178f726" PRIMARY KEY ("id"))`) - await db.query(`CREATE UNIQUE INDEX "IDX_6f883c7979ea8dff46327f67cc" ON "identity" ("index") `) - await db.query(`CREATE UNIQUE INDEX "IDX_bafa9e6c71c3f69cef6602a809" ON "identity" ("account_id") `) - await db.query(`CREATE INDEX "IDX_883ba5be237fba47f2a2f39145" ON "identity" ("name") `) - await db.query(`CREATE TABLE "call" ("id" character varying NOT NULL, "name" text NOT NULL, "block_number" integer NOT NULL, CONSTRAINT "PK_2098af0169792a34f9cfdd39c47" PRIMARY KEY ("id"))`) - await db.query(`CREATE INDEX "IDX_8b212022b7428232091e2f8aa5" ON "call" ("name") `) - await db.query(`CREATE INDEX "IDX_8d62e222f606c249fb4046ad63" ON "call" ("block_number") `) - await db.query(`CREATE TABLE "block" ("id" character varying NOT NULL, "number" integer NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_d0925763efb591c2e2ffb267572" PRIMARY KEY ("id"))`) - await db.query(`CREATE UNIQUE INDEX "IDX_38414873c187a3e0c7943bc4c7" ON "block" ("number") `) - await db.query(`CREATE INDEX "IDX_5c67cbcf4960c1a39e5fe25e87" ON "block" ("timestamp") `) - await db.query(`ALTER TABLE "transfer" ADD CONSTRAINT "FK_76bdfed1a7eb27c6d8ecbb73496" FOREIGN KEY ("from_id") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - await db.query(`ALTER TABLE "transfer" ADD CONSTRAINT "FK_0751309c66e97eac9ef11493623" FOREIGN KEY ("to_id") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - await db.query(`ALTER TABLE "cert" ADD CONSTRAINT "FK_70592e488b2e75cd8a2fa798261" FOREIGN KEY ("issuer_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - await db.query(`ALTER TABLE "cert" ADD CONSTRAINT "FK_262e29ab91c8ebc727cc518f2fb" FOREIGN KEY ("receiver_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - await db.query(`ALTER TABLE "identity" ADD CONSTRAINT "FK_bafa9e6c71c3f69cef6602a8095" FOREIGN KEY ("account_id") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - } - - async down(db) { - await db.query(`DROP TABLE "transfer"`) - await db.query(`DROP INDEX "public"."IDX_d6624eacc30144ea97915fe846"`) - await db.query(`DROP INDEX "public"."IDX_70ff8b624c3118ac3a4862d22c"`) - await db.query(`DROP INDEX "public"."IDX_76bdfed1a7eb27c6d8ecbb7349"`) - await db.query(`DROP INDEX "public"."IDX_0751309c66e97eac9ef1149362"`) - await db.query(`DROP INDEX "public"."IDX_f4007436c1b546ede08a4fd7ab"`) - await db.query(`DROP TABLE "account"`) - await db.query(`DROP TABLE "cert"`) - await db.query(`DROP INDEX "public"."IDX_a0541d4207bde7970641de0641"`) - await db.query(`DROP INDEX "public"."IDX_70592e488b2e75cd8a2fa79826"`) - await db.query(`DROP INDEX "public"."IDX_262e29ab91c8ebc727cc518f2f"`) - await db.query(`DROP TABLE "identity"`) - await db.query(`DROP INDEX "public"."IDX_6f883c7979ea8dff46327f67cc"`) - await db.query(`DROP INDEX "public"."IDX_bafa9e6c71c3f69cef6602a809"`) - await db.query(`DROP INDEX "public"."IDX_883ba5be237fba47f2a2f39145"`) - await db.query(`DROP TABLE "call"`) - await db.query(`DROP INDEX "public"."IDX_8b212022b7428232091e2f8aa5"`) - await db.query(`DROP INDEX "public"."IDX_8d62e222f606c249fb4046ad63"`) - await db.query(`DROP TABLE "block"`) - await db.query(`DROP INDEX "public"."IDX_38414873c187a3e0c7943bc4c7"`) - await db.query(`DROP INDEX "public"."IDX_5c67cbcf4960c1a39e5fe25e87"`) - await db.query(`ALTER TABLE "transfer" DROP CONSTRAINT "FK_76bdfed1a7eb27c6d8ecbb73496"`) - await db.query(`ALTER TABLE "transfer" DROP CONSTRAINT "FK_0751309c66e97eac9ef11493623"`) - await db.query(`ALTER TABLE "cert" DROP CONSTRAINT "FK_70592e488b2e75cd8a2fa798261"`) - await db.query(`ALTER TABLE "cert" DROP CONSTRAINT "FK_262e29ab91c8ebc727cc518f2fb"`) - await db.query(`ALTER TABLE "identity" DROP CONSTRAINT "FK_bafa9e6c71c3f69cef6602a8095"`) - } -} diff --git a/db/migrations/1700673585666-Data.js b/db/migrations/1700673585666-Data.js new file mode 100644 index 0000000..ccce178 --- /dev/null +++ b/db/migrations/1700673585666-Data.js @@ -0,0 +1,149 @@ +module.exports = class Data1700673585666 { + name = 'Data1700673585666' + + async up(db) { + await db.query(`CREATE TABLE "event" ("id" character varying NOT NULL, "index" integer NOT NULL, "phase" text NOT NULL, "pallet" text NOT NULL, "name" text NOT NULL, "args" jsonb, "args_str" text array, "block_id" character varying, "extrinsic_id" character varying, "call_id" character varying, CONSTRAINT "PK_30c2f3bbaf6d34a55f8ae6e4614" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_2b0d35d675c4f99751855c4502" ON "event" ("block_id") `) + await db.query(`CREATE INDEX "IDX_129efedcb305c80256db2d57a5" ON "event" ("extrinsic_id") `) + await db.query(`CREATE INDEX "IDX_83cf1bd59aa4521ed882fa5145" ON "event" ("call_id") `) + await db.query(`CREATE INDEX "IDX_7723d04c5a2f56c4373b6a4048" ON "event" ("pallet") `) + await db.query(`CREATE INDEX "IDX_b535fbe8ec6d832dde22065ebd" ON "event" ("name") `) + await db.query(`CREATE INDEX "IDX_0a00d817e614a91cda40d734cf" ON "event" ("id", "pallet", "name") `) + await db.query(`CREATE TABLE "call" ("id" character varying NOT NULL, "address" integer array NOT NULL, "success" boolean NOT NULL, "error" jsonb, "pallet" text NOT NULL, "name" text NOT NULL, "args" jsonb, "args_str" text array, "block_id" character varying, "extrinsic_id" character varying, "parent_id" character varying, CONSTRAINT "PK_2098af0169792a34f9cfdd39c47" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_bd3f11fd4110d60ac8b96cd62f" ON "call" ("block_id") `) + await db.query(`CREATE INDEX "IDX_dde30e4f2c6a80f9236bfdf259" ON "call" ("extrinsic_id") `) + await db.query(`CREATE INDEX "IDX_11c1e76d5be8f04c472c4a05b9" ON "call" ("parent_id") `) + await db.query(`CREATE INDEX "IDX_d3a8c3d00494950ad6dc93297d" ON "call" ("success") `) + await db.query(`CREATE INDEX "IDX_776bccbd3d7b3001c8708cf4e0" ON "call" ("pallet") `) + await db.query(`CREATE INDEX "IDX_8b212022b7428232091e2f8aa5" ON "call" ("name") `) + await db.query(`CREATE INDEX "IDX_f1e953379e1b3c453cd896bcd4" ON "call" ("id", "pallet", "name") `) + await db.query(`CREATE TABLE "extrinsic" ("id" character varying NOT NULL, "index" integer NOT NULL, "version" integer NOT NULL, "signature" jsonb, "tip" numeric, "fee" numeric, "success" boolean, "error" jsonb, "hash" bytea NOT NULL, "block_id" character varying, "call_id" character varying, CONSTRAINT "PK_80d7db0e4b1e83e30336bc76755" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_a3b99daba1259dab0dd040d4f7" ON "extrinsic" ("block_id") `) + await db.query(`CREATE INDEX "IDX_824d47cc4b2cda726405aa507c" ON "extrinsic" ("call_id") `) + await db.query(`CREATE INDEX "IDX_21e5db7671dfa1b00dbe6dbbd6" ON "extrinsic" ("success") `) + await db.query(`CREATE INDEX "IDX_1f45de0713a55049009e8e8127" ON "extrinsic" ("hash") `) + await db.query(`CREATE TABLE "block" ("id" character varying NOT NULL, "height" integer NOT NULL, "hash" bytea NOT NULL, "parent_hash" bytea NOT NULL, "state_root" bytea NOT NULL, "extrinsicsic_root" bytea NOT NULL, "spec_name" text NOT NULL, "spec_version" integer NOT NULL, "impl_name" text NOT NULL, "impl_version" integer NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "validator" bytea, "extrinsics_count" integer NOT NULL, "calls_count" integer NOT NULL, "events_count" integer NOT NULL, CONSTRAINT "PK_d0925763efb591c2e2ffb267572" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_bce676e2b005104ccb768495db" ON "block" ("height") `) + await db.query(`CREATE INDEX "IDX_f8fba63d7965bfee9f304c487a" ON "block" ("hash") `) + await db.query(`CREATE INDEX "IDX_5b79d140fa8e2c64a7ef223598" ON "block" ("spec_version") `) + await db.query(`CREATE INDEX "IDX_5c67cbcf4960c1a39e5fe25e87" ON "block" ("timestamp") `) + await db.query(`CREATE INDEX "IDX_b7e2f8fe1384a2910825029dcb" ON "block" ("validator") `) + await db.query(`CREATE TABLE "items_counter" ("id" character varying NOT NULL, "type" character varying(10) NOT NULL, "level" character varying(6) NOT NULL, "total" integer NOT NULL, CONSTRAINT "PK_161dcf46142538463f5d7174793" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_68d2eadecb3eeb540d2004acef" ON "items_counter" ("type") `) + await db.query(`CREATE INDEX "IDX_1d9be1d79f197d42dd163f86c8" ON "items_counter" ("level") `) + await db.query(`CREATE INDEX "IDX_e03dd1c60ac7622914f72ac2f1" ON "items_counter" ("total") `) + await db.query(`CREATE TABLE "transfer" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "amount" numeric NOT NULL, "from_id" character varying, "to_id" character varying, CONSTRAINT "PK_fd9ddbdd49a17afcbe014401295" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_d6624eacc30144ea97915fe846" ON "transfer" ("block_number") `) + await db.query(`CREATE INDEX "IDX_70ff8b624c3118ac3a4862d22c" ON "transfer" ("timestamp") `) + await db.query(`CREATE INDEX "IDX_76bdfed1a7eb27c6d8ecbb7349" ON "transfer" ("from_id") `) + await db.query(`CREATE INDEX "IDX_0751309c66e97eac9ef1149362" ON "transfer" ("to_id") `) + await db.query(`CREATE INDEX "IDX_f4007436c1b546ede08a4fd7ab" ON "transfer" ("amount") `) + await db.query(`CREATE TABLE "account" ("id" character varying NOT NULL, CONSTRAINT "PK_54115ee388cdb6d86bb4bf5b2ea" PRIMARY KEY ("id"))`) + await db.query(`CREATE TABLE "cert" ("id" character varying NOT NULL, "issuer_id" character varying, "receiver_id" character varying, CONSTRAINT "PK_6a0ce80cc860598b4f16c00998c" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_70592e488b2e75cd8a2fa79826" ON "cert" ("issuer_id") `) + await db.query(`CREATE INDEX "IDX_262e29ab91c8ebc727cc518f2f" ON "cert" ("receiver_id") `) + await db.query(`CREATE TABLE "smith_cert" ("id" character varying NOT NULL, "issuer_id" character varying, "receiver_id" character varying, CONSTRAINT "PK_ae2ef36c9f6d40348c86230fd35" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_ae67cbd087fcea0e1ec2f70cd0" ON "smith_cert" ("issuer_id") `) + await db.query(`CREATE INDEX "IDX_5e414c1d12af16165881a16b63" ON "smith_cert" ("receiver_id") `) + await db.query(`CREATE TABLE "identity" ("id" character varying NOT NULL, "index" integer NOT NULL, "name" text NOT NULL, "account_id" character varying, CONSTRAINT "REL_bafa9e6c71c3f69cef6602a809" UNIQUE ("account_id"), CONSTRAINT "PK_ff16a44186b286d5e626178f726" PRIMARY KEY ("id"))`) + await db.query(`CREATE UNIQUE INDEX "IDX_6f883c7979ea8dff46327f67cc" ON "identity" ("index") `) + await db.query(`CREATE UNIQUE INDEX "IDX_bafa9e6c71c3f69cef6602a809" ON "identity" ("account_id") `) + await db.query(`CREATE INDEX "IDX_883ba5be237fba47f2a2f39145" ON "identity" ("name") `) + await db.query(`CREATE TABLE "membership" ("id" character varying NOT NULL, "expire_on" integer NOT NULL, "identity_id" character varying, CONSTRAINT "REL_efc905420f5f6bfded16c4eb97" UNIQUE ("identity_id"), CONSTRAINT "PK_83c1afebef3059472e7c37e8de8" PRIMARY KEY ("id"))`) + await db.query(`CREATE UNIQUE INDEX "IDX_efc905420f5f6bfded16c4eb97" ON "membership" ("identity_id") `) + await db.query(`CREATE INDEX "IDX_a3f3d8dc21447800f72c0a27b2" ON "membership" ("expire_on") `) + await db.query(`CREATE TABLE "smith_membership" ("id" character varying NOT NULL, "expire_on" integer NOT NULL, "identity_id" character varying, CONSTRAINT "REL_8645aa09a4cfa921be42f1bdc1" UNIQUE ("identity_id"), CONSTRAINT "PK_ec71fc36eab1a7cb666eb11f60a" PRIMARY KEY ("id"))`) + await db.query(`CREATE UNIQUE INDEX "IDX_8645aa09a4cfa921be42f1bdc1" ON "smith_membership" ("identity_id") `) + await db.query(`CREATE INDEX "IDX_fc1bfbc13c6c92d1ef2a868f60" ON "smith_membership" ("expire_on") `) + await db.query(`ALTER TABLE "event" ADD CONSTRAINT "FK_2b0d35d675c4f99751855c45021" FOREIGN KEY ("block_id") REFERENCES "block"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "event" ADD CONSTRAINT "FK_129efedcb305c80256db2d57a59" FOREIGN KEY ("extrinsic_id") REFERENCES "extrinsic"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "event" ADD CONSTRAINT "FK_83cf1bd59aa4521ed882fa51452" FOREIGN KEY ("call_id") REFERENCES "call"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "call" ADD CONSTRAINT "FK_bd3f11fd4110d60ac8b96cd62f3" FOREIGN KEY ("block_id") REFERENCES "block"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "call" ADD CONSTRAINT "FK_dde30e4f2c6a80f9236bfdf2590" FOREIGN KEY ("extrinsic_id") REFERENCES "extrinsic"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "call" ADD CONSTRAINT "FK_11c1e76d5be8f04c472c4a05b95" FOREIGN KEY ("parent_id") REFERENCES "call"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "extrinsic" ADD CONSTRAINT "FK_a3b99daba1259dab0dd040d4f74" FOREIGN KEY ("block_id") REFERENCES "block"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "extrinsic" ADD CONSTRAINT "FK_824d47cc4b2cda726405aa507ca" FOREIGN KEY ("call_id") REFERENCES "call"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "transfer" ADD CONSTRAINT "FK_76bdfed1a7eb27c6d8ecbb73496" FOREIGN KEY ("from_id") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "transfer" ADD CONSTRAINT "FK_0751309c66e97eac9ef11493623" FOREIGN KEY ("to_id") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "cert" ADD CONSTRAINT "FK_70592e488b2e75cd8a2fa798261" FOREIGN KEY ("issuer_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "cert" ADD CONSTRAINT "FK_262e29ab91c8ebc727cc518f2fb" FOREIGN KEY ("receiver_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "smith_cert" ADD CONSTRAINT "FK_ae67cbd087fcea0e1ec2f70cd04" FOREIGN KEY ("issuer_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "smith_cert" ADD CONSTRAINT "FK_5e414c1d12af16165881a16b638" FOREIGN KEY ("receiver_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "identity" ADD CONSTRAINT "FK_bafa9e6c71c3f69cef6602a8095" FOREIGN KEY ("account_id") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "membership" ADD CONSTRAINT "FK_efc905420f5f6bfded16c4eb978" FOREIGN KEY ("identity_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "smith_membership" ADD CONSTRAINT "FK_8645aa09a4cfa921be42f1bdc1b" FOREIGN KEY ("identity_id") REFERENCES "identity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + } + + async down(db) { + await db.query(`DROP TABLE "event"`) + await db.query(`DROP INDEX "public"."IDX_2b0d35d675c4f99751855c4502"`) + await db.query(`DROP INDEX "public"."IDX_129efedcb305c80256db2d57a5"`) + await db.query(`DROP INDEX "public"."IDX_83cf1bd59aa4521ed882fa5145"`) + await db.query(`DROP INDEX "public"."IDX_7723d04c5a2f56c4373b6a4048"`) + await db.query(`DROP INDEX "public"."IDX_b535fbe8ec6d832dde22065ebd"`) + await db.query(`DROP INDEX "public"."IDX_0a00d817e614a91cda40d734cf"`) + await db.query(`DROP TABLE "call"`) + await db.query(`DROP INDEX "public"."IDX_bd3f11fd4110d60ac8b96cd62f"`) + await db.query(`DROP INDEX "public"."IDX_dde30e4f2c6a80f9236bfdf259"`) + await db.query(`DROP INDEX "public"."IDX_11c1e76d5be8f04c472c4a05b9"`) + await db.query(`DROP INDEX "public"."IDX_d3a8c3d00494950ad6dc93297d"`) + await db.query(`DROP INDEX "public"."IDX_776bccbd3d7b3001c8708cf4e0"`) + await db.query(`DROP INDEX "public"."IDX_8b212022b7428232091e2f8aa5"`) + await db.query(`DROP INDEX "public"."IDX_f1e953379e1b3c453cd896bcd4"`) + await db.query(`DROP TABLE "extrinsic"`) + await db.query(`DROP INDEX "public"."IDX_a3b99daba1259dab0dd040d4f7"`) + await db.query(`DROP INDEX "public"."IDX_824d47cc4b2cda726405aa507c"`) + await db.query(`DROP INDEX "public"."IDX_21e5db7671dfa1b00dbe6dbbd6"`) + await db.query(`DROP INDEX "public"."IDX_1f45de0713a55049009e8e8127"`) + await db.query(`DROP TABLE "block"`) + await db.query(`DROP INDEX "public"."IDX_bce676e2b005104ccb768495db"`) + await db.query(`DROP INDEX "public"."IDX_f8fba63d7965bfee9f304c487a"`) + await db.query(`DROP INDEX "public"."IDX_5b79d140fa8e2c64a7ef223598"`) + await db.query(`DROP INDEX "public"."IDX_5c67cbcf4960c1a39e5fe25e87"`) + await db.query(`DROP INDEX "public"."IDX_b7e2f8fe1384a2910825029dcb"`) + await db.query(`DROP TABLE "items_counter"`) + await db.query(`DROP INDEX "public"."IDX_68d2eadecb3eeb540d2004acef"`) + await db.query(`DROP INDEX "public"."IDX_1d9be1d79f197d42dd163f86c8"`) + await db.query(`DROP INDEX "public"."IDX_e03dd1c60ac7622914f72ac2f1"`) + await db.query(`DROP TABLE "transfer"`) + await db.query(`DROP INDEX "public"."IDX_d6624eacc30144ea97915fe846"`) + await db.query(`DROP INDEX "public"."IDX_70ff8b624c3118ac3a4862d22c"`) + await db.query(`DROP INDEX "public"."IDX_76bdfed1a7eb27c6d8ecbb7349"`) + await db.query(`DROP INDEX "public"."IDX_0751309c66e97eac9ef1149362"`) + await db.query(`DROP INDEX "public"."IDX_f4007436c1b546ede08a4fd7ab"`) + await db.query(`DROP TABLE "account"`) + await db.query(`DROP TABLE "cert"`) + await db.query(`DROP INDEX "public"."IDX_70592e488b2e75cd8a2fa79826"`) + await db.query(`DROP INDEX "public"."IDX_262e29ab91c8ebc727cc518f2f"`) + await db.query(`DROP TABLE "smith_cert"`) + await db.query(`DROP INDEX "public"."IDX_ae67cbd087fcea0e1ec2f70cd0"`) + await db.query(`DROP INDEX "public"."IDX_5e414c1d12af16165881a16b63"`) + await db.query(`DROP TABLE "identity"`) + await db.query(`DROP INDEX "public"."IDX_6f883c7979ea8dff46327f67cc"`) + await db.query(`DROP INDEX "public"."IDX_bafa9e6c71c3f69cef6602a809"`) + await db.query(`DROP INDEX "public"."IDX_883ba5be237fba47f2a2f39145"`) + await db.query(`DROP TABLE "membership"`) + await db.query(`DROP INDEX "public"."IDX_efc905420f5f6bfded16c4eb97"`) + await db.query(`DROP INDEX "public"."IDX_a3f3d8dc21447800f72c0a27b2"`) + await db.query(`DROP TABLE "smith_membership"`) + await db.query(`DROP INDEX "public"."IDX_8645aa09a4cfa921be42f1bdc1"`) + await db.query(`DROP INDEX "public"."IDX_fc1bfbc13c6c92d1ef2a868f60"`) + await db.query(`ALTER TABLE "event" DROP CONSTRAINT "FK_2b0d35d675c4f99751855c45021"`) + await db.query(`ALTER TABLE "event" DROP CONSTRAINT "FK_129efedcb305c80256db2d57a59"`) + await db.query(`ALTER TABLE "event" DROP CONSTRAINT "FK_83cf1bd59aa4521ed882fa51452"`) + await db.query(`ALTER TABLE "call" DROP CONSTRAINT "FK_bd3f11fd4110d60ac8b96cd62f3"`) + await db.query(`ALTER TABLE "call" DROP CONSTRAINT "FK_dde30e4f2c6a80f9236bfdf2590"`) + await db.query(`ALTER TABLE "call" DROP CONSTRAINT "FK_11c1e76d5be8f04c472c4a05b95"`) + await db.query(`ALTER TABLE "extrinsic" DROP CONSTRAINT "FK_a3b99daba1259dab0dd040d4f74"`) + await db.query(`ALTER TABLE "extrinsic" DROP CONSTRAINT "FK_824d47cc4b2cda726405aa507ca"`) + await db.query(`ALTER TABLE "transfer" DROP CONSTRAINT "FK_76bdfed1a7eb27c6d8ecbb73496"`) + await db.query(`ALTER TABLE "transfer" DROP CONSTRAINT "FK_0751309c66e97eac9ef11493623"`) + await db.query(`ALTER TABLE "cert" DROP CONSTRAINT "FK_70592e488b2e75cd8a2fa798261"`) + await db.query(`ALTER TABLE "cert" DROP CONSTRAINT "FK_262e29ab91c8ebc727cc518f2fb"`) + await db.query(`ALTER TABLE "smith_cert" DROP CONSTRAINT "FK_ae67cbd087fcea0e1ec2f70cd04"`) + await db.query(`ALTER TABLE "smith_cert" DROP CONSTRAINT "FK_5e414c1d12af16165881a16b638"`) + await db.query(`ALTER TABLE "identity" DROP CONSTRAINT "FK_bafa9e6c71c3f69cef6602a8095"`) + await db.query(`ALTER TABLE "membership" DROP CONSTRAINT "FK_efc905420f5f6bfded16c4eb978"`) + await db.query(`ALTER TABLE "smith_membership" DROP CONSTRAINT "FK_8645aa09a4cfa921be42f1bdc1b"`) + } +} diff --git a/package-lock.json b/package-lock.json index 082b171..09377e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "squid", "dependencies": { + "@belopash/typeorm-store": "^0.2.0", "@subsquid/archive-registry": "^3.3.0", "@subsquid/graphql-server": "^4.3.1", "@subsquid/ss58": "^2.0.1", @@ -191,6 +192,24 @@ "node": ">=6.9.0" } }, + "node_modules/@belopash/typeorm-store": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@belopash/typeorm-store/-/typeorm-store-0.2.0.tgz", + "integrity": "sha512-oQVXOl1VqVmvMlvPgE+Cq4DeiUTKb//IKtDwf+IWefvlSIQZbcPJncrBHvEVkvP6YTCI46r+wwaF2Uebew2aEA==", + "dependencies": { + "@subsquid/logger": "^1.3.1", + "@subsquid/util-internal": "^2.5.2" + }, + "peerDependencies": { + "@subsquid/typeorm-store": "^1.2.1", + "typeorm": "^0.3.16" + } + }, + "node_modules/@belopash/typeorm-store/node_modules/@subsquid/util-internal": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@subsquid/util-internal/-/util-internal-2.5.2.tgz", + "integrity": "sha512-N7lfZdWEkM35jG5wdGYx25TJKGGLMOx9VInSeRhW9T/3BEmHAuSWI2mIIYnZ8w5L041V8HGo61ijWF6qsXvZjg==" + }, "node_modules/@exodus/schemasafe": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", diff --git a/package.json b/package.json index af40280..c3af04e 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,17 @@ "build": "rm -rf lib && tsc" }, "dependencies": { + "@belopash/typeorm-store": "^0.2.0", "@subsquid/archive-registry": "^3.3.0", "@subsquid/graphql-server": "^4.3.1", "@subsquid/ss58": "^2.0.1", "@subsquid/substrate-processor": "^7.2.1", "@subsquid/typeorm-migration": "^1.2.2", "@subsquid/typeorm-store": "^1.2.4", + "@types/node": "^20.8.4", "dotenv": "^16.3.1", "pg": "8.11.3", "typeorm": "^0.3.17", - "@types/node": "^20.8.4", "typescript": "^5.3.2" }, "devDependencies": { diff --git a/schema.graphql b/schema.graphql index 50e8fc9..35cd182 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,3 +1,119 @@ +# === this part of the schema comes from giant squid === +# https://github.com/subsquid-labs/giant-squid-explorer/blob/main/schema.graphql +# Block / Extrinsic / Call / Event + +type Block @entity { + "BlockHeight-blockHash - e.g. 0001812319-0001c" + id: ID! + height: Int! @index + hash: Bytes! @index + parentHash: Bytes! + stateRoot: Bytes! + extrinsicsicRoot: Bytes! + specName: String! + specVersion: Int! @index + implName: String! + implVersion: Int! + timestamp: DateTime! @index + validator: Bytes @index + + extrinsicsCount: Int! + callsCount: Int! + eventsCount: Int! + + extrinsics: [Extrinsic]! @derivedFrom(field: "block") @cardinality(value: 1000) + calls: [Call]! @derivedFrom(field: "block") @cardinality(value: 1000) + events: [Event]! @derivedFrom(field: "block") @cardinality(value: 1000) +} + +type ExtrinsicSignature { + address: JSON + signature: JSON + signedExtensions: JSON +} + +type Extrinsic @entity { + id: ID! + + block: Block! + call: Call! + + index: Int! + version: Int! + signature: ExtrinsicSignature + tip: BigInt + fee: BigInt + success: Boolean @index + error: JSON + hash: Bytes! @index + + calls: [Call]! @derivedFrom(field: "extrinsic") + events: [Event]! @derivedFrom(field: "extrinsic") +} + +type Call @entity @index(fields: ["id", "pallet", "name"]) { + id: ID! + + block: Block! + extrinsic: Extrinsic + parent: Call + + address: [Int!]! + success: Boolean! @index + error: JSON + + pallet: String! @index + name: String! @index + + args: JSON + argsStr: [String] + + subcalls: [Call]! @derivedFrom(field: "parent") + events: [Event]! @derivedFrom(field: "call") +} + +type Event @entity @index(fields: ["id", "pallet", "name"]) { + "Event id - e.g. 0000000001-000000-272d6" + id: ID! + + block: Block! + extrinsic: Extrinsic + call: Call + + index: Int! + phase: String! + + pallet: String! @index + name: String! @index + + args: JSON + argsStr: [String] +} + +enum CounterLevel { + Global + Pallet + Item +} + +enum ItemType { + Extrinsics + Calls + Events +} + +type ItemsCounter @entity { + id: ID! + type: ItemType! @index + level: CounterLevel! @index + total: Int! @index +} + + +# === this part of the schema is for substrate pallets === +# Balances / + + type Account @entity { "Account address is SS58 format" id: ID! @@ -15,6 +131,9 @@ type Transfer @entity { amount: BigInt! @index } +# === this part of the schema is for Duniter pallets === +# + "Identity" type Identity @entity { "Identity index" @@ -60,15 +179,3 @@ type SmithMembership @entity { identity: Identity! @unique @index expireOn: Int! @index } - -# --- generic call record --- - -type Call @entity { - name: String! @index - blockNumber: Int! @index -} - -type Block @entity { - number: Int! @index @unique - timestamp: DateTime! @index -} \ No newline at end of file diff --git a/src/giant-squid.ts b/src/giant-squid.ts new file mode 100644 index 0000000..24b7a49 --- /dev/null +++ b/src/giant-squid.ts @@ -0,0 +1,126 @@ +import { BlockHeader } from "@subsquid/substrate-processor"; +import { StoreWithCache } from "@belopash/typeorm-store"; +import { decodeHex } from "@subsquid/substrate-processor"; +import * as model from "./model"; +import {Event, Extrinsic, ProcessorContext, Call} from './processor' +import type {Hash} from '@subsquid/substrate-data-raw' + +// functions defined in giant squid +// https://github.com/subsquid-labs/giant-squid-explorer/blob/main/src/main.ts + +// taken from @subsquid/substrate-data +interface BlockNonRequiredFields { + /** + * Root hash of the state merkle tree + */ + stateRoot: Hash + /** + * Root hash of the extrinsics merkle tree + */ + extrinsicsRoot: Hash + /** + * Block timestamp as set by `timestamp.now()` (unix epoch ms, compatible with `Date`). + */ + timestamp?: number + /** + * Account address of block validator + */ + validator?: Hash +} + +export async function saveBlock(ctx: ProcessorContext<StoreWithCache>, block: BlockHeader & BlockNonRequiredFields) { + const entity = new model.Block({ + id: block.id, + height: block.height, + hash: decodeHex(block.hash), + parentHash: decodeHex(block.parentHash), + timestamp: new Date(block.timestamp ?? 0), + extrinsicsicRoot: decodeHex(block.extrinsicsRoot), + specName: block.specName, + specVersion: block.specVersion, + implName: block.implName, + implVersion: block.implVersion, + stateRoot: decodeHex(block.stateRoot), + validator: block.validator ? decodeHex(block.validator) : undefined, + extrinsicsCount: 0, + callsCount: 0, + eventsCount: 0, + }); + + await ctx.store.insert(entity); +} + +export async function saveExtrinsic(ctx: ProcessorContext<StoreWithCache>, extrinsic: Extrinsic) { + const block = await ctx.store.getOrFail(model.Block, extrinsic.block.id); + + const entity = new model.Extrinsic({ + id: extrinsic.id, + block, + error: extrinsic.error, + fee: extrinsic.fee, + hash: decodeHex(extrinsic.hash), + index: extrinsic.index, + signature: new model.ExtrinsicSignature(extrinsic.signature), + success: extrinsic.success, + tip: extrinsic.tip, + version: extrinsic.version, + }); + await ctx.store.insert(entity); + + block.extrinsicsCount += 1; + await ctx.store.upsert(block); +} + +export async function saveCall(ctx: ProcessorContext<StoreWithCache>, call: Call) { + const block = await ctx.store.getOrFail(model.Block, call.block.id); + const extrinsic = await ctx.store.getOrFail(model.Extrinsic, call.getExtrinsic().id); + const parent = call.parentCall ? await ctx.store.getOrFail(model.Call, call.parentCall.id) : undefined; + + const [pallet, name] = call.name.split("."); + + const entity = new model.Call({ + id: call.id, + block, + address: call.address, + args: call.args, + error: call.error, + extrinsic, + name, + pallet, + parent, + success: call.success, + }); + await ctx.store.insert(entity); + + block.callsCount += 1; + await ctx.store.upsert(block); + + if (call.address.length == 0) { + extrinsic.call = entity; + await ctx.store.upsert(extrinsic); + } +} + +export async function saveEvent(ctx: ProcessorContext<StoreWithCache>, event: Event) { + const block = await ctx.store.getOrFail(model.Block, event.block.id); + const extrinsic = event.extrinsic ? await ctx.store.getOrFail(model.Extrinsic, event.extrinsic.id) : undefined; + const call = event.call ? await ctx.store.getOrFail(model.Call, event.call.id) : undefined; + + const [pallet, name] = event.name.split("."); + + const entity = new model.Event({ + id: event.id, + block, + args: event.args, + call, + extrinsic, + index: event.index, + name, + pallet, + phase: event.phase, + }); + await ctx.store.insert(entity); + + block.eventsCount += 1; + await ctx.store.upsert(block); +} diff --git a/src/main.ts b/src/main.ts index 6b4b24f..374b126 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,240 +1,230 @@ -import {TypeormDatabase, Store} from '@subsquid/typeorm-store' -import {In} from 'typeorm' -import * as ss58 from '@subsquid/ss58' -import assert from 'assert' +// import { TypeormDatabase, Store } from "@subsquid/typeorm-store"; +import { StoreWithCache, TypeormDatabaseWithCache } from "@belopash/typeorm-store"; +import { In } from "typeorm"; +import * as ss58 from "@subsquid/ss58"; +import assert from "assert"; -import {processor, ProcessorContext} from './processor' -import {Account, Cert, Identity, Transfer, Call, Block} from './model' -import {events, calls} from './types' -import { string } from './model/generated/marshal' +import { processor, ProcessorContext } from "./processor"; +import { Account, Cert, Identity, Transfer, Call, Block } from "./model"; +import { events, calls } from "./types"; +import { string } from "./model/generated/marshal"; -processor.run(new TypeormDatabase({supportHotBlocks: true}), async (ctx) => { - let events: Events = getEvents(ctx) +import { saveBlock, saveExtrinsic, saveCall, saveEvent } from "./giant-squid"; - let accounts: Map<string, Account> = await createAccounts(ctx, events.transfer) - let idties: Map<number, Identity> = await createIdentities(ctx, events.cert) - let transfers: Transfer[] = createTransfers(events.transfer, accounts) - let certs: Cert[] = createCerts(events.cert, idties) - let theCalls: Call[] = createCalls(events.call) +// main processor loop able to manage a batch of blocks +processor.run(new TypeormDatabaseWithCache(), async (ctx) => { + // this part comes from giant squid + // https://github.com/subsquid-labs/giant-squid-explorer/blob/main/src/main.ts + for (const { header: block, calls, events, extrinsics } of ctx.blocks) { + ctx.log.debug(`block ${block.height}: extrinsics - ${extrinsics.length}, calls - ${calls.length}, events - ${events.length}`); + await saveBlock(ctx, block); + for (const extrinsic of extrinsics) { + await saveExtrinsic(ctx, extrinsic); + } + for (const call of calls.reverse()) { + await saveCall(ctx, call); + } + for (const event of events) { + await saveEvent(ctx, event); + } + } + + let events: DuniterData = getDuniterData(ctx); + + let accounts: Map<string, Account> = await createAccounts(ctx, events.transfer); // TODO create account with system event intead of tx + let idties: Map<number, Identity> = await createIdentities(ctx, events.cert); // TODO create identities with event instead of cert + let transfers: Transfer[] = createTransfers(events.transfer, accounts); + let certs: Cert[] = createCerts(events.cert, idties); - await ctx.store.upsert([...accounts.values()]) - await ctx.store.upsert([...idties.values()]) - await ctx.store.insert(transfers) - await ctx.store.insert(certs) - await ctx.store.insert(theCalls) - await ctx.store.insert(events.blocks) -}) + // UPSERT = update or insert if not existing + await ctx.store.upsert([...accounts.values()]); + await ctx.store.upsert([...idties.values()]); + // INSERT + await ctx.store.insert(transfers); + await ctx.store.insert(certs); +}); -interface Events { - blocks: Block[] - transfer: TransferEvent[] - cert: CertEvent[] - call: CallRecord[] +// a way to group data returned +interface DuniterData { + transfer: TransferEvent[]; + cert: CertEvent[]; } + +// buffer to get all the accounts to fetch before inserting interface TransferEvent { - id: string - blockNumber: number - timestamp: Date - from: string - to: string - amount: bigint + id: string; + blockNumber: number; + timestamp: Date; + from: string; + to: string; + amount: bigint; } interface CertEvent { - id: string - issuer: number - receiver: number - blockNumber: number -} -interface CallRecord { - id: string - name: string - blockNumber: number - // eventNames: string[] // call could be linked to events (not their names) + id: string; + issuer: number; + receiver: number; + blockNumber: number; } -function getEvents(ctx: ProcessorContext<Store>): Events { - let transfers: TransferEvent[] = [] - let certs: CertEvent[] = [] - let all_calls: CallRecord[] = [] - let blocks: Block[] = [] +function getDuniterData(ctx: ProcessorContext<StoreWithCache>): DuniterData { + let transfers: TransferEvent[] = []; + let certs: CertEvent[] = []; - // Blocks - for (let block of ctx.blocks) { - // Block - blocks.push(new Block({ - id: block.header.hash, - number: block.header.height, - timestamp: block.header.timestamp ? new Date(block.header.timestamp) : new Date(0), - })) - - // Events - for (let event of block.events) { - switch (event.name) { - // Balances.Transfer - case events.balances.transfer.name: - let rec: {from: string; to: string; amount: bigint} - if (events.balances.transfer.v700.is(event)) { - rec = events.balances.transfer.v700.decode(event) - } else { - throw new Error('Unsupported spec') - } - assert(block.header.timestamp, `Got an undefined timestamp at block ${block.header.height}`) - transfers.push({ - id: event.id, - blockNumber: block.header.height, - timestamp: new Date(block.header.timestamp), - from: ss58.codec(42).encode(rec.from), - to: ss58.codec(42).encode(rec.to), - amount: rec.amount, - }) - break - // Cert.NewCert - case events.cert.newCert.name: - let cert: {issuer: number, receiver: number} - if (events.cert.newCert.v700.is(event)) { - cert = events.cert.newCert.v700.decode(event) - } else { - throw new Error('Unsupported spec') - } - certs.push({ - id: event.id, - blockNumber: block.header.height, - issuer: cert.issuer, - receiver: cert.receiver, - }) - break - // Cert.RenewCert - case events.cert.renewedCert.name: - let recert: {issuer: number, receiver: number} - if (events.cert.renewedCert.v700.is(event)) { - recert = events.cert.renewedCert.v700.decode(event) - } else { - throw new Error('Unsupported spec') - } - certs.push({ - id: event.id, - blockNumber: block.header.height, - issuer: recert.issuer, - receiver: recert.receiver, - }) - break - default: - // ctx.log.info(`Unhandled event ${event.name}`) - } - } - for (let call of block.calls) { - if (call.name != "Timestamp.set") { - all_calls.push(new Call({ - id: call.id, - name: call.name, - blockNumber: block.header.height, - // eventNames: call.events.map((e: Event) => e.id) - })) - } - } - } - let all_events: Events = { - transfer: transfers, - cert: certs, - call: all_calls, - blocks, + // Blocks + for (let block of ctx.blocks) { + // Events + for (let event of block.events) { + switch (event.name) { + // Balances.Transfer + case events.balances.transfer.name: + let rec: { from: string; to: string; amount: bigint }; + if (events.balances.transfer.v700.is(event)) { + rec = events.balances.transfer.v700.decode(event); + } else { + throw new Error("Unsupported spec"); + } + assert(block.header.timestamp, `Got an undefined timestamp at block ${block.header.height}`); + transfers.push({ + id: event.id, + blockNumber: block.header.height, + timestamp: new Date(block.header.timestamp), + from: ss58.codec(42).encode(rec.from), + to: ss58.codec(42).encode(rec.to), + amount: rec.amount, + }); + break; + // Cert.NewCert + case events.cert.newCert.name: + let cert: { issuer: number; receiver: number }; + if (events.cert.newCert.v700.is(event)) { + cert = events.cert.newCert.v700.decode(event); + } else { + throw new Error("Unsupported spec"); + } + certs.push({ + id: event.id, + blockNumber: block.header.height, + issuer: cert.issuer, + receiver: cert.receiver, + }); + break; + // Cert.RenewCert + case events.cert.renewedCert.name: + let recert: { issuer: number; receiver: number }; + if (events.cert.renewedCert.v700.is(event)) { + recert = events.cert.renewedCert.v700.decode(event); + } else { + throw new Error("Unsupported spec"); + } + certs.push({ + id: event.id, + blockNumber: block.header.height, + issuer: recert.issuer, + receiver: recert.receiver, + }); + break; + default: + // ctx.log.info(`Unhandled event ${event.name}`) + } } - return all_events + } + let all_events: DuniterData = { + transfer: transfers, + cert: certs, + }; + return all_events; } -async function createAccounts(ctx: ProcessorContext<Store>, transferEvents: TransferEvent[]): Promise<Map<string,Account>> { - // collect all accounts from transfers - const accountIds = new Set<string>() - for (let t of transferEvents) { - accountIds.add(t.from) - accountIds.add(t.to) +async function createAccounts(ctx: ProcessorContext<StoreWithCache>, transferEvents: TransferEvent[]): Promise<Map<string, Account>> { + // collect all accounts from transfers + const accountIds = new Set<string>(); + for (let t of transferEvents) { + accountIds.add(t.from); + accountIds.add(t.to); + } + // build a map of accounts ids that are already stored + const accounts = await ctx.store.findBy(Account, { id: In([...accountIds]) }).then((accounts) => { + return new Map(accounts.map((a) => [a.id, a])); + }); + // for each transfer, add an account if not already existing + for (let t of transferEvents) { + updateAccounts(t.from); + updateAccounts(t.to); + } + // function to add inexisting accounts to the map + function updateAccounts(id: string): void { + const acc = accounts.get(id); + if (acc == null) { + accounts.set(id, new Account({ id })); } - // build a map of accounts ids that are already stored - const accounts = await ctx.store.findBy(Account, {id: In([...accountIds])}).then((accounts) => { - return new Map(accounts.map((a) => [a.id, a])) - }) - // for each transfer, add an account if not already existing - for (let t of transferEvents) { - updateAccounts(t.from) - updateAccounts(t.to) - } - // function to add inexisting accounts to the map - function updateAccounts(id: string): void { - const acc = accounts.get(id) - if (acc == null) { - accounts.set(id, new Account({id})) - } - } - // return this account map - return accounts + } + // return this account map + return accounts; } -async function createIdentities(ctx: ProcessorContext<Store>, certEvents: CertEvent[]): Promise<Map<number,Identity>> { - const idtyId = new Set<number>() - for (let c of certEvents) { - idtyId.add(c.issuer) - idtyId.add(c.receiver) - } - const idties = await ctx.store.findBy(Identity, {id: In([...idtyId])}).then((idties) => { - return new Map(idties.map((i) => [i.index, i])) - }) - for (let c of certEvents) { - updateIdties(c.issuer) - updateIdties(c.receiver) - } - function updateIdties(index: number): void { - const acc = idties.get(index) - if (acc == null) { - idties.set(index, new Identity({ - id: String(index), - index, - // account: , - // name: , - })) - } +async function createIdentities(ctx: ProcessorContext<StoreWithCache>, certEvents: CertEvent[]): Promise<Map<number, Identity>> { + const idtyId = new Set<number>(); + for (let c of certEvents) { + idtyId.add(c.issuer); + idtyId.add(c.receiver); + } + const idties = await ctx.store.findBy(Identity, { id: In([...idtyId]) }).then((idties) => { + return new Map(idties.map((i) => [i.index, i])); + }); + for (let c of certEvents) { + updateIdties(c.issuer); + updateIdties(c.receiver); + } + function updateIdties(index: number): void { + const acc = idties.get(index); + if (acc == null) { + idties.set( + index, + new Identity({ + id: String(index), + index, + // account: , + // name: , + }) + ); } - return idties + } + return idties; } function createTransfers(transferEvents: TransferEvent[], accounts: Map<string, Account>): Transfer[] { - let transfers: Transfer[] = [] - for (let t of transferEvents) { - let {id, blockNumber, timestamp, amount} = t - let from = accounts.get(t.from) - let to = accounts.get(t.to) - transfers.push(new Transfer({ - id, - blockNumber, - timestamp, - from, - to, - amount, - })) - } - return transfers + let transfers: Transfer[] = []; + for (let t of transferEvents) { + let { id, blockNumber, timestamp, amount } = t; + let from = accounts.get(t.from); + let to = accounts.get(t.to); + transfers.push( + new Transfer({ + id, + blockNumber, + timestamp, + from, + to, + amount, + }) + ); + } + return transfers; } function createCerts(certEvents: CertEvent[], idties: Map<number, Identity>): Cert[] { - let certs: Cert[] = [] - for (let c of certEvents) { - let {id, blockNumber, issuer, receiver} = c - let issuerIdty = idties.get(issuer) - let receiverIdty = idties.get(receiver) - certs.push(new Cert({ - id, - blockNumber, - issuer: issuerIdty, - receiver: receiverIdty, - })) - } - return certs + let certs: Cert[] = []; + for (let c of certEvents) { + let { id, blockNumber, issuer, receiver } = c; + let issuerIdty = idties.get(issuer); + let receiverIdty = idties.get(receiver); + certs.push( + new Cert({ + id, + issuer: issuerIdty, + receiver: receiverIdty, + }) + ); + } + return certs; } - -function createCalls(theCalls: CallRecord[]): Call[] { - let calls : Call[] = [] - for (let c of theCalls) { - let {id, blockNumber, name} = c - calls.push(new Call({id, blockNumber, name})) - } - return calls -} \ No newline at end of file diff --git a/src/model/generated/_counterLevel.ts b/src/model/generated/_counterLevel.ts new file mode 100644 index 0000000..fd3c55e --- /dev/null +++ b/src/model/generated/_counterLevel.ts @@ -0,0 +1,5 @@ +export enum CounterLevel { + Global = "Global", + Pallet = "Pallet", + Item = "Item", +} diff --git a/src/model/generated/_extrinsicSignature.ts b/src/model/generated/_extrinsicSignature.ts new file mode 100644 index 0000000..a0c4a71 --- /dev/null +++ b/src/model/generated/_extrinsicSignature.ts @@ -0,0 +1,49 @@ +import assert from "assert" +import * as marshal from "./marshal" + +export class ExtrinsicSignature { + private _address!: unknown | undefined | null + private _signature!: unknown | undefined | null + private _signedExtensions!: unknown | undefined | null + + constructor(props?: Partial<Omit<ExtrinsicSignature, 'toJSON'>>, json?: any) { + Object.assign(this, props) + if (json != null) { + this._address = json.address + this._signature = json.signature + this._signedExtensions = json.signedExtensions + } + } + + get address(): unknown | undefined | null { + return this._address + } + + set address(value: unknown | undefined | null) { + this._address = value + } + + get signature(): unknown | undefined | null { + return this._signature + } + + set signature(value: unknown | undefined | null) { + this._signature = value + } + + get signedExtensions(): unknown | undefined | null { + return this._signedExtensions + } + + set signedExtensions(value: unknown | undefined | null) { + this._signedExtensions = value + } + + toJSON(): object { + return { + address: this.address, + signature: this.signature, + signedExtensions: this.signedExtensions, + } + } +} diff --git a/src/model/generated/_itemType.ts b/src/model/generated/_itemType.ts new file mode 100644 index 0000000..4888f07 --- /dev/null +++ b/src/model/generated/_itemType.ts @@ -0,0 +1,5 @@ +export enum ItemType { + Extrinsics = "Extrinsics", + Calls = "Calls", + Events = "Events", +} diff --git a/src/model/generated/block.model.ts b/src/model/generated/block.model.ts index 0f65c6a..46c2a49 100644 --- a/src/model/generated/block.model.ts +++ b/src/model/generated/block.model.ts @@ -1,4 +1,7 @@ -import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_} from "typeorm" +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_, OneToMany as OneToMany_} from "typeorm" +import {Extrinsic} from "./extrinsic.model" +import {Call} from "./call.model" +import {Event} from "./event.model" @Entity_() export class Block { @@ -6,14 +9,65 @@ export class Block { Object.assign(this, props) } + /** + * BlockHeight-blockHash - e.g. 0001812319-0001c + */ @PrimaryColumn_() id!: string - @Index_({unique: true}) + @Index_() + @Column_("int4", {nullable: false}) + height!: number + + @Index_() + @Column_("bytea", {nullable: false}) + hash!: Uint8Array + + @Column_("bytea", {nullable: false}) + parentHash!: Uint8Array + + @Column_("bytea", {nullable: false}) + stateRoot!: Uint8Array + + @Column_("bytea", {nullable: false}) + extrinsicsicRoot!: Uint8Array + + @Column_("text", {nullable: false}) + specName!: string + + @Index_() + @Column_("int4", {nullable: false}) + specVersion!: number + + @Column_("text", {nullable: false}) + implName!: string + @Column_("int4", {nullable: false}) - number!: number + implVersion!: number @Index_() @Column_("timestamp with time zone", {nullable: false}) timestamp!: Date + + @Index_() + @Column_("bytea", {nullable: true}) + validator!: Uint8Array | undefined | null + + @Column_("int4", {nullable: false}) + extrinsicsCount!: number + + @Column_("int4", {nullable: false}) + callsCount!: number + + @Column_("int4", {nullable: false}) + eventsCount!: number + + @OneToMany_(() => Extrinsic, e => e.block) + extrinsics!: Extrinsic[] + + @OneToMany_(() => Call, e => e.block) + calls!: Call[] + + @OneToMany_(() => Event, e => e.block) + events!: Event[] } diff --git a/src/model/generated/call.model.ts b/src/model/generated/call.model.ts index 09553a8..933cfef 100644 --- a/src/model/generated/call.model.ts +++ b/src/model/generated/call.model.ts @@ -1,5 +1,9 @@ -import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_} from "typeorm" +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_, ManyToOne as ManyToOne_, OneToMany as OneToMany_} from "typeorm" +import {Block} from "./block.model" +import {Extrinsic} from "./extrinsic.model" +import {Event} from "./event.model" +@Index_(["id", "pallet", "name"], {unique: false}) @Entity_() export class Call { constructor(props?: Partial<Call>) { @@ -9,11 +13,45 @@ export class Call { @PrimaryColumn_() id!: string + @Index_() + @ManyToOne_(() => Block, {nullable: true}) + block!: Block + + @Index_() + @ManyToOne_(() => Extrinsic, {nullable: true}) + extrinsic!: Extrinsic | undefined | null + + @Index_() + @ManyToOne_(() => Call, {nullable: true}) + parent!: Call | undefined | null + + @Column_("int4", {array: true, nullable: false}) + address!: (number)[] + + @Index_() + @Column_("bool", {nullable: false}) + success!: boolean + + @Column_("jsonb", {nullable: true}) + error!: unknown | undefined | null + @Index_() @Column_("text", {nullable: false}) - name!: string + pallet!: string @Index_() - @Column_("int4", {nullable: false}) - blockNumber!: number + @Column_("text", {nullable: false}) + name!: string + + @Column_("jsonb", {nullable: true}) + args!: unknown | undefined | null + + @Column_("text", {array: true, nullable: true}) + argsStr!: (string | undefined | null)[] | undefined | null + + @OneToMany_(() => Call, e => e.parent) + subcalls!: Call[] + + @OneToMany_(() => Event, e => e.call) + events!: Event[] } diff --git a/src/model/generated/event.model.ts b/src/model/generated/event.model.ts new file mode 100644 index 0000000..43ee5e1 --- /dev/null +++ b/src/model/generated/event.model.ts @@ -0,0 +1,50 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_, ManyToOne as ManyToOne_} from "typeorm" +import {Block} from "./block.model" +import {Extrinsic} from "./extrinsic.model" +import {Call} from "./call.model" + +@Index_(["id", "pallet", "name"], {unique: false}) +@Entity_() +export class Event { + constructor(props?: Partial<Event>) { + Object.assign(this, props) + } + + /** + * Event id - e.g. 0000000001-000000-272d6 + */ + @PrimaryColumn_() + id!: string + + @Index_() + @ManyToOne_(() => Block, {nullable: true}) + block!: Block + + @Index_() + @ManyToOne_(() => Extrinsic, {nullable: true}) + extrinsic!: Extrinsic | undefined | null + + @Index_() + @ManyToOne_(() => Call, {nullable: true}) + call!: Call | undefined | null + + @Column_("int4", {nullable: false}) + index!: number + + @Column_("text", {nullable: false}) + phase!: string + + @Index_() + @Column_("text", {nullable: false}) + pallet!: string + + @Index_() + @Column_("text", {nullable: false}) + name!: string + + @Column_("jsonb", {nullable: true}) + args!: unknown | undefined | null + + @Column_("text", {array: true, nullable: true}) + argsStr!: (string | undefined | null)[] | undefined | null +} diff --git a/src/model/generated/extrinsic.model.ts b/src/model/generated/extrinsic.model.ts new file mode 100644 index 0000000..4eecefd --- /dev/null +++ b/src/model/generated/extrinsic.model.ts @@ -0,0 +1,56 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_, OneToMany as OneToMany_} from "typeorm" +import * as marshal from "./marshal" +import {Block} from "./block.model" +import {Call} from "./call.model" +import {ExtrinsicSignature} from "./_extrinsicSignature" +import {Event} from "./event.model" + +@Entity_() +export class Extrinsic { + constructor(props?: Partial<Extrinsic>) { + Object.assign(this, props) + } + + @PrimaryColumn_() + id!: string + + @Index_() + @ManyToOne_(() => Block, {nullable: true}) + block!: Block + + @Index_() + @ManyToOne_(() => Call, {nullable: true}) + call!: Call + + @Column_("int4", {nullable: false}) + index!: number + + @Column_("int4", {nullable: false}) + version!: number + + @Column_("jsonb", {transformer: {to: obj => obj == null ? undefined : obj.toJSON(), from: obj => obj == null ? undefined : new ExtrinsicSignature(undefined, obj)}, nullable: true}) + signature!: ExtrinsicSignature | undefined | null + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: true}) + tip!: bigint | undefined | null + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: true}) + fee!: bigint | undefined | null + + @Index_() + @Column_("bool", {nullable: true}) + success!: boolean | undefined | null + + @Column_("jsonb", {nullable: true}) + error!: unknown | undefined | null + + @Index_() + @Column_("bytea", {nullable: false}) + hash!: Uint8Array + + @OneToMany_(() => Call, e => e.extrinsic) + calls!: Call[] + + @OneToMany_(() => Event, e => e.extrinsic) + events!: Event[] +} diff --git a/src/model/generated/index.ts b/src/model/generated/index.ts index 8703db3..318f67b 100644 --- a/src/model/generated/index.ts +++ b/src/model/generated/index.ts @@ -1,3 +1,11 @@ +export * from "./block.model" +export * from "./extrinsic.model" +export * from "./_extrinsicSignature" +export * from "./call.model" +export * from "./event.model" +export * from "./itemsCounter.model" +export * from "./_itemType" +export * from "./_counterLevel" export * from "./account.model" export * from "./transfer.model" export * from "./identity.model" @@ -5,5 +13,3 @@ export * from "./cert.model" export * from "./smithCert.model" export * from "./membership.model" export * from "./smithMembership.model" -export * from "./call.model" -export * from "./block.model" diff --git a/src/model/generated/itemsCounter.model.ts b/src/model/generated/itemsCounter.model.ts new file mode 100644 index 0000000..7196bbd --- /dev/null +++ b/src/model/generated/itemsCounter.model.ts @@ -0,0 +1,25 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_} from "typeorm" +import {ItemType} from "./_itemType" +import {CounterLevel} from "./_counterLevel" + +@Entity_() +export class ItemsCounter { + constructor(props?: Partial<ItemsCounter>) { + Object.assign(this, props) + } + + @PrimaryColumn_() + id!: string + + @Index_() + @Column_("varchar", {length: 10, nullable: false}) + type!: ItemType + + @Index_() + @Column_("varchar", {length: 6, nullable: false}) + level!: CounterLevel + + @Index_() + @Column_("int4", {nullable: false}) + total!: number +} diff --git a/src/processor.ts b/src/processor.ts index bf9b4db..46e571a 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -29,16 +29,27 @@ export const processor = new SubstrateBatchProcessor() .addEvent({ // name: [], name: [events.balances.transfer.name, events.cert.newCert.name, events.cert.renewedCert.name, events.cert.removedCert.name], + // extrinsic: true, + // https://github.com/subsquid-labs/giant-squid-explorer/blob/main/src/processor.ts + call: true, extrinsic: true, }) .addCall({ // name: [] - events: true, + // events: true, + // https://github.com/subsquid-labs/giant-squid-explorer/blob/main/src/processor.ts + extrinsic: true, + stack: true, }) .setFields({ - event: { - name: true, - args: true, + // this part of the processor config comes from giant squid + // https://github.com/subsquid-labs/giant-squid-explorer/blob/main/src/processor.ts + block: { + timestamp: true, + digest: true, + extrinsicsRoot: true, + stateRoot: true, + validator: true, }, call: { name: true, @@ -47,14 +58,20 @@ export const processor = new SubstrateBatchProcessor() success: true, error: true, }, + event: { + name: true, + args: true, + phase: true, + }, extrinsic: { + hash: true, success: true, error: true, + fee: true, + signature: true, + tip: true, + version: true, }, - block: { - height: true, - timestamp: true, - } }) // Uncomment to disable RPC ingestion and drastically reduce no of RPC calls //.useArchiveOnly() -- GitLab