From 77bd244a07dfa1340f1ac7a349d5841ca2918fca Mon Sep 17 00:00:00 2001 From: Bryan White Date: Fri, 1 Jul 2022 17:18:46 +0200 Subject: [PATCH 001/143] feat: add graphql entities to schema --- schema.graphql | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/schema.graphql b/schema.graphql index d78d1d400..d42d178ca 100644 --- a/schema.graphql +++ b/schema.graphql @@ -14,6 +14,12 @@ type Transaction @entity { id: ID! blockHeight: BigInt! timestamp: String! + status: String + gasUsed: BigInt + gasWanted: BigInt + messages: [Message] +# fee: BigInt +# memo: String } type Message @entity { @@ -22,4 +28,36 @@ type Message @entity { txHash: String! sender: String! contract: String! -} \ No newline at end of file +} + +enum GovProposalVoteOption { + EMPTY, + YES, + ABSTAIN, + NO, + NO_WITH_VETO, +} + +type GovProposalVote @entity { + id: ID! + proposalId: String! + voterAddress: String! + option: GovProposalVoteOption! +# TODO: +# weightedOptions: [] +} + +type DistDelegatorClaim @entity { + id: ID! + delegatorAddress: String! + validatorAddress: String! + # validator: Validator! + # TODO: introduced in cosmos-sdk (baseline) v0.46 + # amount: BigInt! +} + +type LegacyBridgeSwap @entity { + id: ID! # id field is always required and must look like this + destination: String! + amount: BigInt! +} From 49c1647ab6f05ec496357e80df0c237c8821964a Mon Sep 17 00:00:00 2001 From: Bryan White Date: Fri, 1 Jul 2022 17:17:21 +0200 Subject: [PATCH 002/143] chore: update project.yaml for fetchhub-4 --- project.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/project.yaml b/project.yaml index b2458f8d3..5d0a22f2d 100644 --- a/project.yaml +++ b/project.yaml @@ -9,21 +9,21 @@ runner: name: "@subql/query" version: "*" description: >- - This project can be use as a starting point for developing your Cosmos (Juno) based SubQuery project -repository: https://github.com/subquery/juno-subql-starter + Fetch ledger SubQuery project +repository: https://github.com/fetchai/ledger-subquery schema: file: ./schema.graphql network: - chainId: juno-1 + chainId: fetchhub-4 # You must connect to an archive (non-pruned) node - endpoint: https://rpc.juno-1.api.onfinality.io + endpoint: https://rpc-fetchhub.fetch.ai # Using a dictionary can massively improve indexing speed - dictionary: https://api.subquery.network/sq/subquery/cosmos-juno-dictionary +# dictionary: https://api.subquery.network/sq/subquery/cosmos-juno-dictionary dataSources: - kind: cosmos/Runtime - startBlock: 3062001 # first block on juno-1 + startBlock: 5300201 # first block on fetchhub-4 #chainTypes: # This is a beta feature that allows support for any Cosmos chain by importing the correct protobuf messages # cosmos.slashing.v1beta1: # file: "./proto/cosmos/slashing/v1beta1/tx.proto" From a70dc8c116217f9fb13941f0854c30811ecfa9bf Mon Sep 17 00:00:00 2001 From: Bryan White Date: Fri, 1 Jul 2022 17:20:53 +0200 Subject: [PATCH 003/143] chore: update project.yaml mappings --- project.yaml | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/project.yaml b/project.yaml index 5d0a22f2d..830fe284f 100644 --- a/project.yaml +++ b/project.yaml @@ -36,20 +36,19 @@ dataSources: kind: cosmos/BlockHandler - handler: handleTransaction kind: cosmos/TransactionHandler - - handler: handleEvent - kind: cosmos/EventHandler + - handler: handleGovProposalVote + kind: cosmos/MessageHandler + filter: + type: "/cosmos.gov.v1beta1.MsgVote" + - handler: handleDistDelegatorClaim + kind: cosmos/MessageHandler filter: - type: execute - messageFilter: - type: "/cosmwasm.wasm.v1.MsgExecuteContract" - # contractCall field can be specified here too - #values: # A set of key/value pairs that are present in the message data - #contract: "juno1v99ehkuetkpf0yxdry8ce92yeqaeaa7lyxr2aagkesrw67wcsn8qxpxay0" - - handler: handleMessage + type: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" + - handler: handleLegacyBridgeSwap kind: cosmos/MessageHandler filter: type: "/cosmwasm.wasm.v1.MsgExecuteContract" - # Filter to only messages with the provide_liquidity function call - #contractCall: "provide_liquidity" # The name of the contract function that was called - #values: # A set of key/value pairs that are present in the message data - #contract: "juno1v99ehkuetkpf0yxdry8ce92yeqaeaa7lyxr2aagkesrw67wcsn8qxpxay0" + # Filter to only messages with the vote function call + contractCall: "swap" # The name of the contract function that was called + values: # This is the specific smart contract that we are subscribing to + contract: "fetch1qxxlalvsdjd07p07y3rc5fu6ll8k4tmetpha8n" From 8f78f5c576e60e6e54314885734b014286f8ab36 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Fri, 1 Jul 2022 17:19:39 +0200 Subject: [PATCH 004/143] feat: implement mapping handlers --- src/mappings/mappingHandlers.ts | 65 ++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 934c31822..c12bce653 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -1,10 +1,11 @@ -import { ExecuteEvent, Message, Transaction } from "../types"; +import {ExecuteEvent, Message, Transaction, LegacyBridgeSwap, DistDelegatorClaim, GovProposalVote} from "../types"; import { CosmosEvent, CosmosBlock, CosmosMessage, CosmosTransaction, } from "@subql/types-cosmos"; +import { GovProposalVoteOption } from "../types/enums"; export async function handleBlock(block: CosmosBlock): Promise { // If you wanted to index each block in Cosmos (Juno), you could do that here @@ -15,6 +16,11 @@ export async function handleTransaction(tx: CosmosTransaction): Promise { id: tx.hash, blockHeight: BigInt(tx.block.block.header.height), timestamp: tx.block.block.header.time, + gasUsed: BigInt(Math.trunc(tx.tx.gasUsed)), + gasWanted: BigInt(Math.trunc(tx.tx.gasWanted)), + // TODO: + // memo: tx.tx. + // fee: BigInt(Math.trunc(tx.)), }); await transactionRecord.save(); } @@ -40,3 +46,60 @@ export async function handleEvent(event: CosmosEvent): Promise { await eventRecord.save(); } + +interface GovProposalVoteMsg { + msg: { + proposalId: string; + voter: string; + option: GovProposalVoteOption; + } +} + +interface DistDelegatorClaimMsg { + msg: { + delegatorAddress: string; + validatorAddress: string; + } +} + +interface LegacyBridgeSwapMsg { + msg: { + swap: { + destination: string, + amount: bigint, + } + } +} + +export async function handleGovProposalVote(message: CosmosMessage): Promise { + const vote = new GovProposalVote(`${message.tx.hash}-${message.idx}`); + const {proposalId, voter, option} = message.msg.decodedMsg.msg; + + vote.proposalId = proposalId; + vote.voterAddress = voter; + vote.option = option; + + await vote.save(); +} + +export async function handleDistDelegatorClaim(message: CosmosMessage): Promise { + const claim = new DistDelegatorClaim(`${message.tx.hash}-${message.idx}`); + const {delegatorAddress, validatorAddress} = message.msg.decodedMsg.msg; + + claim.delegatorAddress = delegatorAddress; + claim.validatorAddress = validatorAddress; + + // TODO: + // claim.amount = + + await claim.save(); +} + +export async function handleLegacyBridgeSwap(message: CosmosMessage): Promise { + const swap = new LegacyBridgeSwap(`${message.tx.hash}-${message.idx}`); + + swap.destination = message.msg.decodedMsg.msg.swap.destination; + swap.amount = BigInt(0); //message.msg.decodedMsg.msg.amount; + + await swap.save(); +} From f355a2035376b4dc7b231b6ca014253be828a8cd Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 4 Jul 2022 10:27:02 +0200 Subject: [PATCH 005/143] fix: foreign key constraint error --- schema.graphql | 1 - src/mappings/mappingHandlers.ts | 19 ++++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/schema.graphql b/schema.graphql index d42d178ca..e0f530df3 100644 --- a/schema.graphql +++ b/schema.graphql @@ -17,7 +17,6 @@ type Transaction @entity { status: String gasUsed: BigInt gasWanted: BigInt - messages: [Message] # fee: BigInt # memo: String } diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index c12bce653..1bc35a7a0 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -48,23 +48,19 @@ export async function handleEvent(event: CosmosEvent): Promise { } interface GovProposalVoteMsg { - msg: { proposalId: string; voter: string; option: GovProposalVoteOption; - } } interface DistDelegatorClaimMsg { - msg: { - delegatorAddress: string; - validatorAddress: string; - } + delegatorAddress: string; + validatorAddress: string; } interface LegacyBridgeSwapMsg { msg: { - swap: { + legacyBridgeSwap: { destination: string, amount: bigint, } @@ -73,7 +69,7 @@ interface LegacyBridgeSwapMsg { export async function handleGovProposalVote(message: CosmosMessage): Promise { const vote = new GovProposalVote(`${message.tx.hash}-${message.idx}`); - const {proposalId, voter, option} = message.msg.decodedMsg.msg; + const {proposalId, voter, option} = message.msg.decodedMsg.msg.govProposalVote; vote.proposalId = proposalId; vote.voterAddress = voter; @@ -84,7 +80,7 @@ export async function handleGovProposalVote(message: CosmosMessage): Promise { const claim = new DistDelegatorClaim(`${message.tx.hash}-${message.idx}`); - const {delegatorAddress, validatorAddress} = message.msg.decodedMsg.msg; + const {delegatorAddress, validatorAddress} = message.msg.decodedMsg; claim.delegatorAddress = delegatorAddress; claim.validatorAddress = validatorAddress; @@ -97,9 +93,10 @@ export async function handleDistDelegatorClaim(message: CosmosMessage): Promise { const swap = new LegacyBridgeSwap(`${message.tx.hash}-${message.idx}`); + const {destination, amount} = message.msg.decodedMsg.msg.legacyBridgeSwap; - swap.destination = message.msg.decodedMsg.msg.swap.destination; - swap.amount = BigInt(0); //message.msg.decodedMsg.msg.amount; + swap.destination = destination; + swap.amount = BigInt(amount); await swap.save(); } From 7289377411ee8b24fe7287842b3247483289200b Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 4 Jul 2022 10:29:28 +0200 Subject: [PATCH 006/143] fix: typo --- src/mappings/mappingHandlers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 1bc35a7a0..12212e3c5 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -69,7 +69,7 @@ interface LegacyBridgeSwapMsg { export async function handleGovProposalVote(message: CosmosMessage): Promise { const vote = new GovProposalVote(`${message.tx.hash}-${message.idx}`); - const {proposalId, voter, option} = message.msg.decodedMsg.msg.govProposalVote; + const {proposalId, voter, option} = message.msg.decodedMsg; vote.proposalId = proposalId; vote.voterAddress = voter; From c2d57fe0ef48a8bb97ee7c6fd721476aee7b12be Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 11 Jul 2022 14:52:43 +0200 Subject: [PATCH 007/143] sync: upstream main - bump protobufjs version (#5) --- yarn.lock | 83 +++++++++++++++++++++---------------------------------- 1 file changed, 32 insertions(+), 51 deletions(-) diff --git a/yarn.lock b/yarn.lock index b46227535..f73b1a5a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -775,56 +775,56 @@ "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== "@protobufjs/base64@^1.1.2": version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== "@protobufjs/codegen@^2.0.4": version "2.0.4" - resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== "@protobufjs/eventemitter@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== "@protobufjs/fetch@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== dependencies: "@protobufjs/aspromise" "^1.1.1" "@protobufjs/inquire" "^1.1.0" "@protobufjs/float@^1.0.2": version "1.0.2" - resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== "@protobufjs/inquire@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== "@protobufjs/path@^1.1.2": version "1.1.2" - resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== "@protobufjs/pool@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== "@protobufjs/utf8@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== "@subql/cli@^1.1.0": version "1.1.0" @@ -1113,9 +1113,9 @@ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== "@types/minimatch@*", "@types/minimatch@^3.0.3", "@types/minimatch@^3.0.4": version "3.0.5" @@ -1127,21 +1127,21 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== -"@types/node@*": - version "17.0.41" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b" - integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw== - -"@types/node@>=13.7.0", "@types/node@^17.0.21": - version "17.0.23" - resolved "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz" - integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== +"@types/node@*", "@types/node@>=13.7.0": + version "18.0.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.1.tgz#e91bd73239b338557a84d1f67f7b9e0f25643870" + integrity sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg== "@types/node@^15.6.1": version "15.14.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/node@^17.0.21": + version "17.0.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b" + integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -3655,7 +3655,7 @@ log-symbols@^4.0.0, log-symbols@^4.1.0: long@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== lru-cache@^6.0.0: @@ -4584,7 +4584,7 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -protobufjs@^6.10.2, protobufjs@~6.11.3: +protobufjs@^6.10.2, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: version "6.11.3" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== @@ -4603,25 +4603,6 @@ protobufjs@^6.10.2, protobufjs@~6.11.3: "@types/node" ">=13.7.0" long "^4.0.0" -protobufjs@^6.8.8, protobufjs@~6.11.2: - version "6.11.2" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz" - integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" - "@types/node" ">=13.7.0" - long "^4.0.0" - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" From 6ccd28402008aebf58dde56a9052b33cbc5b584d Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 13 Jul 2022 09:36:18 +0200 Subject: [PATCH 008/143] fix: uncomment chainTypes (#7) --- project.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/project.yaml b/project.yaml index 830fe284f..2ed83953e 100644 --- a/project.yaml +++ b/project.yaml @@ -24,11 +24,11 @@ network: dataSources: - kind: cosmos/Runtime startBlock: 5300201 # first block on fetchhub-4 - #chainTypes: # This is a beta feature that allows support for any Cosmos chain by importing the correct protobuf messages - # cosmos.slashing.v1beta1: - # file: "./proto/cosmos/slashing/v1beta1/tx.proto" - # messages: - # - "MsgUnjail" + chainTypes: # This is a beta feature that allows support for any Cosmos chain by importing the correct protobuf messages + cosmos.slashing.v1beta1: + file: "./proto/cosmos/slashing/v1beta1/tx.proto" + messages: + - "MsgUnjail" mapping: file: "./dist/index.js" handlers: From 6fbb34e20508c310531dd8ac7543280f756e9fe6 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 27 Jul 2022 07:47:58 -0400 Subject: [PATCH 009/143] chore: improve forward compatibility (#3) --- project.yaml | 13 +- schema.graphql | 99 ++++++++++----- src/mappings/mappingHandlers.ts | 205 ++++++++++++++++++++++---------- src/mappings/types/common.ts | 4 + src/mappings/types/index.ts | 1 + src/mappings/types/messages.ts | 29 +++++ 6 files changed, 255 insertions(+), 96 deletions(-) create mode 100644 src/mappings/types/common.ts create mode 100644 src/mappings/types/index.ts create mode 100644 src/mappings/types/messages.ts diff --git a/project.yaml b/project.yaml index 2ed83953e..6fad23a63 100644 --- a/project.yaml +++ b/project.yaml @@ -16,10 +16,9 @@ schema: network: chainId: fetchhub-4 - # You must connect to an archive (non-pruned) node - endpoint: https://rpc-fetchhub.fetch.ai + endpoint: https://rpc-fetchhub.fetch.ai:443 # Using a dictionary can massively improve indexing speed -# dictionary: https://api.subquery.network/sq/subquery/cosmos-juno-dictionary + dictionary: https://api.subquery.network/sq/subquery/cosmos-fetch-ai-dictionary dataSources: - kind: cosmos/Runtime @@ -36,6 +35,10 @@ dataSources: kind: cosmos/BlockHandler - handler: handleTransaction kind: cosmos/TransactionHandler + - handler: handleMessage + kind: cosmos/MessageHandler + - handler: handleEvent + kind: cosmos/EventHandler - handler: handleGovProposalVote kind: cosmos/MessageHandler filter: @@ -44,6 +47,10 @@ dataSources: kind: cosmos/MessageHandler filter: type: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" + - handler: handleExecuteContractMessage + kind: cosmos/MessageHandler + filter: + type: "/cosmwasm.wasm.v1.MsgExecuteContract" - handler: handleLegacyBridgeSwap kind: cosmos/MessageHandler filter: diff --git a/schema.graphql b/schema.graphql index e0f530df3..00c7e410b 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,62 +1,103 @@ -type Block @entity { - id: ID! # The block hash - height: BigInt! +enum GovProposalVoteOption { + EMPTY, + YES, + ABSTAIN, + NO, + NO_WITH_VETO, } -type ExecuteEvent @entity { - id: ID! - blockHeight: BigInt! - txHash: String! - contractAddress: String! +enum TxStatus { + Success + Error +} + +type EventAttribute @jsonField { + key: String! + value: String! +} + +type Block @entity { + id: ID! # The block header hash + chainId: String! @index + height: BigInt! @index + timestamp: String! + transactions: [Transaction] @derivedFrom(field: "block") + messages: [Message] @derivedFrom(field: "block") + events: [Event] @derivedFrom(field: "block") } type Transaction @entity { id: ID! - blockHeight: BigInt! - timestamp: String! - status: String - gasUsed: BigInt - gasWanted: BigInt -# fee: BigInt -# memo: String + block: Block! + gasUsed: BigInt! + gasWanted: BigInt! + fees: String! + memo: String + status: TxStatus! + log: String! + timeoutHeight: BigInt @index + messages: [Message] @derivedFrom(field: "transaction") } type Message @entity { id: ID! - blockHeight: BigInt! - txHash: String! - sender: String! - contract: String! + typeUrl: String! @index + json: String! + transaction: Transaction! + block: Block! } -enum GovProposalVoteOption { - EMPTY, - YES, - ABSTAIN, - NO, - NO_WITH_VETO, +type Event @entity { + id: ID! + type: String! @index + attributes: [EventAttribute]! + log: String! + transaction: Transaction! + block: Block! +} + +type ExecuteContractMessage @entity { + id: ID! + sender: String! @index + contract: String! @index + funds: String! + message: Message! + transaction: Transaction! + block: Block! } type GovProposalVote @entity { id: ID! - proposalId: String! - voterAddress: String! + proposalId: String! @index + voterAddress: String! @index option: GovProposalVoteOption! -# TODO: -# weightedOptions: [] + message: Message! + transaction: Transaction! + block: Block! + # TODO: + # weightedOptions: [] } type DistDelegatorClaim @entity { id: ID! delegatorAddress: String! validatorAddress: String! + message: Message! + transaction: Transaction! + block: Block! + # TODO: # validator: Validator! # TODO: introduced in cosmos-sdk (baseline) v0.46 # amount: BigInt! + # denom: String! } type LegacyBridgeSwap @entity { id: ID! # id field is always required and must look like this destination: String! amount: BigInt! + denom: String! + message: Message! + transaction: Transaction! + block: Block! } diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 12212e3c5..e742eba52 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -1,102 +1,179 @@ -import {ExecuteEvent, Message, Transaction, LegacyBridgeSwap, DistDelegatorClaim, GovProposalVote} from "../types"; import { - CosmosEvent, - CosmosBlock, - CosmosMessage, - CosmosTransaction, -} from "@subql/types-cosmos"; -import { GovProposalVoteOption } from "../types/enums"; + Block, + DistDelegatorClaim, + ExecuteContractMessage, + EventAttribute, + Event, + GovProposalVote, + LegacyBridgeSwap, + Message, + Transaction, + TxStatus +} from "../types"; +import {CosmosBlock, CosmosEvent, CosmosMessage, CosmosTransaction,} from "@subql/types-cosmos"; +import {ExecuteContractMsg, DistDelegatorClaimMsg, GovProposalVoteMsg, LegacyBridgeSwapMsg} from "./types"; + +// messageId returns the id of the message passed or +// that of the message which generated the event passed. +function messageId(msg: CosmosMessage | CosmosEvent): string { + return `${msg.tx.hash}-${msg.idx}`; +} export async function handleBlock(block: CosmosBlock): Promise { - // If you wanted to index each block in Cosmos (Juno), you could do that here + logger.info(`[handleBlock] (block.header.height): indexing block ${block.block.header.height}`) + + const {id, header: {chainId, height, time: timestamp}} = block.block; + const blockEntity = Block.create({ + id, + chainId, + height: BigInt(height), + // TODO: convert to unix timestamp and store as Int. + timestamp, + }); + + await blockEntity.save() } export async function handleTransaction(tx: CosmosTransaction): Promise { - const transactionRecord = Transaction.create({ + logger.info(`[handleTransaction] (block ${tx.block.block.header.height}): indexing transaction ${tx.idx + 1} / ${tx.block.txs.length}`) + logger.debug(`[handleTransaction] (tx.decodedTx): ${JSON.stringify(tx.decodedTx, null, 2)}`) + logger.debug(`[handleTransaction] (tx.tx.log): ${tx.tx.log}`) + + let status = TxStatus.Error; + if (tx.tx.log) { + try { + JSON.parse(tx.tx.log) + status = TxStatus.Success; + } catch { + // NB: assume tx failed + } + } + + const txEntity = Transaction.create({ id: tx.hash, - blockHeight: BigInt(tx.block.block.header.height), - timestamp: tx.block.block.header.time, + blockId: tx.block.block.id, gasUsed: BigInt(Math.trunc(tx.tx.gasUsed)), gasWanted: BigInt(Math.trunc(tx.tx.gasWanted)), - // TODO: - // memo: tx.tx. - // fee: BigInt(Math.trunc(tx.)), + memo: tx.decodedTx.body.memo, + timeoutHeight: BigInt(tx.decodedTx.body.timeoutHeight.toString()), + fees: JSON.stringify(tx.decodedTx.authInfo.fee.amount), + log: tx.tx.log, + status, }); - await transactionRecord.save(); + + await txEntity.save(); } export async function handleMessage(msg: CosmosMessage): Promise { - const messageRecord = Message.create({ - id: `${msg.tx.hash}-${msg.idx}`, - blockHeight: BigInt(msg.block.block.header.height), - txHash: msg.tx.hash, - sender: msg.msg.decodedMsg.sender, - contract: msg.msg.decodedMsg.contract, + logger.info(`[handleMessage] (tx ${msg.tx.hash}): indexing message ${msg.idx + 1} / ${msg.tx.decodedTx.body.messages.length}`) + logger.debug(`[handleMessage] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) + const msgEntity = Message.create({ + id: messageId(msg), + typeUrl: msg.msg.typeUrl, + json: JSON.stringify(msg.msg.decodedMsg), + transactionId: msg.tx.hash, + blockId: msg.block.block.id, }); - await messageRecord.save(); + + await msgEntity.save(); } export async function handleEvent(event: CosmosEvent): Promise { - const eventRecord = ExecuteEvent.create({ - id: `${event.tx.hash}-${event.msg.idx}-${event.idx}`, - blockHeight: BigInt(event.block.block.header.height), - txHash: event.tx.hash, - contractAddress: event.event.attributes.find(attr => attr.key === '_contract_address').value + logger.info(`[handleEvent] (tx ${event.tx.hash}): indexing event ${event.idx + 1} / ${event.tx.tx.events.length}`) + logger.debug(`[handleEvent] (event.event): ${JSON.stringify(event.event, null, 2)}`) + logger.debug(`[handleEvent] (event.log): ${JSON.stringify(event.log, null, 2)}`) + + const eventEntity = Event.create({ + id: `${messageId(event)}-${event.idx}`, + type: event.event.type, + attributes: event.event.attributes as EventAttribute[], + log: event.log.log, + transactionId: event.tx.hash, + blockId: event.block.block.id, }); - await eventRecord.save(); + await eventEntity.save(); } -interface GovProposalVoteMsg { - proposalId: string; - voter: string; - option: GovProposalVoteOption; -} +export async function handleExecuteContractMessage(msg: CosmosMessage): Promise { + logger.info(`[handleExecuteContractMessage] (tx ${msg.tx.hash}): indexing ExecuteContractMessage ${messageId(msg)}`) + logger.debug(`[handleExecuteContractMessage] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) -interface DistDelegatorClaimMsg { - delegatorAddress: string; - validatorAddress: string; -} + const id = messageId(msg); + const msgEntity = ExecuteContractMessage.create({ + id, + sender: msg.msg.decodedMsg.sender, + contract: msg.msg.decodedMsg.contract, + funds: JSON.stringify(msg.msg.decodedMsg.funds), + messageId: id, + transactionId: msg.tx.hash, + blockId: msg.block.block.id, + }); -interface LegacyBridgeSwapMsg { - msg: { - legacyBridgeSwap: { - destination: string, - amount: bigint, - } - } -} + // NB: no need to update msg ids in txs. -export async function handleGovProposalVote(message: CosmosMessage): Promise { - const vote = new GovProposalVote(`${message.tx.hash}-${message.idx}`); - const {proposalId, voter, option} = message.msg.decodedMsg; + await msgEntity.save(); +} - vote.proposalId = proposalId; - vote.voterAddress = voter; - vote.option = option; +export async function handleGovProposalVote(msg: CosmosMessage): Promise { + logger.info(`[handleGovProposalVote] (tx ${msg.tx.hash}): indexing GovProposalVote ${messageId(msg)}`) + logger.debug(`[handleGovProposalVote] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) + + const id = messageId(msg); + const {proposalId, voter, option} = msg.msg.decodedMsg; + const vote = GovProposalVote.create({ + id, + proposalId: proposalId, + voterAddress: voter, + option: option, + messageId: id, + transactionId: msg.tx.hash, + blockId: msg.block.block.id, + }); await vote.save(); } -export async function handleDistDelegatorClaim(message: CosmosMessage): Promise { - const claim = new DistDelegatorClaim(`${message.tx.hash}-${message.idx}`); - const {delegatorAddress, validatorAddress} = message.msg.decodedMsg; - - claim.delegatorAddress = delegatorAddress; - claim.validatorAddress = validatorAddress; +export async function handleDistDelegatorClaim(msg: CosmosMessage): Promise { + logger.info(`[handleDistDelegatorClaim] (tx ${msg.tx.hash}): indexing DistDelegatorClaim ${messageId(msg)}`) + logger.debug(`[handleDistDelegatorClaim] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) + + const id = messageId(msg); + const {delegatorAddress, validatorAddress} = msg.msg.decodedMsg; + const claim = DistDelegatorClaim.create({ + id, + delegatorAddress, + validatorAddress, + messageId: id, + transactionId: msg.tx.hash, + blockId: msg.block.block.id, + }); // TODO: // claim.amount = + // claim.denom = await claim.save(); } -export async function handleLegacyBridgeSwap(message: CosmosMessage): Promise { - const swap = new LegacyBridgeSwap(`${message.tx.hash}-${message.idx}`); - const {destination, amount} = message.msg.decodedMsg.msg.legacyBridgeSwap; - - swap.destination = destination; - swap.amount = BigInt(amount); +export async function handleLegacyBridgeSwap(msg: CosmosMessage): Promise { + logger.info(`[handleLegacyBridgeSwap] (tx ${msg.tx.hash}): indexing LegacyBridgeSwap ${messageId(msg)}`) + logger.debug(`[handleLegacyBridgeSwap] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) + + const id = messageId(msg); + const { + msg: {swap: {destination}}, + funds: [{amount, denom}] + } = msg.msg.decodedMsg; + const legacySwap = LegacyBridgeSwap.create({ + id, + destination, + amount: BigInt(amount), + denom, + messageId: id, + transactionId: msg.tx.hash, + blockId: msg.block.block.id, + }); - await swap.save(); + await legacySwap.save(); } diff --git a/src/mappings/types/common.ts b/src/mappings/types/common.ts new file mode 100644 index 000000000..85a0c1d72 --- /dev/null +++ b/src/mappings/types/common.ts @@ -0,0 +1,4 @@ +export interface Coin { + amount: string, + denom: string, +} diff --git a/src/mappings/types/index.ts b/src/mappings/types/index.ts new file mode 100644 index 000000000..c2e7ea9a1 --- /dev/null +++ b/src/mappings/types/index.ts @@ -0,0 +1 @@ +export * from "./messages"; diff --git a/src/mappings/types/messages.ts b/src/mappings/types/messages.ts new file mode 100644 index 000000000..59d0dbcfc --- /dev/null +++ b/src/mappings/types/messages.ts @@ -0,0 +1,29 @@ +import {GovProposalVoteOption} from "../../types"; +import {Coin} from "./common"; + +export interface ExecuteContractMsg { + sender: string; + contract: string; + funds?: Coin[]; +} + +export interface GovProposalVoteMsg { + proposalId: string; + voter: string; + option: GovProposalVoteOption; +} + +export interface DistDelegatorClaimMsg { + delegatorAddress: string; + validatorAddress: string; +} + +export interface LegacyBridgeSwapMsg extends ExecuteContractMsg{ + msg: { + swap: { + destination: string, + amount: bigint, + }, + }, +} + From b35ae2b6b4d1f13c99a49b7fcd35852bf7142737 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 27 Jul 2022 09:46:37 -0400 Subject: [PATCH 010/143] tests: subquery project e2e (#1) Co-authored-by: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Co-authored-by: Joe --- Pipfile | 14 + Pipfile.lock | 591 ++++++++++++++++++++++++++++++++ scripts/subquery-e2e.py | 123 +++++++ src/mappings/mappingHandlers.ts | 3 +- src/mappings/types/messages.ts | 2 +- 5 files changed, 731 insertions(+), 2 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 scripts/subquery-e2e.py diff --git a/Pipfile b/Pipfile new file mode 100644 index 000000000..ca39b6e2f --- /dev/null +++ b/Pipfile @@ -0,0 +1,14 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +cosmpy = "*" +protobuf = "==3.20.*" +bip-utils = "*" + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 000000000..10723d0bb --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,591 @@ +{ + "_meta": { + "hash": { + "sha256": "f0ac1fbb884f14766b1b3706e80998cbbfd712c14b920a1fd94506db1524f946" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "asn1crypto": { + "hashes": [ + "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", + "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67" + ], + "version": "==1.5.1" + }, + "bech32": { + "hashes": [ + "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899", + "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981" + ], + "markers": "python_version >= '3.5'", + "version": "==1.2.0" + }, + "bip-utils": { + "hashes": [ + "sha256:eeb70f0401e08d381ad1f4cc2fc54caf4fafa4b50614797f3de1a5cdf158e1f8" + ], + "index": "pypi", + "version": "==2.5.1" + }, + "blspy": { + "hashes": [ + "sha256:03c07511ce6e784878fb81cb4320a9a2cab39977e65a673da4dc153ed69d56e5", + "sha256:13f7f65f4be6ed515aa23a95a3e55382fb11fdbbd0e5fdf7a92a5915a4512fa4", + "sha256:184983bb8ca4db2653b8f2acb2ddedc7a40fd48b66ee2c4e3dd37cbfe113a8bd", + "sha256:299f5537cbc752629e58004419244a6855aa8ff40b14228ebdad43fe99977494", + "sha256:3017cf87e209bd1852d274432c7a912ff430cdfa648ba8158a92d071a242ea2c", + "sha256:36633f512928d20354177a39b432cc06590b857ca54d1b540f7efbf8aae627a8", + "sha256:3f4ce55c388f2ef49c2bac894360d5f258a7c3666d276457b5a2a65078cc73d7", + "sha256:4a6294ae5cf4bef8f2cd9a0a27f781ce582dacbdcf302704237f1648c9c356c8", + "sha256:50654052fa7162207e035f5d375009fcc1727c9650a563e7ecea2e7745cc87be", + "sha256:571f9db9c435f719d50d2f984bcce0cabc951c742dc8dd831a7e77056a425d1c", + "sha256:613e4a64a38cfe51f172c4b94c4cc986cccdd46ca896d77b70d428bbd5dc5c26", + "sha256:6e61f1b258710625b3dde363843fe828f7ff8d93c3242df8a04a50e040b2370f", + "sha256:82c1aa54cabfa1a94d4779e2cafbdb6cdd6f53f6c76de07f620eba93ad951dca", + "sha256:b7b5c1f275676bbfa46dd50a0697912601ab6c47b46bbae645791351ffe08fe1", + "sha256:c1a18b3ffdf36d6b522f372c840596b232432f485c38669dfd542eb8e71f0055", + "sha256:cbb81a893a8d56e739cf6698092ee222e8bf4fe6939b37897826a8685c117dce", + "sha256:dd1e4cb1f280d008d00a0de7c4102e64e6ecf52053a9b18fb41b67c6eafce5f6", + "sha256:fccbb5076dc8215b8f11333a65f17b4494d07816695b2502a2158337449b7b5e", + "sha256:fe5d27f6b10b34e5f2706a9b0c85e8d217bac96f648189eb6bf05fb2282809b2" + ], + "markers": "python_version >= '3.7'", + "version": "==1.0.14" + }, + "cachetools": { + "hashes": [ + "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757", + "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db" + ], + "markers": "python_version ~= '3.7'", + "version": "==5.2.0" + }, + "certifi": { + "hashes": [ + "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", + "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + ], + "markers": "python_version >= '3.6'", + "version": "==2022.6.15" + }, + "cffi": { + "hashes": [ + "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", + "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", + "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", + "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", + "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", + "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", + "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", + "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", + "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", + "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", + "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", + "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", + "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", + "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", + "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", + "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", + "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", + "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", + "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", + "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", + "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", + "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", + "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", + "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", + "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", + "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", + "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", + "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", + "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", + "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", + "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", + "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", + "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", + "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", + "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", + "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", + "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", + "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", + "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", + "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", + "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", + "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", + "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", + "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", + "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", + "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", + "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", + "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", + "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", + "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", + "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", + "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", + "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", + "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", + "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", + "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", + "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", + "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", + "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", + "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", + "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", + "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", + "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" + ], + "version": "==1.15.1" + }, + "charset-normalizer": { + "hashes": [ + "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", + "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + ], + "markers": "python_version >= '3.6'", + "version": "==2.1.0" + }, + "click": { + "hashes": [ + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.3" + }, + "coincurve": { + "hashes": [ + "sha256:0503326963916c85b61d16f611ea0545f03c9e418fa8007c233c815429e381e8", + "sha256:1013c1597b65684ae1c3e42497f9ef5a04527fa6136a84a16b34602606428c74", + "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924", + "sha256:30dd44d1039f1d237aaa2da6d14a455ca88df3bcb00610b41f3253fdca1be97b", + "sha256:3d694ad194bee9e8792e2e75879dc5238d8a184010cde36c5ad518fcfe2cd8f2", + "sha256:4ea057f777842396d387103c606babeb3a1b4c6126769cc0a12044312fc6c465", + "sha256:51e56373ac79f4ec1cfc5da53d72c55f5e5ac28d848b0849ef5e687ace857888", + "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e", + "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93", + "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb", + "sha256:6aec70238dbe7a5d66b5f9438ff45b08eb5e0990d49c32ebb65247c5d5b89d7a", + "sha256:73f39579dd651a9fc29da5a8fc0d8153d872bcbc166f876457baced1a1c01501", + "sha256:747215254e51dd4dfbe6dded9235491263da5d88fe372d66541ca16b51ea078f", + "sha256:74cedb3d3a1dc5abe0c9c2396e1b82cc64496babc5b42e007e72e185cb1edad8", + "sha256:8852dc01af4f0fe941ffd04069f7e4fecdce9b867a016f823a02286a1a1f07b5", + "sha256:896b01941254f0a218cf331a9bddfe2d43892f7f1ba10d6e372e2eb744a744c2", + "sha256:8c571445b166c714af4f8155e38a894376c16c0431e88963f2fff474a9985d87", + "sha256:a80a207131813b038351c5bdae8f20f5f774bbf53622081f208d040dd2b7528f", + "sha256:abbd9d017a7638dc38a3b9bb4851f8801b7818d4e5ac22e0c75e373b3c1dbff0", + "sha256:abbefc9ccb170cb255a31df32457c2e43084b9f37589d0694dacc2dea6ddaf7c", + "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4", + "sha256:ad2f6df39ba1e2b7b14bb984505ffa7d0a0ecdd697e8d7dbd19e04bc245c87ed", + "sha256:b1bef812da1da202cdd601a256825abcf26d86e8634fac3ec3e615e3bb3ff08c", + "sha256:b88642edf7f281649b0c0b6ffade051945ccceae4b885e40445634877d0b3049", + "sha256:b956b0b2c85e25a7d00099970ff5d8338254b45e46f0a940f4a2379438ce0dde", + "sha256:c71caffb97dd3d0c243beb62352669b1e5dafa3a4bccdbb27d36bd82f5e65d20", + "sha256:d154e2eb5711db8c5ef52fcd80935b5a0e751c057bc6ffb215a7bb409aedef03", + "sha256:d24284d17162569df917a640f19d9654ba3b43cf560ced8864f270da903f73a5", + "sha256:db874c5c1dcb1f3a19379773b5e8cffc777625a7a7a60dd9a67206e31e62e2e9", + "sha256:dfd4fab857bcd975edc39111cb5f5c104f138dac2e9ace35ea8434d37bcea3be", + "sha256:e2c2e8a1f0b1f8e48049c891af4ae3cad65d115d358bde72f6b8abdbb8a23170", + "sha256:e4beef321fd6434448aab03a0c245f31c4e77f43b54b82108c0948d29852ac7e", + "sha256:f1ef72574aa423bc33665ef4be859164a478bad24d48442da874ef3dc39a474d", + "sha256:f47806527d3184da3e8b146fac92a8ed567bbd225194f4517943d8cdc85f9542" + ], + "markers": "python_version >= '3.7'", + "version": "==17.0.0" + }, + "cosmpy": { + "hashes": [ + "sha256:15c65063a5c10b7ff9ffd83aa35d8281dbf1b51b7da8b9b7a0e131fe517d5fa9", + "sha256:cd60a1637ab0cab20c62ac3e17537e337d3f76fd89b11ef1e3e5007f0ae51f81" + ], + "index": "pypi", + "version": "==0.5.0" + }, + "crcmod": { + "hashes": [ + "sha256:50586ab48981f11e5b117523d97bb70864a2a1af246cf6e4f5c4a21ef4611cd1", + "sha256:69a2e5c6c36d0f096a7beb4cd34e5f882ec5fd232efb710cdb85d4ff196bd52e", + "sha256:737fb308fa2ce9aed2e29075f0d5980d4a89bfbec48a368c607c5c63b3efb90e", + "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e" + ], + "version": "==1.7" + }, + "ecdsa": { + "hashes": [ + "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676", + "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.17.0" + }, + "ed25519-blake2b": { + "hashes": [ + "sha256:d1a1cb9032ec307ce95b41c619440fd4d3fcecc18f224035cc7d6dc7a7d8ef40" + ], + "version": "==1.4" + }, + "google-api-core": { + "hashes": [ + "sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc", + "sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50" + ], + "markers": "python_version >= '3.6'", + "version": "==2.8.2" + }, + "google-api-python-client": { + "hashes": [ + "sha256:0ae3fa7cad2102e24a587986cee53afdbc95bd69b58f2c08f8fd881a20da5f14", + "sha256:ac8c020d4db00914df4f8981236c109959f76c699b0ded428055cf9503c737cb" + ], + "markers": "python_version >= '3.6'", + "version": "==2.52.0" + }, + "google-auth": { + "hashes": [ + "sha256:3b2f9d2f436cc7c3b363d0ac66470f42fede249c3bafcc504e9f0bcbe983cff0", + "sha256:75b3977e7e22784607e074800048f44d6a56df589fb2abe58a11d4d20c97c314" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.9.0" + }, + "google-auth-httplib2": { + "hashes": [ + "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10", + "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac" + ], + "version": "==0.1.0" + }, + "googleapis-common-protos": { + "hashes": [ + "sha256:6f1369b58ed6cf3a4b7054a44ebe8d03b29c309257583a2bbdc064cd1e4a1442", + "sha256:87955d7b3a73e6e803f2572a33179de23989ebba725e05ea42f24838b792e461" + ], + "markers": "python_version >= '3.6'", + "version": "==1.56.3" + }, + "grpcio": { + "hashes": [ + "sha256:0425b5577be202d0a4024536bbccb1b052c47e0766096e6c3a5789ddfd5f400d", + "sha256:06c0739dff9e723bca28ec22301f3711d85c2e652d1c8ae938aa0f7ad632ef9a", + "sha256:08307dc5a6ac4da03146d6c00f62319e0665b01c6ffe805cfcaa955c17253f9c", + "sha256:090dfa19f41efcbe760ae59b34da4304d4be9a59960c9682b7eab7e0b6748a79", + "sha256:0a24b50810aae90c74bbd901c3f175b9645802d2fbf03eadaf418ddee4c26668", + "sha256:0cd44d78f302ff67f11a8c49b786c7ccbed2cfef6f4fd7bb0c3dc9255415f8f7", + "sha256:0d8a7f3eb6f290189f48223a5f4464c99619a9de34200ce80d5092fb268323d2", + "sha256:14d2bc74218986e5edf5527e870b0969d63601911994ebf0dce96288548cf0ef", + "sha256:1bb9afa85e797a646bfcd785309e869e80a375c959b11a17c9680abebacc0cb0", + "sha256:1ec63bbd09586e5cda1bdc832ae6975d2526d04433a764a1cc866caa399e50d4", + "sha256:2061dbe41e43b0a5e1fd423e8a7fb3a0cf11d69ce22d0fac21f1a8c704640b12", + "sha256:324e363bad4d89a8ec7124013371f268d43afd0ac0fdeec1b21c1a101eb7dafb", + "sha256:35dfd981b03a3ec842671d1694fe437ee9f7b9e6a02792157a2793b0eba4f478", + "sha256:43857d06b2473b640467467f8f553319b5e819e54be14c86324dad83a0547818", + "sha256:4706c78b0c183dca815bbb4ef3e8dd2136ccc8d1699f62c585e75e211ad388f6", + "sha256:4d9ad7122f60157454f74a850d1337ba135146cef6fb7956d78c7194d52db0fe", + "sha256:544da3458d1d249bb8aed5504adf3e194a931e212017934bf7bfa774dad37fb3", + "sha256:55782a31ec539f15b34ee56f19131fe1430f38a4be022eb30c85e0b0dcf57f11", + "sha256:55cd8b13c5ef22003889f599b8f2930836c6f71cd7cf3fc0196633813dc4f928", + "sha256:5dbba95fab9b35957b4977b8904fc1fa56b302f9051eff4d7716ebb0c087f801", + "sha256:5f57b9b61c22537623a5577bf5f2f970dc4e50fac5391090114c6eb3ab5a129f", + "sha256:64e097dd08bb408afeeaee9a56f75311c9ca5b27b8b0278279dc8eef85fa1051", + "sha256:664a270d3eac68183ad049665b0f4d0262ec387d5c08c0108dbcfe5b351a8b4d", + "sha256:668350ea02af018ca945bd629754d47126b366d981ab88e0369b53bc781ffb14", + "sha256:67cd275a651532d28620eef677b97164a5438c5afcfd44b15e8992afa9eb598c", + "sha256:68b5e47fcca8481f36ef444842801928e60e30a5b3852c9f4a95f2582d10dcb2", + "sha256:7191ffc8bcf8a630c547287ab103e1fdf72b2e0c119e634d8a36055c1d988ad0", + "sha256:815089435d0f113719eabf105832e4c4fa1726b39ae3fb2ca7861752b0f70570", + "sha256:8dbef03853a0dbe457417c5469cb0f9d5bf47401b49d50c7dad3c495663b699b", + "sha256:91cd292373e85a52c897fa5b4768c895e20a7dc3423449c64f0f96388dd1812e", + "sha256:9298d6f2a81f132f72a7e79cbc90a511fffacc75045c2b10050bb87b86c8353d", + "sha256:96cff5a2081db82fb710db6a19dd8f904bdebb927727aaf4d9c427984b79a4c1", + "sha256:9e63e0619a5627edb7a5eb3e9568b9f97e604856ba228cc1d8a9f83ce3d0466e", + "sha256:a278d02272214ec33f046864a24b5f5aab7f60f855de38c525e5b4ef61ec5b48", + "sha256:a6b2432ac2353c80a56d9015dfc5c4af60245c719628d4193ecd75ddf9cd248c", + "sha256:b821403907e865e8377af3eee62f0cb233ea2369ba0fcdce9505ca5bfaf4eeb3", + "sha256:b88bec3f94a16411a1e0336eb69f335f58229e45d4082b12d8e554cedea97586", + "sha256:bfdb8af4801d1c31a18d54b37f4e49bb268d1f485ecf47f70e78d56e04ff37a7", + "sha256:c79996ae64dc4d8730782dff0d1daacc8ce7d4c2ba9cef83b6f469f73c0655ce", + "sha256:cc34d182c4fd64b6ff8304a606b95e814e4f8ed4b245b6d6cc9607690e3ef201", + "sha256:d0d481ff55ea6cc49dab2c8276597bd4f1a84a8745fedb4bc23e12e9fb9d0e45", + "sha256:e9723784cf264697024778dcf4b7542c851fe14b14681d6268fb984a53f76df1", + "sha256:f4508e8abd67ebcccd0fbde6e2b1917ba5d153f3f20c1de385abd8722545e05f", + "sha256:f515782b168a4ec6ea241add845ccfebe187fc7b09adf892b3ad9e2592c60af1", + "sha256:f89de64d9eb3478b188859214752db50c91a749479011abd99e248550371375f", + "sha256:fcd5d932842df503eb0bf60f9cc35e6fe732b51f499e78b45234e0be41b0018d" + ], + "markers": "python_version >= '3.6'", + "version": "==1.47.0" + }, + "httplib2": { + "hashes": [ + "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585", + "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.20.4" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3.5'", + "version": "==3.3" + }, + "protobuf": { + "hashes": [ + "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf", + "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f", + "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f", + "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7", + "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996", + "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067", + "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c", + "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7", + "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9", + "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c", + "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739", + "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91", + "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c", + "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153", + "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9", + "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388", + "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e", + "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab", + "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde", + "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531", + "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8", + "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7", + "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20", + "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3" + ], + "index": "pypi", + "version": "==3.20.1" + }, + "py-sr25519-bindings": { + "hashes": [ + "sha256:0046dda17c554376f5ba11ea91163b1b883ac61fcc1b1ba588e31b1cb58add28", + "sha256:0a09f5886a706fcee6786f94dd59a04bdc95f2d6c4b7e1b3118ca805d322a055", + "sha256:1121e5da273c81b2e7ba0ca4bef8a89c527a6e25ed3110a95e93fd2caa04c50b", + "sha256:127057216b8cd32ea322d64aa52e70ba7b42a55cd4eb31e8df7c0120934f2445", + "sha256:14a06ca42d5fd07673138bf9970db9091059a1e4d704545bdf156dd05de3e3c6", + "sha256:197892cd89077baf2be51e3307a40de2c32006a2b89fec7d767a1d0b2749712d", + "sha256:1ca0d8e1ca66a17c41e3f6da0c3837e7dbf5b391eb4ea41cfe6026bad0adc8ea", + "sha256:1fb9ca7b65e60b64bd6e81ee2d5e6556d93b317af3c95a2e24bf45a3c44041a0", + "sha256:2ec17c935f8ae9a00bb5d6adac6cc455fd6052042959ace5563448c355e2960a", + "sha256:34786ba33f602d9d4f2495bd29d9f0cb357813a6ee8ae5cd9d37dbda86c141a4", + "sha256:3845f55a88dcba825c16e65fc9e26742cbb9103fba5229fd0c3e9e8deffd323b", + "sha256:3f52666b02075483dfdd294b9d0fd903eaf96ac7140e5be45390b25ded52ca6a", + "sha256:42097fab2702186a6591471bc366d7c804c7c30744acd59d6c6a38fdcad4bedb", + "sha256:4940ebfcb482b33468c41dda9e8c19137c14cc3b993e1084e55387c6acfa11ab", + "sha256:5a0f591f9b474ca31f0ca199ebc5d2c70a8869ff6e211188a1562310614558dd", + "sha256:5e12ca977014f148f4bfb6ba662f1529b15cc8ce030719d726c4e16a379e976e", + "sha256:6715852eb2ea733c4978b77f9a049cc9eb763d54270d5b1263b198256656c38e", + "sha256:67dd46da0a571859418fc938af15ab9a0586e47244d21f0f26c5cbf4daf10a3b", + "sha256:721cae28e86038682dd522191f9cb58a71b3455ef461433ba27a76d2d321197c", + "sha256:72b49cba419f5c76436bf06de4575ee27713afd8b867c4c14cb7a29e1046c30a", + "sha256:855af2efee23ebbb4b5544027b2b9a4818db34717b88741bcfd74f5739e9152f", + "sha256:8e62369aca9455131330cf63b0fcec3be198cabad936317321aa0db9d143435b", + "sha256:9cbee7341ed35d08533c7dac4d227e2fe21b815072ef66178a7ddb77f4e7be36", + "sha256:9e07cc588754da27c03185e2d3823537790f616b5aab3df1703ca0f6f578114c", + "sha256:9e5118cc7e1a77083307d260ccda21a0ec14efb0b98535f8fc9e2c54c7594836", + "sha256:aed508e15e695c5e3147be936db7a40f7bf5cb0a4078a9eb0e25fbdeb7c08982", + "sha256:b901675f8ea8ca4a6e55887ea48448a3f6bf55f22baa4acfa77d7c844e82e9db", + "sha256:cb1edb9db13548252fe3410868111dcda48a5b6ecda11cebb156930f0f472368", + "sha256:ce3639509e87ab04652c62aab626f13a80e308347047173bf74749397d0be539", + "sha256:d13e3a6b262494f66f36de3ba45d1040fee7a0c4104d56086b15315332026db9", + "sha256:d1b1b51d997363a264fb3f91978f21ea784f5ee50ed26e11ed0a0f26c497a4eb", + "sha256:d1c1247b4bf4c5670767e8a35bc259b425b275c8d72c6e3f1cfac47cf9b80e89", + "sha256:f7ca8f4e62aee8bc33916f6422e8ac5ffeeb96bc11f7ca52792354b771216bd8" + ], + "version": "==0.1.4" + }, + "pyasn1": { + "hashes": [ + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + ], + "version": "==0.4.8" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", + "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", + "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", + "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", + "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", + "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", + "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", + "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", + "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", + "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", + "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", + "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", + "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" + ], + "version": "==0.2.8" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pycryptodome": { + "hashes": [ + "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79", + "sha256:0926f7cc3735033061ef3cf27ed16faad6544b14666410727b31fea85a5b16eb", + "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e", + "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88", + "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763", + "sha256:2ea63d46157386c5053cfebcdd9bd8e0c8b7b0ac4a0507a027f5174929403884", + "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13", + "sha256:2ffd8b31561455453ca9f62cb4c24e6b8d119d6d531087af5f14b64bee2c23e6", + "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2", + "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667", + "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a", + "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d", + "sha256:60b4faae330c3624cc5a546ba9cfd7b8273995a15de94ee4538130d74953ec2e", + "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b", + "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83", + "sha256:9135dddad504592bcc18b0d2d95ce86c3a5ea87ec6447ef25cfedea12d6018b8", + "sha256:9c772c485b27967514d0df1458b56875f4b6d025566bf27399d0c239ff1b369f", + "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f", + "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676", + "sha256:a8f06611e691c2ce45ca09bbf983e2ff2f8f4f87313609d80c125aff9fad6e7f", + "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8", + "sha256:b9cc96e274b253e47ad33ae1fccc36ea386f5251a823ccb50593a935db47fdd2", + "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f", + "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9", + "sha256:d2a39a66057ab191e5c27211a7daf8f0737f23acbf6b3562b25a62df65ffcb7b", + "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1", + "sha256:ecaaef2d21b365d9c5ca8427ffc10cebed9d9102749fd502218c23cb9a05feb5", + "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c", + "sha256:ff287bcba9fbeb4f1cccc1f2e90a08d691480735a611ee83c80a7d74ad72b9d9", + "sha256:ff7ae90e36c1715a54446e7872b76102baa5c63aa980917f4aa45e8c78d1a3ec" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.15.0" + }, + "pynacl": { + "hashes": [ + "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", + "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", + "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", + "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", + "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", + "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", + "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", + "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", + "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", + "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543" + ], + "markers": "python_version >= '3.6'", + "version": "==1.5.0" + }, + "pyparsing": { + "hashes": [ + "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", + "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" + ], + "markers": "python_version >= '3.1'", + "version": "==3.0.9" + }, + "python-mbedtls": { + "hashes": [ + "sha256:08da2f4e08865d53213d0f1153efa02adaa6cd730d81af7e27e68d0fc061a6dd", + "sha256:12b47410fd41de58ac63d2b8c15f5e92005911fc8c3094b92d034162fbf1c10d", + "sha256:28bd7b56083a5982eb301366ac96855d02966a45c2d438dcecd733b8f6576100", + "sha256:2faf40df514999519c5667cfe54e4d92dc3da433564618b5d9625d5366b8d616", + "sha256:443f6f6e25943abf1c038b6f3735edc9792204ee83e606d1d92bba6c81f071b3", + "sha256:47ea2ce594ba26388b454678745c0ff4da3ab4873c52b337b7ed5099647e96ee", + "sha256:6ddbd912cb82c1b771696a5bf094b8e1377cfb3cac3df044e541d396180a7766", + "sha256:6f401783f96d8354b3851fa9f2aaeebfe750aca3113512210cda6c9ac0a4ce78", + "sha256:741401c69bf909da74e1cb9159c8cb2c9c2841ab4780d4fbc1a29e43bf651f7e", + "sha256:92f9202bcab4e3f69eff6a0785ace4f2e338057b0ebad7e950e9a794e268cea5", + "sha256:ecd748045d191ba804776edee0c25799e6696ab88f2229087d057d5aa38678fa", + "sha256:f30adb80130912186737099ca5a174c79980b5d65435b679876caf313ae25a51", + "sha256:f7fe9d4818a99e46714b30a14537d107e6137d6688a7d5df7fe253dd25d3e0cd" + ], + "version": "==2.2.0" + }, + "requests": { + "hashes": [ + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" + ], + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==2.28.1" + }, + "rsa": { + "hashes": [ + "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17", + "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb" + ], + "markers": "python_version >= '3.6'", + "version": "==4.8" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "types-certifi": { + "hashes": [ + "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f", + "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a" + ], + "version": "==2021.10.8.3" + }, + "uritemplate": { + "hashes": [ + "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0", + "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e" + ], + "markers": "python_version >= '3.6'", + "version": "==4.1.1" + }, + "urllib3": { + "hashes": [ + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.9" + }, + "wheel": { + "hashes": [ + "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a", + "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.37.1" + } + }, + "develop": {} +} diff --git a/scripts/subquery-e2e.py b/scripts/subquery-e2e.py new file mode 100644 index 000000000..07c112390 --- /dev/null +++ b/scripts/subquery-e2e.py @@ -0,0 +1,123 @@ +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.aerial.tx import Transaction +from cosmpy.crypto.keypairs import PrivateKey +from cosmpy.crypto.address import Address +from cosmpy.protos.cosmos.gov.v1beta1 import tx_pb2 as gov_tx, gov_pb2, query_pb2 as gov_query, query_pb2_grpc +from cosmpy.protos.cosmos.base.v1beta1 import coin_pb2 +from cosmpy.protos.cosmwasm.wasm.v1 import tx_pb2 as wasm_tx, query_pb2 as wasm_query +from cosmpy.aerial.contract import LedgerContract +from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins +from cosmpy.aerial.client import LedgerClient, NetworkConfig, utils +from google.protobuf import any_pb2 +import grpc + +# ledger_client = LedgerClient(NetworkConfig.fetch_mainnet()) + +cfg = NetworkConfig( + chain_id="testing", + url="grpc+http://localhost:9090", + fee_minimum_gas_price=1, + fee_denomination="atestfet", + staking_denomination="atestfet", +) + +gov_cfg = grpc.insecure_channel('localhost:9090') + +ledger_client = LedgerClient(cfg) +gov_module = query_pb2_grpc.QueryStub(gov_cfg) + +""" +Validator wallet +""" +validator_mnemonic = "nut grocery slice visit barrel peanut tumble patch slim logic install evidence fiction shield rich brown around arrest fresh position animal butter forget cost" +validator_seed_bytes = Bip39SeedGenerator(validator_mnemonic).Generate() +validator_bip44_def_ctx = Bip44.FromSeed(validator_seed_bytes, Bip44Coins.COSMOS).DeriveDefaultPath() +validator_wallet = LocalWallet(PrivateKey(validator_bip44_def_ctx.PrivateKey().Raw().ToBytes())) +validator_address = str(validator_wallet.address()) +validator_operator_address = Address(bytes(validator_wallet.address()), prefix="fetchvaloper") + +""" +Delegator wallet +""" +delegator_mnemonic = "dismiss domain uniform image cute buzz ride anxiety nose canvas ripple stock buffalo bitter spirit maximum tone inner couch forum equal usage state scan" +delegator_seed_bytes = Bip39SeedGenerator(delegator_mnemonic).Generate() +delegator_bip44_def_ctx = Bip44.FromSeed(delegator_seed_bytes, Bip44Coins.COSMOS).DeriveDefaultPath() +delegator_wallet = LocalWallet(PrivateKey(delegator_bip44_def_ctx.PrivateKey().Raw().ToBytes())) +delegator_address = str(delegator_wallet.address()) + +""" +Transaction event +""" +tx = ledger_client.send_tokens(delegator_wallet.address(), 10000000, "atestfet", validator_wallet) +tx.wait_to_complete() + +""" +Govt proposal +""" + +proposal_content = any_pb2.Any() +proposal_content.Pack(gov_pb2.TextProposal( + title="Test Proposal", + description="This is a test proposal" +), "") + +msg = gov_tx.MsgSubmitProposal( + content=proposal_content, + initial_deposit=[coin_pb2.Coin( + denom="atestfet", + amount="10000000" + )], + proposer=validator_address +) + +tx = Transaction() +tx.add_message(msg) + +tx = utils.prepare_and_broadcast_basic_transaction(ledger_client, tx, validator_wallet) +tx.wait_to_complete() + +""" +Governance deposit +""" + +msg = gov_tx.MsgDeposit( + proposal_id=1, + depositor=delegator_address, + amount=[coin_pb2.Coin(denom="atestfet", amount="1")] +) +tx = Transaction() +tx.add_message(msg) + +tx = utils.prepare_and_broadcast_basic_transaction(ledger_client, tx, delegator_wallet) +tx.wait_to_complete() + +""" +Governance vote +""" + +msg = gov_tx.MsgVote( + proposal_id=1, + voter=validator_address, + option=gov_pb2.VoteOption.VOTE_OPTION_YES + ) +vote_tx = Transaction() +vote_tx.add_message(msg) + +tx = utils.prepare_and_broadcast_basic_transaction(ledger_client, vote_tx, validator_wallet) +tx.wait_to_complete() + +""" +Delegator reward claim +""" + +delegate_tx = ledger_client.delegate_tokens(validator_operator_address, 100000, delegator_wallet) +delegate_tx.wait_to_complete() + +claim_tx = ledger_client.claim_rewards(validator_operator_address, delegator_wallet) +claim_tx.wait_to_complete() + +undelegate_tx = ledger_client.undelegate_tokens(validator_operator_address, 100000, delegator_wallet) + +""" +Legacy Bridge Swap +""" diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index e742eba52..971317bec 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -5,6 +5,7 @@ import { EventAttribute, Event, GovProposalVote, + GovProposalVoteOption, LegacyBridgeSwap, Message, Transaction, @@ -125,7 +126,7 @@ export async function handleGovProposalVote(msg: CosmosMessage Date: Wed, 27 Jul 2022 14:58:38 +0100 Subject: [PATCH 011/143] chore: initial dockerfile for deployed version (#14) --- .dockerignore | 34 ++++++++++++++++++++++++++++++++++ .gitignore | 3 +-- .nvmrc | 1 + Dockerfile | 32 ++++++++++++++++++++++++++++++++ docker-compose.yml | 2 +- scripts/entrypoint.sh | 21 +++++++++++++++++++++ 6 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 .nvmrc create mode 100644 Dockerfile create mode 100755 scripts/entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..0d0b59974 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Build +dist/ +package-lock.json + +# Node Specific +node_modules/ + +# Deployment +k8s/ + +# Python E2E Scripts +Pipfile +Pipfile.lock +scripts/*.py + +# Git VCS +.git/ +.gitignore + +# Docker +Dockerfile +.dockerignore +docker-compose.yml + +# IDEs +.idea/ + +# MacOS Specific +.DS_Store + +# Extra project files +*.md +LICENSE +.nvmrc diff --git a/.gitignore b/.gitignore index 958a76126..31a832b1f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,7 @@ node_modules/ dist/ -# lock files -yarn.lock +# lock files (not using NPM) package-lock.json # Compiled Java class files diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..b6a7d89c6 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..8d6fef642 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM node:16-slim AS builder + +RUN apt-get update && apt-get install -y tree + +WORKDIR /app + +# add the dependencies +ADD package.json yarn.lock /app/ +RUN yarn install --frozen-lockfile + +# add the remaining parts of the produce the build +COPY . /app +RUN yarn codegen && yarn build + +FROM onfinality/subql-node-cosmos:v0.2.0 + +# add extra tools that are required +ADD https://github.com/mikefarah/yq/releases/download/v4.26.1/yq_linux_amd64 /usr/local/bin/yq +RUN chmod +x /usr/local/bin/yq + +WORKDIR /app + +# add the dependencies +ADD package.json yarn.lock /app/ +RUN yarn install --frozen-lockfile --prod + +COPY --from=builder /app/dist /app/dist +ADD proto /app/proto +ADD project.yaml schema.graphql /app/ +ADD scripts/entrypoint.sh / + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 3ec6629da..79df23055 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: retries: 5 subquery-node: - image: onfinality/subql-node-cosmos:latest + image: onfinality/subql-node-cosmos:v0.2.0 depends_on: "postgres": condition: service_healthy diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh new file mode 100755 index 000000000..8cab93032 --- /dev/null +++ b/scripts/entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -e + +# perform any updates that are required based on the environment variables +if [[ ! -z "${START_BLOCK}" ]]; then + echo "[Config Update] Start Block: ${START_BLOCK}" + yq -i '.dataSources[].startBlock = env(START_BLOCK)' project.yaml +fi + +if [[ ! -z "${CHAIN_ID}" ]]; then + echo "[Config Update] Chain ID: ${CHAIN_ID}" + yq -i '.network.chainId = env(CHAIN_ID)' project.yaml +fi + +if [[ ! -z "${NETWORK_ENDPOINT}" ]]; then + echo "[Config Update] Network Endpoint: ${NETWORK_ENDPOINT}" + yq -i '.network.endpoint = strenv(NETWORK_ENDPOINT)' project.yaml +fi + +# run the main node +exec /sbin/tini -- /usr/local/lib/node_modules/@subql/node-cosmos/bin/run From 62d49ba5288f51c384c9173e91c6a177f7292dd9 Mon Sep 17 00:00:00 2001 From: Ed FitzGerald Date: Thu, 28 Jul 2022 14:11:14 +0100 Subject: [PATCH 012/143] chore: naive helm chart for deployment (#16) --- docker-compose.yml | 2 +- k8s/subquery/.helmignore | 23 ++++++++ k8s/subquery/Chart.yaml | 6 ++ k8s/subquery/templates/_helpers.tpl | 62 ++++++++++++++++++++ k8s/subquery/templates/api/api.cmap.yml | 13 ++++ k8s/subquery/templates/api/api.deploy.yml | 37 ++++++++++++ k8s/subquery/templates/api/api.secrets.yml | 11 ++++ k8s/subquery/templates/api/api.svc.yml | 16 +++++ k8s/subquery/templates/db/db.cmap.yml | 11 ++++ k8s/subquery/templates/db/db.secrets.yml | 11 ++++ k8s/subquery/templates/db/db.sts.yml | 47 +++++++++++++++ k8s/subquery/templates/db/db.svc.yml | 16 +++++ k8s/subquery/templates/name/node.cmap.yml | 14 +++++ k8s/subquery/templates/name/node.secrets.yml | 11 ++++ k8s/subquery/templates/name/node.sts.yml | 35 +++++++++++ k8s/subquery/templates/name/node.svc.yml | 16 +++++ k8s/subquery/values.yaml | 23 ++++++++ scripts/build-docker.sh | 20 +++++++ 18 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 k8s/subquery/.helmignore create mode 100644 k8s/subquery/Chart.yaml create mode 100644 k8s/subquery/templates/_helpers.tpl create mode 100644 k8s/subquery/templates/api/api.cmap.yml create mode 100644 k8s/subquery/templates/api/api.deploy.yml create mode 100644 k8s/subquery/templates/api/api.secrets.yml create mode 100644 k8s/subquery/templates/api/api.svc.yml create mode 100644 k8s/subquery/templates/db/db.cmap.yml create mode 100644 k8s/subquery/templates/db/db.secrets.yml create mode 100644 k8s/subquery/templates/db/db.sts.yml create mode 100644 k8s/subquery/templates/db/db.svc.yml create mode 100644 k8s/subquery/templates/name/node.cmap.yml create mode 100644 k8s/subquery/templates/name/node.secrets.yml create mode 100644 k8s/subquery/templates/name/node.sts.yml create mode 100644 k8s/subquery/templates/name/node.svc.yml create mode 100644 k8s/subquery/values.yaml create mode 100755 scripts/build-docker.sh diff --git a/docker-compose.yml b/docker-compose.yml index 79df23055..44ec23a3e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,7 +39,7 @@ services: retries: 10 graphql-engine: - image: onfinality/subql-query:latest + image: onfinality/subql-query:v1.4.0 ports: - 3000:3000 depends_on: diff --git a/k8s/subquery/.helmignore b/k8s/subquery/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/k8s/subquery/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/k8s/subquery/Chart.yaml b/k8s/subquery/Chart.yaml new file mode 100644 index 000000000..c21703f2f --- /dev/null +++ b/k8s/subquery/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: subquery +description: A helm chart for the deployment of the fetch.ai subquery node +type: application +version: 0.1.0 +appVersion: "1.16.0" diff --git a/k8s/subquery/templates/_helpers.tpl b/k8s/subquery/templates/_helpers.tpl new file mode 100644 index 000000000..b6407fc77 --- /dev/null +++ b/k8s/subquery/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "subquery.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "subquery.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "subquery.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "subquery.labels" -}} +helm.sh/chart: {{ include "subquery.chart" . }} +{{ include "subquery.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "subquery.selectorLabels" -}} +app.kubernetes.io/name: {{ include "subquery.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "subquery.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "subquery.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/k8s/subquery/templates/api/api.cmap.yml b/k8s/subquery/templates/api/api.cmap.yml new file mode 100644 index 000000000..d848bcd7e --- /dev/null +++ b/k8s/subquery/templates/api/api.cmap.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: subquery-api-config + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: api + +data: + DB_DATABASE: {{ .Values.db.name | quote }} + DB_USER: {{ .Values.db.user | quote }} + DB_HOST: "subquery-db" + DB_PORT: "5432" \ No newline at end of file diff --git a/k8s/subquery/templates/api/api.deploy.yml b/k8s/subquery/templates/api/api.deploy.yml new file mode 100644 index 000000000..8ec8a135b --- /dev/null +++ b/k8s/subquery/templates/api/api.deploy.yml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: subquery-api + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: api +spec: + replicas: {{ .Values.subquery.api.replicas }} + selector: + matchLabels: + {{- include "subquery.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: api + template: + metadata: + labels: + {{- include "subquery.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: api + spec: + containers: + - name: api + image: "{{ .Values.subquery.api.image }}:{{ .Values.subquery.api.tag }}" + + args: + - "--name=app" + - "--playground" + - "--indexer=http://subquery-node:3000" + + envFrom: + - configMapRef: + name: subquery-api-config + - secretRef: + name: subquery-api-secrets + + ports: + - containerPort: 3000 + name: api diff --git a/k8s/subquery/templates/api/api.secrets.yml b/k8s/subquery/templates/api/api.secrets.yml new file mode 100644 index 000000000..c192eb4d5 --- /dev/null +++ b/k8s/subquery/templates/api/api.secrets.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +metadata: + name: subquery-api-secrets + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: api + +kind: Secret +type: Opaque +data: + DB_PASS: {{ .Values.db.password | b64enc | quote }} diff --git a/k8s/subquery/templates/api/api.svc.yml b/k8s/subquery/templates/api/api.svc.yml new file mode 100644 index 000000000..5002faa56 --- /dev/null +++ b/k8s/subquery/templates/api/api.svc.yml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: subquery-api + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: api + +spec: + ports: + - port: 3000 + name: web + type: LoadBalancer + selector: + {{- include "subquery.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: api diff --git a/k8s/subquery/templates/db/db.cmap.yml b/k8s/subquery/templates/db/db.cmap.yml new file mode 100644 index 000000000..913dd6edc --- /dev/null +++ b/k8s/subquery/templates/db/db.cmap.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: subquery-db-config + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: db + +data: + POSTGRES_USER: {{ .Values.db.user | quote }} + POSTGRES_DB: {{ .Values.db.name | quote }} diff --git a/k8s/subquery/templates/db/db.secrets.yml b/k8s/subquery/templates/db/db.secrets.yml new file mode 100644 index 000000000..eb935ced2 --- /dev/null +++ b/k8s/subquery/templates/db/db.secrets.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +metadata: + name: subquery-db-secrets + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: db + +kind: Secret +type: Opaque +data: + POSTGRES_PASSWORD: {{ .Values.db.password | b64enc | quote }} diff --git a/k8s/subquery/templates/db/db.sts.yml b/k8s/subquery/templates/db/db.sts.yml new file mode 100644 index 000000000..59a2fa1c9 --- /dev/null +++ b/k8s/subquery/templates/db/db.sts.yml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: subquery-db + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: db + +spec: + selector: + matchLabels: + {{- include "subquery.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: db + serviceName: "subquery-db" + replicas: 1 + minReadySeconds: 10 # by default is 0 + template: + metadata: + labels: + {{- include "subquery.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: db + + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: db + image: "{{ .Values.db.image }}:{{ .Values.db.tag }}" + envFrom: + - configMapRef: + name: subquery-db-config + - secretRef: + name: subquery-db-secrets + ports: + - containerPort: 5432 + name: psql + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + subPath: db-data + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: {{ .Values.db.storageSize }} diff --git a/k8s/subquery/templates/db/db.svc.yml b/k8s/subquery/templates/db/db.svc.yml new file mode 100644 index 000000000..8de40a38e --- /dev/null +++ b/k8s/subquery/templates/db/db.svc.yml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: subquery-db + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: db + +spec: + ports: + - port: 5432 + name: psql + clusterIP: None + selector: + {{- include "subquery.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: db diff --git a/k8s/subquery/templates/name/node.cmap.yml b/k8s/subquery/templates/name/node.cmap.yml new file mode 100644 index 000000000..81e7e0126 --- /dev/null +++ b/k8s/subquery/templates/name/node.cmap.yml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: subquery-node-config + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: node + +data: + DB_DATABASE: {{ .Values.db.name | quote }} + DB_USER: {{ .Values.db.user | quote }} + DB_HOST: "subquery-db" + DB_PORT: "5432" + START_BLOCK: {{ .Values.subquery.node.startBlock | quote }} diff --git a/k8s/subquery/templates/name/node.secrets.yml b/k8s/subquery/templates/name/node.secrets.yml new file mode 100644 index 000000000..8277b405a --- /dev/null +++ b/k8s/subquery/templates/name/node.secrets.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +metadata: + name: subquery-node-secrets + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: node + +kind: Secret +type: Opaque +data: + DB_PASS: {{ .Values.db.password | b64enc | quote }} diff --git a/k8s/subquery/templates/name/node.sts.yml b/k8s/subquery/templates/name/node.sts.yml new file mode 100644 index 000000000..b2de22392 --- /dev/null +++ b/k8s/subquery/templates/name/node.sts.yml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: subquery-node + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: node + +spec: + selector: + matchLabels: + {{- include "subquery.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: node + serviceName: "subquery-node" + replicas: {{ .Values.subquery.node.replicas }} + minReadySeconds: 10 + template: + metadata: + labels: + {{- include "subquery.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: node + spec: + terminationGracePeriodSeconds: 10 + containers: + - name: node + image: "{{ .Values.subquery.node.image }}:{{ .Values.subquery.node.tag }}" + args: ["-f=/app", "--db-schema=app", "--batch-size=1"] + envFrom: + - configMapRef: + name: subquery-node-config + - secretRef: + name: subquery-node-secrets + ports: + - containerPort: 3000 + name: web diff --git a/k8s/subquery/templates/name/node.svc.yml b/k8s/subquery/templates/name/node.svc.yml new file mode 100644 index 000000000..29b272145 --- /dev/null +++ b/k8s/subquery/templates/name/node.svc.yml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: subquery-node + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: node + +spec: + ports: + - port: 3000 + name: web + clusterIP: None + selector: + {{- include "subquery.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: node diff --git a/k8s/subquery/values.yaml b/k8s/subquery/values.yaml new file mode 100644 index 000000000..109274276 --- /dev/null +++ b/k8s/subquery/values.yaml @@ -0,0 +1,23 @@ +subquery: + node: + image: gcr.io/fetch-ai-sandbox/subquery-node + tag: fe4d558 + + startBlock: 6926337 + + + api: + image: onfinality/subql-query + tag: v1.4.0 + + replicas: 1 + +db: + image: postgres + tag: 14-alpine + + name: subquery + user: subquery + password: "mVsM2RcYnzMTUjWKT@9qKMT7mJXpDofeX.bFNnneCe" + + storageSize: 1Gi diff --git a/scripts/build-docker.sh b/scripts/build-docker.sh new file mode 100755 index 000000000..ff0e5e614 --- /dev/null +++ b/scripts/build-docker.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +version=$(git describe --always --tags --dirty=-wip) +registry="gcr.io/fetch-ai-sandbox" +image="subquery-node" + +full_tag="${registry}/${image}:${version}" + +# make sure all the changes have been committed +if [[ "${full_tag}" == *wip ]]; then + echo "You have uncommitted changes - please commit first before using this script" + exit 1 +fi + +# build the image +docker build -t "${full_tag}" . + +# push the image to the registry +docker push "${full_tag}" From f35b06a5a2fbabf3086f06a22d2b518e2aa17b12 Mon Sep 17 00:00:00 2001 From: Ed FitzGerald Date: Thu, 28 Jul 2022 15:22:42 +0100 Subject: [PATCH 013/143] chore: add istio to deployment configuration (#18) --- k8s/subquery/templates/api/api.cert.yml | 13 ++++++++ k8s/subquery/templates/api/api.dns.yml | 14 ++++++++ k8s/subquery/templates/api/api.gateway.yml | 39 ++++++++++++++++++++++ k8s/subquery/templates/api/api.svc.yml | 3 +- k8s/subquery/templates/api/api.vsvc.yml | 25 ++++++++++++++ k8s/subquery/values.yaml | 10 +++++- 6 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 k8s/subquery/templates/api/api.cert.yml create mode 100644 k8s/subquery/templates/api/api.dns.yml create mode 100644 k8s/subquery/templates/api/api.gateway.yml create mode 100644 k8s/subquery/templates/api/api.vsvc.yml diff --git a/k8s/subquery/templates/api/api.cert.yml b/k8s/subquery/templates/api/api.cert.yml new file mode 100644 index 000000000..ca0c40b2a --- /dev/null +++ b/k8s/subquery/templates/api/api.cert.yml @@ -0,0 +1,13 @@ +apiVersion: cert-manager.io/v1alpha2 +kind: Certificate +metadata: + name: "subq-{{ .Release.Namespace }}-{{ .Release.Name }}-cert" + namespace: istio-system +spec: + commonName: {{ .Values.subquery.api.istio.dnsName }} + dnsNames: + - {{ .Values.subquery.api.istio.dnsName }} + issuerRef: + kind: ClusterIssuer + name: letsencrypt-dns + secretName: "subq-{{ .Release.Namespace }}-{{ .Release.Name }}-cert" diff --git a/k8s/subquery/templates/api/api.dns.yml b/k8s/subquery/templates/api/api.dns.yml new file mode 100644 index 000000000..1da372ff7 --- /dev/null +++ b/k8s/subquery/templates/api/api.dns.yml @@ -0,0 +1,14 @@ +apiVersion: externaldns.k8s.io/v1alpha1 +kind: DNSEndpoint +metadata: + name: api-endpoint + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: api +spec: + endpoints: + - dnsName: {{ .Values.subquery.api.istio.dnsName }} + recordTTL: 180 + recordType: CNAME + targets: + - {{ .Values.subquery.api.istio.dnsTarget }} diff --git a/k8s/subquery/templates/api/api.gateway.yml b/k8s/subquery/templates/api/api.gateway.yml new file mode 100644 index 000000000..f30509e0e --- /dev/null +++ b/k8s/subquery/templates/api/api.gateway.yml @@ -0,0 +1,39 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: subquery-api-gateway + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: api + +spec: + selector: + app: {{ .Values.subquery.api.istio.ingressGateway }} + istio: ingressgateway + + servers: + - hosts: + - {{ .Values.subquery.api.istio.dnsName }} + {{- range .Values.subquery.api.istio.additionalDnsNames }} + - {{ . | quote }} + {{- end }} + port: + name: tcp-http + number: 80 + protocol: HTTP + tls: + httpsRedirect: false + - hosts: + - {{ .Values.subquery.api.istio.dnsName }} + {{- range .Values.subquery.api.istio.additionalDnsNames }} + - {{ . | quote }} + {{- end }} + port: + name: tcp-https + number: 443 + protocol: HTTPS + tls: + credentialName: "subq-{{ .Release.Namespace }}-{{ .Release.Name }}-cert" + mode: SIMPLE + privateKey: sds + serverCertificate: sds diff --git a/k8s/subquery/templates/api/api.svc.yml b/k8s/subquery/templates/api/api.svc.yml index 5002faa56..ce36bdd70 100644 --- a/k8s/subquery/templates/api/api.svc.yml +++ b/k8s/subquery/templates/api/api.svc.yml @@ -9,8 +9,7 @@ metadata: spec: ports: - port: 3000 - name: web - type: LoadBalancer + name: api selector: {{- include "subquery.selectorLabels" . | nindent 4 }} app.kubernetes.io/component: api diff --git a/k8s/subquery/templates/api/api.vsvc.yml b/k8s/subquery/templates/api/api.vsvc.yml new file mode 100644 index 000000000..1e224999b --- /dev/null +++ b/k8s/subquery/templates/api/api.vsvc.yml @@ -0,0 +1,25 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: subquery-api-vsvc + labels: + {{- include "subquery.labels" . | nindent 4 }} + app.kubernetes.io/component: api + +spec: + gateways: + - subquery-api-gateway + + hosts: + - {{ .Values.subquery.api.istio.dnsName }} + {{- range .Values.subquery.api.istio.additionalDnsNames }} + - {{ . | quote }} + {{- end }} + + http: + - route: + - destination: + host: subquery-api + port: + number: 3000 + weight: 100 diff --git a/k8s/subquery/values.yaml b/k8s/subquery/values.yaml index 109274276..1b75861b6 100644 --- a/k8s/subquery/values.yaml +++ b/k8s/subquery/values.yaml @@ -4,7 +4,6 @@ subquery: tag: fe4d558 startBlock: 6926337 - api: image: onfinality/subql-query @@ -12,6 +11,15 @@ subquery: replicas: 1 + istio: + # Gateways + ingressGateway: istio-fetch-gaia-ig + + # DNS Settings + dnsName: subquery-staging.sandbox-london-b.fetch-ai.com + dnsTarget: gaia.sandbox-london-b.fetch-ai.com + additionalDnsNames: [] + db: image: postgres tag: 14-alpine From 26cf8f0f303255580ec87ea5bc01dec345ce8f8d Mon Sep 17 00:00:00 2001 From: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Date: Mon, 8 Aug 2022 08:48:18 +0100 Subject: [PATCH 014/143] feat: native transfer handler (#20) * feat: add JSON coin schema type * feat: add NativeTransfer handler and schema * fix: ammend imports * fix: amend NativeTransfer handler prints --- project.yaml | 4 ++++ schema.graphql | 18 ++++++++++++++++++ src/mappings/mappingHandlers.ts | 25 +++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/project.yaml b/project.yaml index 6fad23a63..d7b00618f 100644 --- a/project.yaml +++ b/project.yaml @@ -59,3 +59,7 @@ dataSources: contractCall: "swap" # The name of the contract function that was called values: # This is the specific smart contract that we are subscribing to contract: "fetch1qxxlalvsdjd07p07y3rc5fu6ll8k4tmetpha8n" + - handler: handleNativeTransfer + kind: cosmos/MessageHandler + filter: + type: "/cosmos.bank.v1beta1.MsgSend" diff --git a/schema.graphql b/schema.graphql index 00c7e410b..48aa2f3db 100644 --- a/schema.graphql +++ b/schema.graphql @@ -101,3 +101,21 @@ type LegacyBridgeSwap @entity { transaction: Transaction! block: Block! } + +type Coin @jsonField { + denom: String! + amount: String! +} + +type NativeTransferMsg @jsonField { + toAddress: String! # indexable + fromAddress: String! #indexable + amount: [Coin]! # indexible +} + +type NativeTransfer @entity { + id: ID! + message: NativeTransferMsg! + transaction: Transaction! + block: Block! +} \ No newline at end of file diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 971317bec..59fe492ea 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -9,10 +9,17 @@ import { LegacyBridgeSwap, Message, Transaction, - TxStatus + TxStatus, + NativeTransferMsg, + NativeTransfer } from "../types"; import {CosmosBlock, CosmosEvent, CosmosMessage, CosmosTransaction,} from "@subql/types-cosmos"; -import {ExecuteContractMsg, DistDelegatorClaimMsg, GovProposalVoteMsg, LegacyBridgeSwapMsg} from "./types"; +import { + ExecuteContractMsg, + DistDelegatorClaimMsg, + GovProposalVoteMsg, + LegacyBridgeSwapMsg +} from "./types"; // messageId returns the id of the message passed or // that of the message which generated the event passed. @@ -65,6 +72,20 @@ export async function handleTransaction(tx: CosmosTransaction): Promise { await txEntity.save(); } +export async function handleNativeTransfer(msg: CosmosMessage): Promise { + logger.info(`[handleNativeTransfer] (tx ${msg.tx.hash}): indexing message ${msg.idx + 1} / ${msg.tx.decodedTx.body.messages.length}`) + logger.debug(`[handleNativeTransfer] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) + + const transferEntity = NativeTransfer.create({ + id: messageId(msg), + message: msg.msg.decodedMsg, + transactionId: msg.tx.hash, + blockId: msg.block.block.id + }); + + await transferEntity.save(); +} + export async function handleMessage(msg: CosmosMessage): Promise { logger.info(`[handleMessage] (tx ${msg.tx.hash}): indexing message ${msg.idx + 1} / ${msg.tx.decodedTx.body.messages.length}`) logger.debug(`[handleMessage] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) From d4ccce0a001a60803dc78e9bdd285c2f040f1df0 Mon Sep 17 00:00:00 2001 From: Ian Harris <83638255+ianharris-fetch@users.noreply.github.com> Date: Wed, 10 Aug 2022 10:21:48 +0100 Subject: [PATCH 015/143] feat: rotate-db-secret script (#25) --- .gitignore | 1 + scripts/rotate-db-secret.sh | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100755 scripts/rotate-db-secret.sh diff --git a/.gitignore b/.gitignore index 31a832b1f..084e13ece 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ Thumbs.db .data dist +keys \ No newline at end of file diff --git a/scripts/rotate-db-secret.sh b/scripts/rotate-db-secret.sh new file mode 100755 index 000000000..e88b2e499 --- /dev/null +++ b/scripts/rotate-db-secret.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# CLUSTER=mainnet-v3 +# NAMESPACE=subquery +# EXTERNAL_SECRETS_SA=m-v3-london-ext-secrets@fetch-ai-mainnet-v3.iam.gserviceaccount.com +# GCP_PROJECT=fetch-ai-mainnet-v3 + +# CLUSTER=testnet-v3 +# NAMESPACE=subquery +# EXTERNAL_SECRETS_SA=t-v3-london-ext-secrets@fetch-ai-testnet-v3.iam.gserviceaccount.com +# GCP_PROJECT=fetch-ai-testnet-v3 + +CLUSTER=sandbox +NAMESPACE=subquery +EXTERNAL_SECRETS_SA=sandbox-london-b-ext-secrets@fetch-ai-sandbox.iam.gserviceaccount.com +GCP_PROJECT=fetch-ai-sandbox + +mkdir -p keys +FILE=keys/db.json +SECRET="${CLUSTER}_${NAMESPACE}_postgres" + +echo "{" >$FILE +echo ' "POSTGRES_USER": "'$(tr -dc A-Za-z >$FILE +echo ' "POSTGRES_PASSWORD": "'$(tr -dc A-Za-z0-9 >$FILE +echo "}" >>$FILE + +if [[ ! $(gcloud secrets describe $SECRET --project=${GCP_PROJECT} 2>/dev/null) ]]; then + gcloud secrets create $SECRET --replication-policy=automatic --project=$GCP_PROJECT + gcloud secrets add-iam-policy-binding $SECRET --member="serviceAccount:$EXTERNAL_SECRETS_SA" --role="roles/secretmanager.secretAccessor" --project=$GCP_PROJECT +fi +gcloud secrets versions add $SECRET --data-file=$FILE --project=$GCP_PROJECT + +rm -rf keys From 2d02f8ba14a2c444edf77cf4902230ada7f78aa8 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Thu, 11 Aug 2022 11:16:15 +0200 Subject: [PATCH 016/143] chore: devops testing (#24) --- .github/workflows/charts_push.yml | 20 ++++++++++++++++ docker-compose.yml | 25 ++++++++++++-------- k8s/subquery/templates/api/api.secrets.yml | 15 +++++++----- k8s/subquery/templates/db/db.cmap.yml | 1 + k8s/subquery/templates/db/db.secrets.yml | 15 +++++++----- k8s/subquery/templates/name/node.secrets.yml | 15 +++++++----- k8s/subquery/values.yaml | 7 +++--- project.yaml | 6 ++--- 8 files changed, 69 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/charts_push.yml diff --git a/.github/workflows/charts_push.yml b/.github/workflows/charts_push.yml new file mode 100644 index 000000000..9df19a1e7 --- /dev/null +++ b/.github/workflows/charts_push.yml @@ -0,0 +1,20 @@ +name: Chart Push +on: + pull_request: + branches: + - main + paths: + - k8s/** +jobs: + chart-push: + runs-on: self-hosted + steps: + - uses: 'actions/checkout@v2' + - name: Unistalling of cm-push + run: helm plugin uninstall cm-push + - name: Installing cm-push + if: ${{ failure()||success() }} + run: helm plugin install https://github.com/chartmuseum/helm-push.git + - name: Pushing Chart + if: ${{ failure()||success() }} + run: helm cm-push ./k8s/subquery http://charts.management.fetch-ai.com/ -f diff --git a/docker-compose.yml b/docker-compose.yml index 44ec23a3e..0e351e8f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,36 +2,41 @@ version: "3" services: postgres: - image: postgres:12-alpine + image: postgres:14-alpine ports: - 5432:5432 volumes: - .data/postgres:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD: postgres + POSTGRES_USER: "subquery" + POSTGRES_PASSWORD: "subquery" + POSTGRES_DB: "subquery" healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + test: ["CMD-SHELL", "pg_isready -U subquery"] interval: 5s timeout: 5s retries: 5 subquery-node: - image: onfinality/subql-node-cosmos:v0.2.0 +# image: onfinality/subql-node-cosmos:v0.2.0 + build: . depends_on: "postgres": condition: service_healthy restart: always environment: - DB_USER: postgres - DB_PASS: postgres - DB_DATABASE: postgres + DB_USER: "subquery" + DB_PASS: "subquery" + DB_DATABASE: "subquery" DB_HOST: postgres DB_PORT: 5432 + START_BLOCK: "7099815" volumes: - ./:/app command: - -f=/app - --db-schema=app + - --batch-size=1 healthcheck: test: ["CMD", "curl", "-f", "http://subquery-node:3000/ready"] interval: 3s @@ -49,9 +54,9 @@ services: condition: service_started restart: always environment: - DB_USER: postgres - DB_PASS: postgres - DB_DATABASE: postgres + DB_USER: "subquery" + DB_PASS: "subquery" + DB_DATABASE: "subquery" DB_HOST: postgres DB_PORT: 5432 command: diff --git a/k8s/subquery/templates/api/api.secrets.yml b/k8s/subquery/templates/api/api.secrets.yml index c192eb4d5..bbc9eabf2 100644 --- a/k8s/subquery/templates/api/api.secrets.yml +++ b/k8s/subquery/templates/api/api.secrets.yml @@ -1,11 +1,14 @@ -apiVersion: v1 +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret metadata: name: subquery-api-secrets labels: {{- include "subquery.labels" . | nindent 4 }} app.kubernetes.io/component: api - -kind: Secret -type: Opaque -data: - DB_PASS: {{ .Values.db.password | b64enc | quote }} +spec: + backendType: gcpSecretsManager + projectId: {{ $.Values.db.gcpProject }} + data: + - name: "DB_PASS" + key: {{ $.Values.db.gcpSecret }} + property: POSTGRES_PASSWORD diff --git a/k8s/subquery/templates/db/db.cmap.yml b/k8s/subquery/templates/db/db.cmap.yml index 913dd6edc..9bd77eb3a 100644 --- a/k8s/subquery/templates/db/db.cmap.yml +++ b/k8s/subquery/templates/db/db.cmap.yml @@ -9,3 +9,4 @@ metadata: data: POSTGRES_USER: {{ .Values.db.user | quote }} POSTGRES_DB: {{ .Values.db.name | quote }} + POSTGRES_INITDB_ARGS: "-A scram-sha-256" diff --git a/k8s/subquery/templates/db/db.secrets.yml b/k8s/subquery/templates/db/db.secrets.yml index eb935ced2..743e9316a 100644 --- a/k8s/subquery/templates/db/db.secrets.yml +++ b/k8s/subquery/templates/db/db.secrets.yml @@ -1,11 +1,14 @@ -apiVersion: v1 +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret metadata: name: subquery-db-secrets labels: {{- include "subquery.labels" . | nindent 4 }} app.kubernetes.io/component: db - -kind: Secret -type: Opaque -data: - POSTGRES_PASSWORD: {{ .Values.db.password | b64enc | quote }} +spec: + backendType: gcpSecretsManager + projectId: {{ $.Values.db.gcpProject }} + data: + - name: "POSTGRES_PASSWORD" + key: {{ $.Values.db.gcpSecret }} + property: POSTGRES_PASSWORD diff --git a/k8s/subquery/templates/name/node.secrets.yml b/k8s/subquery/templates/name/node.secrets.yml index 8277b405a..4f2a1b4cb 100644 --- a/k8s/subquery/templates/name/node.secrets.yml +++ b/k8s/subquery/templates/name/node.secrets.yml @@ -1,11 +1,14 @@ -apiVersion: v1 +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret metadata: name: subquery-node-secrets labels: {{- include "subquery.labels" . | nindent 4 }} app.kubernetes.io/component: node - -kind: Secret -type: Opaque -data: - DB_PASS: {{ .Values.db.password | b64enc | quote }} +spec: + backendType: gcpSecretsManager + projectId: {{ $.Values.db.gcpProject }} + data: + - name: "DB_PASS" + key: {{ $.Values.db.gcpSecret }} + property: POSTGRES_PASSWORD diff --git a/k8s/subquery/values.yaml b/k8s/subquery/values.yaml index 1b75861b6..586ec8d9b 100644 --- a/k8s/subquery/values.yaml +++ b/k8s/subquery/values.yaml @@ -1,9 +1,9 @@ subquery: node: image: gcr.io/fetch-ai-sandbox/subquery-node - tag: fe4d558 + tag: 239fe08 - startBlock: 6926337 + startBlock: "7099815" api: image: onfinality/subql-query @@ -26,6 +26,7 @@ db: name: subquery user: subquery - password: "mVsM2RcYnzMTUjWKT@9qKMT7mJXpDofeX.bFNnneCe" + gcpProject: fetch-ai-sandbox + gcpSecret: sandbox_subquery_postgres storageSize: 1Gi diff --git a/project.yaml b/project.yaml index d7b00618f..c8c40dd23 100644 --- a/project.yaml +++ b/project.yaml @@ -13,21 +13,19 @@ description: >- repository: https://github.com/fetchai/ledger-subquery schema: file: ./schema.graphql - network: chainId: fetchhub-4 endpoint: https://rpc-fetchhub.fetch.ai:443 # Using a dictionary can massively improve indexing speed dictionary: https://api.subquery.network/sq/subquery/cosmos-fetch-ai-dictionary - dataSources: - kind: cosmos/Runtime - startBlock: 5300201 # first block on fetchhub-4 + startBlock: 7099815 # first block on fetchhub-4 chainTypes: # This is a beta feature that allows support for any Cosmos chain by importing the correct protobuf messages cosmos.slashing.v1beta1: file: "./proto/cosmos/slashing/v1beta1/tx.proto" messages: - - "MsgUnjail" + - "MsgUnjail" mapping: file: "./dist/index.js" handlers: From 4d7cfa92bfc33cdb8bf4646d4845ead28d64e25a Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 15 Aug 2022 13:55:12 +0200 Subject: [PATCH 017/143] chore: add actions to build and push docker images (#29) chore: add actions to build/push docker images --- .github/workflows/docker_build.yml | 47 +++++++++++++++++++ .github/workflows/docker_deploy_prod.yml | 57 ++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 .github/workflows/docker_build.yml create mode 100644 .github/workflows/docker_deploy_prod.yml diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml new file mode 100644 index 000000000..7a02cf732 --- /dev/null +++ b/.github/workflows/docker_build.yml @@ -0,0 +1,47 @@ +name: Docker build latest + +on: + pull_request: + branches: [main] + +jobs: + ledger-subquery: + name: build + runs-on: ubuntu-latest + + env: + IMAGE_PROJECT_ID: fetch-ai-images + IMAGE_NAME: subquery-node + + steps: + - uses: actions/checkout@v3 + + - name: Get image tag + id: vars + shell: bash + run: | + echo "::set-output name=tag_name::latest" + + # Authenticate to GCP + - id: 'auth' + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{ secrets.DEVOPS_IMAGES_SA_KEY }}' + + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v0' + + - run: 'gcloud info' + + # Configure docker to use the gcloud command-line tool as a credential helper + - name: Configure Docker + run: | + gcloud auth configure-docker -q + + # Docker build ; docker push + - name: Build and push + uses: docker/build-push-action@v3 + with: + push: true + tags: | + gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.tag_name }} diff --git a/.github/workflows/docker_deploy_prod.yml b/.github/workflows/docker_deploy_prod.yml new file mode 100644 index 000000000..91e1b61cd --- /dev/null +++ b/.github/workflows/docker_deploy_prod.yml @@ -0,0 +1,57 @@ +name: Docker deploy prod + +on: + push: + branches: [main] + +jobs: + ledger-subquery: + name: build + runs-on: ubuntu-latest + + env: + IMAGE_PROJECT_ID: fetch-ai-images + IMAGE_NAME: subquery-node + DEPLOYMENT_REPO: fetchai/infra-production-deployment + + steps: + - uses: actions/checkout@v3 + + - name: Get image tag + id: vars + shell: bash + run: | + echo "::set-output name=tag_name::$(git rev-parse --short HEAD)" + + # Authenticate to GCP + - id: 'auth' + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{ secrets.DEVOPS_IMAGES_SA_KEY }}' + + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v0' + + - run: 'gcloud info' + + # Configure docker to use the gcloud command-line tool as a credential helper + - name: Configure Docker + run: | + gcloud auth configure-docker -q + + # Docker build ; docker push + - name: Build and push + uses: docker/build-push-action@v3 + with: + push: true + tags: | + gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.tag_name }} + + # Update manifests - trigger remote action + - name: Repository Dispatch + uses: peter-evans/repository-dispatch@v1 + with: + token: ${{ secrets.REPO_ACCESS_TOKEN }} + repository: '${{ env.DEPLOYMENT_REPO }}' + event-type: render + client-payload: '{"image": "gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.IMAGE_NAME }}", "tag": "${{ steps.vars.outputs.tag_name }}", "ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' From 157075b1efa8fbd6f3ccb89dc7edd8abb782634b Mon Sep 17 00:00:00 2001 From: Bryan White Date: Tue, 16 Aug 2022 10:58:20 +0200 Subject: [PATCH 018/143] chore: subquery db storage size default value (#30) --- k8s/subquery/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/subquery/values.yaml b/k8s/subquery/values.yaml index 586ec8d9b..1c219c1ae 100644 --- a/k8s/subquery/values.yaml +++ b/k8s/subquery/values.yaml @@ -29,4 +29,4 @@ db: gcpProject: fetch-ai-sandbox gcpSecret: sandbox_subquery_postgres - storageSize: 1Gi + storageSize: 7Gi From a5d581f53615135945d890e8781da4c7f23ca1b5 Mon Sep 17 00:00:00 2001 From: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Date: Tue, 16 Aug 2022 16:57:24 +0300 Subject: [PATCH 019/143] Tests:e2e setup (#34) * chore: add GQL dependency * chore: add test setup --- Pipfile | 1 + test/__init__.py | 0 test/base.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 test/__init__.py create mode 100644 test/base.py diff --git a/Pipfile b/Pipfile index ca39b6e2f..5ee60502d 100644 --- a/Pipfile +++ b/Pipfile @@ -7,6 +7,7 @@ name = "pypi" cosmpy = "*" protobuf = "==3.20.*" bip-utils = "*" +gql = {extras = ["all"], version = "*"} [dev-packages] diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/base.py b/test/base.py new file mode 100644 index 000000000..e1e59afbd --- /dev/null +++ b/test/base.py @@ -0,0 +1,75 @@ +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey +from cosmpy.crypto.address import Address +from cosmpy.protos.cosmos.gov.v1beta1 import query_pb2_grpc +from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins +from cosmpy.aerial.client import LedgerClient, NetworkConfig, utils +from gql import Client +from gql.transport.aiohttp import AIOHTTPTransport +import grpc, unittest, psycopg + + +class Base(unittest.TestCase): + delegator_wallet = None + delegator_address = None + + validator_wallet = None + validator_address = None + validator_operator_address = None + + ledger_client = None + db = None + db_cursor = None + gql_client = None + + @classmethod + def setUpClass(cls): + validator_mnemonic = "nut grocery slice visit barrel peanut tumble patch slim logic install evidence fiction shield rich brown around arrest fresh position animal butter forget cost" + cls.validator_wallet = get_wallet(validator_mnemonic) + cls.validator_address = str(cls.validator_wallet.address()) + cls.validator_operator_address = Address(bytes(cls.validator_wallet.address()), prefix="fetchvaloper") + + delegator_mnemonic = "dismiss domain uniform image cute buzz ride anxiety nose canvas ripple stock buffalo bitter spirit maximum tone inner couch forum equal usage state scan" + cls.delegator_wallet = get_wallet(delegator_mnemonic) + cls.delegator_address = str(cls.delegator_wallet.address()) + + cfg = NetworkConfig( + chain_id="testing", + url="grpc+http://localhost:9090", + fee_minimum_gas_price=1, + fee_denomination="atestfet", + staking_denomination="atestfet", + ) + + gov_client = grpc.insecure_channel('localhost:9090') + + cls.ledger_client = LedgerClient(cfg) + cls.gov_module = query_pb2_grpc.QueryStub(gov_client) + + transport = AIOHTTPTransport(url="http://localhost:3000") + cls.gql_client = Client(transport=transport, fetch_schema_from_transport=True) + + cls.db = psycopg.connect( + host="localhost", + port="5432", + dbname="postgres", + user="postgres", + password="postgres", + options=f'-c search_path=app' + ) + + cls.db_cursor = cls.db.cursor() + + @classmethod + def tearDownClass(cls): + cls.db.close() + + +def get_wallet(mnemonic): + seed_bytes = Bip39SeedGenerator(mnemonic).Generate() + bip44_def_ctx = Bip44.FromSeed(seed_bytes, Bip44Coins.COSMOS).DeriveDefaultPath() + return LocalWallet(PrivateKey(bip44_def_ctx.PrivateKey().Raw().ToBytes())) + + +if __name__ == '__main__': + unittest.main() From 2e2bd95f7c0dc2181bb52b65f5b858c426ad1dfa Mon Sep 17 00:00:00 2001 From: Bryan White Date: Thu, 18 Aug 2022 17:04:07 +0200 Subject: [PATCH 020/143] chore: chart push action (#41) --- .github/workflows/charts_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/charts_push.yml b/.github/workflows/charts_push.yml index 9df19a1e7..bff289467 100644 --- a/.github/workflows/charts_push.yml +++ b/.github/workflows/charts_push.yml @@ -1,6 +1,6 @@ name: Chart Push on: - pull_request: + push: branches: - main paths: From 0d5f4740c6c6274d7b78412849fef766c43ee42b Mon Sep 17 00:00:00 2001 From: Bryan White Date: Thu, 18 Aug 2022 18:12:29 +0200 Subject: [PATCH 021/143] chore: more devops testing (#27) --- docker-compose.yml | 1 - k8s/subquery/Chart.yaml | 2 +- k8s/subquery/templates/api/api.cert.yml | 4 ++-- k8s/subquery/templates/api/api.cmap.yml | 2 +- k8s/subquery/templates/api/api.dns.yml | 4 ++-- k8s/subquery/templates/api/api.gateway.yml | 10 +++++----- k8s/subquery/templates/api/api.vsvc.yml | 4 ++-- k8s/subquery/templates/name/node.cmap.yml | 2 ++ k8s/subquery/values.yaml | 13 +++++++------ project.yaml | 6 +++--- 10 files changed, 25 insertions(+), 23 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0e351e8f1..f8a158eb4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,7 +30,6 @@ services: DB_DATABASE: "subquery" DB_HOST: postgres DB_PORT: 5432 - START_BLOCK: "7099815" volumes: - ./:/app command: diff --git a/k8s/subquery/Chart.yaml b/k8s/subquery/Chart.yaml index c21703f2f..a037e8dce 100644 --- a/k8s/subquery/Chart.yaml +++ b/k8s/subquery/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: subquery description: A helm chart for the deployment of the fetch.ai subquery node type: application -version: 0.1.0 +version: 0.1.1 appVersion: "1.16.0" diff --git a/k8s/subquery/templates/api/api.cert.yml b/k8s/subquery/templates/api/api.cert.yml index ca0c40b2a..a86357840 100644 --- a/k8s/subquery/templates/api/api.cert.yml +++ b/k8s/subquery/templates/api/api.cert.yml @@ -4,9 +4,9 @@ metadata: name: "subq-{{ .Release.Namespace }}-{{ .Release.Name }}-cert" namespace: istio-system spec: - commonName: {{ .Values.subquery.api.istio.dnsName }} + commonName: {{ .Values.subquery.api.dns.name }} dnsNames: - - {{ .Values.subquery.api.istio.dnsName }} + - {{ .Values.subquery.api.dns.name }} issuerRef: kind: ClusterIssuer name: letsencrypt-dns diff --git a/k8s/subquery/templates/api/api.cmap.yml b/k8s/subquery/templates/api/api.cmap.yml index d848bcd7e..f25961ab5 100644 --- a/k8s/subquery/templates/api/api.cmap.yml +++ b/k8s/subquery/templates/api/api.cmap.yml @@ -10,4 +10,4 @@ data: DB_DATABASE: {{ .Values.db.name | quote }} DB_USER: {{ .Values.db.user | quote }} DB_HOST: "subquery-db" - DB_PORT: "5432" \ No newline at end of file + DB_PORT: "5432" diff --git a/k8s/subquery/templates/api/api.dns.yml b/k8s/subquery/templates/api/api.dns.yml index 1da372ff7..7c6b36279 100644 --- a/k8s/subquery/templates/api/api.dns.yml +++ b/k8s/subquery/templates/api/api.dns.yml @@ -7,8 +7,8 @@ metadata: app.kubernetes.io/component: api spec: endpoints: - - dnsName: {{ .Values.subquery.api.istio.dnsName }} + - dnsName: {{ .Values.subquery.api.dns.name }} recordTTL: 180 recordType: CNAME targets: - - {{ .Values.subquery.api.istio.dnsTarget }} + - {{ .Values.subquery.api.dns.target }} diff --git a/k8s/subquery/templates/api/api.gateway.yml b/k8s/subquery/templates/api/api.gateway.yml index f30509e0e..93fd53b03 100644 --- a/k8s/subquery/templates/api/api.gateway.yml +++ b/k8s/subquery/templates/api/api.gateway.yml @@ -8,13 +8,13 @@ metadata: spec: selector: - app: {{ .Values.subquery.api.istio.ingressGateway }} + app: {{ .Values.subquery.api.dns.ingressGateway }} istio: ingressgateway servers: - hosts: - - {{ .Values.subquery.api.istio.dnsName }} - {{- range .Values.subquery.api.istio.additionalDnsNames }} + - {{ .Values.subquery.api.dns.name }} + {{- range .Values.subquery.api.dns.additionalNames }} - {{ . | quote }} {{- end }} port: @@ -24,8 +24,8 @@ spec: tls: httpsRedirect: false - hosts: - - {{ .Values.subquery.api.istio.dnsName }} - {{- range .Values.subquery.api.istio.additionalDnsNames }} + - {{ .Values.subquery.api.dns.name }} + {{- range .Values.subquery.api.dns.additionalNames }} - {{ . | quote }} {{- end }} port: diff --git a/k8s/subquery/templates/api/api.vsvc.yml b/k8s/subquery/templates/api/api.vsvc.yml index 1e224999b..589f6c946 100644 --- a/k8s/subquery/templates/api/api.vsvc.yml +++ b/k8s/subquery/templates/api/api.vsvc.yml @@ -11,8 +11,8 @@ spec: - subquery-api-gateway hosts: - - {{ .Values.subquery.api.istio.dnsName }} - {{- range .Values.subquery.api.istio.additionalDnsNames }} + - {{ .Values.subquery.api.dns.name }} + {{- range .Values.subquery.api.dns.additionalDnsNames }} - {{ . | quote }} {{- end }} diff --git a/k8s/subquery/templates/name/node.cmap.yml b/k8s/subquery/templates/name/node.cmap.yml index 81e7e0126..3f04df240 100644 --- a/k8s/subquery/templates/name/node.cmap.yml +++ b/k8s/subquery/templates/name/node.cmap.yml @@ -12,3 +12,5 @@ data: DB_HOST: "subquery-db" DB_PORT: "5432" START_BLOCK: {{ .Values.subquery.node.startBlock | quote }} + CHAIN_ID: {{ .Values.subquery.node.chainId }} + NETWORK_ENDPOINT: {{ .Values.subquery.node.networkEndpoint }} diff --git a/k8s/subquery/values.yaml b/k8s/subquery/values.yaml index 1c219c1ae..99e927c54 100644 --- a/k8s/subquery/values.yaml +++ b/k8s/subquery/values.yaml @@ -2,8 +2,9 @@ subquery: node: image: gcr.io/fetch-ai-sandbox/subquery-node tag: 239fe08 - - startBlock: "7099815" + startBlock: "827201" + networkEndpoint: https://rpc-dorado.fetch.ai:443 + chainId: "dorado-1" api: image: onfinality/subql-query @@ -11,13 +12,13 @@ subquery: replicas: 1 - istio: + dns: # Gateways - ingressGateway: istio-fetch-gaia-ig + ingressGateway: "istio-fetch-gaia-ig" # DNS Settings - dnsName: subquery-staging.sandbox-london-b.fetch-ai.com - dnsTarget: gaia.sandbox-london-b.fetch-ai.com + name: staging.subquery.sandbox-london-b.fetch-ai.com + target: "gaia.sandbox-london-b.fetch-ai.com" additionalDnsNames: [] db: diff --git a/project.yaml b/project.yaml index c8c40dd23..97d41ee4e 100644 --- a/project.yaml +++ b/project.yaml @@ -14,13 +14,13 @@ repository: https://github.com/fetchai/ledger-subquery schema: file: ./schema.graphql network: - chainId: fetchhub-4 - endpoint: https://rpc-fetchhub.fetch.ai:443 + chainId: dorado-1 + endpoint: https://rpc-dorado.fetch.ai:443 # Using a dictionary can massively improve indexing speed dictionary: https://api.subquery.network/sq/subquery/cosmos-fetch-ai-dictionary dataSources: - kind: cosmos/Runtime - startBlock: 7099815 # first block on fetchhub-4 + startBlock: 827201 chainTypes: # This is a beta feature that allows support for any Cosmos chain by importing the correct protobuf messages cosmos.slashing.v1beta1: file: "./proto/cosmos/slashing/v1beta1/tx.proto" From fa590db4d1776dbce0bd3bf73c3f855e1a28f3e7 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Fri, 19 Aug 2022 14:12:56 +0200 Subject: [PATCH 022/143] chore: custom graphql api image (#42) --- .dockerignore | 1 + .github/workflows/docker_build.yml | 20 +++++++++-- .github/workflows/docker_deploy_prod.yml | 19 +++++++--- .gitignore | 4 ++- .gitmodules | 4 +++ docker-compose.yml | 14 ++++++-- docker/api.dockerfile | 36 +++++++++++++++++++ Dockerfile => docker/node.dockerfile | 12 +++---- k8s/subquery/values.yaml | 2 +- scripts/{entrypoint.sh => node-entrypoint.sh} | 0 subql | 1 + 11 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 .gitmodules create mode 100644 docker/api.dockerfile rename Dockerfile => docker/node.dockerfile (74%) rename scripts/{entrypoint.sh => node-entrypoint.sh} (100%) create mode 160000 subql diff --git a/.dockerignore b/.dockerignore index 0d0b59974..e05dc6b4f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -18,6 +18,7 @@ scripts/*.py .gitignore # Docker +docker/ Dockerfile .dockerignore docker-compose.yml diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 7a02cf732..7f541323e 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -11,10 +11,13 @@ jobs: env: IMAGE_PROJECT_ID: fetch-ai-images - IMAGE_NAME: subquery-node + NODE_IMAGE_NAME: subquery-node + API_IMAGE_NAME: subquery-api steps: - uses: actions/checkout@v3 + with: + submodules: recursive - name: Get image tag id: vars @@ -39,9 +42,20 @@ jobs: gcloud auth configure-docker -q # Docker build ; docker push - - name: Build and push + - name: Build and push subquery node + uses: docker/build-push-action@v3 + with: + context: . + file: ./docker/node.dockerfile + push: true + tags: | + gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.NODE_IMAGE_NAME }}:${{ steps.vars.outputs.tag_name }} + + - name: Build and push graphql api uses: docker/build-push-action@v3 with: + context: . + file: ./docker/api.dockerfile push: true tags: | - gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.tag_name }} + gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.API_IMAGE_NAME }}:${{ steps.vars.outputs.tag_name }} diff --git a/.github/workflows/docker_deploy_prod.yml b/.github/workflows/docker_deploy_prod.yml index 91e1b61cd..2c7e4bbc7 100644 --- a/.github/workflows/docker_deploy_prod.yml +++ b/.github/workflows/docker_deploy_prod.yml @@ -11,17 +11,21 @@ jobs: env: IMAGE_PROJECT_ID: fetch-ai-images - IMAGE_NAME: subquery-node + NODE_IMAGE_NAME: subquery-node + API_IMAGE_NAME: subquery-api DEPLOYMENT_REPO: fetchai/infra-production-deployment steps: - uses: actions/checkout@v3 + with: + submodules: recursive - name: Get image tag id: vars shell: bash run: | - echo "::set-output name=tag_name::$(git rev-parse --short HEAD)" + echo "::set-output name=node_tag_name::$(git rev-parse --short HEAD)" + echo "::set-output name=api_tag_name::$(cd ./subql && git rev-parse --short HEAD)" # Authenticate to GCP - id: 'auth' @@ -40,12 +44,19 @@ jobs: gcloud auth configure-docker -q # Docker build ; docker push - - name: Build and push + - name: Build and push subquery node + uses: docker/build-push-action@v3 + with: + push: true + tags: | + gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.NODE_IMAGE_NAME }}:${{ steps.vars.outputs.node_tag_name }} + + - name: Build and push graphql api uses: docker/build-push-action@v3 with: push: true tags: | - gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.tag_name }} + gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.API_IMAGE_NAME }}:${{ steps.vars.outputs.api_tag_name }} # Update manifests - trigger remote action - name: Repository Dispatch diff --git a/.gitignore b/.gitignore index 084e13ece..0db42d78b 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,6 @@ Thumbs.db .data dist -keys \ No newline at end of file +keys + +docker-compose.override.yml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..d37b4cdd8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "subql"] + path = subql + url = https://github.com/fetchai/subql + branch = main diff --git a/docker-compose.yml b/docker-compose.yml index f8a158eb4..1cd0d3a8b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,8 +18,9 @@ services: retries: 5 subquery-node: -# image: onfinality/subql-node-cosmos:v0.2.0 - build: . + build: + context: . + dockerfile: ./docker/node.dockerfile depends_on: "postgres": condition: service_healthy @@ -43,7 +44,9 @@ services: retries: 10 graphql-engine: - image: onfinality/subql-query:v1.4.0 + build: + context: . + dockerfile: ./docker/api.dockerfile ports: - 3000:3000 depends_on: @@ -58,6 +61,11 @@ services: DB_DATABASE: "subquery" DB_HOST: postgres DB_PORT: 5432 + volumes: + - ./subql/packages/query/dist:/app/dist + - ./subql/node_modules:/app/node_modules + - ./subql/packages/query/node_modules/.bin:/app/node_modules/.bin + entrypoint: ["/sbin/tini", "--", "yarn", "start:prod"] command: - --name=app - --playground diff --git a/docker/api.dockerfile b/docker/api.dockerfile new file mode 100644 index 000000000..97911bc61 --- /dev/null +++ b/docker/api.dockerfile @@ -0,0 +1,36 @@ +FROM node:16-slim AS builder + +RUN apt-get update && apt-get install -y tree + +WORKDIR /build + +# declare path args +ARG subql_path=../subql +ARG query_pkg_path=${subql_path}/packages/query +ARG subql_package_json_path=${subql_path}/package.json +ARG subql_yarn_lock_path=${subql_path}/yarn.lock + +# add the dependencies +ADD $subql_package_json_path $subql_yarn_lock_path /build/ + +# add the remaining parts of the produce the build +COPY $subql_path /build/ + +# TODO: +#RUN yarn install --frozen-lockfile +RUN yarn install +RUN yarn build + +FROM onfinality/subql-query:v1.4.0 + +WORKDIR /app + +COPY --from=builder /build/packages/query/dist /app/dist +COPY --from=builder /build/packages/query/bin /app/bin +COPY --from=builder /build/packages/query/package.json /app/ +COPY --from=builder /build/node_modules /app/node_modules + +COPY --from=builder /build/packages/common/dist /app/node_modules/@subql/common +COPY --from=builder /build/packages/utils/dist /app/node_modules/@subql/utils + +ENTRYPOINT ["/sbin/tini", "--", "yarn", "start:prod"] diff --git a/Dockerfile b/docker/node.dockerfile similarity index 74% rename from Dockerfile rename to docker/node.dockerfile index 8d6fef642..d663ff011 100644 --- a/Dockerfile +++ b/docker/node.dockerfile @@ -5,11 +5,11 @@ RUN apt-get update && apt-get install -y tree WORKDIR /app # add the dependencies -ADD package.json yarn.lock /app/ +ADD ../package.json yarn.lock /app/ RUN yarn install --frozen-lockfile # add the remaining parts of the produce the build -COPY . /app +COPY .. /app RUN yarn codegen && yarn build FROM onfinality/subql-node-cosmos:v0.2.0 @@ -21,12 +21,12 @@ RUN chmod +x /usr/local/bin/yq WORKDIR /app # add the dependencies -ADD package.json yarn.lock /app/ +ADD ../package.json yarn.lock /app/ RUN yarn install --frozen-lockfile --prod COPY --from=builder /app/dist /app/dist -ADD proto /app/proto -ADD project.yaml schema.graphql /app/ -ADD scripts/entrypoint.sh / +ADD ../proto /app/proto +ADD ../project.yaml schema.graphql /app/ +ADD ../scripts/node-entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] diff --git a/k8s/subquery/values.yaml b/k8s/subquery/values.yaml index 99e927c54..f012af93d 100644 --- a/k8s/subquery/values.yaml +++ b/k8s/subquery/values.yaml @@ -7,7 +7,7 @@ subquery: chainId: "dorado-1" api: - image: onfinality/subql-query + image: gcr.io/fetch-ai-sandbox/subquery-api tag: v1.4.0 replicas: 1 diff --git a/scripts/entrypoint.sh b/scripts/node-entrypoint.sh similarity index 100% rename from scripts/entrypoint.sh rename to scripts/node-entrypoint.sh diff --git a/subql b/subql new file mode 160000 index 000000000..095699caa --- /dev/null +++ b/subql @@ -0,0 +1 @@ +Subproject commit 095699caae7d1ab96a626bdbdd8baaed3376267f From 98456b9a43c4b3cd3a6b431c9d85b0a2e2354349 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Fri, 19 Aug 2022 15:31:34 +0200 Subject: [PATCH 023/143] fix: docker deploy prod action (#44) --- .github/workflows/docker_deploy_prod.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/docker_deploy_prod.yml b/.github/workflows/docker_deploy_prod.yml index 2c7e4bbc7..d13c653bc 100644 --- a/.github/workflows/docker_deploy_prod.yml +++ b/.github/workflows/docker_deploy_prod.yml @@ -47,6 +47,8 @@ jobs: - name: Build and push subquery node uses: docker/build-push-action@v3 with: + context: . + file: ./docker/node.dockerfile push: true tags: | gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.NODE_IMAGE_NAME }}:${{ steps.vars.outputs.node_tag_name }} @@ -54,6 +56,8 @@ jobs: - name: Build and push graphql api uses: docker/build-push-action@v3 with: + context: . + file: ./docker/node.dockerfile push: true tags: | gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.API_IMAGE_NAME }}:${{ steps.vars.outputs.api_tag_name }} From 0ba780246b978fe81a406cbc2f7ccef1dbfcffb8 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 22 Aug 2022 10:52:05 +0200 Subject: [PATCH 024/143] fix: dockerfile paths (#45) --- .dockerignore | 1 + docker/api.dockerfile | 2 +- docker/node.dockerfile | 12 ++++++------ scripts/build-docker.sh | 18 +++++++++++++++--- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.dockerignore b/.dockerignore index e05dc6b4f..2bf4dc4dd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -33,3 +33,4 @@ docker-compose.yml *.md LICENSE .nvmrc +.data/ diff --git a/docker/api.dockerfile b/docker/api.dockerfile index 97911bc61..83db3bc61 100644 --- a/docker/api.dockerfile +++ b/docker/api.dockerfile @@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y tree WORKDIR /build # declare path args -ARG subql_path=../subql +ARG subql_path=./subql ARG query_pkg_path=${subql_path}/packages/query ARG subql_package_json_path=${subql_path}/package.json ARG subql_yarn_lock_path=${subql_path}/yarn.lock diff --git a/docker/node.dockerfile b/docker/node.dockerfile index d663ff011..8688e2e27 100644 --- a/docker/node.dockerfile +++ b/docker/node.dockerfile @@ -5,11 +5,11 @@ RUN apt-get update && apt-get install -y tree WORKDIR /app # add the dependencies -ADD ../package.json yarn.lock /app/ +ADD ./package.json yarn.lock /app/ RUN yarn install --frozen-lockfile # add the remaining parts of the produce the build -COPY .. /app +COPY . /app RUN yarn codegen && yarn build FROM onfinality/subql-node-cosmos:v0.2.0 @@ -21,12 +21,12 @@ RUN chmod +x /usr/local/bin/yq WORKDIR /app # add the dependencies -ADD ../package.json yarn.lock /app/ +ADD ./package.json yarn.lock /app/ RUN yarn install --frozen-lockfile --prod COPY --from=builder /app/dist /app/dist -ADD ../proto /app/proto -ADD ../project.yaml schema.graphql /app/ -ADD ../scripts/node-entrypoint.sh /entrypoint.sh +ADD ./proto /app/proto +ADD ./project.yaml schema.graphql /app/ +ADD ./scripts/node-entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] diff --git a/scripts/build-docker.sh b/scripts/build-docker.sh index ff0e5e614..6dd10e154 100755 --- a/scripts/build-docker.sh +++ b/scripts/build-docker.sh @@ -1,9 +1,21 @@ #!/bin/bash set -e -version=$(git describe --always --tags --dirty=-wip) +image=$1 +if [[ $image == "" ]]; then + echo "Usage: build-docker.sh [git root]" + echo "" + echo "Examples:" + echo " ./build-docker.sh subquery-node ./docker/node.dockerfile" + echo " ./build-docker.sh subquery-api ./docker/api.dockerfile ./subql" + exit 1 +fi + +dockerfile_path=${2:-"$(pwd)/Dockerfile"} +git_root=${3:-"."} + +version=$(cd ${git_root} && git describe --always --tags --dirty=-wip | sed -e "s,/,_,g") registry="gcr.io/fetch-ai-sandbox" -image="subquery-node" full_tag="${registry}/${image}:${version}" @@ -14,7 +26,7 @@ if [[ "${full_tag}" == *wip ]]; then fi # build the image -docker build -t "${full_tag}" . +docker build -t "${full_tag}" -f "${dockerfile_path}" . # push the image to the registry docker push "${full_tag}" From 8092bcffe07f6c7c230b3f87ab18e1b67534b418 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Tue, 23 Aug 2022 22:25:22 +0200 Subject: [PATCH 025/143] tests: e2e native primitive coverage (#46) --- Pipfile | 1 + Pipfile.lock | 554 ++++++++++++++++++++++++++++++--- test/base.py | 29 +- test/helpers/field_enums.py | 63 ++++ test/test_native_primitives.py | 241 ++++++++++++++ 5 files changed, 828 insertions(+), 60 deletions(-) create mode 100644 test/helpers/field_enums.py create mode 100644 test/test_native_primitives.py diff --git a/Pipfile b/Pipfile index 5ee60502d..7129edb77 100644 --- a/Pipfile +++ b/Pipfile @@ -8,6 +8,7 @@ cosmpy = "*" protobuf = "==3.20.*" bip-utils = "*" gql = {extras = ["all"], version = "*"} +psycopg = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 10723d0bb..ed5ff8dbd 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f0ac1fbb884f14766b1b3706e80998cbbfd712c14b920a1fd94506db1524f946" + "sha256": "fe7fcd4d416f546d2c5c448ab8d89dd41de5027936c972b4dce0f83203ce8f68" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,91 @@ ] }, "default": { + "aiohttp": { + "hashes": [ + "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3", + "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782", + "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75", + "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf", + "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7", + "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675", + "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1", + "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785", + "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4", + "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf", + "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5", + "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15", + "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca", + "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8", + "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac", + "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8", + "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef", + "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516", + "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700", + "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2", + "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8", + "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0", + "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676", + "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad", + "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155", + "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db", + "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd", + "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091", + "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602", + "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411", + "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93", + "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd", + "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec", + "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51", + "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7", + "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17", + "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d", + "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00", + "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923", + "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440", + "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32", + "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e", + "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1", + "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724", + "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a", + "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8", + "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2", + "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33", + "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b", + "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2", + "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632", + "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b", + "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2", + "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316", + "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74", + "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96", + "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866", + "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44", + "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950", + "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa", + "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c", + "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a", + "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd", + "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd", + "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9", + "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421", + "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2", + "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922", + "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4", + "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237", + "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642", + "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578" + ], + "version": "==3.8.1" + }, + "aiosignal": { + "hashes": [ + "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a", + "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.0" + }, "asn1crypto": { "hashes": [ "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", @@ -23,6 +108,30 @@ ], "version": "==1.5.1" }, + "async-timeout": { + "hashes": [ + "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", + "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.2" + }, + "attrs": { + "hashes": [ + "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", + "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" + ], + "markers": "python_version >= '3.5'", + "version": "==22.1.0" + }, + "backoff": { + "hashes": [ + "sha256:407f1bc0f22723648a8880821b935ce5df8475cf04f7b6b5017ae264d30f6069", + "sha256:b135e6d7c7513ba2bfd6895bc32bc8c66c6f3b0279b4c6cd866053cfd7d3126b" + ], + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==2.1.2" + }, "bech32": { "hashes": [ "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899", @@ -33,35 +142,42 @@ }, "bip-utils": { "hashes": [ - "sha256:eeb70f0401e08d381ad1f4cc2fc54caf4fafa4b50614797f3de1a5cdf158e1f8" + "sha256:ce8c4434937508a86f4bc2473dfe0db6c821cf165157cde9225ba43a43b4c699" ], "index": "pypi", - "version": "==2.5.1" + "version": "==2.6.0" }, "blspy": { "hashes": [ - "sha256:03c07511ce6e784878fb81cb4320a9a2cab39977e65a673da4dc153ed69d56e5", - "sha256:13f7f65f4be6ed515aa23a95a3e55382fb11fdbbd0e5fdf7a92a5915a4512fa4", - "sha256:184983bb8ca4db2653b8f2acb2ddedc7a40fd48b66ee2c4e3dd37cbfe113a8bd", - "sha256:299f5537cbc752629e58004419244a6855aa8ff40b14228ebdad43fe99977494", - "sha256:3017cf87e209bd1852d274432c7a912ff430cdfa648ba8158a92d071a242ea2c", - "sha256:36633f512928d20354177a39b432cc06590b857ca54d1b540f7efbf8aae627a8", - "sha256:3f4ce55c388f2ef49c2bac894360d5f258a7c3666d276457b5a2a65078cc73d7", - "sha256:4a6294ae5cf4bef8f2cd9a0a27f781ce582dacbdcf302704237f1648c9c356c8", - "sha256:50654052fa7162207e035f5d375009fcc1727c9650a563e7ecea2e7745cc87be", - "sha256:571f9db9c435f719d50d2f984bcce0cabc951c742dc8dd831a7e77056a425d1c", - "sha256:613e4a64a38cfe51f172c4b94c4cc986cccdd46ca896d77b70d428bbd5dc5c26", - "sha256:6e61f1b258710625b3dde363843fe828f7ff8d93c3242df8a04a50e040b2370f", - "sha256:82c1aa54cabfa1a94d4779e2cafbdb6cdd6f53f6c76de07f620eba93ad951dca", - "sha256:b7b5c1f275676bbfa46dd50a0697912601ab6c47b46bbae645791351ffe08fe1", - "sha256:c1a18b3ffdf36d6b522f372c840596b232432f485c38669dfd542eb8e71f0055", - "sha256:cbb81a893a8d56e739cf6698092ee222e8bf4fe6939b37897826a8685c117dce", - "sha256:dd1e4cb1f280d008d00a0de7c4102e64e6ecf52053a9b18fb41b67c6eafce5f6", - "sha256:fccbb5076dc8215b8f11333a65f17b4494d07816695b2502a2158337449b7b5e", - "sha256:fe5d27f6b10b34e5f2706a9b0c85e8d217bac96f648189eb6bf05fb2282809b2" + "sha256:1656acfc3f90e5b382cb410a0ad6bc04d8a754413b2e76ceb85b3781f0060a36", + "sha256:41f094534deb1a9d48b31f9080f7103a2a7a510417d68a8f7c776542a8a5a63e", + "sha256:515d5e603fe5519f0babe4d86919ada58844ef332c98b6e1f56e2494b213e5bc", + "sha256:58ec9edd6b244c4deedf9e05496a06bb9d0472db8664427fc623344d5bedd68e", + "sha256:5ad21255460c9e71fdaf2c41f666417ecb4dc1bc6635c49eea6b64b02f85e6ee", + "sha256:62f7f4596ae549d255ca0d51a1451fb9f2d00aef7d82ad65be43a9a097584c13", + "sha256:6e3c4886d148b83d645c6d4ada92bd9dc74a2a09e494b29742fe10e184cf5afb", + "sha256:78b46c6a4c12a485ee871f6542a0f20a37db2c6a509572c166689db957d018a2", + "sha256:7d06a59dc3fc71c3cef2201405539bcab1ed8e5ca0c8ba3ae41e619d89055f1d", + "sha256:8691c2186d329fe874124e1fa1183bcfc515b21078c49f20e185a279e02a482d", + "sha256:9548e58b21d7f35500d09bff1e73f12033915ed137fa1cadd3690514d062362d", + "sha256:9b3d67f3c5f5f1ed2c28a7ebfdf5825ca61bd05e1c1cf6f6fe52d2c8b2ed0d76", + "sha256:9ead4c59c805bc5ef6682dbc59440d7cd5ebabc4f8b448bc132bf5f0b6e93a01", + "sha256:b8087f32fb614cb48edd492a8b3984e9b3cc2f1ceb04a48c0967c89fc612f370", + "sha256:ca98a765331f0eae78a5a4f86f2db223dff3cca08148a0753694de4a47f7e641", + "sha256:dd0530482674e14d02d6f9ea8ac5be42ebebd43e9e22ac7fbaa24625572a72d7", + "sha256:e8e2b0f65fc7b4da48777437230d59e183521221a2da2fb7654a6de06f55519b", + "sha256:ef44d0f3556711b5056d0c10d314da28a857eb01f1c068c5520411bb1af01141", + "sha256:f4af785b457690632f2268e866afb480a3572001318f765c731c4f58fcc85c5f" ], "markers": "python_version >= '3.7'", - "version": "==1.0.14" + "version": "==1.0.15" + }, + "botocore": { + "hashes": [ + "sha256:81101abab2013992a84019739d85a6fa8ec6f1f42fb09ad07e0bf4c8f0efce60", + "sha256:81df81b1a1c5608b3a7aca6a837acb27552c3ecc92584a58b171c60754b1d4eb" + ], + "version": "==1.27.56" }, "cachetools": { "hashes": [ @@ -71,6 +187,40 @@ "markers": "python_version ~= '3.7'", "version": "==5.2.0" }, + "cbor2": { + "hashes": [ + "sha256:0a3a1b2f6b83ab4ce806df48360cc16d34cd315f17549dbda9fdd371bea04497", + "sha256:0e4ae67a697c664b579b87c4ef9d60e26c146b95bff443a9a38abb16f6981ff0", + "sha256:20291dad09cf9c4e5f434d376dd9d60f5ab5e066b308005f50e7c5e22e504214", + "sha256:2de925608dc6d73cd1aab08800bff38f71f90459c15db3a71a67023b0fc697da", + "sha256:2f30f7ef329ea6ec630ceabe5a539fed407b9c81e27e2322644e3efbbd1b2a76", + "sha256:37ae0ce5afe864d1a1c5b05becaf8aaca7b7131cb7b0b935d7e79b29fb1cea28", + "sha256:3843a9bb970343e9c896aa71a34fa80983cd0ddec6eacdb2284b5e83f4ee7511", + "sha256:4b09ff6148a8cd529512479a1d6521fb7687fb03b448973933c3b03711d00bfc", + "sha256:4e8590193fcbbb9477010ca0f094f6540a5e723965c90eea7a37edbe75f0ec4d", + "sha256:5aaf3406c9d661d11f87e792edb9a38561dba1441afba7fb883d6d963e67f32c", + "sha256:5c50da4702ac5ca3a8e7cb9f34f62b4ea91bc81b76c2fba03888b366da299cd8", + "sha256:62b863c5ee6ced4032afe948f3c1484f375550995d3b8498145237fe28e546c2", + "sha256:62fc15bfe187e4994c457e6055687514c417d6099de62dd33ae766561f05847e", + "sha256:6bc8c5606aa0ae510bdb3c7d987f92df39ef87d09e0f0588a4d1daffd3cb0453", + "sha256:6fab0e00c28305db59f7005150447d08dd13da6a82695a2132c28beba590fd2c", + "sha256:70789805b9aebd215626188aa05bb09908ed51e3268d4db5ae6a08276efdbcb1", + "sha256:8a643b19ace1584043bbf4e2d0b4fae8bebd6b6ffab14ea6478d3ff07f58e854", + "sha256:9538ab1b4207e76ee02a52362d77e312921ec1dc75b6fb42182887d87d0ca53e", + "sha256:981b9ffc4f2947a0f030e71ce5eac31334bc81369dd57c6c1273c94c6cdb0b5a", + "sha256:ab6c934806759d453a9bb5318f2703c831e736be005ac35d5bd5cf2093ba57b1", + "sha256:b35c5d4d14fe804f718d5a5968a528970d2a7046aa87045538f189a98e5c7055", + "sha256:c07975f956baddb8dfeca4966f1871fd2482cb36af24c461f763732a44675225", + "sha256:c617c7f94936d65ed9c8e99c6c03e3dc83313d69c6bfea810014ec658e9b1a9d", + "sha256:cbca58220f52fd50d8985e4079e10c71196d538fb6685f157f608a29253409a4", + "sha256:cbe7cdeed26cd8ec2dcfed2b8876bc137ad8b9e0abb07aa5fb05770148a4b5c7", + "sha256:d21ccd1ec802e88dba1c373724a09538a0237116ab589c5301ca4c59478f7c10", + "sha256:d549abea7115c8a0d7c61a31a895c031f902a7b4c875f9efd8ce41e466baf83a", + "sha256:e10f2f4fcf5ab6a8b24d22f7109f48cad8143f669795899370170d7b36ed309f" + ], + "markers": "python_version >= '3.7'", + "version": "==5.4.3" + }, "certifi": { "hashes": [ "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", @@ -150,11 +300,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", - "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "version": "==2.1.1" }, "click": { "hashes": [ @@ -206,11 +356,11 @@ }, "cosmpy": { "hashes": [ - "sha256:15c65063a5c10b7ff9ffd83aa35d8281dbf1b51b7da8b9b7a0e131fe517d5fa9", - "sha256:cd60a1637ab0cab20c62ac3e17537e337d3f76fd89b11ef1e3e5007f0ae51f81" + "sha256:08319ecfaab5bfcfad8418be4197625d7bfd2697188e35bd20848f262211bb57", + "sha256:cb50a5aa0ca9ab60db0ada2cbf3b81611a5325ab1cb7e0a5f58cfb3832d4f084" ], "index": "pypi", - "version": "==0.5.0" + "version": "==0.5.1" }, "crcmod": { "hashes": [ @@ -223,11 +373,11 @@ }, "ecdsa": { "hashes": [ - "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676", - "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa" + "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49", + "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd" ], "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.17.0" + "version": "==0.18.0" }, "ed25519-blake2b": { "hashes": [ @@ -235,6 +385,71 @@ ], "version": "==1.4" }, + "frozenlist": { + "hashes": [ + "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e", + "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04", + "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944", + "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845", + "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f", + "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f", + "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb", + "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2", + "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3", + "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f", + "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a", + "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131", + "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1", + "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa", + "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8", + "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b", + "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2", + "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e", + "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b", + "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b", + "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10", + "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170", + "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8", + "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b", + "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989", + "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd", + "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03", + "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519", + "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189", + "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b", + "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca", + "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff", + "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96", + "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d", + "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5", + "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2", + "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204", + "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a", + "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8", + "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141", + "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792", + "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9", + "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c", + "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c", + "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221", + "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d", + "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc", + "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f", + "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9", + "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef", + "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3", + "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab", + "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b", + "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db", + "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988", + "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6", + "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc", + "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83", + "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" + }, "google-api-core": { "hashes": [ "sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc", @@ -245,19 +460,19 @@ }, "google-api-python-client": { "hashes": [ - "sha256:0ae3fa7cad2102e24a587986cee53afdbc95bd69b58f2c08f8fd881a20da5f14", - "sha256:ac8c020d4db00914df4f8981236c109959f76c699b0ded428055cf9503c737cb" + "sha256:cbc39ac20322d6da3989cb54271f15ae6e58fa1ce63bf1fbb1ac1cdeebbc7b6a", + "sha256:ec4412545b0c5978a833bb03993a46121ad2c700f32af0cba23f8439b3f5fb02" ], - "markers": "python_version >= '3.6'", - "version": "==2.52.0" + "markers": "python_version >= '3.7'", + "version": "==2.57.0" }, "google-auth": { "hashes": [ - "sha256:3b2f9d2f436cc7c3b363d0ac66470f42fede249c3bafcc504e9f0bcbe983cff0", - "sha256:75b3977e7e22784607e074800048f44d6a56df589fb2abe58a11d4d20c97c314" + "sha256:be62acaae38d0049c21ca90f27a23847245c9f161ff54ede13af2cb6afecbac9", + "sha256:ed65ecf9f681832298e29328e1ef0a3676e3732b2e56f41532d45f70a22de0fb" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==2.9.0" + "version": "==2.11.0" }, "google-auth-httplib2": { "hashes": [ @@ -268,11 +483,30 @@ }, "googleapis-common-protos": { "hashes": [ - "sha256:6f1369b58ed6cf3a4b7054a44ebe8d03b29c309257583a2bbdc064cd1e4a1442", - "sha256:87955d7b3a73e6e803f2572a33179de23989ebba725e05ea42f24838b792e461" + "sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394", + "sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417" ], - "markers": "python_version >= '3.6'", - "version": "==1.56.3" + "markers": "python_version >= '3.7'", + "version": "==1.56.4" + }, + "gql": { + "extras": [ + "all" + ], + "hashes": [ + "sha256:59c8a0b8f0a2f3b0b2ff970c94de86f82f65cb1da3340bfe57143e5f7ea82f71", + "sha256:ca81aa8314fa88a8c57dd1ce34941278e0c352d762eb721edcba0387829ea7c0" + ], + "index": "pypi", + "version": "==3.4.0" + }, + "graphql-core": { + "hashes": [ + "sha256:9d1bf141427b7d54be944587c8349df791ce60ade2e3cccaf9c56368c133c201", + "sha256:f83c658e4968998eed1923a2e3e3eddd347e005ac0315fbb7ca4d70ea9156323" + ], + "markers": "python_version >= '3.6' and python_version < '4.0'", + "version": "==3.2.1" }, "grpcio": { "hashes": [ @@ -342,6 +576,79 @@ "markers": "python_version >= '3.5'", "version": "==3.3" }, + "jmespath": { + "hashes": [ + "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", + "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" + ], + "markers": "python_version >= '3.7'", + "version": "==1.0.1" + }, + "multidict": { + "hashes": [ + "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", + "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c", + "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672", + "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51", + "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032", + "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2", + "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b", + "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80", + "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88", + "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a", + "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d", + "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389", + "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c", + "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9", + "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c", + "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516", + "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b", + "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43", + "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee", + "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227", + "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d", + "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae", + "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7", + "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4", + "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9", + "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f", + "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013", + "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9", + "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e", + "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693", + "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a", + "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15", + "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb", + "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96", + "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87", + "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376", + "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658", + "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0", + "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071", + "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360", + "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc", + "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3", + "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba", + "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8", + "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9", + "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2", + "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3", + "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68", + "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8", + "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d", + "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49", + "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608", + "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57", + "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86", + "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20", + "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293", + "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849", + "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937", + "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d" + ], + "markers": "python_version >= '3.7'", + "version": "==6.0.2" + }, "protobuf": { "hashes": [ "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf", @@ -372,6 +679,14 @@ "index": "pypi", "version": "==3.20.1" }, + "psycopg": { + "hashes": [ + "sha256:0f5e18920ed978f5063e48acc5ca4389225db7d06a03090d2bbb7a0ec7a640b2", + "sha256:44ca63373c33957ca852fefa1940f8cc5d4c11493b7f6710b0ab250ff5abc50c" + ], + "index": "pypi", + "version": "==3.0.16" + }, "py-sr25519-bindings": { "hashes": [ "sha256:0046dda17c554376f5ba11ea91163b1b883ac61fcc1b1ba588e31b1cb58add28", @@ -383,19 +698,23 @@ "sha256:1ca0d8e1ca66a17c41e3f6da0c3837e7dbf5b391eb4ea41cfe6026bad0adc8ea", "sha256:1fb9ca7b65e60b64bd6e81ee2d5e6556d93b317af3c95a2e24bf45a3c44041a0", "sha256:2ec17c935f8ae9a00bb5d6adac6cc455fd6052042959ace5563448c355e2960a", + "sha256:34635da67e6798bb4535543e63c3e1d3b19f17a4df1f9b613c69b66fc8296fa3", "sha256:34786ba33f602d9d4f2495bd29d9f0cb357813a6ee8ae5cd9d37dbda86c141a4", "sha256:3845f55a88dcba825c16e65fc9e26742cbb9103fba5229fd0c3e9e8deffd323b", "sha256:3f52666b02075483dfdd294b9d0fd903eaf96ac7140e5be45390b25ded52ca6a", "sha256:42097fab2702186a6591471bc366d7c804c7c30744acd59d6c6a38fdcad4bedb", + "sha256:465c2ae7e3191f24ac10519aece185772a6b9d6179b8948fad78c3b1dbe77d8c", "sha256:4940ebfcb482b33468c41dda9e8c19137c14cc3b993e1084e55387c6acfa11ab", "sha256:5a0f591f9b474ca31f0ca199ebc5d2c70a8869ff6e211188a1562310614558dd", "sha256:5e12ca977014f148f4bfb6ba662f1529b15cc8ce030719d726c4e16a379e976e", "sha256:6715852eb2ea733c4978b77f9a049cc9eb763d54270d5b1263b198256656c38e", "sha256:67dd46da0a571859418fc938af15ab9a0586e47244d21f0f26c5cbf4daf10a3b", + "sha256:6be4f52320d021fe8a86d72d71fae821b6c251f12b36e7e3deee5a1140da55b6", "sha256:721cae28e86038682dd522191f9cb58a71b3455ef461433ba27a76d2d321197c", "sha256:72b49cba419f5c76436bf06de4575ee27713afd8b867c4c14cb7a29e1046c30a", "sha256:855af2efee23ebbb4b5544027b2b9a4818db34717b88741bcfd74f5739e9152f", "sha256:8e62369aca9455131330cf63b0fcec3be198cabad936317321aa0db9d143435b", + "sha256:9b80e4aa4037b1e8fd3d5ff0fb4e2c3d92e04b2936f29c79d5411c5a22562f25", "sha256:9cbee7341ed35d08533c7dac4d227e2fe21b815072ef66178a7ddb77f4e7be36", "sha256:9e07cc588754da27c03185e2d3823537790f616b5aab3df1703ca0f6f578114c", "sha256:9e5118cc7e1a77083307d260ccda21a0ec14efb0b98535f8fc9e2c54c7594836", @@ -513,6 +832,14 @@ "markers": "python_version >= '3.1'", "version": "==3.0.9" }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, "python-mbedtls": { "hashes": [ "sha256:08da2f4e08865d53213d0f1153efa02adaa6cd730d81af7e27e68d0fc061a6dd", @@ -536,16 +863,23 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_version >= '3.7' and python_version < '4.0'", "version": "==2.28.1" }, + "requests-toolbelt": { + "hashes": [ + "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", + "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" + ], + "version": "==0.9.1" + }, "rsa": { "hashes": [ - "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17", - "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb" + "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", + "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" ], "markers": "python_version >= '3.6'", - "version": "==4.8" + "version": "==4.9" }, "six": { "hashes": [ @@ -572,11 +906,64 @@ }, "urllib3": { "hashes": [ - "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", - "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", + "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", + "version": "==1.26.12" + }, + "websockets": { + "hashes": [ + "sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af", + "sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c", + "sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76", + "sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47", + "sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69", + "sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079", + "sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c", + "sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55", + "sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02", + "sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559", + "sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3", + "sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e", + "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978", + "sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98", + "sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae", + "sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755", + "sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d", + "sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991", + "sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1", + "sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680", + "sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247", + "sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f", + "sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2", + "sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7", + "sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4", + "sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667", + "sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb", + "sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094", + "sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36", + "sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79", + "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500", + "sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e", + "sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582", + "sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442", + "sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd", + "sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6", + "sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731", + "sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4", + "sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d", + "sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8", + "sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f", + "sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677", + "sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8", + "sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9", + "sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e", + "sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b", + "sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916", + "sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.9" + "version": "==10.3" }, "wheel": { "hashes": [ @@ -585,6 +972,71 @@ ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==0.37.1" + }, + "yarl": { + "hashes": [ + "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb", + "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3", + "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035", + "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453", + "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d", + "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a", + "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231", + "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f", + "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae", + "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b", + "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3", + "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507", + "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd", + "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae", + "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe", + "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c", + "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4", + "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64", + "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357", + "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54", + "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461", + "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4", + "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497", + "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0", + "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1", + "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957", + "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350", + "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780", + "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843", + "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548", + "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6", + "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40", + "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee", + "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b", + "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6", + "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0", + "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e", + "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880", + "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc", + "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e", + "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead", + "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28", + "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf", + "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd", + "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae", + "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0", + "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0", + "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae", + "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda", + "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546", + "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802", + "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be", + "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07", + "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936", + "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272", + "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc", + "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a", + "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28", + "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b" + ], + "markers": "python_version >= '3.7'", + "version": "==1.8.1" } }, "develop": {} diff --git a/test/base.py b/test/base.py index e1e59afbd..a7963025e 100644 --- a/test/base.py +++ b/test/base.py @@ -8,6 +8,16 @@ from gql.transport.aiohttp import AIOHTTPTransport import grpc, unittest, psycopg +# TODO: support overriding somehow (e.g. CLI args) +DB_HOST = "localhost" +DB_PORT = "5432" +DB_NAME = "subquery" +DB_USER = "subquery" +DB_PASS = "subquery" +FETCHD_HOST = "localhost" +FETCHD_GRPC_PORT = "9090" +GRAPHQL_API_URL = "http://localhost:3000" + class Base(unittest.TestCase): delegator_wallet = None @@ -35,26 +45,26 @@ def setUpClass(cls): cfg = NetworkConfig( chain_id="testing", - url="grpc+http://localhost:9090", + url=f"grpc+http://{FETCHD_HOST}:{FETCHD_GRPC_PORT}", fee_minimum_gas_price=1, fee_denomination="atestfet", staking_denomination="atestfet", ) - gov_client = grpc.insecure_channel('localhost:9090') + gov_client = grpc.insecure_channel(f"{FETCHD_HOST}:{FETCHD_GRPC_PORT}") cls.ledger_client = LedgerClient(cfg) cls.gov_module = query_pb2_grpc.QueryStub(gov_client) - transport = AIOHTTPTransport(url="http://localhost:3000") + transport = AIOHTTPTransport(url=GRAPHQL_API_URL) cls.gql_client = Client(transport=transport, fetch_schema_from_transport=True) cls.db = psycopg.connect( - host="localhost", - port="5432", - dbname="postgres", - user="postgres", - password="postgres", + host=DB_HOST, + port=DB_PORT, + dbname=DB_NAME, + user=DB_USER, + password=DB_PASS, options=f'-c search_path=app' ) @@ -62,7 +72,8 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.db.close() + if cls.db is not None: + cls.db.close() def get_wallet(mnemonic): diff --git a/test/helpers/field_enums.py b/test/helpers/field_enums.py new file mode 100644 index 000000000..7f568e37d --- /dev/null +++ b/test/helpers/field_enums.py @@ -0,0 +1,63 @@ +from enum import Enum + + +class NamedFields(Enum): + @classmethod + def select_column_names(cls): + return [f'"{field.name}"' for field in cls] + + @classmethod + def select_query(cls, table): + return f"SELECT {', '.join(cls.select_column_names())} FROM {table}" + + +class BlockFields(NamedFields): + id = 0 + chain_id = 1 + height = 2 + timestamp = 3 + + @classmethod + def select_query(cls, table="blocks"): + return super().select_query(table) + + +class TxFields(NamedFields): + id = 0 + block_id = 1 + gas_used = 2 + gas_wanted = 3 + fees = 4 + memo = 5 + status = 6 + log = 7 + timeout_height = 8 + + @classmethod + def select_query(cls, table="transactions"): + return super().select_query(table) + + +class MsgFields(NamedFields): + id = 0 + transaction_id = 1 + block_id = 2 + type_url = 3 + json = 4 + + @classmethod + def select_query(cls, table="messages"): + return super().select_query(table) + + +class EventFields(NamedFields): + id = 0 + transaction_id = 1 + block_id = 2 + type = 3 + attributes = 4 + + @classmethod + def select_query(cls, table="events"): + return super().select_query(table) + diff --git a/test/test_native_primitives.py b/test/test_native_primitives.py new file mode 100644 index 000000000..3b921dcde --- /dev/null +++ b/test/test_native_primitives.py @@ -0,0 +1,241 @@ +import json +import re + +from gql import gql + +import base +import time +import unittest + +import base +from helpers.field_enums import BlockFields, TxFields, MsgFields, EventFields +from helpers.regexes import block_id_regex, tx_id_regex, msg_id_regex, event_id_regex + + +class TestNativePrimitives(base.Base): + tables = ("blocks", "transactions", "messages", "events") + + amount = 5000000 + denom = "atestfet" + + expected_blocks_len = 2 + expected_txs_len = 2 + expected_msgs_len = 2 + expected_msg_type_url = '/cosmos.bank.v1beta1.MsgSend' + # NB: for each transfer: + # - coin_received + # - coin_spent + # - message + # - transfer + expected_events_len = 2 * 4 + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.db_cursor.execute(f"TRUNCATE table {', '.join(cls.tables)} CASCADE") + cls.db.commit() + + for table in list(reversed(cls.tables)): + results = cls.db_cursor.execute(f"SELECT id FROM {table}").fetchall() + if len(results) != 0: + raise Exception(f"truncation of table \"{table}\" failed, {len(results)} records remain") + + tx = cls.ledger_client.send_tokens(cls.delegator_wallet.address(), cls.amount, cls.denom, cls.validator_wallet) + tx.wait_to_complete() + if not tx.response.is_successful(): + raise Exception(f"first set-up tx failed") + + tx = cls.ledger_client.send_tokens(cls.delegator_wallet.address(), cls.amount, cls.denom, cls.validator_wallet) + tx.wait_to_complete() + if not tx.response.is_successful(): + raise Exception(f"second set-up tx failed") + + # Wait for subql node to sync + time.sleep(5) + + def test_blocks(self): + blocks = self.db_cursor.execute(BlockFields.select_query()).fetchall() + self.assertNotEqual(blocks, [], f"\nDBError: block table is empty - maybe indexer did not find an entry?") + + self.assertGreaterEqual(len(blocks), self.expected_blocks_len) + for block in blocks: + # NB: continually increments while test run + self.assertGreaterEqual(len(blocks), 2) + self.assertRegex(block[BlockFields.id.value], block_id_regex) + # TODO: expect proper chainId + self.assertNotEqual(block[BlockFields.chain_id.value], "") + self.assertTrue(block[BlockFields.height.value] > 0) + # TODO: assert timestamp within last 5 min + # TODO: timestamp is a number + self.assertNotEqual(block[BlockFields.timestamp.value], "") + + def test_blocks_query(self): + query = gql(""" + query { + blocks { + nodes { + id, + chainId, + height, + timestamp + } + } + } + """) + + result = self.gql_client.execute(query) + blocks = result["blocks"]["nodes"] + self.assertIsNotNone(blocks) + self.assertGreater(len(blocks), 0) + + for block in blocks: + # TODO: expect proper chainId + self.assertNotEqual(block["chainId"], "") + self.assertGreater(int(block["height"]), 0) + # TODO: timestamp should be unix timestamp + self.assertNotEqual(block["timestamp"], "") + + def test_transactions(self): + txs = self.db_cursor.execute(TxFields.select_query()).fetchall() + self.assertEqual(len(txs), self.expected_txs_len) + for tx in txs: + self.assertRegex(tx[TxFields.id.value], tx_id_regex) + self.assertTrue(len(tx[TxFields.block_id.value]) == 64) + self.assertGreater(tx[TxFields.gas_used.value], 0) + self.assertGreater(tx[TxFields.gas_wanted.value], 0) + + fees = json.loads(tx[TxFields.fees.value]) + # print(fees[0]) + self.assertEqual(len(fees), 1) + self.assertEqual(fees[0]["denom"], self.denom) + self.assertGreater(int(fees[0]["amount"]), 0) + self.assertEqual(tx[TxFields.memo.value], "") + self.assertEqual(tx[TxFields.status.value], "Success") + self.assertNotEqual(tx[TxFields.log.value], "") + + def test_transactions_query(self): + query = gql(""" + query { + transactions { + nodes { + id + block { + id + } + gasUsed + gasWanted + # TODO: + # fees + } + } + } + """) + + result = self.gql_client.execute(query) + txs = result["transactions"]["nodes"] + self.assertIsNotNone(txs) + self.assertEqual(len(txs), self.expected_txs_len) + + for tx in txs: + self.assertRegex(tx["id"], tx_id_regex) + self.assertRegex(tx["block"]["id"], block_id_regex) + self.assertGreater(int(tx["gasUsed"]), 0) + self.assertGreater(int(tx["gasWanted"]), 0) + # TODO: fees + + def test_messages(self): + + msgs = self.db_cursor.execute(MsgFields.select_query()).fetchall() + self.assertEqual(len(msgs), self.expected_msgs_len) + for msg in msgs: + self.assertRegex(msg[MsgFields.id.value], msg_id_regex) + self.assertRegex(msg[MsgFields.transaction_id.value], tx_id_regex) + self.assertNotEqual(msg[MsgFields.block_id.value], "") + self.assertEqual(msg[MsgFields.type_url.value], self.expected_msg_type_url) + self.assertNotEqual(msg[MsgFields.json.value], "") + + def test_messages_query(self): + query = gql(""" + query { + messages { + nodes { + id + block { + id + } + transaction { + id + } + typeUrl + json + } + } + } + """) + + result = self.gql_client.execute(query) + msgs = result["messages"]["nodes"] + self.assertIsNotNone(msgs) + self.assertEqual(len(msgs), self.expected_msgs_len) + + for msg in msgs: + self.assertRegex(msg["id"], msg_id_regex) + self.assertRegex(msg["block"]["id"], block_id_regex) + self.assertRegex(msg["transaction"]["id"], tx_id_regex) + self.assertEqual(msg["typeUrl"], self.expected_msg_type_url) + # TODO: assert on parsed json (?) + self.assertNotEqual(msg["json"], "") + + def test_events(self): + events = self.db_cursor.execute(EventFields.select_query()).fetchall() + self.assertEqual(len(events), self.expected_events_len) + for event in events: + self.assertRegex(event[EventFields.id.value], event_id_regex) + self.assertRegex(event[EventFields.transaction_id.value], tx_id_regex) + self.assertNotEqual(event[EventFields.block_id.value], "") + self.assertNotEqual(event[EventFields.type.value], "") + # TODO: more assertions (?) + + def test_events_query(self): + query = gql(""" + query { + events { + nodes { + id + block { + id + } + transaction { + id + } + attributes + } + } + } + """) + + result = self.gql_client.execute(query) + events = result["events"]["nodes"] + self.assertIsNotNone(events) + self.assertEqual(len(events), self.expected_events_len) + + for event in events: + self.assertRegex(event["id"], event_id_regex) + self.assertRegex(event["block"]["id"], block_id_regex) + self.assertRegex(event["transaction"]["id"], tx_id_regex) + + attributes = event["attributes"] + self.assertGreater(len(attributes), 0) + for attr in attributes: + for field in ["key", "value"]: + self.assertTrue(field in list(attr)) + self.assertNotEqual(attr[field], "") + + # These three event types have an "amount" key/value + if attr["key"] in ["coin_spent", "coin_received", "transfer"]: + self.assertEqual(attr["value"], f"{self.amount}{self.denom}") + + +if __name__ == '__main__': + unittest.main() From 3e1bc6747c183b61f303b36565eaba7fe6207f0e Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 24 Aug 2022 11:03:32 +0200 Subject: [PATCH 026/143] chore: change block timestamp to Date type (#47) --- schema.graphql | 4 ++-- src/mappings/mappingHandlers.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/schema.graphql b/schema.graphql index 48aa2f3db..f11770e89 100644 --- a/schema.graphql +++ b/schema.graphql @@ -20,7 +20,7 @@ type Block @entity { id: ID! # The block header hash chainId: String! @index height: BigInt! @index - timestamp: String! + timestamp: Date! transactions: [Transaction] @derivedFrom(field: "block") messages: [Message] @derivedFrom(field: "block") events: [Event] @derivedFrom(field: "block") @@ -118,4 +118,4 @@ type NativeTransfer @entity { message: NativeTransferMsg! transaction: Transaction! block: Block! -} \ No newline at end of file +} diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 59fe492ea..f8b2e1e1c 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -30,12 +30,12 @@ function messageId(msg: CosmosMessage | CosmosEvent): string { export async function handleBlock(block: CosmosBlock): Promise { logger.info(`[handleBlock] (block.header.height): indexing block ${block.block.header.height}`) - const {id, header: {chainId, height, time: timestamp}} = block.block; + const {id, header: {chainId, height, time}} = block.block; + const timestamp = new Date(time); const blockEntity = Block.create({ id, chainId, height: BigInt(height), - // TODO: convert to unix timestamp and store as Int. timestamp, }); From 40a241d15a9af56f7d4c4d577bb3e0e4aae2d1a2 Mon Sep 17 00:00:00 2001 From: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Date: Wed, 24 Aug 2022 13:24:19 +0100 Subject: [PATCH 027/143] test: add native transfer e2e test (#35) * test: add native transfer e2e test * test: expand native transfer queries * chore: update Pipfile & .lock * refactor: amend variable names & add TODOs * chore: amend native transfer schema and base.py helper func --- Pipfile | 1 + Pipfile.lock | 28 +++---- schema.graphql | 11 ++- src/mappings/mappingHandlers.ts | 10 ++- test/base.py | 20 ++++- test/test_native_transfer.py | 137 ++++++++++++++++++++++++++++++++ 6 files changed, 184 insertions(+), 23 deletions(-) create mode 100644 test/test_native_transfer.py diff --git a/Pipfile b/Pipfile index 7129edb77..21251656a 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,7 @@ protobuf = "==3.20.*" bip-utils = "*" gql = {extras = ["all"], version = "*"} psycopg = "*" +python-dateutil = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index ed5ff8dbd..16dc71d19 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "fe7fcd4d416f546d2c5c448ab8d89dd41de5027936c972b4dce0f83203ce8f68" + "sha256": "cefbf14b4853883edb80222128e149bad3e3f9d770e66abda964cabbb7f9125c" }, "pipfile-spec": 6, "requires": { @@ -129,7 +129,7 @@ "sha256:407f1bc0f22723648a8880821b935ce5df8475cf04f7b6b5017ae264d30f6069", "sha256:b135e6d7c7513ba2bfd6895bc32bc8c66c6f3b0279b4c6cd866053cfd7d3126b" ], - "markers": "python_version >= '3.7' and python_version < '4.0'", + "markers": "python_version >= '3.7' and python_version < '4'", "version": "==2.1.2" }, "bech32": { @@ -142,10 +142,10 @@ }, "bip-utils": { "hashes": [ - "sha256:ce8c4434937508a86f4bc2473dfe0db6c821cf165157cde9225ba43a43b4c699" + "sha256:74caa7d15126155f006213d5daaa3dbadb4ed79dbb59a2a352708d92cc4d5297" ], "index": "pypi", - "version": "==2.6.0" + "version": "==2.6.1" }, "blspy": { "hashes": [ @@ -174,10 +174,10 @@ }, "botocore": { "hashes": [ - "sha256:81101abab2013992a84019739d85a6fa8ec6f1f42fb09ad07e0bf4c8f0efce60", - "sha256:81df81b1a1c5608b3a7aca6a837acb27552c3ecc92584a58b171c60754b1d4eb" + "sha256:4cc12ab3b6f195292e3e34da6aa356edb8acee51e8405d048b50ccbb64a1b9f3", + "sha256:d904ee4743bd4b2517634b012c98dcb166f82e9d20e50d8367cd078779181ec2" ], - "version": "==1.27.56" + "version": "==1.27.58" }, "cachetools": { "hashes": [ @@ -460,11 +460,11 @@ }, "google-api-python-client": { "hashes": [ - "sha256:cbc39ac20322d6da3989cb54271f15ae6e58fa1ce63bf1fbb1ac1cdeebbc7b6a", - "sha256:ec4412545b0c5978a833bb03993a46121ad2c700f32af0cba23f8439b3f5fb02" + "sha256:3af6a181763a8cb18f2b9d973760ba32e4fe2af09e4ce06626ff6a53777c33fa", + "sha256:8f4eeed1b46f3f445307719aa3e9678e429d90c0af4f22ada707a72ba10fe02d" ], "markers": "python_version >= '3.7'", - "version": "==2.57.0" + "version": "==2.58.0" }, "google-auth": { "hashes": [ @@ -505,7 +505,7 @@ "sha256:9d1bf141427b7d54be944587c8349df791ce60ade2e3cccaf9c56368c133c201", "sha256:f83c658e4968998eed1923a2e3e3eddd347e005ac0315fbb7ca4d70ea9156323" ], - "markers": "python_version >= '3.6' and python_version < '4.0'", + "markers": "python_version >= '3.6' and python_version < '4'", "version": "==3.2.1" }, "grpcio": { @@ -837,7 +837,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "index": "pypi", "version": "==2.8.2" }, "python-mbedtls": { @@ -863,7 +863,7 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '3.7' and python_version < '4.0'", + "markers": "python_version >= '3.7' and python_version < '4'", "version": "==2.28.1" }, "requests-toolbelt": { @@ -909,7 +909,7 @@ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", "version": "==1.26.12" }, "websockets": { diff --git a/schema.graphql b/schema.graphql index f11770e89..a803d1f94 100644 --- a/schema.graphql +++ b/schema.graphql @@ -108,14 +108,17 @@ type Coin @jsonField { } type NativeTransferMsg @jsonField { - toAddress: String! # indexable - fromAddress: String! #indexable - amount: [Coin]! # indexible + toAddress: String! @index + fromAddress: String! @index + amount: [Coin]! @index } type NativeTransfer @entity { id: ID! - message: NativeTransferMsg! + toAddress: String! @index + fromAddress: String! @index + amounts: [Coin]! + denom: String! @index transaction: Transaction! block: Block! } diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index f8b2e1e1c..54cb62835 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -75,10 +75,15 @@ export async function handleTransaction(tx: CosmosTransaction): Promise { export async function handleNativeTransfer(msg: CosmosMessage): Promise { logger.info(`[handleNativeTransfer] (tx ${msg.tx.hash}): indexing message ${msg.idx + 1} / ${msg.tx.decodedTx.body.messages.length}`) logger.debug(`[handleNativeTransfer] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) - + const {toAddress, fromAddress, amount: amounts} = msg.msg.decodedMsg; + // workaround: assuming one denomination per transfer message + const denom = amounts[0].denom; const transferEntity = NativeTransfer.create({ id: messageId(msg), - message: msg.msg.decodedMsg, + toAddress, + fromAddress, + amounts, + denom, transactionId: msg.tx.hash, blockId: msg.block.block.id }); @@ -120,7 +125,6 @@ export async function handleEvent(event: CosmosEvent): Promise { export async function handleExecuteContractMessage(msg: CosmosMessage): Promise { logger.info(`[handleExecuteContractMessage] (tx ${msg.tx.hash}): indexing ExecuteContractMessage ${messageId(msg)}`) logger.debug(`[handleExecuteContractMessage] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) - const id = messageId(msg); const msgEntity = ExecuteContractMessage.create({ id, diff --git a/test/base.py b/test/base.py index a7963025e..ae1749a6a 100644 --- a/test/base.py +++ b/test/base.py @@ -4,9 +4,9 @@ from cosmpy.protos.cosmos.gov.v1beta1 import query_pb2_grpc from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins from cosmpy.aerial.client import LedgerClient, NetworkConfig, utils -from gql import Client +from gql import Client, gql from gql.transport.aiohttp import AIOHTTPTransport -import grpc, unittest, psycopg +import grpc, unittest, psycopg, dateutil.parser as dp # TODO: support overriding somehow (e.g. CLI args) DB_HOST = "localhost" @@ -75,6 +75,22 @@ def tearDownClass(cls): if cls.db is not None: cls.db.close() + def get_latest_block_timestamp(self): + query_get_time = gql( # get the timestamp from the latest block + """ + query getDate { + blocks (orderBy:TIMESTAMP_DESC, first:1) { + nodes { + timestamp + } + } + } + """ + ) + result = self.gql_client.execute(query_get_time)["blocks"]["nodes"][0]["timestamp"] + result = dp.parse(result) # parse into datetime obj + return result + def get_wallet(mnemonic): seed_bytes = Bip39SeedGenerator(mnemonic).Generate() diff --git a/test/test_native_transfer.py b/test/test_native_transfer.py new file mode 100644 index 000000000..8c87fa8ed --- /dev/null +++ b/test/test_native_transfer.py @@ -0,0 +1,137 @@ +from gql import gql +import time, unittest, base, dateutil.parser as dp, datetime as dt, json + + +class TestNativeTransfer(base.Base): + amount = 5000000 + denom = "atestfet" + msg_type = '/cosmos.bank.v1beta1.MsgSend' + db_query = 'SELECT amounts, denom, to_address, from_address from native_transfers' + + def test_native_transfer(self): + self.db_cursor.execute('TRUNCATE table native_transfers') + self.db.commit() + self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nError: table not empty after truncation") + + tx = self.ledger_client.send_tokens(self.delegator_wallet.address(), self.amount, self.denom, self.validator_wallet) + tx.wait_to_complete() + self.assertTrue(tx.response.is_successful(), "\nTXError: transfer tx unsuccessful") + + # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution + time.sleep(5) + + native_transfer = self.db_cursor.execute(self.db_query).fetchone() + self.assertIsNotNone(native_transfer, "\nDBError: table is empty - maybe indexer did not find an entry?") + self.assertEqual(native_transfer[0][0]['amount'], str(self.amount), "\nDBError: fund amount does not match") + self.assertEqual(native_transfer[1], self.denom, "\nDBError: fund denomination does not match") + self.assertEqual(native_transfer[2], self.delegator_address, "\nDBError: swap sender address does not match") + self.assertEqual(native_transfer[3], self.validator_address, "\nDBError: sender address does not match") + + def test_retrieve_transfer(self): # As of now, this test depends on the execution of the previous test in this class. + result = self.get_latest_block_timestamp() + time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before + time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format + time_latest = json.dumps(result.isoformat()) + + # query native transactions, query related block and filter by timestamp, returning all within last five minutes + query_get_by_range = gql( + """ + query getByRange { + nativeTransfers ( + filter: { + block: { + timestamp: { + greaterThanOrEqualTo: """ + time_before + """, + lessThanOrEqualTo: """ + time_latest + """ + } + } + }) { + nodes { + denom + toAddress + fromAddress + } + } + } + """ + ) + + # query native transactions, filter by recipient address + query_get_by_to_address = gql( + """ + query getByToAddress { + nativeTransfers ( + filter: { + toAddress: { + equalTo: """+json.dumps(self.delegator_address)+""" + } + } + ) { + nodes { + denom + toAddress + fromAddress + } + } + } + """ + ) + + # query native transactions, filter by sender address + query_get_by_from_address = gql( + """ + query getByFromAddress { + nativeTransfers ( + filter: { + fromAddress: { + equalTo: """+json.dumps(self.validator_address)+""" + } + } + ) { + nodes { + denom + toAddress + fromAddress + } + } + } + """ + ) + + # query native transactions, filter by denomination + query_get_by_denom = gql( + """ + query getByDenom { + nativeTransfers ( + filter: { + denom: { + equalTo:\""""+self.denom+"""\" + } + }) { + nodes { + denom + toAddress + fromAddress + } + } + } + """ + ) + + queries = [query_get_by_range, query_get_by_to_address, query_get_by_from_address, query_get_by_denom] + for query in queries: + result = self.gql_client.execute(query) + """ + ["nativeTransfers"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. + This provides {"toAddress":address, "fromAddress":address, "denom":denom, "amount":["amount":amount, "denom":denom]} + which can be destructured for the values of interest. + """ + message = result["nativeTransfers"]["nodes"] + self.assertTrue(message, "\nGQLError: No results returned from query") + self.assertEqual(message[0]["denom"], self.denom, "\nGQLError: fund denomination does not match") + self.assertEqual(message[0]["toAddress"], self.delegator_address, "\nGQLError: destination address does not match") + self.assertEqual(message[0]["fromAddress"], self.validator_address, "\nGQLError: from address does not match") + + +if __name__ == '__main__': + unittest.main() From edcff7196c6c1116465517d3623047a8c2eb3039 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 24 Aug 2022 16:00:26 +0200 Subject: [PATCH 028/143] test: add missing helper: regexes.py (#48) --- test/helpers/regexes.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 test/helpers/regexes.py diff --git a/test/helpers/regexes.py b/test/helpers/regexes.py new file mode 100644 index 000000000..a7bbc63cd --- /dev/null +++ b/test/helpers/regexes.py @@ -0,0 +1,7 @@ +import re + +block_id_regex = re.compile("^\w{64}$") +tx_id_regex = block_id_regex +msg_id_regex = re.compile("^\w{64}-\d+$") +event_id_regex = re.compile("^\w{64}-\d+-\d+$") +native_addr_id_regex = re.compile("^fetch\w{64}-\d+-\d+$") From 1d243095de7dd45a02adf6d78534a3c50bc91d10 Mon Sep 17 00:00:00 2001 From: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Date: Wed, 24 Aug 2022 15:51:38 +0100 Subject: [PATCH 029/143] test: add gov proposal vote e2e test (#36) --- test/test_gov_proposal_vote.py | 149 +++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 test/test_gov_proposal_vote.py diff --git a/test/test_gov_proposal_vote.py b/test/test_gov_proposal_vote.py new file mode 100644 index 000000000..51e19864b --- /dev/null +++ b/test/test_gov_proposal_vote.py @@ -0,0 +1,149 @@ +from cosmpy.aerial.tx import Transaction +from cosmpy.protos.cosmos.gov.v1beta1 import tx_pb2 as gov_tx, gov_pb2 +from cosmpy.protos.cosmos.base.v1beta1 import coin_pb2 +from cosmpy.aerial.client import utils +from google.protobuf import any_pb2 +from gql import gql +import base, json, time, unittest, datetime as dt + + +class TestGovernance(base.Base): + vote_tx = None + denom = "atestfet" + amount = "10000000" + option = 'YES' + db_query = 'SELECT voter_address, option from gov_proposal_votes' + + def setUp(self): + proposal_content = any_pb2.Any() + proposal_content.Pack(gov_pb2.TextProposal( + title="Test Proposal", + description="This is a test proposal" + ), "") + + msg = gov_tx.MsgSubmitProposal( + content=proposal_content, + initial_deposit=[coin_pb2.Coin( + denom=self.denom, + amount=self.amount + )], + proposer=self.validator_address + ) + + tx = Transaction() + tx.add_message(msg) + + tx = utils.prepare_and_broadcast_basic_transaction(self.ledger_client, tx, self.validator_wallet) + tx.wait_to_complete() + self.assertTrue(tx.response.is_successful(), "\nTXError: governance proposal tx unsuccessful") + + self.msg = gov_tx.MsgVote( + proposal_id=1, + voter=self.validator_address, + option=gov_pb2.VoteOption.VOTE_OPTION_YES + ) + self.vote_tx = Transaction() + self.vote_tx.add_message(self.msg) + + def test_proposal_vote(self): + self.db_cursor.execute('TRUNCATE table gov_proposal_votes') + self.db.commit() + self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nDBError: table not empty after truncation") + + tx = utils.prepare_and_broadcast_basic_transaction(self.ledger_client, self.vote_tx, self.validator_wallet) + tx.wait_to_complete() + self.assertTrue(tx.response.is_successful(), "\nTXError: vote tx unsuccessful") + + # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution + time.sleep(5) + + row = self.db_cursor.execute(self.db_query).fetchone() + self.assertIsNotNone(row, "\nDBError: table is empty - maybe indexer did not find an entry?") + self.assertEqual(row[0], self.validator_address, "\nDBError: voter address does not match") + self.assertEqual(row[1], self.option, "\nDBError: voter option does not match") + + def test_retrieve_vote(self): # As of now, this test depends on the execution of the previous test in this class. + result = self.get_latest_block_timestamp() + time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before + time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format + time_latest = json.dumps(result.isoformat()) + + # query governance votes, query related block and filter by timestamp, returning all within last five minutes + query_by_timestamp = gql( + """ + query get_votes_by_timestamp { + govProposalVotes ( + filter: { + block: { + timestamp: { + greaterThanOrEqualTo: """ + time_before + """, + lessThanOrEqualTo: """ + time_latest + """ + } + } + }) { + nodes { + transactionId + voterAddress + option + } + } + } + """ + ) + + # query governance votes, filter by voter + query_by_voter = gql( + """ + query get_votes_by_voter { + govProposalVotes ( + filter: { + voterAddress: { + equalTo: \""""+str(self.validator_address)+"""\" + } + }) { + nodes { + transactionId + voterAddress + option + } + } + } + """ + ) + + # query governance votes, filter by option + query_by_option = gql( + """ + query get_votes_by_option { + govProposalVotes ( + filter: { + option: { + equalTo: """+self.option+""" + } + }) { + nodes { + transactionId + voterAddress + option + } + } + } + """ + ) + + queries = [query_by_timestamp, query_by_voter, query_by_option] + for query in queries: + result = self.gql_client.execute(query) + """ + ["govProposalVotes"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. + This provides {"voterAddress":voter address, "option":voter option} + which can be destructured for the values of interest. + """ + message = result["govProposalVotes"]["nodes"] + self.assertTrue(message[0], "\nGQLError: No results returned from query") + self.assertEqual(message[0]["voterAddress"], self.validator_address, "\nGQLError: voter address does not match") + self.assertEqual(message[0]["option"], self.option, "\nGQLError: voter option does not match") + + +if __name__ == '__main__': + unittest.main() From 4b05e092c2a16fc455cd0cba570cd03904e335bb Mon Sep 17 00:00:00 2001 From: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Date: Wed, 24 Aug 2022 15:57:02 +0100 Subject: [PATCH 030/143] test: add delegation reward claim e2e test (#38) --- test/test_delegation_reward_claim.py | 116 +++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 test/test_delegation_reward_claim.py diff --git a/test/test_delegation_reward_claim.py b/test/test_delegation_reward_claim.py new file mode 100644 index 000000000..261201f53 --- /dev/null +++ b/test/test_delegation_reward_claim.py @@ -0,0 +1,116 @@ +import base +from gql import gql +import time, unittest, datetime as dt, json + + +class TestDelegation(base.Base): + amount = 100 + db_query = 'SELECT delegator_address, validator_address from dist_delegator_claims' + + def setUp(self): + delegate_tx = self.ledger_client.delegate_tokens(self.validator_operator_address, self.amount, self.validator_wallet) + delegate_tx.wait_to_complete() + self.assertTrue(delegate_tx.response.is_successful(), "\nTXError: delegation tx unsuccessful") + + def test_claim_rewards(self): + self.db_cursor.execute('TRUNCATE table dist_delegator_claims') + self.db.commit() + self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nDBError: table not empty after truncation") + + claim_tx = self.ledger_client.claim_rewards(self.validator_operator_address, self.validator_wallet) + claim_tx.wait_to_complete() + self.assertTrue(claim_tx.response.is_successful(), "\nTXError: reward claim tx unsuccessful") + + # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution + time.sleep(5) + + row = self.db_cursor.execute(self.db_query).fetchone() + self.assertIsNotNone(row, "\nDBError: table is empty - maybe indexer did not find an entry?") + self.assertEqual(row[0], self.validator_address, "\nDBError: delegation address does not match") + self.assertEqual(row[1], self.validator_operator_address, "\nDBError: delegation address does not match") + + def test_retrieve_claim(self): # As of now, this test depends on the execution of the previous test in this class. + result = self.get_latest_block_timestamp() + time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before + time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format + time_latest = json.dumps(result.isoformat()) + + # query governance votes, query related block and filter by timestamp, returning all within last five minutes + query_by_timestamp = gql( + """ + query get_votes_by_timestamp { + distDelegatorClaims ( + filter: { + block: { + timestamp: { + greaterThanOrEqualTo: """ + time_before + """, + lessThanOrEqualTo: """ + time_latest + """ + } + } + }) { + nodes { + transactionId + validatorAddress + delegatorAddress + } + } + } + """ + ) + + # query delegator reward claims, filter by validator address + query_by_validator = gql( + """ + query getByValidator { + distDelegatorClaims ( + filter: { + validatorAddress: { + equalTo:\""""+str(self.validator_operator_address)+"""\" + } + }) { + nodes { + transactionId + validatorAddress + delegatorAddress + } + } + } + """ + ) + + # query delegator reward claims, filter by delegator address + query_by_delegator = gql( + """ + query getByValidator { + distDelegatorClaims ( + filter: { + delegatorAddress: { + equalTo:\""""+str(self.validator_address)+"""\" + } + }) { + nodes { + transactionId + validatorAddress + delegatorAddress + } + } + } + """ + ) + + queries = [query_by_timestamp, query_by_validator, query_by_delegator] + for query in queries: + result = self.gql_client.execute(query) + """ + ["distDelegatorClaims"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. + This provides {"delegatorAddress":delegator address, "validatorAddress":validator option} + which can be destructured for the values of interest. + """ + message = result["distDelegatorClaims"]["nodes"] + self.assertTrue(message[0], "\nGQLError: No results returned from query") + self.assertEqual(message[0]["delegatorAddress"], self.validator_address, "\nGQLError: delegation address does not match") + self.assertEqual(message[0]["validatorAddress"], self.validator_operator_address, "\nGQLError: validator address does not match") + + +if __name__ == '__main__': + unittest.main() From e85ad893a63118e5aec9dfe49d64816da6e820ce Mon Sep 17 00:00:00 2001 From: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Date: Thu, 25 Aug 2022 10:01:45 +0100 Subject: [PATCH 031/143] test: add legacy bridge swap e2e test (#37) --- test/test_legacy_bridge_swap.py | 147 ++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 test/test_legacy_bridge_swap.py diff --git a/test/test_legacy_bridge_swap.py b/test/test_legacy_bridge_swap.py new file mode 100644 index 000000000..19ceaf4c8 --- /dev/null +++ b/test/test_legacy_bridge_swap.py @@ -0,0 +1,147 @@ +from cosmpy.aerial.contract import LedgerContract +from gql import gql +import time, unittest, os, requests, base, decimal, datetime as dt, json + + +class TestContractSwap(base.Base): + contract = None + amount = decimal.Decimal(10000) + denom = "atestfet" + db_query = 'SELECT destination, amount, denom from legacy_bridge_swaps' + + def setUp(self): + url = "https://github.com/fetchai/fetch-ethereum-bridge-v1/releases/download/v0.2.0/bridge.wasm" + if not os.path.exists("../.contract"): + os.mkdir("../.contract") + try: + temp = open("../.contract/bridge.wasm", "rb") + temp.close() + except: + contract_request = requests.get(url) + file = open("../.contract/bridge.wasm", "wb").write(contract_request.content) + + self.contract = LedgerContract("../.contract/bridge.wasm", self.ledger_client) + + # TODO: avoid deploying with every test run + # Instead, query for active contract and skip if found. + + self.contract.deploy( + {"cap": "250000000000000000000000000", + "reverse_aggregated_allowance": "3000000000000000000000000", + "reverse_aggregated_allowance_approver_cap": "3000000000000000000000000", + "lower_swap_limit": "1", + "upper_swap_limit": "1000000000000000000000000", + "swap_fee": "0", + "paused_since_block": 18446744073709551615, + "denom": "atestfet", + "next_swap_id": 0 + }, + self.validator_wallet + ) + + def test_contract_swap(self): + self.db_cursor.execute('TRUNCATE table legacy_bridge_swaps') + self.db.commit() + self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nDBError: table not empty after truncation") + + self.contract.execute( + {"swap": {"destination": self.validator_address}}, + self.validator_wallet, + funds=str(self.amount)+self.denom + ) + + # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution + time.sleep(12) + + row = self.db_cursor.execute(self.db_query).fetchone() + self.assertIsNotNone(row, "\nDBError: table is empty - maybe indexer did not find an entry?") + self.assertEqual(row[0], self.validator_address, "\nDBError: swap sender address does not match") + self.assertEqual(row[1], self.amount, "\nDBError: fund amount does not match") + self.assertEqual(row[2], self.denom, "\nDBError: fund denomination does not match") + + def test_retrieve_swap(self): # As of now, this test depends on the execution of the previous test in this class. + result = self.get_latest_block_timestamp() + time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before + time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format + time_latest = json.dumps(result.isoformat()) + + # query legacy bridge swaps, query related block and filter by timestamp, returning all within last five minutes + query_get_by_range = gql( + """ + query blocker { + legacyBridgeSwaps ( + filter: { + block: { + timestamp: { + greaterThanOrEqualTo: """ + time_before + """, + lessThanOrEqualTo: """ + time_latest + """ + } + } + }) { + nodes { + destination + amount + denom + } + } + } + """ + ) + + # query bridge swaps, filter by destination address - TODO: match correct 'destination' address + query_get_by_address = gql( + """ + query getByAddress { + legacyBridgeSwaps ( + filter: { + destination: { + equalTo:\""""+str(self.validator_address)+"""\" + } + }) { + nodes { + destination + amount + denom + } + } + } + """ + ) + + # query legacy bridge swaps, filter by amount + query_get_by_amount = gql( + """ + query getByAmount { + legacyBridgeSwaps ( + filter: { + amount: { + greaterThan: "1" + } + }) { + nodes { + destination + amount + denom + } + } + } + """ + ) + + queries = [query_get_by_range, query_get_by_amount, query_get_by_address] + for query in queries: + result = self.gql_client.execute(query) + """ + ["legacyBridgeSwaps"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. + This provides {"destination":destination address, "amount":amount, "denom":denomination} + which can be destructured for the values of interest. + """ + message = result["legacyBridgeSwaps"]["nodes"] + self.assertTrue(message, "\nGQLError: No results returned from query") + self.assertEqual(message[0]["destination"], self.validator_address, "\nGQLError: swap destination address does not match") + self.assertEqual(int(message[0]["amount"]), int(self.amount), "\nGQLError: fund amount does not match") + self.assertEqual(message[0]["denom"], self.denom, "\nGQLError: fund denomination does not match") + + +if __name__ == '__main__': + unittest.main() From 39abb15d27c48a704e295bb735a0f1f1ee32b422 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Thu, 25 Aug 2022 16:01:17 +0200 Subject: [PATCH 032/143] fix: serialize binary event attribute values with JSON (#49) --- src/mappings/mappingHandlers.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 54cb62835..10bdef269 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -110,10 +110,16 @@ export async function handleEvent(event: CosmosEvent): Promise { logger.debug(`[handleEvent] (event.event): ${JSON.stringify(event.event, null, 2)}`) logger.debug(`[handleEvent] (event.log): ${JSON.stringify(event.log, null, 2)}`) + // NB: sanitize attribute values (may contain non-text characters) + const attributes = event.event.attributes.map((attribute) => { + const {key, value} = attribute; + return {key, value: JSON.stringify(value)}; + }); + const eventEntity = Event.create({ id: `${messageId(event)}-${event.idx}`, type: event.event.type, - attributes: event.event.attributes as EventAttribute[], + attributes, log: event.log.log, transactionId: event.tx.hash, blockId: event.block.block.id, From 535182b6eb513b8bce8b0997a8fd193e87e7f54d Mon Sep 17 00:00:00 2001 From: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Date: Thu, 25 Aug 2022 17:10:42 +0100 Subject: [PATCH 033/143] fix: extend primitive ExecuteContractMessage (#28) --- schema.graphql | 4 +- src/mappings/mappingHandlers.ts | 12 ++-- src/mappings/types/messages.ts | 3 +- test/base_contract.py | 42 ++++++++++++ test/test_execute_contract_message.py | 98 +++++++++++++++++++++++++++ test/test_legacy_bridge_swap.py | 45 +++--------- 6 files changed, 158 insertions(+), 46 deletions(-) create mode 100644 test/base_contract.py create mode 100644 test/test_execute_contract_message.py diff --git a/schema.graphql b/schema.graphql index a803d1f94..3631dabd7 100644 --- a/schema.graphql +++ b/schema.graphql @@ -58,9 +58,9 @@ type Event @entity { type ExecuteContractMessage @entity { id: ID! - sender: String! @index contract: String! @index - funds: String! + method: String! @index + funds: [Coin]! message: Message! transaction: Transaction! block: Block! diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 10bdef269..0da23d314 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -36,7 +36,7 @@ export async function handleBlock(block: CosmosBlock): Promise { id, chainId, height: BigInt(height), - timestamp, + timestamp }); await blockEntity.save() @@ -132,14 +132,16 @@ export async function handleExecuteContractMessage(msg: CosmosMessage Date: Fri, 26 Aug 2022 13:29:55 +0200 Subject: [PATCH 034/143] feat: add signerAddress field to tx (#50) --- schema.graphql | 2 ++ src/mappings/mappingHandlers.ts | 30 +++++++++++++++++++++----- test/helpers/field_enums.py | 1 + test/test_native_primitives.py | 37 ++++++++++++++++++++++++++++----- 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/schema.graphql b/schema.graphql index 3631dabd7..e53c26d76 100644 --- a/schema.graphql +++ b/schema.graphql @@ -36,6 +36,8 @@ type Transaction @entity { status: TxStatus! log: String! timeoutHeight: BigInt @index + # NB: only the first signer! + signerAddress: String @index messages: [Message] @derivedFrom(field: "transaction") } diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 0da23d314..6e1809d3a 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -1,17 +1,16 @@ import { Block, DistDelegatorClaim, - ExecuteContractMessage, - EventAttribute, Event, + ExecuteContractMessage, GovProposalVote, GovProposalVoteOption, LegacyBridgeSwap, Message, - Transaction, - TxStatus, + NativeTransfer, NativeTransferMsg, - NativeTransfer + Transaction, + TxStatus } from "../types"; import {CosmosBlock, CosmosEvent, CosmosMessage, CosmosTransaction,} from "@subql/types-cosmos"; import { @@ -20,6 +19,9 @@ import { GovProposalVoteMsg, LegacyBridgeSwapMsg } from "./types"; +import {SignerInfo} from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import {toBech32} from "@cosmjs/encoding"; +import {createHash} from "crypto"; // messageId returns the id of the message passed or // that of the message which generated the event passed. @@ -57,6 +59,23 @@ export async function handleTransaction(tx: CosmosTransaction): Promise { } } + const pubKey: Uint8Array | undefined = tx.decodedTx.authInfo.signerInfos[0]?.publicKey?.value; + let signerAddress; + if (typeof (pubKey) !== "undefined") { + // TODO: check key type and handle respectively + // NB: ripemd160(sha256(pubKey)) only works for secp256k1 keys + const ripemd160 = createHash("ripemd160"); + const sha256 = createHash("sha256"); + // TODO: understand why!!! + // NB: pubKey has 2 "extra" bytes at the beginning as compared to the + // base64-decoded representation/ of the same key when imported to + // fetchd (`fetchd keys add --recover`) and shown (`fetchd keys show`). + sha256.update(pubKey.slice(2)); + ripemd160.update(sha256.digest()); + // TODO: move prefix to config value or constant + signerAddress = toBech32("fetch", ripemd160.digest()); + } + const txEntity = Transaction.create({ id: tx.hash, blockId: tx.block.block.id, @@ -67,6 +86,7 @@ export async function handleTransaction(tx: CosmosTransaction): Promise { fees: JSON.stringify(tx.decodedTx.authInfo.fee.amount), log: tx.tx.log, status, + signerAddress, }); await txEntity.save(); diff --git a/test/helpers/field_enums.py b/test/helpers/field_enums.py index 7f568e37d..acaf2a004 100644 --- a/test/helpers/field_enums.py +++ b/test/helpers/field_enums.py @@ -32,6 +32,7 @@ class TxFields(NamedFields): status = 6 log = 7 timeout_height = 8 + signer_address = 9 @classmethod def select_query(cls, table="transactions"): diff --git a/test/test_native_primitives.py b/test/test_native_primitives.py index 3b921dcde..7ce5021db 100644 --- a/test/test_native_primitives.py +++ b/test/test_native_primitives.py @@ -41,12 +41,12 @@ def setUpClass(cls): if len(results) != 0: raise Exception(f"truncation of table \"{table}\" failed, {len(results)} records remain") - tx = cls.ledger_client.send_tokens(cls.delegator_wallet.address(), cls.amount, cls.denom, cls.validator_wallet) + tx = cls.ledger_client.send_tokens(cls.delegator_address, cls.amount, cls.denom, cls.validator_wallet) tx.wait_to_complete() if not tx.response.is_successful(): raise Exception(f"first set-up tx failed") - tx = cls.ledger_client.send_tokens(cls.delegator_wallet.address(), cls.amount, cls.denom, cls.validator_wallet) + tx = cls.ledger_client.send_tokens(cls.validator_address, int(cls.amount / 10), cls.denom, cls.delegator_wallet) tx.wait_to_complete() if not tx.response.is_successful(): raise Exception(f"second set-up tx failed") @@ -104,9 +104,11 @@ def test_transactions(self): self.assertTrue(len(tx[TxFields.block_id.value]) == 64) self.assertGreater(tx[TxFields.gas_used.value], 0) self.assertGreater(tx[TxFields.gas_wanted.value], 0) + tx_signer_address = tx[TxFields.signer_address.value] + self.assertTrue(tx_signer_address == self.validator_address or + tx_signer_address == self.delegator_address) fees = json.loads(tx[TxFields.fees.value]) - # print(fees[0]) self.assertEqual(len(fees), 1) self.assertEqual(fees[0]["denom"], self.denom) self.assertGreater(int(fees[0]["amount"]), 0) @@ -125,6 +127,7 @@ def test_transactions_query(self): } gasUsed gasWanted + signerAddress # TODO: # fees } @@ -142,6 +145,8 @@ def test_transactions_query(self): self.assertRegex(tx["block"]["id"], block_id_regex) self.assertGreater(int(tx["gasUsed"]), 0) self.assertGreater(int(tx["gasWanted"]), 0) + self.assertTrue(tx["signerAddress"] == self.validator_address or + tx["signerAddress"] == self.delegator_address) # TODO: fees def test_messages(self): @@ -156,7 +161,7 @@ def test_messages(self): self.assertNotEqual(msg[MsgFields.json.value], "") def test_messages_query(self): - query = gql(""" + query_all = gql(""" query { messages { nodes { @@ -174,7 +179,7 @@ def test_messages_query(self): } """) - result = self.gql_client.execute(query) + result = self.gql_client.execute(query_all) msgs = result["messages"]["nodes"] self.assertIsNotNone(msgs) self.assertEqual(len(msgs), self.expected_msgs_len) @@ -187,6 +192,28 @@ def test_messages_query(self): # TODO: assert on parsed json (?) self.assertNotEqual(msg["json"], "") + def test_messages_by_tx_signer_query(self): + for address in [self.validator_address, self.delegator_address]: + query_by_tx_signer = gql(""" + query { + messages (filter: {transaction: {signerAddress: {equalTo: """ + json.dumps(address) + """}}}) { + nodes { + transaction { + signerAddress + } + } + } + } + """) + + result = self.gql_client.execute(query_by_tx_signer) + msgs = result["messages"]["nodes"] + self.assertIsNotNone(msgs) + self.assertEqual(len(msgs), self.expected_msgs_len / 2) + + for msg in msgs: + self.assertEqual(msg["transaction"]["signerAddress"], address) + def test_events(self): events = self.db_cursor.execute(EventFields.select_query()).fetchall() self.assertEqual(len(events), self.expected_events_len) From f544e49662c71fec982bd4049c5344cbf2f58ad8 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 29 Aug 2022 11:29:05 +0200 Subject: [PATCH 035/143] chore: add fetchd node to docker-compose.yml (#58) --- docker-compose.yml | 18 ++++++++++ docker/fetchd.dockerfile | 61 ++++++++++++++++++++++++++++++++ scripts/00_setup_fetchd_local.sh | 39 ++++++++++++++++++++ scripts/fetchd-entrypoint.sh | 9 +++++ 4 files changed, 127 insertions(+) create mode 100644 docker/fetchd.dockerfile create mode 100755 scripts/00_setup_fetchd_local.sh create mode 100644 scripts/fetchd-entrypoint.sh diff --git a/docker-compose.yml b/docker-compose.yml index 1cd0d3a8b..3e91af988 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,8 @@ services: depends_on: "postgres": condition: service_healthy + "fetch-node": + condition: service_started restart: always environment: DB_USER: "subquery" @@ -31,6 +33,9 @@ services: DB_DATABASE: "subquery" DB_HOST: postgres DB_PORT: 5432 + START_BLOCK: "1" + NETWORK_ENDPOINT: "http://fetch-node:26657" + CHAIN_ID: "testing" volumes: - ./:/app command: @@ -70,3 +75,16 @@ services: - --name=app - --playground - --indexer=http://subquery-node:3000 + + fetch-node: + build: + context: . + dockerfile: ./docker/fetchd.dockerfile + environment: + FETCHMNEMONIC: "nut grocery slice visit barrel peanut tumble patch slim logic install evidence fiction shield rich brown around arrest fresh position animal butter forget cost" + ports: + - "26657:26657" + - "1317:1317" + - "9090:9090" +# volumes: +# - fetch_node_data:/root/.fetchd/ diff --git a/docker/fetchd.dockerfile b/docker/fetchd.dockerfile new file mode 100644 index 000000000..275c89a35 --- /dev/null +++ b/docker/fetchd.dockerfile @@ -0,0 +1,61 @@ +FROM ubuntu:20.04 as base + +USER root + +WORKDIR /workdir + +SHELL [ "/bin/bash", "-c" ] + +ENV DEBIAN_FRONTEND noninteractive + +ARG fetchd_version="0.10.0-rc1" +ENV FETCHD_VER=${fetchd_version} + +ARG golang_version="1.16.6" +ENV GOLANG_VER=${golang_version} + +ARG fetchd_password="12345678" +ENV PASSWORD=${fetchd_password} + +ARG token="atestfet" +ENV TOKEN=${token} + +#ARG validator_key_file="validator.key" +#ENV VALIDATOR_KEY_FILE=${validator_key_file} + +#ARG validator_key_pwd="12345678" +#ENV VALIDATOR_KEY_PWD=${validator_key_pwd} +ENV FETCHMNEMONIC="nut grocery slice visit barrel peanut tumble patch slim logic install evidence fiction shield rich brown around arrest fresh position animal butter forget cost" + +#################### +### dependencies ### +#################### + + +# utils +RUN apt-get update && apt-get install -y wget make curl git jq python3 python3-pip +ENV LC_ALL C.UTF-8 +ENV LANG C.UTF-8 + +# golang +RUN wget https://golang.org/dl/go${GOLANG_VER}.linux-amd64.tar.gz && \ + tar -xzvf go${GOLANG_VER}.linux-amd64.tar.gz -C /usr/local && \ + mkdir /root/go +ENV PATH="${PATH}:/usr/local/go/bin:/root/go/bin" + +# fetchd (https://docs.fetch.ai/ledger_v2/building/) +RUN git clone https://github.com/fetchai/fetchd.git && cd fetchd && \ + git checkout v${FETCHD_VER} && \ + make install && fetchd version + +######################## +### setup local node ### +######################## + +COPY ./scripts/fetchd-entrypoint.sh /scripts_docker/entrypoint.sh +COPY ./scripts/00_setup_fetchd_local.sh /scripts_docker/00_setup_fetchd_local.sh +ENV VALIDATOR_KEY_FILE="/scripts_docker/${VALIDATOR_KEY_FILE}" + +EXPOSE 26657 + +ENTRYPOINT ["/bin/bash", "/scripts_docker/entrypoint.sh"] diff --git a/scripts/00_setup_fetchd_local.sh b/scripts/00_setup_fetchd_local.sh new file mode 100755 index 000000000..35d278c7d --- /dev/null +++ b/scripts/00_setup_fetchd_local.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -e + +# Initialise test chain +# Clean state +rm -rf ~/.fetchd/* + +# Configure fetchcli +fetchd config chain-id testing +fetchd config output json +fetchd config keyring-backend test + +# SETUP LOCAL CHAIN +# Initialize the genesis.json file that will help you to bootstrap the network +fetchd init --chain-id=testing testing +sed -i "s/stake/$TOKEN/" ~/.fetchd/config/genesis.json + +# Enable rest API +sed -i '/^\[api\]$/,/^\[/ s/^enable = false/enable = true/' ~/.fetchd/config/app.toml +# Allow all origins on RPC endpoint +sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["\*"\]/' ~/.fetchd/config/config.toml + +# update the block parameters to match mainnet +cp ~/.fetchd/config/genesis.json ~/.fetchd/config.genesis.json.bak +jq '.consensus_params.block.max_gas = "2000000" | .consensus_params.block.max_bytes = "200000" | .consensus_params.evidence.max_bytes = "200000"' ~/.fetchd/config.genesis.json.bak > ~/.fetchd/config/genesis.json + +# Create a key to hold your validator account +(echo "$FETCHMNEMONIC"; echo "$PASSWORD"; echo "$PASSWORD") | fetchd keys add validator --recover +# Add validator to genesis block and give him some stake +echo "$PASSWORD" | fetchd add-genesis-account $(fetchd keys show validator -a) 1000000000000000000000000$TOKEN +# add some fund to relayer and admin accounts (1MFET) +fetchd add-genesis-account fetch1vmvxe6xgkqfe9fsp63p4f5pgp0jqe7h6505pnk 1000000000000000000000000$TOKEN +fetchd add-genesis-account fetch1ka9j6a8u0lnt8rm86d9ntyurm39jylcu3dstng 1000000000000000000000000$TOKEN + +# Generate the transaction that creates your validator +(echo "$PASSWORD"; echo "$PASSWORD"; echo "$PASSWORD") | fetchd gentx validator 1000000000000000000$TOKEN --keyring-backend test --chain-id testing + +# Add the generated bonding transaction to the genesis file +fetchd collect-gentxs diff --git a/scripts/fetchd-entrypoint.sh b/scripts/fetchd-entrypoint.sh new file mode 100644 index 000000000..067041413 --- /dev/null +++ b/scripts/fetchd-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +if [ ! -f "~/.fetchd/config/genesis.json" ]; then + chmod +x /scripts_docker/00_setup_fetchd_local.sh + /scripts_docker/00_setup_fetchd_local.sh +fi + +fetchd start --rpc.laddr tcp://0.0.0.0:26657 | tee -a fetchd.logs \ No newline at end of file From a08f29f5ed985a210636745e09fbc9f3e192189e Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 29 Aug 2022 11:31:49 +0200 Subject: [PATCH 036/143] test: tidying up e2e tests (#52) --- test/base.py | 18 +++++++ test/helpers/field_enums.py | 65 ++++++++++++++++++++++ test/test_delegation_reward_claim.py | 53 +++++++++--------- test/test_execute_contract_message.py | 67 ++++++++++++----------- test/test_gov_proposal_vote.py | 77 ++++++++++++++------------- test/test_legacy_bridge_swap.py | 51 ++++++++++-------- test/test_native_primitives.py | 17 ++---- test/test_native_transfer.py | 43 ++++++++------- 8 files changed, 246 insertions(+), 145 deletions(-) diff --git a/test/base.py b/test/base.py index ae1749a6a..22dbf0226 100644 --- a/test/base.py +++ b/test/base.py @@ -18,6 +18,13 @@ FETCHD_GRPC_PORT = "9090" GRAPHQL_API_URL = "http://localhost:3000" +CASCADE_TRUNCATE_TABLES = frozenset({"blocks", "transactions", "messages", "events"}) + + +class TruncationException(Exception): + def __init__(self, table, count): + super().__init__(f"truncation of table \"{table}\" failed, {count} records remain") + class Base(unittest.TestCase): delegator_wallet = None @@ -91,6 +98,17 @@ def get_latest_block_timestamp(self): result = dp.parse(result) # parse into datetime obj return result + @classmethod + def clean_db(cls, ensure_empty_tables={}): + table_names = list(CASCADE_TRUNCATE_TABLES.union(ensure_empty_tables)) + cls.db_cursor.execute(f"TRUNCATE table {', '.join(table_names)} CASCADE") + cls.db.commit() + + for table in table_names: + count = cls.db_cursor.execute(f"SELECT id from {table}").rowcount + if count != 0: + raise TruncationException(table, count) + def get_wallet(mnemonic): seed_bytes = Bip39SeedGenerator(mnemonic).Generate() diff --git a/test/helpers/field_enums.py b/test/helpers/field_enums.py index acaf2a004..dd6512693 100644 --- a/test/helpers/field_enums.py +++ b/test/helpers/field_enums.py @@ -62,3 +62,68 @@ class EventFields(NamedFields): def select_query(cls, table="events"): return super().select_query(table) + +class NativeTransferFields(NamedFields): + id = 0 + amounts = 1 + denom = 2 + to_address = 3 + from_address = 4 + + @classmethod + def select_query(cls, table="native_transfers"): + return super().select_query(table) + + +class LegacyBridgeSwapFields(NamedFields): + id = 0 + message_id = 1 + transaction_id = 2 + block_id = 3 + destination = 4 + amount = 5 + denom = 6 + + @classmethod + def select_query(cls, table="legacy_bridge_swaps"): + return super().select_query(table) + + +class GovProposalVoteFields(NamedFields): + id = 0 + message_id = 1 + transaction_id = 2 + block_id = 3 + proposal_id = 4 + voter_address = 5 + option = 6 + + @classmethod + def select_query(cls, table="gov_proposal_votes"): + return super().select_query(table) + + +class ExecuteContractMessageFields(NamedFields): + id = 0 + message_id = 1 + transaction_id = 2 + contract = 3 + method = 4 + funds = 5 + + @classmethod + def select_query(cls, table="execute_contract_messages"): + return super().select_query(table) + + +class DistDelegatorClaimFields(NamedFields): + id = 0 + message_id = 1 + transaction_id = 2 + block_id = 3 + delegator_address = 4 + validator_address = 5 + + @classmethod + def select_query(cls, table="dist_delegator_claims"): + return super().select_query(table) diff --git a/test/test_delegation_reward_claim.py b/test/test_delegation_reward_claim.py index 261201f53..676e779a8 100644 --- a/test/test_delegation_reward_claim.py +++ b/test/test_delegation_reward_claim.py @@ -1,39 +1,44 @@ -import base +import datetime as dt +import json +import time +import unittest + from gql import gql -import time, unittest, datetime as dt, json + +import base +from helpers.field_enums import DistDelegatorClaimFields class TestDelegation(base.Base): amount = 100 - db_query = 'SELECT delegator_address, validator_address from dist_delegator_claims' - def setUp(self): - delegate_tx = self.ledger_client.delegate_tokens(self.validator_operator_address, self.amount, self.validator_wallet) - delegate_tx.wait_to_complete() - self.assertTrue(delegate_tx.response.is_successful(), "\nTXError: delegation tx unsuccessful") + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.clean_db({"dist_delegator_claims"}) - def test_claim_rewards(self): - self.db_cursor.execute('TRUNCATE table dist_delegator_claims') - self.db.commit() - self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nDBError: table not empty after truncation") + delegate_tx = cls.ledger_client.delegate_tokens(cls.validator_operator_address, cls.amount, cls.validator_wallet) + delegate_tx.wait_to_complete() + cls.assertTrue(delegate_tx.response.is_successful(), "\nTXError: delegation tx unsuccessful") - claim_tx = self.ledger_client.claim_rewards(self.validator_operator_address, self.validator_wallet) + claim_tx = cls.ledger_client.claim_rewards(cls.validator_operator_address, cls.validator_wallet) claim_tx.wait_to_complete() - self.assertTrue(claim_tx.response.is_successful(), "\nTXError: reward claim tx unsuccessful") + cls.assertTrue(claim_tx.response.is_successful(), "\nTXError: reward claim tx unsuccessful") # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution time.sleep(5) - row = self.db_cursor.execute(self.db_query).fetchone() + def test_claim_rewards(self): + row = self.db_cursor.execute(DistDelegatorClaimFields.select_query()).fetchone() self.assertIsNotNone(row, "\nDBError: table is empty - maybe indexer did not find an entry?") - self.assertEqual(row[0], self.validator_address, "\nDBError: delegation address does not match") - self.assertEqual(row[1], self.validator_operator_address, "\nDBError: delegation address does not match") + self.assertEqual(row[DistDelegatorClaimFields.delegator_address.value], self.validator_address, "\nDBError: delegation address does not match") + self.assertEqual(row[DistDelegatorClaimFields.validator_address.value], self.validator_operator_address, "\nDBError: delegation address does not match") def test_retrieve_claim(self): # As of now, this test depends on the execution of the previous test in this class. - result = self.get_latest_block_timestamp() - time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before - time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format - time_latest = json.dumps(result.isoformat()) + latest_block_timestamp = self.get_latest_block_timestamp() + # create a second timestamp for five minutes before + min_timestamp = (latest_block_timestamp - dt.timedelta(minutes=5)).isoformat() # convert both to JSON ISO format + max_timestamp = latest_block_timestamp.isoformat() # query governance votes, query related block and filter by timestamp, returning all within last five minutes query_by_timestamp = gql( @@ -43,8 +48,8 @@ def test_retrieve_claim(self): # As of now, this test depends on the execution filter: { block: { timestamp: { - greaterThanOrEqualTo: """ + time_before + """, - lessThanOrEqualTo: """ + time_latest + """ + greaterThanOrEqualTo: """ + json.dumps(min_timestamp) + """, + lessThanOrEqualTo: """ + json.dumps(max_timestamp) + """ } } }) { @@ -65,7 +70,7 @@ def test_retrieve_claim(self): # As of now, this test depends on the execution distDelegatorClaims ( filter: { validatorAddress: { - equalTo:\""""+str(self.validator_operator_address)+"""\" + equalTo:\"""" + str(self.validator_operator_address) + """\" } }) { nodes { @@ -85,7 +90,7 @@ def test_retrieve_claim(self): # As of now, this test depends on the execution distDelegatorClaims ( filter: { delegatorAddress: { - equalTo:\""""+str(self.validator_address)+"""\" + equalTo:\"""" + str(self.validator_address) + """\" } }) { nodes { diff --git a/test/test_execute_contract_message.py b/test/test_execute_contract_message.py index b8710adb2..317b1b883 100644 --- a/test/test_execute_contract_message.py +++ b/test/test_execute_contract_message.py @@ -1,39 +1,46 @@ +import datetime as dt +import json +import time +import unittest + from gql import gql + from base_contract import BaseContract -import time, unittest, datetime as dt, json +from helpers.field_enums import ExecuteContractMessageFields class TestContractExecution(BaseContract): amount = '10000' denom = "atestfet" method = 'swap' - db_query = 'SELECT contract, method, funds from execute_contract_messages' - def test_contract_execution(self): - self.db_cursor.execute('TRUNCATE table execute_contract_messages') - self.db.commit() - self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nDBError: table not empty after truncation") - self.contract.execute( - {self.method: {"destination": self.validator_address}}, - self.validator_wallet, - funds=str(self.amount)+self.denom + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.clean_db({"execute_contract_messages"}) + + cls.contract.execute( + {cls.method: {"destination": cls.validator_address}}, + cls.validator_wallet, + funds=str(cls.amount) + cls.denom ) # primitive solution to wait for indexer to observe and handle new tx - TODO: substitute with more robust solution time.sleep(12) - row = self.db_cursor.execute(self.db_query).fetchone() - self.assertIsNotNone(row, "\nDBError: table is empty - maybe indexer did not find an entry?") - self.assertEqual(row[0], self.contract.address, "\nDBError: contract address does not match") - self.assertEqual(row[1], self.method, "\nDBError: contract method does not match") - self.assertEqual(row[2][0]["amount"], self.amount, "\nDBError: fund amount does not match") - self.assertEqual(row[2][0]["denom"], self.denom, "\nDBError: fund denomination does not match") + def test_contract_execution(self): + execMsgs = self.db_cursor.execute(ExecuteContractMessageFields.select_query()).fetchone() + self.assertIsNotNone(execMsgs, "\nDBError: table is empty - maybe indexer did not find an entry?") + self.assertEqual(execMsgs[ExecuteContractMessageFields.contract.value], self.contract.address, "\nDBError: contract address does not match") + self.assertEqual(execMsgs[ExecuteContractMessageFields.method.value], self.method, "\nDBError: contract method does not match") + self.assertEqual(execMsgs[ExecuteContractMessageFields.funds.value][0]["amount"], self.amount, "\nDBError: fund amount does not match") + self.assertEqual(execMsgs[ExecuteContractMessageFields.funds.value][0]["denom"], self.denom, "\nDBError: fund denomination does not match") def test_contract_execution_retrieval(self): # As of now, this test depends on the execution of the previous test in this class. - result = self.get_latest_block_timestamp() - time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before - time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format - time_latest = json.dumps(result.isoformat()) + latest_block_timestamp = self.get_latest_block_timestamp() + # create a second timestamp for five minutes before + min_timestamp = (latest_block_timestamp - dt.timedelta(minutes=5)).isoformat() # convert both to JSON ISO format + max_timestamp = json.dumps(latest_block_timestamp.isoformat()) # query execute contract messages, query related block and filter by timestamp, returning all within last five minutes query_get_by_range = gql( @@ -43,8 +50,8 @@ def test_contract_execution_retrieval(self): # As of now, this test depends on filter: { block: { timestamp: { - greaterThanOrEqualTo: """ + time_before + """, - lessThanOrEqualTo: """ + time_latest + """ + greaterThanOrEqualTo: """ + json.dumps(min_timestamp) + """, + lessThanOrEqualTo: """ + json.dumps(max_timestamp) + """ } } }) { @@ -65,7 +72,7 @@ def test_contract_execution_retrieval(self): # As of now, this test depends on executeContractMessages ( filter: { method: { - equalTo:\""""+str(self.method)+"""\" + equalTo:\"""" + str(self.method) + """\" } }) { nodes { @@ -80,18 +87,18 @@ def test_contract_execution_retrieval(self): # As of now, this test depends on queries = [query_get_by_range, query_get_by_method] for query in queries: - result = self.gql_client.execute(query) + results = self.gql_client.execute(query) """ ["executeContractMessages"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. This provides {"contract":contract address, "method":method, "funds":funds} which can be destructured for the values of interest. """ - message = result["executeContractMessages"]["nodes"] - self.assertTrue(message, "\nGQLError: No results returned from query") - self.assertEqual(message[0]["contract"], self.contract.address, "\nGQLError: contract address does not match") - self.assertEqual(message[0]["method"], self.method, "\nGQLError: contract method does not match") - self.assertEqual(int(message[0]["funds"][0]["amount"]), int(self.amount), "\nGQLError: fund amount does not match") - self.assertEqual(message[0]["funds"][0]["denom"], self.denom, "\nGQLError: fund denomination does not match") + execMsgs = results["executeContractMessages"]["nodes"] + self.assertTrue(execMsgs, "\nGQLError: No results returned from query") + self.assertEqual(execMsgs[0]["contract"], self.contract.address, "\nGQLError: contract address does not match") + self.assertEqual(execMsgs[0]["method"], self.method, "\nGQLError: contract method does not match") + self.assertEqual(int(execMsgs[0]["funds"][0]["amount"]), int(self.amount), "\nGQLError: fund amount does not match") + self.assertEqual(execMsgs[0]["funds"][0]["denom"], self.denom, "\nGQLError: fund denomination does not match") if __name__ == '__main__': diff --git a/test/test_gov_proposal_vote.py b/test/test_gov_proposal_vote.py index 51e19864b..336b7f138 100644 --- a/test/test_gov_proposal_vote.py +++ b/test/test_gov_proposal_vote.py @@ -1,10 +1,16 @@ +from cosmpy.aerial.client import utils from cosmpy.aerial.tx import Transaction -from cosmpy.protos.cosmos.gov.v1beta1 import tx_pb2 as gov_tx, gov_pb2 from cosmpy.protos.cosmos.base.v1beta1 import coin_pb2 -from cosmpy.aerial.client import utils +from cosmpy.protos.cosmos.gov.v1beta1 import tx_pb2 as gov_tx, gov_pb2 from google.protobuf import any_pb2 from gql import gql -import base, json, time, unittest, datetime as dt + +import base +import datetime as dt +import json +import time +import unittest +from helpers.field_enums import GovProposalVoteFields class TestGovernance(base.Base): @@ -12,9 +18,12 @@ class TestGovernance(base.Base): denom = "atestfet" amount = "10000000" option = 'YES' - db_query = 'SELECT voter_address, option from gov_proposal_votes' - def setUp(self): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.clean_db({"gov_proposal_votes"}) + proposal_content = any_pb2.Any() proposal_content.Pack(gov_pb2.TextProposal( title="Test Proposal", @@ -24,32 +33,28 @@ def setUp(self): msg = gov_tx.MsgSubmitProposal( content=proposal_content, initial_deposit=[coin_pb2.Coin( - denom=self.denom, - amount=self.amount + denom=cls.denom, + amount=cls.amount )], - proposer=self.validator_address + proposer=cls.validator_address ) tx = Transaction() tx.add_message(msg) - tx = utils.prepare_and_broadcast_basic_transaction(self.ledger_client, tx, self.validator_wallet) + tx = utils.prepare_and_broadcast_basic_transaction(cls.ledger_client, tx, cls.validator_wallet) tx.wait_to_complete() - self.assertTrue(tx.response.is_successful(), "\nTXError: governance proposal tx unsuccessful") + cls.assertTrue(tx.response.is_successful(), "\nTXError: governance proposal tx unsuccessful") - self.msg = gov_tx.MsgVote( + cls.msg = gov_tx.MsgVote( proposal_id=1, - voter=self.validator_address, + voter=cls.validator_address, option=gov_pb2.VoteOption.VOTE_OPTION_YES ) - self.vote_tx = Transaction() - self.vote_tx.add_message(self.msg) + cls.vote_tx = Transaction() + cls.vote_tx.add_message(cls.msg) def test_proposal_vote(self): - self.db_cursor.execute('TRUNCATE table gov_proposal_votes') - self.db.commit() - self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nDBError: table not empty after truncation") - tx = utils.prepare_and_broadcast_basic_transaction(self.ledger_client, self.vote_tx, self.validator_wallet) tx.wait_to_complete() self.assertTrue(tx.response.is_successful(), "\nTXError: vote tx unsuccessful") @@ -57,16 +62,16 @@ def test_proposal_vote(self): # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution time.sleep(5) - row = self.db_cursor.execute(self.db_query).fetchone() - self.assertIsNotNone(row, "\nDBError: table is empty - maybe indexer did not find an entry?") - self.assertEqual(row[0], self.validator_address, "\nDBError: voter address does not match") - self.assertEqual(row[1], self.option, "\nDBError: voter option does not match") + vote = self.db_cursor.execute(GovProposalVoteFields.select_query()).fetchone() + self.assertIsNotNone(vote, "\nDBError: table is empty - maybe indexer did not find an entry?") + self.assertEqual(vote[GovProposalVoteFields.voter_address.value], self.validator_address, "\nDBError: voter address does not match") + self.assertEqual(vote[GovProposalVoteFields.option.value], self.option, "\nDBError: voter option does not match") def test_retrieve_vote(self): # As of now, this test depends on the execution of the previous test in this class. - result = self.get_latest_block_timestamp() - time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before - time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format - time_latest = json.dumps(result.isoformat()) + latest_block_timestamp = self.get_latest_block_timestamp() + # create a second timestamp for five minutes before + min_timestamp = (latest_block_timestamp - dt.timedelta(minutes=5)).isoformat() # convert both to JSON ISO format + max_timestamp = latest_block_timestamp.isoformat() # query governance votes, query related block and filter by timestamp, returning all within last five minutes query_by_timestamp = gql( @@ -76,8 +81,8 @@ def test_retrieve_vote(self): # As of now, this test depends on the execution o filter: { block: { timestamp: { - greaterThanOrEqualTo: """ + time_before + """, - lessThanOrEqualTo: """ + time_latest + """ + greaterThanOrEqualTo: """ + json.dumps(min_timestamp) + """, + lessThanOrEqualTo: """ + json.dumps(max_timestamp) + """ } } }) { @@ -98,7 +103,7 @@ def test_retrieve_vote(self): # As of now, this test depends on the execution o govProposalVotes ( filter: { voterAddress: { - equalTo: \""""+str(self.validator_address)+"""\" + equalTo: \"""" + str(self.validator_address) + """\" } }) { nodes { @@ -118,7 +123,7 @@ def test_retrieve_vote(self): # As of now, this test depends on the execution o govProposalVotes ( filter: { option: { - equalTo: """+self.option+""" + equalTo: """ + self.option + """ } }) { nodes { @@ -131,18 +136,18 @@ def test_retrieve_vote(self): # As of now, this test depends on the execution o """ ) - queries = [query_by_timestamp, query_by_voter, query_by_option] - for query in queries: + for query in [query_by_timestamp, query_by_voter, query_by_option]: result = self.gql_client.execute(query) """ ["govProposalVotes"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. This provides {"voterAddress":voter address, "option":voter option} which can be destructured for the values of interest. """ - message = result["govProposalVotes"]["nodes"] - self.assertTrue(message[0], "\nGQLError: No results returned from query") - self.assertEqual(message[0]["voterAddress"], self.validator_address, "\nGQLError: voter address does not match") - self.assertEqual(message[0]["option"], self.option, "\nGQLError: voter option does not match") + votes = result["govProposalVotes"]["nodes"] + self.assertTrue(votes[0], "\nGQLError: No results returned from query") + self.assertEqual(votes[0]["voterAddress"], self.validator_address, + "\nGQLError: voter address does not match") + self.assertEqual(votes[0]["option"], self.option, "\nGQLError: voter option does not match") if __name__ == '__main__': diff --git a/test/test_legacy_bridge_swap.py b/test/test_legacy_bridge_swap.py index cdc6b54bd..4f0a75de6 100644 --- a/test/test_legacy_bridge_swap.py +++ b/test/test_legacy_bridge_swap.py @@ -1,36 +1,41 @@ -from cosmpy.aerial.contract import LedgerContract +import datetime as dt +import decimal +import json +import time +import unittest + from gql import gql + from base_contract import BaseContract -import time, unittest, decimal, datetime as dt, json +from helpers.field_enums import LegacyBridgeSwapFields class TestContractSwap(BaseContract): amount = decimal.Decimal(10000) denom = "atestfet" - db_query = 'SELECT destination, amount, denom from legacy_bridge_swaps'\ + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.clean_db({"legacy_bridge_swaps"}) - def test_contract_swap(self): - self.db_cursor.execute('TRUNCATE table legacy_bridge_swaps') - self.db.commit() - self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nDBError: table not empty after truncation") - - self.contract.execute( - {"swap": {"destination": self.validator_address}}, - self.validator_wallet, - funds=str(self.amount)+self.denom + cls.contract.execute( + {"swap": {"destination": cls.validator_address}}, + cls.validator_wallet, + funds=str(cls.amount)+cls.denom ) # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution time.sleep(12) - row = self.db_cursor.execute(self.db_query).fetchone() - self.assertIsNotNone(row, "\nDBError: table is empty - maybe indexer did not find an entry?") - self.assertEqual(row[0], self.validator_address, "\nDBError: swap sender address does not match") - self.assertEqual(row[1], self.amount, "\nDBError: fund amount does not match") - self.assertEqual(row[2], self.denom, "\nDBError: fund denomination does not match") + def test_contract_swap(self): + swap = self.db_cursor.execute(LegacyBridgeSwapFields.select_query()).fetchone() + self.assertIsNotNone(swap, "\nDBError: table is empty - maybe indexer did not find an entry?") + self.assertEqual(swap[LegacyBridgeSwapFields.destination.value], self.validator_address, "\nDBError: swap sender address does not match") + self.assertEqual(swap[LegacyBridgeSwapFields.amount.value], self.amount, "\nDBError: fund amount does not match") + self.assertEqual(swap[LegacyBridgeSwapFields.denom.value], self.denom, "\nDBError: fund denomination does not match") - def test_retrieve_swap(self): # As of now, this test depends on the execution of the previous test in this class. + def test_retrieve_swap(self): result = self.get_latest_block_timestamp() time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format @@ -107,11 +112,11 @@ def test_retrieve_swap(self): # As of now, this test depends on the execution o This provides {"destination":destination address, "amount":amount, "denom":denomination} which can be destructured for the values of interest. """ - message = result["legacyBridgeSwaps"]["nodes"] - self.assertTrue(message, "\nGQLError: No results returned from query") - self.assertEqual(message[0]["destination"], self.validator_address, "\nGQLError: swap destination address does not match") - self.assertEqual(int(message[0]["amount"]), int(self.amount), "\nGQLError: fund amount does not match") - self.assertEqual(message[0]["denom"], self.denom, "\nGQLError: fund denomination does not match") + swaps = result["legacyBridgeSwaps"]["nodes"] + self.assertNotEqual(swaps, [], "\nGQLError: No results returned from query") + self.assertEqual(swaps[0]["destination"], self.validator_address, "\nGQLError: swap destination address does not match") + self.assertEqual(int(swaps[0]["amount"]), int(self.amount), "\nGQLError: fund amount does not match") + self.assertEqual(swaps[0]["denom"], self.denom, "\nGQLError: fund denomination does not match") if __name__ == '__main__': diff --git a/test/test_native_primitives.py b/test/test_native_primitives.py index 7ce5021db..d5319c2a7 100644 --- a/test/test_native_primitives.py +++ b/test/test_native_primitives.py @@ -1,9 +1,7 @@ import json -import re from gql import gql -import base import time import unittest @@ -32,24 +30,15 @@ class TestNativePrimitives(base.Base): @classmethod def setUpClass(cls): super().setUpClass() - - cls.db_cursor.execute(f"TRUNCATE table {', '.join(cls.tables)} CASCADE") - cls.db.commit() - - for table in list(reversed(cls.tables)): - results = cls.db_cursor.execute(f"SELECT id FROM {table}").fetchall() - if len(results) != 0: - raise Exception(f"truncation of table \"{table}\" failed, {len(results)} records remain") + cls.clean_db() tx = cls.ledger_client.send_tokens(cls.delegator_address, cls.amount, cls.denom, cls.validator_wallet) tx.wait_to_complete() - if not tx.response.is_successful(): - raise Exception(f"first set-up tx failed") + cls.assertTrue(tx.response.is_successful(), f"first set-up tx failed") tx = cls.ledger_client.send_tokens(cls.validator_address, int(cls.amount / 10), cls.denom, cls.delegator_wallet) tx.wait_to_complete() - if not tx.response.is_successful(): - raise Exception(f"second set-up tx failed") + cls.assertTrue(tx.response.is_successful(), f"second set-up tx failed") # Wait for subql node to sync time.sleep(5) diff --git a/test/test_native_transfer.py b/test/test_native_transfer.py index 8c87fa8ed..e22810b99 100644 --- a/test/test_native_transfer.py +++ b/test/test_native_transfer.py @@ -1,31 +1,38 @@ +import base +import datetime as dt +import json +import time +import unittest + from gql import gql -import time, unittest, base, dateutil.parser as dp, datetime as dt, json + +from helpers.field_enums import NativeTransferFields class TestNativeTransfer(base.Base): amount = 5000000 denom = "atestfet" msg_type = '/cosmos.bank.v1beta1.MsgSend' - db_query = 'SELECT amounts, denom, to_address, from_address from native_transfers' - def test_native_transfer(self): - self.db_cursor.execute('TRUNCATE table native_transfers') - self.db.commit() - self.assertFalse(self.db_cursor.execute(self.db_query).fetchall(), "\nError: table not empty after truncation") + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.clean_db({"native_transfers"}) - tx = self.ledger_client.send_tokens(self.delegator_wallet.address(), self.amount, self.denom, self.validator_wallet) + tx = cls.ledger_client.send_tokens(cls.delegator_address, cls.amount, cls.denom, cls.validator_wallet) tx.wait_to_complete() - self.assertTrue(tx.response.is_successful(), "\nTXError: transfer tx unsuccessful") + cls.assertTrue(tx.response.is_successful(), "TXError: transfer unsuccessful") # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution time.sleep(5) - native_transfer = self.db_cursor.execute(self.db_query).fetchone() + def test_native_transfer(self): + native_transfer = self.db_cursor.execute(NativeTransferFields.select_query()).fetchone() self.assertIsNotNone(native_transfer, "\nDBError: table is empty - maybe indexer did not find an entry?") - self.assertEqual(native_transfer[0][0]['amount'], str(self.amount), "\nDBError: fund amount does not match") - self.assertEqual(native_transfer[1], self.denom, "\nDBError: fund denomination does not match") - self.assertEqual(native_transfer[2], self.delegator_address, "\nDBError: swap sender address does not match") - self.assertEqual(native_transfer[3], self.validator_address, "\nDBError: sender address does not match") + self.assertEqual(native_transfer[NativeTransferFields.amounts.value][0]['amount'], str(self.amount), "\nDBError: fund amount does not match") + self.assertEqual(native_transfer[NativeTransferFields.denom.value], self.denom, "\nDBError: fund denomination does not match") + self.assertEqual(native_transfer[NativeTransferFields.to_address.value], self.delegator_address, "\nDBError: swap sender address does not match") + self.assertEqual(native_transfer[NativeTransferFields.from_address.value], self.validator_address, "\nDBError: sender address does not match") def test_retrieve_transfer(self): # As of now, this test depends on the execution of the previous test in this class. result = self.get_latest_block_timestamp() @@ -126,11 +133,11 @@ def test_retrieve_transfer(self): # As of now, this test depends on the executi This provides {"toAddress":address, "fromAddress":address, "denom":denom, "amount":["amount":amount, "denom":denom]} which can be destructured for the values of interest. """ - message = result["nativeTransfers"]["nodes"] - self.assertTrue(message, "\nGQLError: No results returned from query") - self.assertEqual(message[0]["denom"], self.denom, "\nGQLError: fund denomination does not match") - self.assertEqual(message[0]["toAddress"], self.delegator_address, "\nGQLError: destination address does not match") - self.assertEqual(message[0]["fromAddress"], self.validator_address, "\nGQLError: from address does not match") + native_transfers = result["nativeTransfers"]["nodes"] + self.assertNotEqual(native_transfers, [], "\nGQLError: No results returned from query") + self.assertEqual(native_transfers[0]["denom"], self.denom, "\nGQLError: fund denomination does not match") + self.assertEqual(native_transfers[0]["toAddress"], self.delegator_address, "\nGQLError: destination address does not match") + self.assertEqual(native_transfers[0]["fromAddress"], self.validator_address, "\nGQLError: from address does not match") if __name__ == '__main__': From 3dc5a56c21ac8ff81b5feafd5b0fa6f1311855f2 Mon Sep 17 00:00:00 2001 From: amit-solanki <75420735+amit-solanki@users.noreply.github.com> Date: Tue, 30 Aug 2022 12:04:18 +0530 Subject: [PATCH 037/143] fix: added chainTypes in network (#64) Added chaintypes in network --- project.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/project.yaml b/project.yaml index 97d41ee4e..b1b5ce67c 100644 --- a/project.yaml +++ b/project.yaml @@ -16,16 +16,16 @@ schema: network: chainId: dorado-1 endpoint: https://rpc-dorado.fetch.ai:443 + chainTypes: # This is a beta feature that allows support for any Cosmos chain by importing the correct protobuf messages + cosmos.slashing.v1beta1: + file: "./proto/cosmos/slashing/v1beta1/tx.proto" + messages: + - "MsgUnjail" # Using a dictionary can massively improve indexing speed dictionary: https://api.subquery.network/sq/subquery/cosmos-fetch-ai-dictionary dataSources: - kind: cosmos/Runtime startBlock: 827201 - chainTypes: # This is a beta feature that allows support for any Cosmos chain by importing the correct protobuf messages - cosmos.slashing.v1beta1: - file: "./proto/cosmos/slashing/v1beta1/tx.proto" - messages: - - "MsgUnjail" mapping: file: "./dist/index.js" handlers: From a2ef40a96cf9b2369022ea7100dced2b02d16224 Mon Sep 17 00:00:00 2001 From: amit-solanki <75420735+amit-solanki@users.noreply.github.com> Date: Tue, 30 Aug 2022 12:15:39 +0530 Subject: [PATCH 038/143] fix: api docker build github action (#65) --- .github/workflows/docker_deploy_prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker_deploy_prod.yml b/.github/workflows/docker_deploy_prod.yml index d13c653bc..04ec7f657 100644 --- a/.github/workflows/docker_deploy_prod.yml +++ b/.github/workflows/docker_deploy_prod.yml @@ -57,7 +57,7 @@ jobs: uses: docker/build-push-action@v3 with: context: . - file: ./docker/node.dockerfile + file: ./docker/api.dockerfile push: true tags: | gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.API_IMAGE_NAME }}:${{ steps.vars.outputs.api_tag_name }} From 6a468ed101655e36d9eb5926a632428400f6d38c Mon Sep 17 00:00:00 2001 From: amit-solanki <75420735+amit-solanki@users.noreply.github.com> Date: Tue, 30 Aug 2022 12:42:15 +0530 Subject: [PATCH 039/143] chore: added support to update bridge contract address (#63) --- scripts/node-entrypoint.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/node-entrypoint.sh b/scripts/node-entrypoint.sh index 8cab93032..d0aeb0b64 100755 --- a/scripts/node-entrypoint.sh +++ b/scripts/node-entrypoint.sh @@ -17,5 +17,10 @@ if [[ ! -z "${NETWORK_ENDPOINT}" ]]; then yq -i '.network.endpoint = strenv(NETWORK_ENDPOINT)' project.yaml fi +if [[ ! -z "${LEGACY_BRIDGE_CONTRACT_ADDRESS}" ]]; then + echo "[Config Update] Legacy Bridge Contract Address: ${LEGACY_BRIDGE_CONTRACT_ADDRESS}" + yq -i '.dataSources[].mapping.handlers |= map(select(.handler == "handleLegacyBridgeSwap").filter.values.contract = env(LEGACY_BRIDGE_CONTRACT_ADDRESS))' project.yaml +fi + # run the main node exec /sbin/tini -- /usr/local/lib/node_modules/@subql/node-cosmos/bin/run From 9aac5655756e5491b976a4ab0d49ac6882e5f259 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 31 Aug 2022 08:44:46 +0200 Subject: [PATCH 040/143] fix: devops bits (#68) --- docker-compose.yml | 3 +-- project.yaml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3e91af988..4d21a5f56 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,8 +5,6 @@ services: image: postgres:14-alpine ports: - 5432:5432 - volumes: - - .data/postgres:/var/lib/postgresql/data environment: POSTGRES_USER: "subquery" POSTGRES_PASSWORD: "subquery" @@ -36,6 +34,7 @@ services: START_BLOCK: "1" NETWORK_ENDPOINT: "http://fetch-node:26657" CHAIN_ID: "testing" + LEGACY_BRIDGE_CONTRACT_ADDRESS: "fetch14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9szlkpka" volumes: - ./:/app command: diff --git a/project.yaml b/project.yaml index b1b5ce67c..457ce7f34 100644 --- a/project.yaml +++ b/project.yaml @@ -56,7 +56,7 @@ dataSources: # Filter to only messages with the vote function call contractCall: "swap" # The name of the contract function that was called values: # This is the specific smart contract that we are subscribing to - contract: "fetch1qxxlalvsdjd07p07y3rc5fu6ll8k4tmetpha8n" + contract: "fetch1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsa26575" - handler: handleNativeTransfer kind: cosmos/MessageHandler filter: From a66c5c4d27f223f2730f499e1f32d9376cf126c7 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Wed, 31 Aug 2022 10:52:19 +0200 Subject: [PATCH 041/143] chores: bump fetchd version to current stable (#69) --- docker/fetchd.dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/fetchd.dockerfile b/docker/fetchd.dockerfile index 275c89a35..118b79715 100644 --- a/docker/fetchd.dockerfile +++ b/docker/fetchd.dockerfile @@ -8,10 +8,10 @@ SHELL [ "/bin/bash", "-c" ] ENV DEBIAN_FRONTEND noninteractive -ARG fetchd_version="0.10.0-rc1" +ARG fetchd_version="0.10.6" ENV FETCHD_VER=${fetchd_version} -ARG golang_version="1.16.6" +ARG golang_version="1.18.5" ENV GOLANG_VER=${golang_version} ARG fetchd_password="12345678" From e5f5d603163466d91c2bbc4ab849f3d9d73d44b3 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 31 Aug 2022 11:36:39 +0200 Subject: [PATCH 042/143] chore: add message field to native transfer entity (#67) --- schema.graphql | 7 +- src/mappings/mappingHandlers.ts | 14 +-- src/mappings/types/messages.ts | 6 ++ test/helpers/graphql.py | 23 +++++ test/test_native_transfer.py | 156 ++++++++++++++------------------ 5 files changed, 104 insertions(+), 102 deletions(-) create mode 100644 test/helpers/graphql.py diff --git a/schema.graphql b/schema.graphql index e53c26d76..87e056fde 100644 --- a/schema.graphql +++ b/schema.graphql @@ -109,18 +109,13 @@ type Coin @jsonField { amount: String! } -type NativeTransferMsg @jsonField { - toAddress: String! @index - fromAddress: String! @index - amount: [Coin]! @index -} - type NativeTransfer @entity { id: ID! toAddress: String! @index fromAddress: String! @index amounts: [Coin]! denom: String! @index + message: Message! transaction: Transaction! block: Block! } diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 6e1809d3a..ca9ef46ba 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -8,16 +8,16 @@ import { LegacyBridgeSwap, Message, NativeTransfer, - NativeTransferMsg, Transaction, TxStatus } from "../types"; import {CosmosBlock, CosmosEvent, CosmosMessage, CosmosTransaction,} from "@subql/types-cosmos"; import { - ExecuteContractMsg, - DistDelegatorClaimMsg, - GovProposalVoteMsg, - LegacyBridgeSwapMsg + ExecuteContractMsg, + DistDelegatorClaimMsg, + GovProposalVoteMsg, + LegacyBridgeSwapMsg, + NativeTransferMsg } from "./types"; import {SignerInfo} from "cosmjs-types/cosmos/tx/v1beta1/tx"; import {toBech32} from "@cosmjs/encoding"; @@ -98,12 +98,14 @@ export async function handleNativeTransfer(msg: CosmosMessage const {toAddress, fromAddress, amount: amounts} = msg.msg.decodedMsg; // workaround: assuming one denomination per transfer message const denom = amounts[0].denom; + const id = messageId(msg); const transferEntity = NativeTransfer.create({ - id: messageId(msg), + id, toAddress, fromAddress, amounts, denom, + messageId: id, transactionId: msg.tx.hash, blockId: msg.block.block.id }); diff --git a/src/mappings/types/messages.ts b/src/mappings/types/messages.ts index 331d424a5..fa9a76bd0 100644 --- a/src/mappings/types/messages.ts +++ b/src/mappings/types/messages.ts @@ -1,5 +1,11 @@ import {Coin} from "./common"; +export interface NativeTransferMsg { + toAddress: string; + fromAddress: string; + amount: Coin[]; +} + export interface ExecuteContractMsg { contract: string; msg: object; diff --git a/test/helpers/graphql.py b/test/helpers/graphql.py new file mode 100644 index 000000000..f204c04c5 --- /dev/null +++ b/test/helpers/graphql.py @@ -0,0 +1,23 @@ +import json +import re + +from gql import gql + +json_keys_regex = re.compile('"(\w+)":') + + +def to_gql(obj): + # NB: strip quotes from object keys + return json_keys_regex.sub("\g<1>:", json.dumps(obj)) + + +def test_filtered_query(root_entity, _filter, nodes_string): + filter_string = to_gql(_filter) + + return gql(""" + query { + """ + root_entity + """ (filter: """ + filter_string + """) { + nodes """ + nodes_string + """ + } + } + """) diff --git a/test/test_native_transfer.py b/test/test_native_transfer.py index e22810b99..3e6844ba7 100644 --- a/test/test_native_transfer.py +++ b/test/test_native_transfer.py @@ -7,6 +7,8 @@ from gql import gql from helpers.field_enums import NativeTransferFields +from helpers.graphql import test_filtered_query +from helpers.regexes import msg_id_regex, block_id_regex, tx_id_regex class TestNativeTransfer(base.Base): @@ -34,110 +36,84 @@ def test_native_transfer(self): self.assertEqual(native_transfer[NativeTransferFields.to_address.value], self.delegator_address, "\nDBError: swap sender address does not match") self.assertEqual(native_transfer[NativeTransferFields.from_address.value], self.validator_address, "\nDBError: sender address does not match") - def test_retrieve_transfer(self): # As of now, this test depends on the execution of the previous test in this class. + def test_retrieve_transfer(self): result = self.get_latest_block_timestamp() - time_before = result - dt.timedelta(minutes=5) # create a second timestamp for five minutes before - time_before = json.dumps(time_before.isoformat()) # convert both to JSON ISO format - time_latest = json.dumps(result.isoformat()) + # create a second timestamp for five minutes before + min_timestamp = (result - dt.timedelta(minutes=5)).isoformat() # convert both to JSON ISO format + max_timestamp = result.isoformat() + + native_transfer_nodes = """ + { + id, + message { id } + transaction { id } + block { id } + amounts + denom + toAddress + fromAddress + } + """ + + def filtered_native_transaction_query(_filter): + return test_filtered_query("nativeTransfers", _filter, native_transfer_nodes) # query native transactions, query related block and filter by timestamp, returning all within last five minutes - query_get_by_range = gql( - """ - query getByRange { - nativeTransfers ( - filter: { - block: { - timestamp: { - greaterThanOrEqualTo: """ + time_before + """, - lessThanOrEqualTo: """ + time_latest + """ - } - } - }) { - nodes { - denom - toAddress - fromAddress - } + filter_by_block_timestamp_range = filtered_native_transaction_query({ + "block": { + "timestamp": { + "greaterThanOrEqualTo": min_timestamp, + "lessThanOrEqualTo": max_timestamp } } - """ - ) + }) # query native transactions, filter by recipient address - query_get_by_to_address = gql( - """ - query getByToAddress { - nativeTransfers ( - filter: { - toAddress: { - equalTo: """+json.dumps(self.delegator_address)+""" - } - } - ) { - nodes { - denom - toAddress - fromAddress - } - } + filter_by_to_address_equals = filtered_native_transaction_query({ + "toAddress": { + "equalTo": self.delegator_address } - """ - ) + }) # query native transactions, filter by sender address - query_get_by_from_address = gql( - """ - query getByFromAddress { - nativeTransfers ( - filter: { - fromAddress: { - equalTo: """+json.dumps(self.validator_address)+""" - } - } - ) { - nodes { - denom - toAddress - fromAddress - } - } + filter_by_from_address_equals = filtered_native_transaction_query({ + "fromAddress": { + "equalTo": self.validator_address } - """ - ) + }) # query native transactions, filter by denomination - query_get_by_denom = gql( - """ - query getByDenom { - nativeTransfers ( - filter: { - denom: { - equalTo:\""""+self.denom+"""\" - } - }) { - nodes { - denom - toAddress - fromAddress - } - } + filter_by_denom_equals = filtered_native_transaction_query({ + "denom": { + "equalTo": self.denom } - """ - ) - - queries = [query_get_by_range, query_get_by_to_address, query_get_by_from_address, query_get_by_denom] - for query in queries: - result = self.gql_client.execute(query) - """ - ["nativeTransfers"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. - This provides {"toAddress":address, "fromAddress":address, "denom":denom, "amount":["amount":amount, "denom":denom]} - which can be destructured for the values of interest. - """ - native_transfers = result["nativeTransfers"]["nodes"] - self.assertNotEqual(native_transfers, [], "\nGQLError: No results returned from query") - self.assertEqual(native_transfers[0]["denom"], self.denom, "\nGQLError: fund denomination does not match") - self.assertEqual(native_transfers[0]["toAddress"], self.delegator_address, "\nGQLError: destination address does not match") - self.assertEqual(native_transfers[0]["fromAddress"], self.validator_address, "\nGQLError: from address does not match") + }) + + for (name, query) in [ + ("by block timestamp range", filter_by_block_timestamp_range), + ("by toAddress equals", filter_by_to_address_equals), + ("by fromAddress equals", filter_by_from_address_equals), + ("by denom equals", filter_by_denom_equals) + ]: + with self.subTest(name): + result = self.gql_client.execute(query) + """ + ["nativeTransfers"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. + This provides {"toAddress":address, "fromAddress":address, "denom":denom, "amount":["amount":amount, "denom":denom]} + which can be destructured for the values of interest. + """ + native_transfers = result["nativeTransfers"]["nodes"] + self.assertNotEqual(native_transfers, [], "\nGQLError: No results returned from query") + self.assertRegex(native_transfers[0]["id"], msg_id_regex) + self.assertRegex(native_transfers[0]["message"]["id"], msg_id_regex) + self.assertRegex(native_transfers[0]["transaction"]["id"], tx_id_regex) + self.assertRegex(native_transfers[0]["block"]["id"], block_id_regex) + # NB: `amount` is a list of `Coin`s (i.e. [{amount: "", denom: ""}, ...]) + self.assertEqual(int(native_transfers[0]["amounts"][0]["amount"]), self.amount, "\nGQLError: fund amount does not match") + self.assertEqual(native_transfers[0]["amounts"][0]["denom"], self.denom, "\nGQLError: fund denom does not match") + self.assertEqual(native_transfers[0]["denom"], self.denom, "\nGQLError: fund denomination does not match") + self.assertEqual(native_transfers[0]["toAddress"], self.delegator_address, "\nGQLError: destination address does not match") + self.assertEqual(native_transfers[0]["fromAddress"], self.validator_address, "\nGQLError: from address does not match") if __name__ == '__main__': From e0062b4057ebea7ec061d5e512155cd48d36cab3 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 31 Aug 2022 13:34:59 +0200 Subject: [PATCH 043/143] chore: add executeContractMessage field to LegacyBridgeSwap entity (#62) --- schema.graphql | 1 + src/mappings/mappingHandlers.ts | 1 + test/test_legacy_bridge_swap.py | 124 ++++++++++++++------------------ 3 files changed, 56 insertions(+), 70 deletions(-) diff --git a/schema.graphql b/schema.graphql index 87e056fde..3e5d7f87d 100644 --- a/schema.graphql +++ b/schema.graphql @@ -99,6 +99,7 @@ type LegacyBridgeSwap @entity { destination: String! amount: BigInt! denom: String! + executeContractMessage: ExecuteContractMessage! message: Message! transaction: Transaction! block: Block! diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index ca9ef46ba..39db34663 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -226,6 +226,7 @@ export async function handleLegacyBridgeSwap(msg: CosmosMessage Date: Thu, 1 Sep 2022 11:45:49 +0200 Subject: [PATCH 044/143] chore: add DistDelegatorClaim amount & denom (#66) --- project.yaml | 4 + schema.graphql | 6 +- src/mappings/mappingHandlers.ts | 42 ++++++++- src/mappings/utils.ts | 28 ++++++ test/helpers/field_enums.py | 2 + test/test_delegation_reward_claim.py | 127 ++++++++++++--------------- 6 files changed, 136 insertions(+), 73 deletions(-) create mode 100644 src/mappings/utils.ts diff --git a/project.yaml b/project.yaml index 457ce7f34..e6096c60a 100644 --- a/project.yaml +++ b/project.yaml @@ -61,3 +61,7 @@ dataSources: kind: cosmos/MessageHandler filter: type: "/cosmos.bank.v1beta1.MsgSend" + - handler: handleDelegatorWithdrawRewardEvent + kind: cosmos/EventHandler + filter: + type: "withdraw_rewards" diff --git a/schema.graphql b/schema.graphql index 3e5d7f87d..27543a0c7 100644 --- a/schema.graphql +++ b/schema.graphql @@ -89,9 +89,9 @@ type DistDelegatorClaim @entity { block: Block! # TODO: # validator: Validator! - # TODO: introduced in cosmos-sdk (baseline) v0.46 - # amount: BigInt! - # denom: String! + # TODO: also available via msg in cosmos-sdk (baseline) v0.46 + amount: BigInt! + denom: String! } type LegacyBridgeSwap @entity { diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 39db34663..dbcb07f8d 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -19,9 +19,9 @@ import { LegacyBridgeSwapMsg, NativeTransferMsg } from "./types"; -import {SignerInfo} from "cosmjs-types/cosmos/tx/v1beta1/tx"; import {toBech32} from "@cosmjs/encoding"; import {createHash} from "crypto"; +import {parseCoins} from "./utils"; // messageId returns the id of the message passed or // that of the message which generated the event passed. @@ -203,6 +203,8 @@ export async function handleDistDelegatorClaim(msg: CosmosMessage { + logger.debug(`[handleDelegateWithdrawRewardEvent] (event.event): ${JSON.stringify(event.event, null, 2)}`) + logger.debug(`[handleDelegateWithdrawRewardEvent] (event.log): ${JSON.stringify(event.log, null, 2)}`) + + const attrs: Record = event.event.attributes.reduce((acc, attr) => { + acc[attr.key] = attr.value; + return acc; + }, {}); + + if (typeof(attrs.amount) === "undefined" || typeof(attrs.validator) === "undefined") { + // Skip this call as unprocessable and allow indexer to continue. + logger.warn(`[handleDelegateWithdrawRewardEvent] (!SKIPPED!) malformed attributes: ${JSON.stringify(attrs)}`); + return; + } + + const claims = await DistDelegatorClaim.getByTransactionId(event.tx.hash); + + const {amount: amountStr, validator} = attrs as {amount: string, validator: string}; + const claim = claims.find((claim) => claim.validatorAddress === validator); + if (typeof(claim) === "undefined") { + // Skip this call as unprocessable and allow indexer to continue. + logger.warn(`[handleDelegateWithdrawRewardEvent] (!SKIPPED!) no claim msgs found in tx: ${event.tx.hash}`); + return; + } + + const coins = parseCoins(amountStr); + if (coins.length === 0) { + // Skip this call as unprocessable and allow indexer to continue. + logger.warn(`[handleDelegateWithdrawRewardEvent] (!SKIPPED!) error parsing claim amount: ${amountStr}`); + return; + } + + const {amount, denom} = coins[0]; + claim.amount = BigInt(amount); + claim.denom = denom; + await claim.save(); +} diff --git a/src/mappings/utils.ts b/src/mappings/utils.ts new file mode 100644 index 000000000..8461d06fa --- /dev/null +++ b/src/mappings/utils.ts @@ -0,0 +1,28 @@ +/* `parseCoins` implementation copied from @cosmjs/proto-signing + see: https://github.com/cosmos/cosmjs/blob/9970f77f18aa19843d31d3da8a9b99cd07186951/packages/proto-signing/src/coins.ts + */ + +import { Coin } from "@cosmjs/amino"; +import { Uint64 } from "@cosmjs/math"; + +/** + * Takes a coins list like "819966000ucosm,700000000ustake" and parses it. + * + * This is a Stargate ready version of parseCoins from @cosmjs/amino. + * It supports more denoms. + */ +export function parseCoins(input: string): Coin[] { + return input + .replace(/\s/g, "") + .split(",") + .filter(Boolean) + .map((part) => { + // Denom regex from Stargate (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/types/coin.go#L599-L601) + const match = part.match(/^([0-9]+)([a-zA-Z][a-zA-Z0-9/]{2,127})$/); + if (!match) throw new Error("Got an invalid coin string"); + return { + amount: Uint64.fromString(match[1]).toString(), + denom: match[2], + }; + }); +} diff --git a/test/helpers/field_enums.py b/test/helpers/field_enums.py index dd6512693..09f6d2b19 100644 --- a/test/helpers/field_enums.py +++ b/test/helpers/field_enums.py @@ -123,6 +123,8 @@ class DistDelegatorClaimFields(NamedFields): block_id = 3 delegator_address = 4 validator_address = 5 + amount = 6 + denom = 7 @classmethod def select_query(cls, table="dist_delegator_claims"): diff --git a/test/test_delegation_reward_claim.py b/test/test_delegation_reward_claim.py index 676e779a8..610fe0fb3 100644 --- a/test/test_delegation_reward_claim.py +++ b/test/test_delegation_reward_claim.py @@ -1,12 +1,12 @@ import datetime as dt -import json +import re import time import unittest -from gql import gql - import base from helpers.field_enums import DistDelegatorClaimFields +from helpers.graphql import test_filtered_query +from helpers.regexes import msg_id_regex, block_id_regex, tx_id_regex class TestDelegation(base.Base): @@ -40,81 +40,70 @@ def test_retrieve_claim(self): # As of now, this test depends on the execution min_timestamp = (latest_block_timestamp - dt.timedelta(minutes=5)).isoformat() # convert both to JSON ISO format max_timestamp = latest_block_timestamp.isoformat() - # query governance votes, query related block and filter by timestamp, returning all within last five minutes - query_by_timestamp = gql( + dist_delegate_claim_nodes = """ + { + id + message { id } + transaction { id } + block { id } + validatorAddress + delegatorAddress + amount + denom + } """ - query get_votes_by_timestamp { - distDelegatorClaims ( - filter: { - block: { - timestamp: { - greaterThanOrEqualTo: """ + json.dumps(min_timestamp) + """, - lessThanOrEqualTo: """ + json.dumps(max_timestamp) + """ - } - } - }) { - nodes { - transactionId - validatorAddress - delegatorAddress - } + + def filtered_dist_delegate_claim_query(_filter): + return test_filtered_query("distDelegatorClaims", _filter, dist_delegate_claim_nodes) + + # query governance votes, query related block and filter by timestamp, returning all within last five minutes + filter_by_block_timestamp_range = filtered_dist_delegate_claim_query({ + "block": { + "timestamp": { + "greaterThanOrEqualTo": min_timestamp, + "lessThanOrEqualTo": max_timestamp } } - """ - ) + }) # query delegator reward claims, filter by validator address - query_by_validator = gql( - """ - query getByValidator { - distDelegatorClaims ( - filter: { - validatorAddress: { - equalTo:\"""" + str(self.validator_operator_address) + """\" - } - }) { - nodes { - transactionId - validatorAddress - delegatorAddress - } - } + filter_by_validator_equals = filtered_dist_delegate_claim_query({ + "validatorAddress": { + "equalTo": str(self.validator_operator_address) } - """ - ) + }) # query delegator reward claims, filter by delegator address - query_by_delegator = gql( - """ - query getByValidator { - distDelegatorClaims ( - filter: { - delegatorAddress: { - equalTo:\"""" + str(self.validator_address) + """\" - } - }) { - nodes { - transactionId - validatorAddress - delegatorAddress - } - } + filter_by_delegator_equals = filtered_dist_delegate_claim_query({ + "delegatorAddress": { + "equalTo": str(self.validator_address) } - """ - ) - - queries = [query_by_timestamp, query_by_validator, query_by_delegator] - for query in queries: - result = self.gql_client.execute(query) - """ - ["distDelegatorClaims"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. - This provides {"delegatorAddress":delegator address, "validatorAddress":validator option} - which can be destructured for the values of interest. - """ - message = result["distDelegatorClaims"]["nodes"] - self.assertTrue(message[0], "\nGQLError: No results returned from query") - self.assertEqual(message[0]["delegatorAddress"], self.validator_address, "\nGQLError: delegation address does not match") - self.assertEqual(message[0]["validatorAddress"], self.validator_operator_address, "\nGQLError: validator address does not match") + }) + + for (name, query) in [ + ("by block timestamp range", filter_by_block_timestamp_range), + ("by validator equals", filter_by_validator_equals), + ("by delegator equals", filter_by_delegator_equals) + ]: + with self.subTest(name): + result = self.gql_client.execute(query) + """ + ["distDelegatorClaims"]["nodes"][0] denotes the sequence of keys to access the message contents queried for above. + This provides {"delegatorAddress":delegator address, "validatorAddress":validator option} + which can be destructured for the values of interest. + """ + claims = result["distDelegatorClaims"]["nodes"] + self.assertTrue(claims[0], "\nGQLError: No results returned from query") + self.assertRegex(claims[0]["id"], msg_id_regex) + self.assertRegex(claims[0]["message"]["id"], msg_id_regex) + self.assertRegex(claims[0]["transaction"]["id"], tx_id_regex) + self.assertRegex(claims[0]["block"]["id"], block_id_regex) + self.assertEqual(claims[0]["delegatorAddress"], self.validator_address, + "\nGQLError: delegation address does not match") + self.assertEqual(claims[0]["validatorAddress"], self.validator_operator_address, + "\nGQLError: validator address does not match") + self.assertRegex(claims[0]["amount"], re.compile("^\d+$")) + self.assertRegex(claims[0]["denom"], re.compile("^[\w/]{2,127}$")) if __name__ == '__main__': From a7fddd78f999a9e89433ce45258f0d0dc7d296a1 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Thu, 1 Sep 2022 11:57:38 +0200 Subject: [PATCH 045/143] chore: bump version & update README (#51) --- README.md | 45 ++++++++++++++++++++++++++++++++++++++------- package.json | 8 ++++---- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2c6121327..ea2f000f4 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,60 @@ -# SubQuery Starter Project for Juno +# Ledger SubQuery -This is a starter project for Indexing Juno. It includes a simple blockhandler and an event handler. This project indexes all transfer events in juno-1. +This is the Fetch ledger SubQuery project, an indexer for the Fetch network. -# Getting Started -### 1. Install dependencies +# Developing + +## Getting Started + +### 1. Ensure submodules are updated + +```shell +git submodule update --init --recursive +``` + +### 2. Install dependencies ```shell yarn + +# install submodule dependencies +(cd ./subql && yarn) ``` -### 2. Generate types +### 3. Generate types ```shell yarn codegen ``` -### 3. Build +### 4. Build ```shell yarn build + +# build submodule +(cd ./subql && yarn build) ``` -### 4. Run locally +### 5. Run locally ```shell yarn start:docker ``` + +## End-to-end Testing + +### 1. Install dependencies + +```shell +pipenv install +``` + +### 2. Run all e2e tests + +_Note: end-to-end tests will truncate tables in the DB and interact with the configured fetchd node._ + +```shell +pipenv run python -m unittest discover -s ./test +``` diff --git a/package.json b/package.json index a214822bd..7d041d456 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "cosmos-subquery-starter", - "version": "0.0.1", - "description": "This project can be use as a starting point for developing your Cosmos (Juno) based SubQuery project", + "name": "ledger-subquery", + "version": "0.1.0", + "description": "This is the Fetch Ledger SubQuery project, an indexer for the Fetch network.", "main": "dist/index.js", "scripts": { "build": "subql build", @@ -17,7 +17,7 @@ "schema.graphql", "project.yaml" ], - "author": "SubQuery Team", + "author": "SubQuery & Fetch.ai Teams", "license": "MIT", "devDependencies": { "@cosmjs/stargate": "^0.28.9", From ae1e8da70bb7e4c6346fbff6b3ae39adfc851b88 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Thu, 1 Sep 2022 16:21:40 +0200 Subject: [PATCH 046/143] fix: parseCoins (#71) --- src/mappings/utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mappings/utils.ts b/src/mappings/utils.ts index 8461d06fa..e11df3d4d 100644 --- a/src/mappings/utils.ts +++ b/src/mappings/utils.ts @@ -3,7 +3,6 @@ */ import { Coin } from "@cosmjs/amino"; -import { Uint64 } from "@cosmjs/math"; /** * Takes a coins list like "819966000ucosm,700000000ustake" and parses it. @@ -21,7 +20,7 @@ export function parseCoins(input: string): Coin[] { const match = part.match(/^([0-9]+)([a-zA-Z][a-zA-Z0-9/]{2,127})$/); if (!match) throw new Error("Got an invalid coin string"); return { - amount: Uint64.fromString(match[1]).toString(), + amount: match[1].toString(), denom: match[2], }; }); From ba230d1971bd3b10ddfb2c4101841a4a087ea400 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 5 Sep 2022 09:25:20 +0200 Subject: [PATCH 047/143] chore: bump version to v0.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d041d456..5462c1aaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ledger-subquery", - "version": "0.1.0", + "version": "0.1.1", "description": "This is the Fetch Ledger SubQuery project, an indexer for the Fetch network.", "main": "dist/index.js", "scripts": { From 795ad2a357e6bfb1210d08c36e45e89e2d568fb4 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Mon, 5 Sep 2022 10:06:50 +0200 Subject: [PATCH 048/143] fix: bridge contract remove hardcoded address (#70) --- docker-compose.yml | 1 - project.yaml | 4 +-- schema.graphql | 1 + src/mappings/mappingHandlers.ts | 16 ++++++++--- test/base_contract.py | 39 +++++++++++++++------------ test/helpers/field_enums.py | 1 + test/test_execute_contract_message.py | 4 +-- test/test_legacy_bridge_swap.py | 31 +++++++++++---------- 8 files changed, 57 insertions(+), 40 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4d21a5f56..1fab76650 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,6 @@ services: START_BLOCK: "1" NETWORK_ENDPOINT: "http://fetch-node:26657" CHAIN_ID: "testing" - LEGACY_BRIDGE_CONTRACT_ADDRESS: "fetch14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9szlkpka" volumes: - ./:/app command: diff --git a/project.yaml b/project.yaml index e6096c60a..816244a9e 100644 --- a/project.yaml +++ b/project.yaml @@ -53,10 +53,8 @@ dataSources: kind: cosmos/MessageHandler filter: type: "/cosmwasm.wasm.v1.MsgExecuteContract" - # Filter to only messages with the vote function call + # Filter to only messages with the swap function call contractCall: "swap" # The name of the contract function that was called - values: # This is the specific smart contract that we are subscribing to - contract: "fetch1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsa26575" - handler: handleNativeTransfer kind: cosmos/MessageHandler filter: diff --git a/schema.graphql b/schema.graphql index 27543a0c7..ab7329e4c 100644 --- a/schema.graphql +++ b/schema.graphql @@ -97,6 +97,7 @@ type DistDelegatorClaim @entity { type LegacyBridgeSwap @entity { id: ID! # id field is always required and must look like this destination: String! + contract: String! amount: BigInt! denom: String! executeContractMessage: ExecuteContractMessage! diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index dbcb07f8d..d48a64782 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -215,17 +215,27 @@ export async function handleDistDelegatorClaim(msg: CosmosMessage): Promise { - logger.info(`[handleLegacyBridgeSwap] (tx ${msg.tx.hash}): indexing LegacyBridgeSwap ${messageId(msg)}`) + const id = messageId(msg); + logger.info(`[handleLegacyBridgeSwap] (tx ${msg.tx.hash}): indexing LegacyBridgeSwap ${id}`) logger.debug(`[handleLegacyBridgeSwap] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) - const id = messageId(msg); const { msg: {swap: {destination}}, - funds: [{amount, denom}] + funds: [{amount, denom}], + contract, } = msg.msg.decodedMsg; + + // gracefully skip indexing "swap" messages that doesn't fullfill the bridge contract + // otherwise, the node will just crashloop trying to save the message to the db with required null fields. + if (!destination || !amount || !denom || !contract) { + logger.warn(`[handleLegacyBridgeSwap] (tx ${msg.tx.hash}): cannot index message (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) + return + } + const legacySwap = LegacyBridgeSwap.create({ id, destination, + contract, amount: BigInt(amount), denom, executeContractMessageId: id, diff --git a/test/base_contract.py b/test/base_contract.py index b729bf7fe..942bf6b1b 100644 --- a/test/base_contract.py +++ b/test/base_contract.py @@ -2,12 +2,12 @@ from cosmpy.aerial.contract import LedgerContract -class BaseContract(base.Base): +class BridgeContract(base.Base): contract = None @classmethod def setUpClass(cls): - super(BaseContract, cls).setUpClass() + super(BridgeContract, cls).setUpClass() url = "https://github.com/fetchai/fetch-ethereum-bridge-v1/releases/download/v0.2.0/bridge.wasm" if not os.path.exists("../.contract"): os.mkdir("../.contract") @@ -16,24 +16,29 @@ def setUpClass(cls): temp.close() except: contract_request = requests.get(url) - file = open("../.contract/bridge.wasm", "wb").write(contract_request.content) + file = open("../.contract/bridge.wasm", "wb") + file.write(contract_request.content) + file.close() + # LedgerContract will attempt to discover any existing contract having the same bytecode hash + # see https://github.com/fetchai/cosmpy/blob/master/cosmpy/aerial/contract/__init__.py#L74 cls.contract = LedgerContract("../.contract/bridge.wasm", cls.ledger_client) - - # TODO: avoid deploying with every test run - # Instead, query for active contract and skip if found. - + + # deploy will store the contract only if no existing contracts was found during init. + # and it will instantiate the contract only if contract.address is None + # see: https://github.com/fetchai/cosmpy/blob/master/cosmpy/aerial/contract/__init__.py#L168-L179 cls.contract.deploy( - {"cap": "250000000000000000000000000", - "reverse_aggregated_allowance": "3000000000000000000000000", - "reverse_aggregated_allowance_approver_cap": "3000000000000000000000000", - "lower_swap_limit": "1", - "upper_swap_limit": "1000000000000000000000000", - "swap_fee": "0", - "paused_since_block": 18446744073709551615, - "denom": "atestfet", - "next_swap_id": 0 - }, + { + "cap": "250000000000000000000000000", + "reverse_aggregated_allowance": "3000000000000000000000000", + "reverse_aggregated_allowance_approver_cap": "3000000000000000000000000", + "lower_swap_limit": "1", + "upper_swap_limit": "1000000000000000000000000", + "swap_fee": "0", + "paused_since_block": 18446744073709551615, + "denom": "atestfet", + "next_swap_id": 0 + }, cls.validator_wallet ) diff --git a/test/helpers/field_enums.py b/test/helpers/field_enums.py index 09f6d2b19..a748b959d 100644 --- a/test/helpers/field_enums.py +++ b/test/helpers/field_enums.py @@ -83,6 +83,7 @@ class LegacyBridgeSwapFields(NamedFields): destination = 4 amount = 5 denom = 6 + contract = 7 @classmethod def select_query(cls, table="legacy_bridge_swaps"): diff --git a/test/test_execute_contract_message.py b/test/test_execute_contract_message.py index 317b1b883..b313687f4 100644 --- a/test/test_execute_contract_message.py +++ b/test/test_execute_contract_message.py @@ -5,11 +5,11 @@ from gql import gql -from base_contract import BaseContract +from base_contract import BridgeContract from helpers.field_enums import ExecuteContractMessageFields -class TestContractExecution(BaseContract): +class TestContractExecution(BridgeContract): amount = '10000' denom = "atestfet" method = 'swap' diff --git a/test/test_legacy_bridge_swap.py b/test/test_legacy_bridge_swap.py index 06f4956a7..483cc504f 100644 --- a/test/test_legacy_bridge_swap.py +++ b/test/test_legacy_bridge_swap.py @@ -1,19 +1,13 @@ import datetime as dt import decimal -import json -import re -import time import unittest +import time -from gql import gql - -from base_contract import BaseContract +from base_contract import BridgeContract from helpers.field_enums import LegacyBridgeSwapFields from helpers.graphql import test_filtered_query -from helpers.regexes import msg_id_regex, tx_id_regex, block_id_regex - -class TestContractSwap(BaseContract): +class TestContractSwap(BridgeContract): amount = decimal.Decimal(10000) denom = "atestfet" @@ -22,19 +16,19 @@ def setUpClass(cls): super().setUpClass() cls.clean_db({"legacy_bridge_swaps"}) - cls.contract.execute( + resp = cls.contract.execute( {"swap": {"destination": cls.validator_address}}, cls.validator_wallet, funds=str(cls.amount)+cls.denom ) - - # primitive solution to wait for indexer to observe and handle new tx - TODO: add robust solution - time.sleep(12) + cls.ledger_client.wait_for_query_tx(resp.tx_hash) + time.sleep(5) # stil need to give some extra time for the indexer to pickup the tx def test_contract_swap(self): swap = self.db_cursor.execute(LegacyBridgeSwapFields.select_query()).fetchone() self.assertIsNotNone(swap, "\nDBError: table is empty - maybe indexer did not find an entry?") self.assertEqual(swap[LegacyBridgeSwapFields.destination.value], self.validator_address, "\nDBError: swap sender address does not match") + self.assertEqual(swap[LegacyBridgeSwapFields.contract.value], self.contract.address, "\nDBError: contract address does not match") self.assertEqual(swap[LegacyBridgeSwapFields.amount.value], self.amount, "\nDBError: fund amount does not match") self.assertEqual(swap[LegacyBridgeSwapFields.denom.value], self.denom, "\nDBError: fund denomination does not match") @@ -48,6 +42,7 @@ def test_retrieve_swap(self): { id destination + contract amount denom executeContractMessage { id } @@ -76,6 +71,13 @@ def filtered_legacy_bridge_swap_query(_filter): "equalTo": str(self.validator_address) } }) + + # query bridge swaps, filter by contract address + filter_by_contract_equals = filtered_legacy_bridge_swap_query({ + "contract": { + "equalTo": str(self.contract.address) + } + }) # query legacy bridge swaps, filter by amount filter_by_amount_above = filtered_legacy_bridge_swap_query({ @@ -87,7 +89,8 @@ def filtered_legacy_bridge_swap_query(_filter): for (name, query) in [ ("by block timestamp range", filter_by_block_timestamp_range), ("by amount above", filter_by_amount_above), - ("by destination equals", filter_by_destination_equals) + ("by destination equals", filter_by_destination_equals), + ("by contract equals", filter_by_contract_equals), ]: with self.subTest(name): result = self.gql_client.execute(query) From 79b4a419649f56fa3bc9b626cd5f26735e1e98b9 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 5 Sep 2022 14:36:52 +0200 Subject: [PATCH 049/143] fix: remove LEGACY_BRIDGE_CONTRACT_ADDRESS from node-entrypoint.sh (#74) --- scripts/node-entrypoint.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/node-entrypoint.sh b/scripts/node-entrypoint.sh index d0aeb0b64..8cab93032 100755 --- a/scripts/node-entrypoint.sh +++ b/scripts/node-entrypoint.sh @@ -17,10 +17,5 @@ if [[ ! -z "${NETWORK_ENDPOINT}" ]]; then yq -i '.network.endpoint = strenv(NETWORK_ENDPOINT)' project.yaml fi -if [[ ! -z "${LEGACY_BRIDGE_CONTRACT_ADDRESS}" ]]; then - echo "[Config Update] Legacy Bridge Contract Address: ${LEGACY_BRIDGE_CONTRACT_ADDRESS}" - yq -i '.dataSources[].mapping.handlers |= map(select(.handler == "handleLegacyBridgeSwap").filter.values.contract = env(LEGACY_BRIDGE_CONTRACT_ADDRESS))' project.yaml -fi - # run the main node exec /sbin/tini -- /usr/local/lib/node_modules/@subql/node-cosmos/bin/run From b596ea20c3ceb4ee4bb0e3a5818d26d56b365924 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 5 Sep 2022 14:37:10 +0200 Subject: [PATCH 050/143] chore: remove unneeded CI workflow (#73) --- .github/workflows/docker_build.yml | 61 ------------------------------ 1 file changed, 61 deletions(-) delete mode 100644 .github/workflows/docker_build.yml diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml deleted file mode 100644 index 7f541323e..000000000 --- a/.github/workflows/docker_build.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Docker build latest - -on: - pull_request: - branches: [main] - -jobs: - ledger-subquery: - name: build - runs-on: ubuntu-latest - - env: - IMAGE_PROJECT_ID: fetch-ai-images - NODE_IMAGE_NAME: subquery-node - API_IMAGE_NAME: subquery-api - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Get image tag - id: vars - shell: bash - run: | - echo "::set-output name=tag_name::latest" - - # Authenticate to GCP - - id: 'auth' - uses: 'google-github-actions/auth@v0' - with: - credentials_json: '${{ secrets.DEVOPS_IMAGES_SA_KEY }}' - - - name: 'Set up Cloud SDK' - uses: 'google-github-actions/setup-gcloud@v0' - - - run: 'gcloud info' - - # Configure docker to use the gcloud command-line tool as a credential helper - - name: Configure Docker - run: | - gcloud auth configure-docker -q - - # Docker build ; docker push - - name: Build and push subquery node - uses: docker/build-push-action@v3 - with: - context: . - file: ./docker/node.dockerfile - push: true - tags: | - gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.NODE_IMAGE_NAME }}:${{ steps.vars.outputs.tag_name }} - - - name: Build and push graphql api - uses: docker/build-push-action@v3 - with: - context: . - file: ./docker/api.dockerfile - push: true - tags: | - gcr.io/${{ env.IMAGE_PROJECT_ID }}/${{ env.API_IMAGE_NAME }}:${{ steps.vars.outputs.tag_name }} From 66bd83207d6c84ade914dc4f9ac8fda13728d751 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 5 Sep 2022 15:44:55 +0200 Subject: [PATCH 051/143] chore: run e2e tests in CI (#53) Co-authored-by: daeMOn --- .dockerignore | 2 +- .github/workflows/test.yml | 37 +++++++++++++++++ ci-compose.yml | 82 ++++++++++++++++++++++++++++++++++++++ docker/fetchd.dockerfile | 23 +---------- 4 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 ci-compose.yml diff --git a/.dockerignore b/.dockerignore index 2bf4dc4dd..3578a064c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,7 @@ dist/ package-lock.json # Node Specific -node_modules/ +*/node_modules/ # Deployment k8s/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..e8f17db3e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,37 @@ +name: End-to-end tests + +on: + pull_request: + branches: [main] + +jobs: + e2e-tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Update + run: sudo apt update + - name: Install OS dependencies + run: sudo apt install -y python3-dev + + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Start-up local test environment + uses: isbang/compose-action@v1.2.0 + with: + compose-file: "./ci-compose.yml" + + # NB: doing this here gives the test env time to come up + - name: Install test dependencies + uses: palewire/install-python-pipenv-pipfile@v2 + with: + python-version: 3.9 + + - name: Run end-to-end tests + run: pipenv run python -m unittest discover -s ./test diff --git a/ci-compose.yml b/ci-compose.yml new file mode 100644 index 000000000..3cca59d5f --- /dev/null +++ b/ci-compose.yml @@ -0,0 +1,82 @@ +version: "3" + +services: + postgres: + image: postgres:14-alpine + ports: + - 5432:5432 + volumes: + - .data/postgres:/var/lib/postgresql/data + environment: + POSTGRES_USER: "subquery" + POSTGRES_PASSWORD: "subquery" + POSTGRES_DB: "subquery" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U subquery"] + interval: 5s + timeout: 5s + retries: 5 + + subquery-node: + build: + context: . + dockerfile: ./docker/node.dockerfile + depends_on: + "postgres": + condition: service_healthy + "fetch-node": + condition: service_started + restart: always + environment: + DB_USER: "subquery" + DB_PASS: "subquery" + DB_DATABASE: "subquery" + DB_HOST: postgres + DB_PORT: 5432 + START_BLOCK: "1" + NETWORK_ENDPOINT: "http://fetch-node:26657" + CHAIN_ID: "testing" + command: + - -f=/app + - --db-schema=app + - --batch-size=1 + healthcheck: + test: ["CMD", "curl", "-f", "http://subquery-node:3000/ready"] + interval: 3s + timeout: 5s + retries: 10 + + graphql-engine: + build: + context: . + dockerfile: ./docker/api.dockerfile + ports: + - 3000:3000 + depends_on: + "postgres": + condition: service_healthy + "subquery-node": + condition: service_started + restart: always + environment: + DB_USER: "subquery" + DB_PASS: "subquery" + DB_DATABASE: "subquery" + DB_HOST: postgres + DB_PORT: 5432 + entrypoint: ["/sbin/tini", "--", "yarn", "start:prod"] + command: + - --name=app + - --playground + - --indexer=http://subquery-node:3000 + + fetch-node: + build: + context: . + dockerfile: ./docker/fetchd.dockerfile + environment: + FETCHMNEMONIC: "nut grocery slice visit barrel peanut tumble patch slim logic install evidence fiction shield rich brown around arrest fresh position animal butter forget cost" + ports: + - "26657:26657" + - "1317:1317" + - "9090:9090" diff --git a/docker/fetchd.dockerfile b/docker/fetchd.dockerfile index 118b79715..a9f45aad1 100644 --- a/docker/fetchd.dockerfile +++ b/docker/fetchd.dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 as base +FROM fetchai/fetchd:0.10.6 USER root @@ -27,27 +27,6 @@ ENV TOKEN=${token} #ENV VALIDATOR_KEY_PWD=${validator_key_pwd} ENV FETCHMNEMONIC="nut grocery slice visit barrel peanut tumble patch slim logic install evidence fiction shield rich brown around arrest fresh position animal butter forget cost" -#################### -### dependencies ### -#################### - - -# utils -RUN apt-get update && apt-get install -y wget make curl git jq python3 python3-pip -ENV LC_ALL C.UTF-8 -ENV LANG C.UTF-8 - -# golang -RUN wget https://golang.org/dl/go${GOLANG_VER}.linux-amd64.tar.gz && \ - tar -xzvf go${GOLANG_VER}.linux-amd64.tar.gz -C /usr/local && \ - mkdir /root/go -ENV PATH="${PATH}:/usr/local/go/bin:/root/go/bin" - -# fetchd (https://docs.fetch.ai/ledger_v2/building/) -RUN git clone https://github.com/fetchai/fetchd.git && cd fetchd && \ - git checkout v${FETCHD_VER} && \ - make install && fetchd version - ######################## ### setup local node ### ######################## From bcd93406446c51e5c22d1173edb19da8fe13a408 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Tue, 6 Sep 2022 08:10:17 +0200 Subject: [PATCH 052/143] fix: rework contract handling in tests (#72) Co-authored-by: Bryan White --- .gitignore | 1 + Pipfile | 1 + Pipfile.lock | 171 +++++++++++++++++--------- scripts/00_setup_fetchd_local.sh | 2 +- test/base_contract.py | 47 ------- test/contracts.py | 61 +++++++++ test/test_execute_contract_message.py | 19 +-- test/test_legacy_bridge_swap.py | 14 ++- 8 files changed, 196 insertions(+), 120 deletions(-) delete mode 100644 test/base_contract.py create mode 100644 test/contracts.py diff --git a/.gitignore b/.gitignore index 0db42d78b..97e0e6b47 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ dist keys docker-compose.override.yml +.contract \ No newline at end of file diff --git a/Pipfile b/Pipfile index 21251656a..42327f4c1 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ bip-utils = "*" gql = {extras = ["all"], version = "*"} psycopg = "*" python-dateutil = "*" +dataclasses-json = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 16dc71d19..2ef9b33fe 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "cefbf14b4853883edb80222128e149bad3e3f9d770e66abda964cabbb7f9125c" + "sha256": "73ad0b88df8b9d56286ae9f7875f2eb12053f28c821d8291ebcbbf1e62811cd2" }, "pipfile-spec": 6, "requires": { @@ -174,10 +174,10 @@ }, "botocore": { "hashes": [ - "sha256:4cc12ab3b6f195292e3e34da6aa356edb8acee51e8405d048b50ccbb64a1b9f3", - "sha256:d904ee4743bd4b2517634b012c98dcb166f82e9d20e50d8367cd078779181ec2" + "sha256:6293d1cb392a4779cf0a44055cae9ac0728809c14a11f2d91e679a00a9beae20", + "sha256:6c8c8c82b38ba2353bd3bc071019ab44d8a160b9d17f3ab166f0ceaf1ca38c12" ], - "version": "==1.27.58" + "version": "==1.27.66" }, "cachetools": { "hashes": [ @@ -371,6 +371,14 @@ ], "version": "==1.7" }, + "dataclasses-json": { + "hashes": [ + "sha256:bc285b5f892094c3a53d558858a88553dd6a61a11ab1a8128a0e554385dcc5dd", + "sha256:c2c11bc8214fbf709ffc369d11446ff6945254a7f09128154a7620613d8fda90" + ], + "index": "pypi", + "version": "==0.5.7" + }, "ecdsa": { "hashes": [ "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49", @@ -452,11 +460,11 @@ }, "google-api-core": { "hashes": [ - "sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc", - "sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50" + "sha256:1d053734f14591939e7764e99c31253fed46bf2578da0dcd82821f17a6dd991c", + "sha256:325529859836a479244b0882c1a77320fd35cb108df2ec1232e3e908ea56eda4" ], - "markers": "python_version >= '3.6'", - "version": "==2.8.2" + "markers": "python_version >= '3.7'", + "version": "==2.10.0" }, "google-api-python-client": { "hashes": [ @@ -510,55 +518,55 @@ }, "grpcio": { "hashes": [ - "sha256:0425b5577be202d0a4024536bbccb1b052c47e0766096e6c3a5789ddfd5f400d", - "sha256:06c0739dff9e723bca28ec22301f3711d85c2e652d1c8ae938aa0f7ad632ef9a", - "sha256:08307dc5a6ac4da03146d6c00f62319e0665b01c6ffe805cfcaa955c17253f9c", - "sha256:090dfa19f41efcbe760ae59b34da4304d4be9a59960c9682b7eab7e0b6748a79", - "sha256:0a24b50810aae90c74bbd901c3f175b9645802d2fbf03eadaf418ddee4c26668", - "sha256:0cd44d78f302ff67f11a8c49b786c7ccbed2cfef6f4fd7bb0c3dc9255415f8f7", - "sha256:0d8a7f3eb6f290189f48223a5f4464c99619a9de34200ce80d5092fb268323d2", - "sha256:14d2bc74218986e5edf5527e870b0969d63601911994ebf0dce96288548cf0ef", - "sha256:1bb9afa85e797a646bfcd785309e869e80a375c959b11a17c9680abebacc0cb0", - "sha256:1ec63bbd09586e5cda1bdc832ae6975d2526d04433a764a1cc866caa399e50d4", - "sha256:2061dbe41e43b0a5e1fd423e8a7fb3a0cf11d69ce22d0fac21f1a8c704640b12", - "sha256:324e363bad4d89a8ec7124013371f268d43afd0ac0fdeec1b21c1a101eb7dafb", - "sha256:35dfd981b03a3ec842671d1694fe437ee9f7b9e6a02792157a2793b0eba4f478", - "sha256:43857d06b2473b640467467f8f553319b5e819e54be14c86324dad83a0547818", - "sha256:4706c78b0c183dca815bbb4ef3e8dd2136ccc8d1699f62c585e75e211ad388f6", - "sha256:4d9ad7122f60157454f74a850d1337ba135146cef6fb7956d78c7194d52db0fe", - "sha256:544da3458d1d249bb8aed5504adf3e194a931e212017934bf7bfa774dad37fb3", - "sha256:55782a31ec539f15b34ee56f19131fe1430f38a4be022eb30c85e0b0dcf57f11", - "sha256:55cd8b13c5ef22003889f599b8f2930836c6f71cd7cf3fc0196633813dc4f928", - "sha256:5dbba95fab9b35957b4977b8904fc1fa56b302f9051eff4d7716ebb0c087f801", - "sha256:5f57b9b61c22537623a5577bf5f2f970dc4e50fac5391090114c6eb3ab5a129f", - "sha256:64e097dd08bb408afeeaee9a56f75311c9ca5b27b8b0278279dc8eef85fa1051", - "sha256:664a270d3eac68183ad049665b0f4d0262ec387d5c08c0108dbcfe5b351a8b4d", - "sha256:668350ea02af018ca945bd629754d47126b366d981ab88e0369b53bc781ffb14", - "sha256:67cd275a651532d28620eef677b97164a5438c5afcfd44b15e8992afa9eb598c", - "sha256:68b5e47fcca8481f36ef444842801928e60e30a5b3852c9f4a95f2582d10dcb2", - "sha256:7191ffc8bcf8a630c547287ab103e1fdf72b2e0c119e634d8a36055c1d988ad0", - "sha256:815089435d0f113719eabf105832e4c4fa1726b39ae3fb2ca7861752b0f70570", - "sha256:8dbef03853a0dbe457417c5469cb0f9d5bf47401b49d50c7dad3c495663b699b", - "sha256:91cd292373e85a52c897fa5b4768c895e20a7dc3423449c64f0f96388dd1812e", - "sha256:9298d6f2a81f132f72a7e79cbc90a511fffacc75045c2b10050bb87b86c8353d", - "sha256:96cff5a2081db82fb710db6a19dd8f904bdebb927727aaf4d9c427984b79a4c1", - "sha256:9e63e0619a5627edb7a5eb3e9568b9f97e604856ba228cc1d8a9f83ce3d0466e", - "sha256:a278d02272214ec33f046864a24b5f5aab7f60f855de38c525e5b4ef61ec5b48", - "sha256:a6b2432ac2353c80a56d9015dfc5c4af60245c719628d4193ecd75ddf9cd248c", - "sha256:b821403907e865e8377af3eee62f0cb233ea2369ba0fcdce9505ca5bfaf4eeb3", - "sha256:b88bec3f94a16411a1e0336eb69f335f58229e45d4082b12d8e554cedea97586", - "sha256:bfdb8af4801d1c31a18d54b37f4e49bb268d1f485ecf47f70e78d56e04ff37a7", - "sha256:c79996ae64dc4d8730782dff0d1daacc8ce7d4c2ba9cef83b6f469f73c0655ce", - "sha256:cc34d182c4fd64b6ff8304a606b95e814e4f8ed4b245b6d6cc9607690e3ef201", - "sha256:d0d481ff55ea6cc49dab2c8276597bd4f1a84a8745fedb4bc23e12e9fb9d0e45", - "sha256:e9723784cf264697024778dcf4b7542c851fe14b14681d6268fb984a53f76df1", - "sha256:f4508e8abd67ebcccd0fbde6e2b1917ba5d153f3f20c1de385abd8722545e05f", - "sha256:f515782b168a4ec6ea241add845ccfebe187fc7b09adf892b3ad9e2592c60af1", - "sha256:f89de64d9eb3478b188859214752db50c91a749479011abd99e248550371375f", - "sha256:fcd5d932842df503eb0bf60f9cc35e6fe732b51f499e78b45234e0be41b0018d" + "sha256:1471e6f25a8e47d9f88499f48c565fc5b2876e8ee91bfb0ff33eaadd188b7ea6", + "sha256:19f9c021ae858d3ef6d5ec4c0acf3f0b0a61e599e5aa36c36943c209520a0e66", + "sha256:1c924d4e0493fd536ba3b82584b370e8b3c809ef341f9f828cff2dc3c761b3ab", + "sha256:1d065f40fe74b52b88a6c42d4373a0983f1b0090f952a0747f34f2c11d6cbc64", + "sha256:1ff1be0474846ed15682843b187e6062f845ddfeaceb2b28972073f474f7b735", + "sha256:2563357697f5f2d7fd80c1b07a57ef4736551327ad84de604e7b9f6c1b6b4e20", + "sha256:2b6c336409937fd1cd2bf78eb72651f44d292d88da5e63059a4e8bd01b9d7411", + "sha256:3340cb2224cc397954def015729391d85fb31135b5a7efca363e73e6f1b0e908", + "sha256:346bef672a1536d59437210f16af35389d715d2b321bfe4899b3d6476a196706", + "sha256:3d319a0c89ffac9b8dfc75bfe727a4c835d18bbccc14203b20eb5949c6c7d87d", + "sha256:460f5bec23fffa3c041aeba1f93a0f06b7a29e6a4da3658a52e1a866494920ab", + "sha256:4786323555a9f2c6380cd9a9922bcfd42165a51d68d242eebfcdfdc667651c96", + "sha256:53b6306f9473020bc47ddf64ca704356466e63d5f88f5c2a7bf0a4692e7f03c4", + "sha256:53fa2fc1a1713195fa7acf7443a6f59b6ac7837607690f813c66cc18a9cb8135", + "sha256:598c8c42420443c55431eba1821c7a2f72707f1ff674a4de9e0bb03282923cfb", + "sha256:5a6a750c8324f3974e95265d3f9a0541573c537af1f67b3f6f46bf9c0b2e1b36", + "sha256:5d81cd3c161291339ed3b469250c2f5013c3083dea7796e93aedff8f05fdcec1", + "sha256:626822d799d8fab08f07c8d95ef5c36213d24143f7cad3f548e97413db9f4110", + "sha256:660217eccd2943bf23ea9a36e2a292024305aec04bf747fbcff1f5032b83610e", + "sha256:741eeff39a26d26da2b6d74ff0559f882ee95ee4e3b20c0b4b829021cb917f96", + "sha256:7cee20a4f873d61274d70c28ff63d19677d9eeea869c6a9cbaf3a00712336b6c", + "sha256:8bbaa6647986b874891bc682a1093df54cbdb073b5d4b844a2b480c47c7ffafd", + "sha256:934aad7350d9577f4275e787f3d91d3c8ff4efffa8d6b807d343d3c891ff53eb", + "sha256:9477967e605ba08715dcc769b5ee0f0d8b22bda40ef25a0df5a8759e5a4d21a5", + "sha256:97dc35a99c61d5f35ec6457d3df0a4695ba9bb04a35686e1c254462b15c53f98", + "sha256:9d116106cf220c79e91595523c893f1cf09ec0c2ea49de4fb82152528b7e6833", + "sha256:9fba1d0ba7cf56811728f1951c800a9aca6677e86433c5e353f2cc2c4039fda6", + "sha256:a15409bc1d05c52ecb00f5e42ab8ff280e7149f2eb854728f628fb2a0a161a5b", + "sha256:a1b81849061c67c2ffaa6ed27aa3d9b0762e71e68e784e24b0330b7b1c67470a", + "sha256:a5edbcb8289681fcb5ded7542f2b7dd456489e83007a95e32fcaf55e9f18603e", + "sha256:a661d4b9b314327dec1e92ed57e591e8e5eb055700e0ba9e9687f734d922dcb6", + "sha256:b005502c59835f9ba3c3f8742f64c19eeb3db41eae1a89b035a559b39b421803", + "sha256:b01faf7934c606d5050cf055c1d03943180f23d995d68d04cf50c80d1ef2c65a", + "sha256:b0fa666fecdb1b118d37823937e9237afa17fe734fc4dbe6dd642e1e4cca0246", + "sha256:c54734a6eb3be544d332e65c846236d02e5fc71325e8c53af91e83a46b87b506", + "sha256:c6b6969c529521c86884a13745a4b68930db1ef2e051735c0f479d0a7adb25b6", + "sha256:ca382028cdfd2d79b7704b2acb8ae1fb54e9e1a03a6765e1895ba89a6fcfaba1", + "sha256:ca5209ef89f7607be47a308fa92308cf079805ed556ecda672f00039a26e366f", + "sha256:d03009a26f7edca9f0a581aa5d3153242b815b858cb4790e34a955afb303c6ba", + "sha256:d751f8beb383c4a5a95625d7ccc1ab183b98b02c6a88924814ea7fbff530872d", + "sha256:dad2501603f954f222a6e555413c454a5f8d763ab910fbab3855bcdfef6b3148", + "sha256:dbba883c2b6d63949bc98ab1950bc22cf7c8d4e8cb68de6edde49d3cccd8fd26", + "sha256:e02f6ba10a3d4e289fa7ae91b301783a750d118b60f17924ca05e506c7d29bc8", + "sha256:f0ef1dafb4eadeaca58aec8c721a5a73d551064b0c63d57fa003e233277c642e", + "sha256:f29627d66ae816837fd32c9450dc9c54780962cd74d034513ed829ba3ab46652", + "sha256:f3a99ed422c38bd1bc893cb2cb2cea6d64173ec30927f699e95f5f58bdf625cf" ], "markers": "python_version >= '3.6'", - "version": "==1.47.0" + "version": "==1.48.1" }, "httplib2": { "hashes": [ @@ -584,6 +592,21 @@ "markers": "python_version >= '3.7'", "version": "==1.0.1" }, + "marshmallow": { + "hashes": [ + "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408", + "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0" + ], + "markers": "python_version >= '3.7'", + "version": "==3.17.1" + }, + "marshmallow-enum": { + "hashes": [ + "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58", + "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072" + ], + "version": "==1.5.1" + }, "multidict": { "hashes": [ "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", @@ -649,6 +672,21 @@ "markers": "python_version >= '3.7'", "version": "==6.0.2" }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, "protobuf": { "hashes": [ "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf", @@ -681,11 +719,11 @@ }, "psycopg": { "hashes": [ - "sha256:0f5e18920ed978f5063e48acc5ca4389225db7d06a03090d2bbb7a0ec7a640b2", - "sha256:44ca63373c33957ca852fefa1940f8cc5d4c11493b7f6710b0ab250ff5abc50c" + "sha256:1dfbbf221de14eab4bf01c6a5f15496f737d6b6e0db698f9e6081a692c598dc1", + "sha256:46a13ea46d16523c0f4fbf98a573881ba483ca94a920940629e29a688925121a" ], "index": "pypi", - "version": "==3.0.16" + "version": "==3.1" }, "py-sr25519-bindings": { "hashes": [ @@ -829,7 +867,7 @@ "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" ], - "markers": "python_version >= '3.1'", + "markers": "python_full_version >= '3.6.8'", "version": "==3.0.9" }, "python-dateutil": { @@ -896,6 +934,21 @@ ], "version": "==2021.10.8.3" }, + "typing-extensions": { + "hashes": [ + "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", + "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" + ], + "markers": "python_version < '3.11'", + "version": "==4.3.0" + }, + "typing-inspect": { + "hashes": [ + "sha256:5fbf9c1e65d4fa01e701fe12a5bca6c6e08a4ffd5bc60bfac028253a447c5188", + "sha256:8b1ff0c400943b6145df8119c41c244ca8207f1f10c9c057aeed1560e4806e3d" + ], + "version": "==0.8.0" + }, "uritemplate": { "hashes": [ "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0", diff --git a/scripts/00_setup_fetchd_local.sh b/scripts/00_setup_fetchd_local.sh index 35d278c7d..0bb42be27 100755 --- a/scripts/00_setup_fetchd_local.sh +++ b/scripts/00_setup_fetchd_local.sh @@ -22,7 +22,7 @@ sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["\*"\]/' ~/.fetch # update the block parameters to match mainnet cp ~/.fetchd/config/genesis.json ~/.fetchd/config.genesis.json.bak -jq '.consensus_params.block.max_gas = "2000000" | .consensus_params.block.max_bytes = "200000" | .consensus_params.evidence.max_bytes = "200000"' ~/.fetchd/config.genesis.json.bak > ~/.fetchd/config/genesis.json +jq '.consensus_params.block.max_gas = "3000000" | .consensus_params.block.max_bytes = "300000" | .consensus_params.evidence.max_bytes = "300000"' ~/.fetchd/config.genesis.json.bak > ~/.fetchd/config/genesis.json # Create a key to hold your validator account (echo "$FETCHMNEMONIC"; echo "$PASSWORD"; echo "$PASSWORD") | fetchd keys add validator --recover diff --git a/test/base_contract.py b/test/base_contract.py deleted file mode 100644 index 942bf6b1b..000000000 --- a/test/base_contract.py +++ /dev/null @@ -1,47 +0,0 @@ -import unittest, base, requests, os -from cosmpy.aerial.contract import LedgerContract - - -class BridgeContract(base.Base): - contract = None - - @classmethod - def setUpClass(cls): - super(BridgeContract, cls).setUpClass() - url = "https://github.com/fetchai/fetch-ethereum-bridge-v1/releases/download/v0.2.0/bridge.wasm" - if not os.path.exists("../.contract"): - os.mkdir("../.contract") - try: - temp = open("../.contract/bridge.wasm", "rb") - temp.close() - except: - contract_request = requests.get(url) - file = open("../.contract/bridge.wasm", "wb") - file.write(contract_request.content) - file.close() - - # LedgerContract will attempt to discover any existing contract having the same bytecode hash - # see https://github.com/fetchai/cosmpy/blob/master/cosmpy/aerial/contract/__init__.py#L74 - cls.contract = LedgerContract("../.contract/bridge.wasm", cls.ledger_client) - - # deploy will store the contract only if no existing contracts was found during init. - # and it will instantiate the contract only if contract.address is None - # see: https://github.com/fetchai/cosmpy/blob/master/cosmpy/aerial/contract/__init__.py#L168-L179 - cls.contract.deploy( - { - "cap": "250000000000000000000000000", - "reverse_aggregated_allowance": "3000000000000000000000000", - "reverse_aggregated_allowance_approver_cap": "3000000000000000000000000", - "lower_swap_limit": "1", - "upper_swap_limit": "1000000000000000000000000", - "swap_fee": "0", - "paused_since_block": 18446744073709551615, - "denom": "atestfet", - "next_swap_id": 0 - }, - cls.validator_wallet - ) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/contracts.py b/test/contracts.py new file mode 100644 index 000000000..b7c049c32 --- /dev/null +++ b/test/contracts.py @@ -0,0 +1,61 @@ +from dataclasses import dataclass +from dataclasses_json import dataclass_json +import requests, os +from cosmpy.aerial.contract import LedgerContract +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import Wallet +from typing import List + + +@dataclass_json +@dataclass +class BridgeContractConfig: + cap: str + reverse_aggregated_allowance: str + reverse_aggregated_allowance_approver_cap: str + lower_swap_limit: str + upper_swap_limit: str + swap_fee: str + paused_since_block: int + denom: str + next_swap_id: int + +DefaultBridgeContractConfig = BridgeContractConfig( + cap = "250000000000000000000000000", + reverse_aggregated_allowance = "3000000000000000000000000", + reverse_aggregated_allowance_approver_cap = "3000000000000000000000000", + lower_swap_limit = "1", + upper_swap_limit = "1000000000000000000000000", + swap_fee = "0", + paused_since_block = 18446744073709551615, + denom = "atestfet", + next_swap_id = 0 +) + +class BridgeContract(LedgerContract): + + def __init__(self, client: LedgerClient, admin: Wallet, cfg: BridgeContractConfig): + url = "https://github.com/fetchai/fetch-ethereum-bridge-v1/releases/download/v0.2.0/bridge.wasm" + if not os.path.exists(".contract"): + os.mkdir(".contract") + try: + temp = open(".contract/bridge.wasm", "rb") + temp.close() + except: + contract_request = requests.get(url) + file = open(".contract/bridge.wasm", "wb") + file.write(contract_request.content) + file.close() + + # LedgerContract will attempt to discover any existing contract having the same bytecode hash + # see https://github.com/fetchai/cosmpy/blob/master/cosmpy/aerial/contract/__init__.py#L74 + super().__init__(".contract/bridge.wasm", client) + + # deploy will store the contract only if no existing contracts was found during init. + # and it will instantiate the contract only if contract.address is None + # see: https://github.com/fetchai/cosmpy/blob/master/cosmpy/aerial/contract/__init__.py#L168-L179 + self.deploy( + cfg.to_dict(), + admin, + store_gas_limit=3000000 + ) diff --git a/test/test_execute_contract_message.py b/test/test_execute_contract_message.py index b313687f4..e6a7b77ef 100644 --- a/test/test_execute_contract_message.py +++ b/test/test_execute_contract_message.py @@ -5,33 +5,36 @@ from gql import gql -from base_contract import BridgeContract +import base +from contracts import BridgeContract, DefaultBridgeContractConfig from helpers.field_enums import ExecuteContractMessageFields -class TestContractExecution(BridgeContract): +class TestContractExecution(base.Base): amount = '10000' denom = "atestfet" method = 'swap' + _contract: BridgeContract + @classmethod def setUpClass(cls): super().setUpClass() cls.clean_db({"execute_contract_messages"}) - cls.contract.execute( + cls._contract = BridgeContract(cls.ledger_client, cls.validator_wallet, DefaultBridgeContractConfig) + resp = cls._contract.execute( {cls.method: {"destination": cls.validator_address}}, cls.validator_wallet, funds=str(cls.amount) + cls.denom ) - - # primitive solution to wait for indexer to observe and handle new tx - TODO: substitute with more robust solution - time.sleep(12) + cls.ledger_client.wait_for_query_tx(resp.tx_hash) + time.sleep(5) # stil need to give some extra time for the indexer to pickup the tx def test_contract_execution(self): execMsgs = self.db_cursor.execute(ExecuteContractMessageFields.select_query()).fetchone() self.assertIsNotNone(execMsgs, "\nDBError: table is empty - maybe indexer did not find an entry?") - self.assertEqual(execMsgs[ExecuteContractMessageFields.contract.value], self.contract.address, "\nDBError: contract address does not match") + self.assertEqual(execMsgs[ExecuteContractMessageFields.contract.value], self._contract.address, "\nDBError: contract address does not match") self.assertEqual(execMsgs[ExecuteContractMessageFields.method.value], self.method, "\nDBError: contract method does not match") self.assertEqual(execMsgs[ExecuteContractMessageFields.funds.value][0]["amount"], self.amount, "\nDBError: fund amount does not match") self.assertEqual(execMsgs[ExecuteContractMessageFields.funds.value][0]["denom"], self.denom, "\nDBError: fund denomination does not match") @@ -95,7 +98,7 @@ def test_contract_execution_retrieval(self): # As of now, this test depends on """ execMsgs = results["executeContractMessages"]["nodes"] self.assertTrue(execMsgs, "\nGQLError: No results returned from query") - self.assertEqual(execMsgs[0]["contract"], self.contract.address, "\nGQLError: contract address does not match") + self.assertEqual(execMsgs[0]["contract"], self._contract.address, "\nGQLError: contract address does not match") self.assertEqual(execMsgs[0]["method"], self.method, "\nGQLError: contract method does not match") self.assertEqual(int(execMsgs[0]["funds"][0]["amount"]), int(self.amount), "\nGQLError: fund amount does not match") self.assertEqual(execMsgs[0]["funds"][0]["denom"], self.denom, "\nGQLError: fund denomination does not match") diff --git a/test/test_legacy_bridge_swap.py b/test/test_legacy_bridge_swap.py index 483cc504f..5cebefce0 100644 --- a/test/test_legacy_bridge_swap.py +++ b/test/test_legacy_bridge_swap.py @@ -3,20 +3,24 @@ import unittest import time -from base_contract import BridgeContract +import base +from contracts import BridgeContract, DefaultBridgeContractConfig from helpers.field_enums import LegacyBridgeSwapFields from helpers.graphql import test_filtered_query -class TestContractSwap(BridgeContract): +class TestContractSwap(base.Base): amount = decimal.Decimal(10000) denom = "atestfet" + _contract: BridgeContract + @classmethod def setUpClass(cls): super().setUpClass() cls.clean_db({"legacy_bridge_swaps"}) - resp = cls.contract.execute( + cls._contract = BridgeContract(cls.ledger_client, cls.validator_wallet, DefaultBridgeContractConfig) + resp = cls._contract.execute( {"swap": {"destination": cls.validator_address}}, cls.validator_wallet, funds=str(cls.amount)+cls.denom @@ -28,7 +32,7 @@ def test_contract_swap(self): swap = self.db_cursor.execute(LegacyBridgeSwapFields.select_query()).fetchone() self.assertIsNotNone(swap, "\nDBError: table is empty - maybe indexer did not find an entry?") self.assertEqual(swap[LegacyBridgeSwapFields.destination.value], self.validator_address, "\nDBError: swap sender address does not match") - self.assertEqual(swap[LegacyBridgeSwapFields.contract.value], self.contract.address, "\nDBError: contract address does not match") + self.assertEqual(swap[LegacyBridgeSwapFields.contract.value], self._contract.address, "\nDBError: contract address does not match") self.assertEqual(swap[LegacyBridgeSwapFields.amount.value], self.amount, "\nDBError: fund amount does not match") self.assertEqual(swap[LegacyBridgeSwapFields.denom.value], self.denom, "\nDBError: fund denomination does not match") @@ -75,7 +79,7 @@ def filtered_legacy_bridge_swap_query(_filter): # query bridge swaps, filter by contract address filter_by_contract_equals = filtered_legacy_bridge_swap_query({ "contract": { - "equalTo": str(self.contract.address) + "equalTo": str(self._contract.address) } }) From 7b222822295ae9c0647ed031f18ffef7a3f0a886 Mon Sep 17 00:00:00 2001 From: amit-solanki <75420735+amit-solanki@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:07:05 +0530 Subject: [PATCH 053/143] Increase disk size to 30Gi (#85) --- k8s/subquery/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/subquery/values.yaml b/k8s/subquery/values.yaml index f012af93d..5ae3d8514 100644 --- a/k8s/subquery/values.yaml +++ b/k8s/subquery/values.yaml @@ -30,4 +30,4 @@ db: gcpProject: fetch-ai-sandbox gcpSecret: sandbox_subquery_postgres - storageSize: 7Gi + storageSize: 30Gi From d48bcdec32f3d9a51035b8ff76261e3288ba8164 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Tue, 13 Sep 2022 10:59:52 +0200 Subject: [PATCH 054/143] feat: account balance tracking (#88) Co-authored-by: Bryan White --- docker-compose.yml | 2 +- project.yaml | 8 ++++ schema.graphql | 16 +++++++ src/mappings/mappingHandlers.ts | 82 +++++++++++++++++++++++++++++++++ test/helpers/field_enums.py | 13 ++++++ test/helpers/regexes.py | 3 +- test/test_native_balances.py | 76 ++++++++++++++++++++++++++++++ 7 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 test/test_native_balances.py diff --git a/docker-compose.yml b/docker-compose.yml index 1fab76650..029e6bfdc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,7 +56,7 @@ services: "postgres": condition: service_healthy "subquery-node": - condition: service_started + condition: service_healthy restart: always environment: DB_USER: "subquery" diff --git a/project.yaml b/project.yaml index 816244a9e..417d40f74 100644 --- a/project.yaml +++ b/project.yaml @@ -63,3 +63,11 @@ dataSources: kind: cosmos/EventHandler filter: type: "withdraw_rewards" + - handler: handleNativeBalanceDecrement + kind: cosmos/EventHandler + filter: + type: "coin_spent" + - handler: handleNativeBalanceIncrement + kind: cosmos/EventHandler + filter: + type: "coin_received" diff --git a/schema.graphql b/schema.graphql index ab7329e4c..996fd4b74 100644 --- a/schema.graphql +++ b/schema.graphql @@ -121,3 +121,19 @@ type NativeTransfer @entity { transaction: Transaction! block: Block! } + +type Account @entity { + # id is the address + id: ID! + nativeBalanceChanges: [NativeBalanceChange]! @derivedFrom(field: "account") +} + +type NativeBalanceChange @entity { + id: ID! + balanceOffset: BigInt! + denom: String! @index + account: Account! + event: Event! + transaction: Transaction! + block: Block! +} diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index d48a64782..e1610df19 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -1,4 +1,6 @@ import { + Account, + NativeBalanceChange, Block, DistDelegatorClaim, Event, @@ -284,3 +286,83 @@ export async function handleDelegatorWithdrawRewardEvent(event: CosmosEvent): Pr claim.denom = denom; await claim.save(); } + +export async function handleNativeBalanceDecrement(event: CosmosEvent): Promise { + logger.info(`[handleNativeBalanceDecrement] (tx ${event.tx.hash}): indexing event ${event.idx + 1} / ${event.tx.tx.events.length}`) + logger.debug(`[handleNativeBalanceDecrement] (event.event): ${JSON.stringify(event.event, null, 2)}`) + logger.debug(`[handleNativeBalanceDecrement] (event.log): ${JSON.stringify(event.log, null, 2)}`) + + // sample event.event.attributes: + // [ + // {"key":"spender","value":"fetch1jv65s3grqf6v6jl3dp4t6c9t9rk99cd85zdctg"}, + // {"key":"amount","value":"75462013217046121atestfet"}, + // {"key":"spender","value":"fetch1wurz7uwmvchhc8x0yztc7220hxs9jxdjdsrqmn"}, + // {"key":"amount","value":"100atestfet"} + // ] + let spendEvents = []; + for (const [i, e] of Object.entries(event.event.attributes)) { + if (e.key !== "spender") { + continue + } + const spender = e.value; + const amountStr = event.event.attributes[parseInt(i)+1].value; + + const coin = parseCoins(amountStr)[0]; + const amount = BigInt(0) - BigInt(coin.amount); // save a negative amount for a "spend" event + spendEvents.push({spender: spender, amount: amount, denom: coin.denom}) + }; + + for (const [i, spendEvent] of Object.entries(spendEvents)) { + await saveNativeBalanceEvent(`${messageId(event)}-spend-${i}`, spendEvent.spender, spendEvent.amount, spendEvent.denom, event); + } +} + +export async function handleNativeBalanceIncrement(event: CosmosEvent): Promise { + logger.info(`[handleNativeBalanceIncrement] (tx ${event.tx.hash}): indexing event ${event.idx + 1} / ${event.tx.tx.events.length}`) + logger.debug(`[handleNativeBalanceIncrement] (event.event): ${JSON.stringify(event.event, null, 2)}`) + logger.debug(`[handleNativeBalanceIncrement] (event.log): ${JSON.stringify(event.log, null, 2)}`) + + // sample event.event.attributes: + // [ + // {"key":"receiver","value":"fetch1jv65s3grqf6v6jl3dp4t6c9t9rk99cd85zdctg"}, + // {"key":"amount","value":"75462013217046121atestfet"}, + // {"key":"receiver","value":"fetch1wurz7uwmvchhc8x0yztc7220hxs9jxdjdsrqmn"}, + // {"key":"amount","value":"100atestfet"} + // ] + let receiveEvents = []; + for (const [i, e] of Object.entries(event.event.attributes)) { + if (e.key !== "receiver") { + continue + } + const receiver = e.value; + const amountStr = event.event.attributes[parseInt(i)+1].value; + + const coin = parseCoins(amountStr)[0]; + const amount = BigInt(coin.amount); + receiveEvents.push({receiver: receiver, amount: amount, denom: coin.denom}) + }; + + for (const [i, receiveEvent] of Object.entries(receiveEvents)) { + await saveNativeBalanceEvent(`${messageId(event)}-receive-${i}`, receiveEvent.receiver, receiveEvent.amount, receiveEvent.denom, event); + } +} + +async function saveNativeBalanceEvent(id: string, address: string, amount: BigInt, denom: string, event: CosmosEvent) { + let accountEntity = await Account.get(address) + if (typeof(accountEntity) === "undefined") { + accountEntity = Account.create({id: address}); + await accountEntity.save(); + } + + const nativeBalanceChangeEntity = NativeBalanceChange.create({ + id, + accountId: address, + balanceOffset: amount.valueOf(), + denom: denom, + eventId: `${messageId(event)}-${event.idx}`, + blockId: event.block.block.id, + transactionId: event.tx.hash, + }); + + await nativeBalanceChangeEntity.save() +} \ No newline at end of file diff --git a/test/helpers/field_enums.py b/test/helpers/field_enums.py index a748b959d..3f6228539 100644 --- a/test/helpers/field_enums.py +++ b/test/helpers/field_enums.py @@ -130,3 +130,16 @@ class DistDelegatorClaimFields(NamedFields): @classmethod def select_query(cls, table="dist_delegator_claims"): return super().select_query(table) + +class NativeBalanceChangeFields(NamedFields): + id = 0 + balance_offset = 1 + denom = 2 + account_id = 3 + event_id = 4 + transaction_id = 5 + block_id = 6 + + @classmethod + def select_query(cls, table="native_balance_changes"): + return super().select_query(table) \ No newline at end of file diff --git a/test/helpers/regexes.py b/test/helpers/regexes.py index a7bbc63cd..38893f92a 100644 --- a/test/helpers/regexes.py +++ b/test/helpers/regexes.py @@ -3,5 +3,4 @@ block_id_regex = re.compile("^\w{64}$") tx_id_regex = block_id_regex msg_id_regex = re.compile("^\w{64}-\d+$") -event_id_regex = re.compile("^\w{64}-\d+-\d+$") -native_addr_id_regex = re.compile("^fetch\w{64}-\d+-\d+$") +event_id_regex = re.compile("^\w{64}-\d+-\d+$") \ No newline at end of file diff --git a/test/test_native_balances.py b/test/test_native_balances.py new file mode 100644 index 000000000..ebb32d772 --- /dev/null +++ b/test/test_native_balances.py @@ -0,0 +1,76 @@ +import time +import unittest +from gql import gql +import base +from helpers.field_enums import NativeBalanceChangeFields + +class TestNativeBalances(base.Base): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.clean_db({"native_balance_changes"}) + + tx = cls.ledger_client.send_tokens(cls.delegator_wallet.address(), 10*10**18, "atestfet", cls.validator_wallet) + tx.wait_to_complete() + cls.assertTrue(tx.response.is_successful(), "first set-up tx failed") + + tx = cls.ledger_client.send_tokens(cls.validator_wallet.address(), 3*10**18, "atestfet", cls.delegator_wallet) + tx.wait_to_complete() + cls.assertTrue(tx.response.is_successful(), "second set-up tx failed") + + # Wait for subql node to sync + time.sleep(5) + + def test_account_balance_tracking_db(self): + events = self.db_cursor.execute(NativeBalanceChangeFields.select_query()).fetchall() + self.assertGreater(len(events), 0) + + total = { + self.validator_wallet.address(): 0, + self.delegator_wallet.address(): 0, + } + + for event in events: + self.assertTrue( + (event[NativeBalanceChangeFields.account_id.value] == self.validator_wallet.address() or + event[NativeBalanceChangeFields.account_id.value] == self.delegator_wallet.address()) + ) + self.assertNotEqual(int(event[NativeBalanceChangeFields.balance_offset.value]), 0) + self.assertEqual(event[NativeBalanceChangeFields.denom.value], "atestfet") + + total[event[NativeBalanceChangeFields.account_id.value]] += event[NativeBalanceChangeFields.balance_offset.value] + + self.assertEqual(total[self.validator_wallet.address()], -7*10**18) + self.assertEqual(total[self.delegator_wallet.address()], 7*10**18) + + def test_account_balance_tracking_query(self): + query = gql(""" + query { + nativeBalanceChanges{ + groupedAggregates(groupBy: [ACCOUNT_ID, DENOM]){ + sum{ + balanceOffset + } + keys + } + } + } + """) + + result = self.gql_client.execute(query) + validator_balance = 0 + delegator_balance = 0 + for balance in result["nativeBalanceChanges"]["groupedAggregates"]: + self.assertTrue("atestfet" in balance["keys"]) + if self.validator_address in balance["keys"]: + validator_balance += int(balance["sum"]["balanceOffset"]) + elif self.delegator_address in balance["keys"]: + delegator_balance += int(balance["sum"]["balanceOffset"]) + else: + self.fail("couldn't find validator or delegator address in keys") + + self.assertEqual(-7*10**18, validator_balance) + self.assertEqual(7*10**18, delegator_balance) + +if __name__ == '__main__': + unittest.main() From ef7505fe7a7d4e307c1dc3e57cc8c28452c9d314 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Thu, 15 Sep 2022 15:19:17 +0200 Subject: [PATCH 055/143] docs: add introduction (#96) Co-authored-by: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> --- docs/assets/architecture.puml | 38 +++++++ docs/assets/architecture.svg | 99 ++++++++++++++++ docs/assets/architecture_legend.puml | 13 +++ docs/assets/architecture_legend.svg | 28 +++++ docs/assets/entities.puml | 67 +++++++++++ docs/assets/entities.svg | 163 +++++++++++++++++++++++++++ docs/assets/entities_legend.puml | 23 ++++ docs/assets/entities_legend.svg | 71 ++++++++++++ docs/introduction.md | 49 ++++++++ mkdocs.yml | 24 ++++ 10 files changed, 575 insertions(+) create mode 100644 docs/assets/architecture.puml create mode 100644 docs/assets/architecture.svg create mode 100644 docs/assets/architecture_legend.puml create mode 100644 docs/assets/architecture_legend.svg create mode 100644 docs/assets/entities.puml create mode 100644 docs/assets/entities.svg create mode 100644 docs/assets/entities_legend.puml create mode 100644 docs/assets/entities_legend.svg create mode 100644 docs/introduction.md create mode 100644 mkdocs.yml diff --git a/docs/assets/architecture.puml b/docs/assets/architecture.puml new file mode 100644 index 000000000..b0dc03149 --- /dev/null +++ b/docs/assets/architecture.puml @@ -0,0 +1,38 @@ +@startuml +component "SubQuery Node" as subql { + component "Fetch service" as subql_fetch +' () "mappingHandler.ts" as subql_handlers +' component "Sandbox service" as sandbox + +' sandbox --> subql_handlers + + component "Store service" as store + component "Indexer manager" as manager + + manager --> store +} + +component "Graphql API" as gql { + port "public gql endpoint" as gql_port + + component "Apollo server" as apollo + gql_port -> apollo + + component Postgraphile + + apollo -> Postgraphile +} + +component "Postgres DB" as db { + port "private DB endpoint " as db_port +} + +Postgraphile ..> db_port + +component "Fetch Node" as fetchd { + port "public rpc endpoint" as rpc +} + +store ..> db_port +subql_fetch ...> rpc +@enduml diff --git a/docs/assets/architecture.svg b/docs/assets/architecture.svg new file mode 100644 index 000000000..34abf0335 --- /dev/null +++ b/docs/assets/architecture.svg @@ -0,0 +1,99 @@ +SubQuery NodeGraphql APIPostgres DBFetch NodeFetch serviceStore serviceIndexer managerpublic gql endpointApollo serverPostgraphileprivate DB endpointpublic rpc endpoint \ No newline at end of file diff --git a/docs/assets/architecture_legend.puml b/docs/assets/architecture_legend.puml new file mode 100644 index 000000000..9d2ead183 --- /dev/null +++ b/docs/assets/architecture_legend.puml @@ -0,0 +1,13 @@ +@startuml +package Legend { + component "UML component" as c2 + + component "UML component with port" as c1 { + port " port " as p1 + } + + + c2 <-- c1 : "Uses in-process\n\n" + c2 <.. p1 : "Uses over network" +} +@enduml diff --git a/docs/assets/architecture_legend.svg b/docs/assets/architecture_legend.svg new file mode 100644 index 000000000..b536a9705 --- /dev/null +++ b/docs/assets/architecture_legend.svg @@ -0,0 +1,28 @@ +LegendUML component with portUML componentportUses in-process  Uses over network \ No newline at end of file diff --git a/docs/assets/entities.puml b/docs/assets/entities.puml new file mode 100644 index 000000000..4b914050f --- /dev/null +++ b/docs/assets/entities.puml @@ -0,0 +1,67 @@ +@startuml +entity Block { + +id + +chainId + +height + timestamp + ..relations.. + transactions + messages + events +} + +entity Transaction { + +id + gasUsed + gasWanted + fees + memo + status + log + +timeoutHeight + +signerAddress + ..relations.. + block + messages + events +} + +Transaction }|--|| Block +Transaction::block ...|| Block + +entity Message { + +id + +typeUrl + json + ..relations.. + transaction + block +} + +Transaction::messages ..|{ Message + +Transaction ||--|{ Message +Message::block ..|| Block +Message::transaction .|| Transaction + +entity Event { + +id + +type + attributes + log + ..relations.. + block + transaction +} + +object EventAttribute { + key + value +} + +Event::attributes -|{ EventAttribute + +Event }|--|| Transaction +Event::transaction ..|| Transaction +Event::block ..|| Block +@enduml diff --git a/docs/assets/entities.svg b/docs/assets/entities.svg new file mode 100644 index 000000000..f5aaf3fe8 --- /dev/null +++ b/docs/assets/entities.svg @@ -0,0 +1,163 @@ +BlockidchainIdheighttimestamptransactionsmessageseventsrelationsTransactionidgasUsedgasWantedfeesmemostatuslogtimeoutHeightsignerAddressblockmessageseventsrelationsMessageidtypeUrljsontransactionblock relationsEventidtypeattributeslogblocktransactionrelationsEventAttributekeyvalue \ No newline at end of file diff --git a/docs/assets/entities_legend.puml b/docs/assets/entities_legend.puml new file mode 100644 index 000000000..fbc36406f --- /dev/null +++ b/docs/assets/entities_legend.puml @@ -0,0 +1,23 @@ +@startuml +package legend { + entity Parent + entity Child { + parent + } + + Child::parent ..|| Parent : "API accessor" + Parent ||--|{ Child : " Foreign Key Relation" + + entity Entity { + +indexed field + non-indexed field + } + + object Object { + key + } + + Entity -|| Object : "Exactly one" + Entity -|{ Object : "One or many" +} +@enduml diff --git a/docs/assets/entities_legend.svg b/docs/assets/entities_legend.svg new file mode 100644 index 000000000..42556a641 --- /dev/null +++ b/docs/assets/entities_legend.svg @@ -0,0 +1,71 @@ +legendParentChildparentEntityindexed fieldnon-indexed fieldObjectkeyAPI accessorForeign Key RelationExactly oneOne or many \ No newline at end of file diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 000000000..a9c119a3e --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,49 @@ +# Introduction + +The ledger-subquery is a [SubQuery](https://www.subquery.network/)-based indexer for the Fetch ledger. +This indexer provides a [Graphql](https://www.subquery.network/) API for querying tracked entities. +For a list of tracked entities, see the [schema.graphql file](https://github.com/fetchai/ledger-subquery/blob/main/schema.graphql). + +## Architecture + +### Component Diagram + +![subquery architecture component diagram legend](./assets/architecture_legend.svg) +![subquery architecture component diagram](./assets/architecture.svg) + +## Querying + +The graphql API relies heavily on [postgraphile (as a library)](https://www.graphile.org/postgraphile/usage-library/). +Postgraphile plugins also play a critical role; in particular, the [connection-filter](https://github.com/graphile-contrib/postgraphile-plugin-connection-filter) and [pg-aggregates](https://github.com/graphile/pg-aggregates) plugins. +For more information, please refer to their documentation: + +- [connection-filter plugin](https://github.com/graphile-contrib/postgraphile-plugin-connection-filter) + - [operators](https://github.com/graphile-contrib/postgraphile-plugin-connection-filter/blob/master/docs/operators.md#json-jsonb) + - [query examples](https://github.com/graphile-contrib/postgraphile-plugin-connection-filter/blob/master/docs/examples.md) +- [pg-aggregates plugin](https://github.com/graphile/pg-aggregates) + +Additional examples of queries and use cases can be found in the [end-to-end test suite](https://github.com/fetchai/ledger-subquery/blob/main/test). + +## Entities + +Entities tracked by the indexer exist at varying levels of abstraction. "Lower-level" entities include the [primitives](#primitive-entities) (i.e. blocks, transactions, messages, and events), upon which "higher-level" entities are constructed (e.g. LegacyBridgeSwaps). + +Some entities are derived from objects which do not correspond to any network state change (e.g. failed transactions and their messages). +In the case of failed transactions, it is desirable to index the associated data for end-user reference. +This notion may also apply to other objects but should be considered carefully to avoid storing invalid or useless data. + +### Primitive entities + +_(see: [schema.graphql](https://github.com/ledger-subquery/blob/main/schema.graphql))_ + +- blocks +- transactions +- messages +- events + +### Relationship diagram + +![entity relationship diagram legend](./assets/entities_legend.svg) + +![entity relationship diagram](./assets/entities.svg) + diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..d333ab423 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,24 @@ +site_name: Ledger SubQuery +site_url: https://docs.fetch.ai/ledger-subquery +site_description: Everything you need to know about the Fetch Ledger SubQuery indexer. +repo_url: https://github.com/fetchai/ledger-subquery +site_author: developer@fetch.ai + +theme: + name: material + logo: images/logo.png + favicon: assets/images/favicon.ico + feature: + tabs: true + custom_dir: custom_theme + +nav: + - Introduction: 'introduction.md' + +extra_css: + - css/style.css + +markdown_extensions: + - admonition + - pymdownx.superfences + - pymdownx.highlight From cad3ec5c6d0f4cbe91b8a842585c89c1e5386de3 Mon Sep 17 00:00:00 2001 From: Joey Sumner <47419781+Jonathansumner@users.noreply.github.com> Date: Thu, 15 Sep 2022 14:19:49 +0100 Subject: [PATCH 056/143] Feat: CW20 transfer (#86) --- docker-compose.yml | 2 +- project.yaml | 7 ++ schema.graphql | 11 +++ src/mappings/mappingHandlers.ts | 45 +++++++++++-- src/mappings/types/messages.ts | 1 - test/contracts.py | 55 +++++++++++---- test/helpers/field_enums.py | 15 +++++ test/test_CW20_transfer.py | 114 ++++++++++++++++++++++++++++++++ 8 files changed, 229 insertions(+), 21 deletions(-) create mode 100644 test/test_CW20_transfer.py diff --git a/docker-compose.yml b/docker-compose.yml index 029e6bfdc..1fab76650 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,7 +56,7 @@ services: "postgres": condition: service_healthy "subquery-node": - condition: service_healthy + condition: service_started restart: always environment: DB_USER: "subquery" diff --git a/project.yaml b/project.yaml index 417d40f74..cb25d8669 100644 --- a/project.yaml +++ b/project.yaml @@ -63,6 +63,13 @@ dataSources: kind: cosmos/EventHandler filter: type: "withdraw_rewards" + - handler: handleCW20Transfer + kind: cosmos/EventHandler + filter: + type: "execute" + messageFilter: + type: "/cosmwasm.wasm.v1.MsgExecuteContract" + contractCall: "transfer" - handler: handleNativeBalanceDecrement kind: cosmos/EventHandler filter: diff --git a/schema.graphql b/schema.graphql index 996fd4b74..148bc49b2 100644 --- a/schema.graphql +++ b/schema.graphql @@ -122,6 +122,17 @@ type NativeTransfer @entity { block: Block! } +type CW20Transfer @entity { + id: ID! + toAddress: String! @index + fromAddress: String! @index + contract: String! @index + amount: BigInt! + message: Message! + transaction: Transaction! + block: Block! +} + type Account @entity { # id is the address id: ID! diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index e1610df19..2bf910add 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -10,8 +10,9 @@ import { LegacyBridgeSwap, Message, NativeTransfer, + CW20Transfer, Transaction, - TxStatus + TxStatus, } from "../types"; import {CosmosBlock, CosmosEvent, CosmosMessage, CosmosTransaction,} from "@subql/types-cosmos"; import { @@ -19,7 +20,7 @@ import { DistDelegatorClaimMsg, GovProposalVoteMsg, LegacyBridgeSwapMsg, - NativeTransferMsg + NativeTransferMsg, } from "./types"; import {toBech32} from "@cosmjs/encoding"; import {createHash} from "crypto"; @@ -173,6 +174,40 @@ export async function handleExecuteContractMessage(msg: CosmosMessage { + const id = messageId(event.msg); + logger.info(`[handleCW20Transfer] (tx ${event.tx.hash}): indexing CW20Transfer ${id}`); + + const msg = event.msg.msg.decodedMsg; + const contract = msg.contract, fromAddress = msg.sender; + const toAddress = msg.msg?.transfer?.recipient; + const amount = msg.msg?.transfer?.amount; + + if (typeof(amount)==="undefined" || typeof(toAddress)==="undefined" || typeof(fromAddress)==="undefined" || typeof(contract)==="undefined") { + logger.warn(`[handleCW20Transfer] (${event.tx.hash}): (!SKIPPED!) message is malformed (event.msg.msg.decodedMsg): ${JSON.stringify(event.msg.msg.decodedMsg, null, 2)}`) + return + } + + if (!fromAddress || !amount || !toAddress || !contract) { + logger.warn(`[handleCW20Transfer] (tx ${event.tx.hash}): cannot index event (event.event): ${JSON.stringify(event.event, null, 2)}`) + return + } + + const cw20transfer = CW20Transfer.create({ + id, + toAddress, + fromAddress, + contract, + amount, + messageId: id, + transactionId: event.tx.hash, + blockId: event.block.block.id + }); + + await cw20transfer.save(); +} + export async function handleGovProposalVote(msg: CosmosMessage): Promise { logger.info(`[handleGovProposalVote] (tx ${msg.tx.hash}): indexing GovProposalVote ${messageId(msg)}`) logger.debug(`[handleGovProposalVote] (msg.msg): ${JSON.stringify(msg.msg, null, 2)}`) @@ -226,14 +261,14 @@ export async function handleLegacyBridgeSwap(msg: CosmosMessage Date: Mon, 19 Sep 2022 09:33:37 +0200 Subject: [PATCH 057/143] docs: add endpoints to introduction (#99) --- docs/introduction.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/introduction.md b/docs/introduction.md index a9c119a3e..ec316c909 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -4,6 +4,14 @@ The ledger-subquery is a [SubQuery](https://www.subquery.network/)-based indexer This indexer provides a [Graphql](https://www.subquery.network/) API for querying tracked entities. For a list of tracked entities, see the [schema.graphql file](https://github.com/fetchai/ledger-subquery/blob/main/schema.graphql). +## Endpoints + +| Network | API / Playground URL | +| --- | --- | +| Fetchhub (mainnet) | https://subquery.fetch.ai | +| Dorado (testnet) | https://subquery-dorado.fetch.ai | + + ## Architecture ### Component Diagram From b0d866b7535fb6d569c95a6cca6a78f79ac8135b Mon Sep 17 00:00:00 2001 From: Bryan White Date: Mon, 19 Sep 2022 18:23:18 +0200 Subject: [PATCH 058/143] chore: make endpoints links (#100) --- docs/introduction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/introduction.md b/docs/introduction.md index ec316c909..ae8a98c11 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -8,8 +8,8 @@ For a list of tracked entities, see the [schema.graphql file](https://github.com | Network | API / Playground URL | | --- | --- | -| Fetchhub (mainnet) | https://subquery.fetch.ai | -| Dorado (testnet) | https://subquery-dorado.fetch.ai | +| Fetchhub (mainnet) | [https://subquery.fetch.ai](https://subquery.fetch.ai) | +| Dorado (testnet) | [https://subquery-dorado.fetch.ai](https://subquery-dorado.fetch.ai) | ## Architecture From a979868688f8006e4ddb5e99e0a943dc91d694f2 Mon Sep 17 00:00:00 2001 From: Bryan White Date: Wed, 21 Sep 2022 09:29:26 +0200 Subject: [PATCH 059/143] docs: add missing relationship and cleanup (#103) --- docs/assets/entities.puml | 17 +++--- docs/assets/entities.svg | 116 ++++++++------------------------------ 2 files changed, 34 insertions(+), 99 deletions(-) diff --git a/docs/assets/entities.puml b/docs/assets/entities.puml index 4b914050f..9782ee89f 100644 --- a/docs/assets/entities.puml +++ b/docs/assets/entities.puml @@ -21,13 +21,14 @@ entity Transaction { +timeoutHeight +signerAddress ..relations.. - block - messages events + messages + block + } Transaction }|--|| Block -Transaction::block ...|| Block +Transaction::block ..|| Block entity Message { +id @@ -38,11 +39,11 @@ entity Message { block } -Transaction::messages ..|{ Message - Transaction ||--|{ Message +Transaction::messages ...|{ Message + Message::block ..|| Block -Message::transaction .|| Transaction +Message::transaction ..|| Transaction entity Event { +id @@ -50,8 +51,9 @@ entity Event { attributes log ..relations.. - block transaction + block + } object EventAttribute { @@ -59,6 +61,7 @@ object EventAttribute { value } +Transaction::events .|{ Event Event::attributes -|{ EventAttribute Event }|--|| Transaction diff --git a/docs/assets/entities.svg b/docs/assets/entities.svg index f5aaf3fe8..0577de410 100644 --- a/docs/assets/entities.svg +++ b/docs/assets/entities.svg @@ -1,18 +1,19 @@ -BlockidchainIdheighttimestamptransactionsmessageseventsrelationsTransactionidgasUsedgasWantedfeesmemostatuslogtimeoutHeightsignerAddressblockmessageseventsrelationsMessageidtypeUrljsontransactionblock relationsEventidtypeattributeslogblocktransactionrelationsEventAttributekeyvalueBlockidchainIdheighttimestamptransactionsmessageseventsrelationsTransactionidgasUsedgasWantedfeesmemostatuslogtimeoutHeightsignerAddresseventsmessagesblock relationsMessageidtypeUrljsontransactionblockrelationsEventidtypeattributeslogtransactionblock relationsEventAttributekeyvalueSubQuery NodeGraphql APIPostgres DBFetch NodeSubQuery NodeGraphql APIPostgres DBFetchd NodeFetch serviceStore serviceIndexer managerpublic gql endpointIndexer managerpublic gql endpointApollo serverPostgraphileprivate DB endpointpublic rpc endpointPostgraphileprivate DB endpointpublic rpc endpointBlockidchainIdheighttimestamptransactionsmessageseventsrelationsTransactionidgasUsedgasWantedfeesmemostatuslogtimeoutHeightsignerAddresseventsmessagesblock relationsMessageidtypeUrljsontransactionblockrelationsEventidtypeattributeslogtransactionblock relationsEventAttributekeyvalue \ No newline at end of file diff --git a/docs/assets/entities.puml b/docs/assets/entities_api.puml similarity index 64% rename from docs/assets/entities.puml rename to docs/assets/entities_api.puml index 9782ee89f..ebf072db8 100644 --- a/docs/assets/entities.puml +++ b/docs/assets/entities_api.puml @@ -24,12 +24,8 @@ entity Transaction { events messages block - } -Transaction }|--|| Block -Transaction::block ..|| Block - entity Message { +id +typeUrl @@ -39,32 +35,40 @@ entity Message { block } -Transaction ||--|{ Message -Transaction::messages ...|{ Message - -Message::block ..|| Block -Message::transaction ..|| Transaction - entity Event { +id +type - attributes log ..relations.. + attributes transaction block } -object EventAttribute { - key - value +entity EventAttribute { + +id + +key + +value + ..relations.. + event } -Transaction::events .|{ Event -Event::attributes -|{ EventAttribute -Event }|--|| Transaction +Block::transaction ..o{ Transaction +Block::messages ..o{ Message +Block::events ..o{ Event + +Transaction::events ..|{ Event +Transaction::messages ..|{ Message +Transaction::block ..|| Block + +Message::transaction ..|| Transaction +Message::block ...|| Block + +Event::attributes ..|{ EventAttribute Event::transaction ..|| Transaction Event::block ..|| Block + +EventAttribute::event ..|| Event @enduml diff --git a/docs/assets/entities_api.svg b/docs/assets/entities_api.svg new file mode 100644 index 000000000..eecbf9a0e --- /dev/null +++ b/docs/assets/entities_api.svg @@ -0,0 +1,101 @@ +BlockidchainIdheighttimestamptransactionsmessageseventsrelationsTransactionidgasUsedgasWantedfeesmemostatuslogtimeoutHeightsignerAddresseventsmessagesblockrelationsMessageidtypeUrljsontransactionblockrelationsEventidtypelogattributestransactionblock relationsEventAttributeidkeyvalueeventrelations \ No newline at end of file diff --git a/docs/assets/entities_db.puml b/docs/assets/entities_db.puml new file mode 100644 index 000000000..63cfecd1b --- /dev/null +++ b/docs/assets/entities_db.puml @@ -0,0 +1,66 @@ +@startuml +entity Block { + +id + +chainId + +height + timestamp + ..relations.. + transactions + messages + events +} + +entity Transaction { + +id + gasUsed + gasWanted + fees + memo + status + log + +timeoutHeight + +signerAddress + ..relations.. + events + messages + block +} + +entity Message { + +id + +typeUrl + json + ..relations.. + transaction + block +} + +entity Event { + +id + +type + log + ..relations.. + attributes + transaction + block + +} + +entity EventAttribute { + +id + +key + +value + ..relations.. + event +} + + +Block ||--o{ Transaction +Block ||--o{ Message +Block ||--o{ Event + +Transaction ||--|{ Event +Transaction ||--|{ Message + +EventAttribute }|--|| Event +@enduml diff --git a/docs/assets/entities_db.svg b/docs/assets/entities_db.svg new file mode 100644 index 000000000..7c4a9e206 --- /dev/null +++ b/docs/assets/entities_db.svg @@ -0,0 +1,87 @@ +BlockidchainIdheighttimestamptransactionsmessageseventsrelationsTransactionidgasUsedgasWantedfeesmemostatuslogtimeoutHeightsignerAddresseventsmessagesblockrelationsMessageidtypeUrljsontransactionblockrelationsEventidtypelogattributestransactionblock relationsEventAttributeidkeyvalueeventrelations \ No newline at end of file diff --git a/docs/assets/entities_legend.puml b/docs/assets/entities_legend.puml index fbc36406f..19fe8f9e5 100644 --- a/docs/assets/entities_legend.puml +++ b/docs/assets/entities_legend.puml @@ -13,11 +13,10 @@ package legend { non-indexed field } - object Object { - key - } + entity Other {} - Entity -|| Object : "Exactly one" - Entity -|{ Object : "One or many" + Entity -|| Other : "Exactly one" + Entity -|{ Other : "One or many" + Entity .o{ Other : "Zero or many" } @enduml diff --git a/docs/assets/entities_legend.svg b/docs/assets/entities_legend.svg index 42556a641..279a389c3 100644 --- a/docs/assets/entities_legend.svg +++ b/docs/assets/entities_legend.svg @@ -1,12 +1,14 @@ -legendParentChildparentEntityindexed fieldnon-indexed fieldObjectkeyAPI accessorForeign Key RelationExactly oneOne or manylegendParentChildparentEntityindexed fieldnon-indexed fieldOtherAPI accessorForeign Key RelationExactly oneOne or manyZero or many