From beca7a0aded6aff54d8d00bedbbfab71fb85f866 Mon Sep 17 00:00:00 2001 From: Shjorty <201505261@post.au.dk> Date: Tue, 7 Feb 2023 13:31:40 +0100 Subject: [PATCH 1/5] Bump versions + Update README/CHANGELOG + update documentation for grpc v2 release --- docs/JSON-RPC.md | 24 + packages/nodejs/READMEV2.md => docs/gRPC.md | 14 +- {packages/nodejs => docs}/grpc-migration.md | 4 +- docs/grpc-v1.md | 551 +++++++++++++++++++ packages/common/CHANGELOG.md | 50 +- packages/common/README.md | 14 +- packages/common/package.json | 9 +- packages/nodejs/CHANGELOG.md | 6 +- packages/nodejs/README.md | 552 +------------------- packages/nodejs/package.json | 5 +- packages/nodejs/src/clientV2.ts | 13 +- packages/nodejs/test/testHelpers.ts | 2 +- packages/rust-bindings/CHANGELOG.md | 2 +- packages/rust-bindings/package.json | 7 +- packages/web/CHANGELOG.md | 6 +- packages/web/README.md | 36 +- packages/web/package.json | 9 +- yarn.lock | 12 +- 18 files changed, 696 insertions(+), 620 deletions(-) create mode 100644 docs/JSON-RPC.md rename packages/nodejs/READMEV2.md => docs/gRPC.md (97%) rename {packages/nodejs => docs}/grpc-migration.md (93%) create mode 100644 docs/grpc-v1.md diff --git a/docs/JSON-RPC.md b/docs/JSON-RPC.md new file mode 100644 index 000000000..426ecc703 --- /dev/null +++ b/docs/JSON-RPC.md @@ -0,0 +1,24 @@ +# JSON-RPC client + +> :warning: **This explains behaviour of the deprecated JSON-RPC client**: check out [the documentation the gRPC client](./GRPC) + +This describes the JSON-RPC client, which can interact with the [Concordium JSON-RPC server](https://github.com/Concordium/concordium-json-rpc) + +## Creating a client +To create a client, one needs a provider, which handles sending and receiving over a specific protocol. Currently the only one available is the HTTP provider. +The HTTP provider needs the URL to the JSON-RPC server. The following example demonstrates how to create a client that connects to a local server on port 9095: +```js +const client = new JsonRpcClient(new HttpProvider("http://localhost:9095")); +``` + +## API Entrypoints +Currently the client only supports the following entrypoints, with the same interface as the grpc v1 node client: + +- [sendTransaction](./GrpcV1.md#send-account-transaction) +- [getTransactionStatus](./GrpcV1.md#gettransactionstatus) +- [getInstanceInfo](./GrpcV1.md#getInstanceInfo) +- [getConsensusStatus](./GrpcV1.md#getconsensusstatus) +- [getAccountInfo](./GrpcV1.md#getAccountInfo) +- [getCryptographicParameters](./GrpcV1.md#getcryptographicparameters) +- [invokeContract](./GrpcV1.md#invokecontract) +- [getModuleSource](./GrpcV1.md#getModuleSource) diff --git a/packages/nodejs/READMEV2.md b/docs/gRPC.md similarity index 97% rename from packages/nodejs/READMEV2.md rename to docs/gRPC.md index af6cec084..48e902d53 100644 --- a/packages/nodejs/READMEV2.md +++ b/docs/gRPC.md @@ -1,10 +1,6 @@ -# Concordium Nodejs SDK +# Concordium gRPC client -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](https://github.com/Concordium/.github/blob/main/.github/CODE_OF_CONDUCT.md) - -Wrappers for interacting with the Concordium node, using nodejs. - -[Note that this package contains and exports the functions from the common-sdk, check the readme of that package for an overview of those](../common/README.md). +This document describes the different endpoints for the concordium gRPC V2 client. **Table of Contents** - [ConcordiumNodeClient](#concordiumnodeclient) @@ -50,11 +46,13 @@ const client = new ConcordiumNodeClient( The access is controlled by the credentials and the metadata. If the node does not support TLS an insecure connection can be established using `credentials.createInsecure()` instead of `credentials.createSsl()`. +Note that the web-sdk and node-sdk each exposes a helper function `createConcordiumClient` that creates a client using the appropriate transport (gRPC-web for web and regular gRPC for nodeJS). + ## Send Account Transaction The following example demonstrates how to send any account transaction. -See the Constructing transactions section for the [common package](../common#constructing-transactions) for how to create an account transaction. -See the signing a transaction section for the [common package](../common#sign-an-account-transaction) for how to sign an account transaction. +See the Constructing transactions section for the [common package](./#constructing-transactions) for how to create an account transaction. +See the signing a transaction section for the [common package](./#sign-an-account-transaction) for how to sign an account transaction. ```js diff --git a/packages/nodejs/grpc-migration.md b/docs/grpc-migration.md similarity index 93% rename from packages/nodejs/grpc-migration.md rename to docs/grpc-migration.md index 9f2a680a3..ae8530276 100644 --- a/packages/nodejs/grpc-migration.md +++ b/docs/grpc-migration.md @@ -1,4 +1,4 @@ -# Migration guide from GRPCv1 to v2: +# Migration guide from Concordium gRPC v1 to v2: ## General: Blockhash inputs are now optional, and if given must be given as a hex encoded string. If the blockhash is not given the function will use the last final block. @@ -39,4 +39,4 @@ Returns a stream of anonymity revokers instead of a list. ## getBakerList -Returns a stream of BakerIds instead of a list. \ No newline at end of file +Returns a stream of BakerIds instead of a list. diff --git a/docs/grpc-v1.md b/docs/grpc-v1.md new file mode 100644 index 000000000..7773eb0bd --- /dev/null +++ b/docs/grpc-v1.md @@ -0,0 +1,551 @@ +# ConcordiumNodeClient + +> :warning: **This explains behaviour of the deprecated v1 concordium client**: check out [the documentation the v2 client](./gRPC) + +- [ConcordiumNodeClient](#concordiumnodeclient) + - [Creating a client](#creating-a-client) + - [Send Account Transaction](#send-account-transaction) + - [Create a new account](#create-a-new-account) + - [Construct IdentityInput for creating credentials](#construct-identityinput-for-creating-credentials) + - [Construct from user-cli output:](#construct-from-user-cli-output) + - [Construct from mobile wallet export:](#construct-from-mobile-wallet-export) + - [getAccountInfo](#getaccountinfo) + - [getNextAccountNonce](#getnextaccountnonce) + - [getTransactionStatus](#gettransactionstatus) + - [getBlockSummary](#getblocksummary) + - [getBlockInfo](#getblockinfo) + - [getBlocksAtHeight](#getblocksatheight) + - [getConsensusStatus](#getconsensusstatus) + - [getCryptographicParameters](#getcryptographicparameters) + - [getIdentityProviders](#getidentityproviders) + - [getAnonymityRevokers](#getanonymityrevokers) + - [getPeerList](#getpeerlist) + - [getBakerList](#getbakerlist) + - [getPoolStatus](#getpoolstatus) + - [getRewardStatus](#getrewardstatus) + - [Check block for transfers with memo](#check-block-for-transfers-with-memo) + - [getInstances](#getinstances) + - [getInstanceInfo](#getinstanceinfo) + - [invokeContract](#invokecontract) + - [getModuleSource](#getModuleSource) + +The ConcordiumNodeClient defines the interface to be used to send and receive data from +a concordium-node. + +## Creating a client +The current node setup only allows for insecure connections, which can be set up in the following way. +The access is controlled by the credentials and the metadata. +```js +import { credentials, Metadata } from "@grpc/grpc-js"; +import { ConcordiumNodeClient } from "@concordium/node-sdk"; + +const metadata = new Metadata(); +metadata.add("authentication", "rpcadmin"); + +const insecureCredentials = credentials.createInsecure(); +const client = new ConcordiumNodeClient( + "127.0.0.1", // ip address + 10000, // port + insecureCredentials, + metadata, + 15000 // timeout in ms +); +``` + +## Send Account Transaction +The following example demonstrates how to send any account transaction. + +See the Constructing transactions section for the [common package](../common#constructing-transactions) for how to create an account transaction. +See the signing a transaction section for the [common package](../common#sign-an-account-transaction) for how to sign an account transaction. + +```js + +let accountTransaction: AccountTransaction; +// Create the transaction +// ... + +let signatures: AccountTransactionSignature; +// Sign the transaction +// ... + +// Send the transaction to the node. +const success = await client.sendAccountTransaction(accountTransaction, signatures); +if (success) { + // The node accepted the transaction. This does not ensure that the transaction + // will end up in a block, only that the format of the submitted transaction was valid. +} else { + // The node rejected the transaction. +} + +// Check the status of the transaction. Should be checked with an appropriate interval, +// as it will take some time for the transaction to be processed. +const transactionHash = getAccountTransactionHash(accountTransaction, signatures); +const transactionStatus = await client.getTransactionStatus(transactionHash); +``` + +## Create a new account +The following example demonstrates how to create a new account on an existing +identity. The `credentialIndex` should be the next unused credential index for that identity, and keeping track of that index is done off-chain. Note that index `0` is used by the initial account that was created together with the identity. +See [Construct IdentityInput](#Construct-identityInput-for-creating-credentials) for how to construct an IdentityInput. +```js +const lastFinalizedBlockHash = (await client.getConsensusStatus()).lastFinalizedBlock; +const cryptographicParameters = await client.getCryptographicParameters(lastFinalizedBlockHash); +if (!cryptographicParameters) { + throw new Error('Cryptographic parameters were not found on a block that has been finalized.'); +} + +// The parts of the identity required to create a new account, parsed from +// e.g. a wallet export. +const identityInput: IdentityInput = ... + +// Require just one key on the credential to sign. This can be any number +// up to the number of public keys added to the credential. +const threshold: number = 1; + +// The index of the credential that will be created. This index is per identity +// and has to be in sequence, and not already used. Note that index 0 is used +// by the initial credential that was created with the identity. +const credentialIndex: number = 1; + +// In this example the credential on the account will have two keys. Note that +// the credential information has to be signed (in order) by corresponding +// private keys. +const publicKeys: VerifyKey[] = [ + { + schemeId: "Ed25519", + verifyKey: "c8cd7623c5a9316d8e2fccb51e1deee615bdb5d324fb4a6d33801848fb5e459e" + }, + { + schemeId: "Ed25519", + verifyKey: "b6baf645540d0ea6ae5ff0b87dff324340ae1120a5c430ffee60d5f370b2ab75" + } +]; + +// The attributes to reveal about the account holder on chain. This can be empty +const revealedAttributes: AttributeKey[] = ['firstName', 'nationality']; + +const expiry = new TransactionExpiry(new Date(Date.now() + 3600000)); +const credentialDeploymentTransaction: CredentialDeploymentTransaction = + createCredentialDeploymentTransaction( + identityInput, + cryptographicParameters.value, + threshold, + publicKeys, + credentialIndex, + revealedAttributes, + expiry + ); +const hashToSign: Buffer = getCredentialDeploymentSignDigest( + credentialDeploymentTransaction +); + +// The next step is to sign the credential information with each private key that matches +// one of the public keys in the credential information. +const signingKey1 = "1053de23867e0f92a48814aabff834e2ca0b518497abaef71cad4e1be506334a"; +const signingKey2 = "fcd0e499f5dc7a989a37f8c89536e9af956170d7f502411855052ff75cfc3646"; + +const signature1 = Buffer.from(await ed.sign(hashToSign, signingKey1)).toString('hex'); +const signature2 = Buffer.from(await ed.sign(hashToSign, signingKey2)).toString('hex'); +const signatures: string[] = [signature1, signature2]; + +// The address that the account created by the transaction will get can +// be derived ahead of time. +const accountAddress: AccountAddress = getAccountAddress(credentialDeploymentTransaction.cdi.credId); + +// Send the transaction to the node +const success = await client.sendCredentialDeploymentTransaction( + credentialDeploymentTransaction, + signatures +); +if (success) { + // The node accepted the transaction. This does not ensure that the transaction + // will end up in a block, only that the format of the submitted transaction was valid. +} else { + // The node rejected the transaction. +} + +// Check the status of the transaction. Should be checked with an appropriate interval, +// as it will take some time for the transaction to be processed. +const transactionHash = getCredentialDeploymentTransactionHash(credentialDeploymentTransaction, signatures); +const transactionStatus = await client.getTransactionStatus(transactionHash); +``` + +## Construct IdentityInput for creating credentials + +When creating a new identity the user will choose an identity provider, create an id-use-data object, which contains the private data to use for the identity, and obtain an identity object from the identity provider. + +To create accounts/credentials on that identity, this SDK expects an "IdentityInput" object, which contains the identity object, the id-use-data, and the identity provider's information. + +### Construct from user-cli output: + +Below is an example of how to construct the identityInput, with a plaintext id-use-data.json from the [user-cli guide](https://github.com/Concordium/concordium-base/blob/main/rust-bins/docs/user-cli.md#generate-a-request-for-the-identity-object), and an id-object file. + +```js +// First we load the files. We assume here that they are available as local files. +const rawIdUseData = fs.readFileSync( + 'path/to/id-use-data.json', + 'utf8' +); +const rawIdObject = fs.readFileSync( + 'path/to/id-object.json', + 'utf8' +); + +// Then we parse them. We assume here that they are both version 0. +const idUseData = JSON.parse(rawIdUseData).value; +const identityObject = JSON.parse(rawIdObject).value; + +// Finally we construct the IdentityInput: +const identityInput: IdentityInput = { + identityObject, + identityProvider: { + ipInfo: idUseData.ipInfo, + arsInfos: idUseData.ars, + }, + idCredSecret: idUseData.idUseData.aci.credentialHolderInformation.idCredSecret, + prfKey: idUseData.idUseData.aci.prfKey, + randomness: idUseData.idUseData.randomness, +}; +``` + +### Construct from mobile wallet export: + +The following is an example of how to construct the identityInput for the _i_-th identity from a mobile wallet export: + +```js +// We assume the export is available as a local file: +const rawData = fs.readFileSync( + 'path/to/export.concordiumwallet', + 'utf8' +); +const mobileWalletExport: EncryptedData = JSON.parse(rawData); +const decrypted: MobileWalletExport = decryptMobileWalletExport( + mobileWalletExport, + password +); +const identity = decrypted.value.identities[i]; +const identityInput: IdentityInput = { + identityObject: identity.identityObject, + identityProvider: identity.identityProvider, + idCredSecret: identity.privateIdObjectData.aci.credentialHolderInformation.idCredSecret, + prfKey: identity.privateIdObjectData.aci.prfKey, + randomness: identity.privateIdObjectData.randomness, +}; +``` + +## getAccountInfo +Retrieves information about an account. The function must be provided an account address or a credential registration id. +If a credential registration id is provided, then the node returns the information of the account, +which the corresponding credential is (or was) deployed to. +If there is no account that matches the address or credential id at the provided +block, then undefined will be returned. +```js +const accountAddress = new AccountAddress("3sAHwfehRNEnXk28W7A3XB3GzyBiuQkXLNRmDwDGPUe8JsoAcU"); +const blockHash = "6b01f2043d5621192480f4223644ef659dd5cda1e54a78fc64ad642587c73def"; +const accountInfo: AccountInfo = await client.getAccountInfo(accountAddress, blockHash); +const amount: bigint = accountInfo.accountAmount; + +// Nationality for the account creator, if the information has been revealed. +const nationality: string = accountInfo.accountCredentials[0].value.contents.policy.revealedAttributes["nationality"]; +``` + +To check if the account is a baker or a delegator, one can use the functions `isDelegatorAccount` and `isBakerAccount`. +```js +... +const accountInfo: AccountInfo = await client.getAccountInfo(accountAddress, blockHash); +if (isDelegatorAccount(accountInfo)) { + const delegationDetails = accountInfo.accountDelegation; + ... +} else if (isBakerAccount(accountInfo) { + const bakingDetails = accountInfo.accountBaker; + ... +} else { + // Neither a baker nor a delegator +} +``` +Furthermore there are different versions, based on Protocol version, of a baker's accountInfo. +In protocol version 4 the concept of baker pools was introduced, so to get baker pool information one should confirm the version with `isBakerAccountV0` or `isBakerAccountV1`. + +```js +... +const accountInfo: AccountInfo = await client.getAccountInfo(accountAddress, blockHash); +if (isBakerAccountV1(accountInfo)) { + const bakerPoolInfo = accountInfo.accountBaker.bakerPoolInfo; + ... +} else if (isBakerAccountV0(accountInfo) { + // accountInfo is from protocol version < 4, so it will not contain bakerPoolInfo + ... +} +``` + +## getNextAccountNonce +Retrieves the next account nonce, i.e. the nonce that must be set in the account transaction +header for the next transaction submitted by that account. Along with the nonce there is a boolean +that indicates whether all transactions are finalized. If this is true, then the nonce is reliable, +if not then the next nonce might be off. +```js +const accountAddress = new AccountAddress("3VwCfvVskERFAJ3GeJy2mNFrzfChqUymSJJCvoLAP9rtAwMGYt"); +const nextAccountNonce: NextAccountNonce = await client.getNextAccountNonce(accountAddress); +const nonce: bigint = nextAccountNonce.nonce; +const allFinal: boolean = nextAccountNonce.allFinal; +if (allFinal) { + // nonce is reliable +} +``` + +## getTransactionStatus +Retrieves status information about a transaction. +```js +const transactionHash = "f1f5f966e36b95d5474e6b85b85c273c81bac347c38621a0d8fefe68b69a430f"; +const transactionStatus: TransactionStatus = await client.getTransactionStatus(transactionHash); +const isFinalized = transactionStatus.status === TransactionStatusEnum.Finalized; +... +``` +Note that there will be no outcomes for a transaction that has only been received: +```js +if (transactionStatus.status === TransactionStatusEnum.Received) { + const outcomes = Object.values(transactionStatus.outcomes); + // outcomes.length === 0. +} +``` +If the transaction has been finalized, then there is exactly one outcome: +```js +if (transactionStatus.status === TransactionStatusEnum.Finalized) { + const outcomes = Object.values(transactionStatus.outcomes); + // outcomes.length === 1. +} +``` +A transaction was successful if it is finalized and it has a successful outcome: +```js +if (transactionStatus.status === TransactionStatusEnum.Finalized) { + const event = Object.values(response.outcomes)[0]; + if (event.result.outcome === "success") { + // transaction was successful. + } +} +``` + +## getBlockSummary +Retrives a summary for a specific block. The summary contains information about finalization, the +current chain parameters, a list of the governance keys, information about any queued chain parameter +updates and a summary of any transactions within the block. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8761d08554756f42bf268a42749"; +const blockSummary: BlockSummary = await client.getBlockSummary(blockHash); +const numberOfFinalizers = blockSummary.finalizationData.finalizers.length; +... +``` + +Blocks before protocol version 4 have a different type than those from higher protocol versions. +To determine the version, use `isBlockSummaryV1` and `isBlockSummaryV0`: + +```js +... +const blockSummary: BlockSummary = await client.getBlockSummary(blockHash); +if (isBlockSummaryV0(blockSummary)) { + // This block is from protocol version <= 3, and so the summary has version 0 structure + ... +} else if (isBlockSummaryV1(blockSummary) { + // This block is from protocol version >= 4, and so the summary has version 1 structure + ... +} else { + // Must be a future version of a blockSummary (or the given object is not a blockSummary) +} +``` + +There are also type checks for specific fields in the summary, which can be found in [blockSummaryHelpers](../common/src/blockSummaryHelpers.ts). + +## getBlockInfo +Retrieves information about a specific block. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8761d08554756f42bf268a42749"; +const blockInfo: BlockInfo = await client.getBlockInfo(blockHash); +const transactionsCount = blockInfo.transactionCount; +... +``` + +## getBlocksAtHeight +Retrieves the hashes of blocks at a specific height. +```js +const blockHeight: bigint = 5310n; +const blocksAtHeight: string[] = await client.getBlocksAtHeight(blockHeight); +``` + +## getConsensusStatus +Retrieves the current consensus status from the node. +```js +const consensusStatus: ConsensusStatus = await client.getConsensusStatus(); +const bestBlock = consensusStatus.bestBlock; +... +``` + +## getCryptographicParameters +Retrieves the global cryptographic parameters for the blockchain at a specific block. +These are a required input for e.g. creating credentials. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8761d08554756f42bf268a42749" +const cryptographicParameters = await client.getCryptographicParameters(blockHash); +... +``` + +## getIdentityProviders +Retrieves the list of identity providers at a specific block. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; +const identityProviders = await client.getIdentityProviders(blockHash); +... +``` + +## getAnonymityRevokers +Retrieves the list of anonymity revokers at a specific block. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; +const identityProviders = await client.getAnonymityRevokers(blockHash); +... +``` + +## getPeerList +Retrieves the list of peers that the node is connected to, including some +connection information about them. A boolean parameter determines if this +should include bootstrapper nodes or not. +```js +const peerListResponse = await client.getPeerList(false); +const peersList = peerListResponse.getPeersList(); +... +``` + +## getBakerList +Retrieves the list of ID's for registered bakers on the network at a specific block. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; +const bakerIds = await client.getBakerList(blockHash); +... +``` + +## getPoolStatus +Retrieves the status of a pool (either a specific baker or passive delegation) at a specific block. +If a baker ID is specified, the status of that baker is returned. To get the status of passive delegation, baker ID should be left undefined. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; +const bakerId = BigInt(1); + +const bakerStatus = await client.getPoolStatus(blockHash, bakerId); +const passiveDelegationStatus = await client.getPoolStatus(blockHash); +... +``` + +## getRewardStatus +Retrieves the current amount of funds in the system at a specific block, and the state of the special accounts. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; + +const rewardStatus = await client.getRewardStatus(blockHash); +``` + +Protocol version 4 expanded the amount of information in the response, so one should check the type to access that. +This information includes information about the payday and total amount of funds staked. +```js +if (isRewardStatusV1(rewardStatus)) { + const nextPaydayTime = rewardStatus.nextPaydayTime; + ... +} +``` + +## Check block for transfers with memo +The following example demonstrates how to check and parse a block +for transfers with a memo. +```js +const blockHash = "b49bb1c06c697b7d6539c987082c5a0dc6d86d91208874517ab17da752472edf"; +const blockSummary = await client.getBlockSummary(blockHash); +const transactionSummaries = blockSummary.transactionSummaries; + +for (const transactionSummary of transactionSummaries) { + if (transactionSummary.result.outcome === 'success') { + if (instanceOfTransferWithMemoTransactionSummary(transactionSummary)) { + const [transferredEvent, memoEvent] = transactionSummary.result.events; + + const toAddress = transferredEvent.to.address; + const amount = transferredEvent.amount; + const memo = memoEvent.memo; + + // Apply business logic to toAddress, amount and memo... + } + } +} +``` + +## getInstances +Used to get the full list of contract instances on the chain at a specific block. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; + +const instances = await client.getInstances(blockHash); +... +``` + +## getInstanceInfo +Used to get information about a specific contract instance, at a specific block. + +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; +const contractAddress = { index: 1n, subindex: 0n }; + +const instanceInfo = await client.getInstanceInfo(contractAddress, blockHash); +const name = instanceInfo.name; +... +``` + +Note that only version 0 contracts returns the model. (use `isInstanceInfoV0`/`isInstanceInfoV1` to check the version) + +## invokeContract +Used to simulate a contract update, and to trigger view functions. + +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; +const contractAddress = { index: 1n, subindex: 0n }; +const invoker = new AccountAddress('3tXiu8d4CWeuC12irAB7YVb1hzp3YxsmmmNzzkdujCPqQ9EjDm'); +const result = await client.invokeContract( + { + invoker: invoker, + contract: contractAddress, + method: 'PiggyBank.smash', + amount: undefined, + parameter: undefined, + energy: 30000n, + }, + blockHash +); + +if (!result) { + // The node could not attempt the invocation, most likely the contract doesn't exist. +} + +if (result.tag === 'failure') { + // Invoke was unsuccesful + const rejectReason = result.reason; // Describes why the update failed; + ... +} else { + const events = result.events; // a list of events that would be generated by the update + const returnValue = result.returnValue; // If the invoked method has return value + ... +} +``` + +Note that some of the parts of the context are optional: + - amount: defaults to 0 + - energy: defaults to 10 million + - parameter: defaults to no parameters + - invoker: uses the zero account address, which can be used instead of finding a random address. + +## getModuleSource +This commands gets the source of a module on the chain. + +Note that this returns the raw bytes of the source, as a buffer. +```js +const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; +const moduleReference = "c0e51cd55ccbff4fa8da9bb76c9917e83ae8286d86b47647104bf715b4821c1a"; +const source = await client.getModuleSource(moduleReference, blockHash); +if (!source) { + // the blockHash is unknown or the module doesn't exist at the given blockHash +} +``` diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index bfa0e8b97..923919ca2 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -1,35 +1,11 @@ # Changelog -## Unreleased - -- Added the following GRPCv2 functions: - - `getAccountList()` - - `getModuleList()` - - `getAncestors()` - - `getInstanceState()` - - `instanceStateLookup()` - - `getIdentityProviders()` - - `getAnonymityRevokers()` - - `getBlocksAtHeight()` - - `getBlockInfo()` - - `getBakerList()` - - `getPoolDelegators()` - - `getPoolDelegatorsRewardPeriod()` - - `getPassiveDelegators()` - - `getPassiveDelegatorsRewardPeriod()` - - `getBranches()` - -## 6.4.0 - -- Added `getFinalizedBlocks()` & `getBlocks()` GRPCv2 functions. -- Added public helper function `waitForTransactionFinalization()` to client - -## 6.3.0 +## 6.3.0 2023-02-07 ### Added -- Added a GRPCv2 client starting with the following functions: - - `getAccountInfo()` +- Added a gRPC v2 client starting with the following functions: +- `getAccountInfo()` - `getNextAccountSequenceNumber()` - `getCryptographicParameters()` - `getBlockItemStatus()` @@ -44,7 +20,25 @@ - `getPoolInfo()` - `getPassiveDelegationInfo()` - `getTokenomicsInfo()` - + - `getAccountList()` + - `getModuleList()` + - `getAncestors()` + - `getInstanceState()` + - `instanceStateLookup()` + - `getIdentityProviders()` + - `getAnonymityRevokers()` + - `getBlocksAtHeight()` + - `getBlockInfo()` + - `getBakerList()` + - `getPoolDelegators()` + - `getPoolDelegatorsRewardPeriod()` + - `getPassiveDelegators()` + - `getPassiveDelegatorsRewardPeriod()` + - `getBranches()` + - `getFinalizedBlocks()` + - `getBlocks()` + - helper function `waitForTransactionFinalization` that returns a promise that resolves when the transaction finalizes + - Serialization: - `serializeAccountTransactionPayload()` - `serializeCredentialDeploymentPayload()` diff --git a/packages/common/README.md b/packages/common/README.md index c464eafcb..d2a6f2e61 100644 --- a/packages/common/README.md +++ b/packages/common/README.md @@ -765,6 +765,16 @@ const proof = getIdProof({ }) ``` +# ConcordiumNodeClient +The SDK provides a gRPC client, which can interact with the [Concordium Node](https://github.com/Concordium/concordium-node) + +For an overview of the endpoints, [check here](../../docs/gRPC.md). + +To create a client, the helper functions in the node or web sdk should be used as they use the appropriate transport. +The [nodejs SDK uses a regular gRPC transport](../nodejs#ConcordiumNodeClient), while the [web SDK uses a gRPC-web transport](../web#ConcordiumNodeClient). + # JSON-RPC client -The SDK also provides a JSON-RPC client, but it is primarily used for web, [so it has been documented in the web-sdk package instead](../web#JSON-RPC-client). -The nodejs SDK also provides a [gRPC client, which can interact directly with a node](../nodejs#ConcordiumNodeClient). + +> :warning: **The JSON-RPC client has been deprecated**: the gRPC client should be used instead to communicate directly with a node + +The SDK also provides a JSON-RPC client, [check here for the documentation](../../docs/JSON-RPC.md). diff --git a/packages/common/package.json b/packages/common/package.json index 7dc219fd5..8b182c3d5 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,10 +1,15 @@ { "name": "@concordium/common-sdk", - "version": "6.2.0", + "version": "6.3.0", "license": "Apache-2.0", "engines": { "node": ">=14.16.0" }, + "repository": { + "type": "git", + "url": "https://github.com/Concordium/concordium-node-sdk-js", + "directory": "packages/common" + }, "main": "lib/index.js", "types": "lib/index.d.ts", "files": [ @@ -45,7 +50,7 @@ "build": "rm -rf grpc; mkdir -p grpc; yarn generate && tsc" }, "dependencies": { - "@concordium/rust-bindings": "0.9.0", + "@concordium/rust-bindings": "0.10.0", "@grpc/grpc-js": "^1.3.4", "@noble/ed25519": "^1.7.1", "@protobuf-ts/runtime-rpc": "^2.8.2", diff --git a/packages/nodejs/CHANGELOG.md b/packages/nodejs/CHANGELOG.md index a38fffba3..577c02393 100644 --- a/packages/nodejs/CHANGELOG.md +++ b/packages/nodejs/CHANGELOG.md @@ -1,14 +1,14 @@ # Changelog -## Unreleased +## 6.2.0 2023-2-7 ### Added -- Added a `createConcordiumClient` function to create the GRPC v2 client. +- Added a `createConcordiumClient` function to create the gRPC v2 client. ### Changed -- Bumped @concordium/common-sdk to 6.1.0. (adds support for id statements and proofs) +- Bumped @concordium/common-sdk to 6.3.0. (Adds the initial version of gRPC v2 client) ### Fixed diff --git a/packages/nodejs/README.md b/packages/nodejs/README.md index d2dd9a2fb..35f43af58 100644 --- a/packages/nodejs/README.md +++ b/packages/nodejs/README.md @@ -9,558 +9,36 @@ Wrappers for interacting with the Concordium node, using nodejs. **Table of Contents** - [ConcordiumNodeClient](#concordiumnodeclient) - - [Creating a client](#creating-a-client) - - [Send Account Transaction](#send-account-transaction) - - [Create a new account](#create-a-new-account) - - [Construct IdentityInput for creating credentials](#construct-identityinput-for-creating-credentials) - - [Construct from user-cli output:](#construct-from-user-cli-output) - - [Construct from mobile wallet export:](#construct-from-mobile-wallet-export) - - [getAccountInfo](#getaccountinfo) - - [getNextAccountNonce](#getnextaccountnonce) - - [getTransactionStatus](#gettransactionstatus) - - [getBlockSummary](#getblocksummary) - - [getBlockInfo](#getblockinfo) - - [getBlocksAtHeight](#getblocksatheight) - - [getConsensusStatus](#getconsensusstatus) - - [getCryptographicParameters](#getcryptographicparameters) - - [getIdentityProviders](#getidentityproviders) - - [getAnonymityRevokers](#getanonymityrevokers) - - [getPeerList](#getpeerlist) - - [getBakerList](#getbakerlist) - - [getPoolStatus](#getpoolstatus) - - [getRewardStatus](#getrewardstatus) - - [Check block for transfers with memo](#check-block-for-transfers-with-memo) - - [getInstances](#getinstances) - - [getInstanceInfo](#getinstanceinfo) - - [invokeContract](#invokecontract) - - [getModuleSource](#getModuleSource) - [Build](#build) - [Building for a release](#building-for-a-release) - [Publishing a release](#publishing-a-release) - [Test](#test) # ConcordiumNodeClient +The SDK provides a gRPC client, which can interact with the [Concordium Node](https://github.com/Concordium/concordium-node) -The ConcordiumNodeClient defines the interface to be used to send and receive data from -a concordium-node. +For an overview of the endpoints, [check here](../../docs/gRPC.md). -## Creating a client -The current node setup only allows for insecure connections, which can be set up in the following way. -The access is controlled by the credentials and the metadata. -```js -import { credentials, Metadata } from "@grpc/grpc-js"; -import { ConcordiumNodeClient } from "@concordium/node-sdk"; - -const metadata = new Metadata(); -metadata.add("authentication", "rpcadmin"); - -const insecureCredentials = credentials.createInsecure(); -const client = new ConcordiumNodeClient( - "127.0.0.1", // ip address - 10000, // port - insecureCredentials, - metadata, - 15000 // timeout in ms -); -``` - -## Send Account Transaction -The following example demonstrates how to send any account transaction. - -See the Constructing transactions section for the [common package](../common#constructing-transactions) for how to create an account transaction. -See the signing a transaction section for the [common package](../common#sign-an-account-transaction) for how to sign an account transaction. - -```js - -let accountTransaction: AccountTransaction; -// Create the transaction -// ... - -let signatures: AccountTransactionSignature; -// Sign the transaction -// ... - -// Send the transaction to the node. -const success = await client.sendAccountTransaction(accountTransaction, signatures); -if (success) { - // The node accepted the transaction. This does not ensure that the transaction - // will end up in a block, only that the format of the submitted transaction was valid. -} else { - // The node rejected the transaction. -} - -// Check the status of the transaction. Should be checked with an appropriate interval, -// as it will take some time for the transaction to be processed. -const transactionHash = getAccountTransactionHash(accountTransaction, signatures); -const transactionStatus = await client.getTransactionStatus(transactionHash); -``` - -## Create a new account -The following example demonstrates how to create a new account on an existing -identity. The `credentialIndex` should be the next unused credential index for that identity, and keeping track of that index is done off-chain. Note that index `0` is used by the initial account that was created together with the identity. -See [Construct IdentityInput](#Construct-identityInput-for-creating-credentials) for how to construct an IdentityInput. -```js -const lastFinalizedBlockHash = (await client.getConsensusStatus()).lastFinalizedBlock; -const cryptographicParameters = await client.getCryptographicParameters(lastFinalizedBlockHash); -if (!cryptographicParameters) { - throw new Error('Cryptographic parameters were not found on a block that has been finalized.'); -} - -// The parts of the identity required to create a new account, parsed from -// e.g. a wallet export. -const identityInput: IdentityInput = ... - -// Require just one key on the credential to sign. This can be any number -// up to the number of public keys added to the credential. -const threshold: number = 1; - -// The index of the credential that will be created. This index is per identity -// and has to be in sequence, and not already used. Note that index 0 is used -// by the initial credential that was created with the identity. -const credentialIndex: number = 1; - -// In this example the credential on the account will have two keys. Note that -// the credential information has to be signed (in order) by corresponding -// private keys. -const publicKeys: VerifyKey[] = [ - { - schemeId: "Ed25519", - verifyKey: "c8cd7623c5a9316d8e2fccb51e1deee615bdb5d324fb4a6d33801848fb5e459e" - }, - { - schemeId: "Ed25519", - verifyKey: "b6baf645540d0ea6ae5ff0b87dff324340ae1120a5c430ffee60d5f370b2ab75" - } -]; - -// The attributes to reveal about the account holder on chain. This can be empty -const revealedAttributes: AttributeKey[] = ['firstName', 'nationality']; - -const expiry = new TransactionExpiry(new Date(Date.now() + 3600000)); -const credentialDeploymentTransaction: CredentialDeploymentTransaction = - createCredentialDeploymentTransaction( - identityInput, - cryptographicParameters.value, - threshold, - publicKeys, - credentialIndex, - revealedAttributes, - expiry - ); -const hashToSign: Buffer = getCredentialDeploymentSignDigest( - credentialDeploymentTransaction -); - -// The next step is to sign the credential information with each private key that matches -// one of the public keys in the credential information. -const signingKey1 = "1053de23867e0f92a48814aabff834e2ca0b518497abaef71cad4e1be506334a"; -const signingKey2 = "fcd0e499f5dc7a989a37f8c89536e9af956170d7f502411855052ff75cfc3646"; - -const signature1 = Buffer.from(await ed.sign(hashToSign, signingKey1)).toString('hex'); -const signature2 = Buffer.from(await ed.sign(hashToSign, signingKey2)).toString('hex'); -const signatures: string[] = [signature1, signature2]; - -// The address that the account created by the transaction will get can -// be derived ahead of time. -const accountAddress: AccountAddress = getAccountAddress(credentialDeploymentTransaction.cdi.credId); - -// Send the transaction to the node -const success = await client.sendCredentialDeploymentTransaction( - credentialDeploymentTransaction, - signatures -); -if (success) { - // The node accepted the transaction. This does not ensure that the transaction - // will end up in a block, only that the format of the submitted transaction was valid. -} else { - // The node rejected the transaction. -} - -// Check the status of the transaction. Should be checked with an appropriate interval, -// as it will take some time for the transaction to be processed. -const transactionHash = getCredentialDeploymentTransactionHash(credentialDeploymentTransaction, signatures); -const transactionStatus = await client.getTransactionStatus(transactionHash); -``` - -## Construct IdentityInput for creating credentials - -When creating a new identity the user will choose an identity provider, create an id-use-data object, which contains the private data to use for the identity, and obtain an identity object from the identity provider. - -To create accounts/credentials on that identity, this SDK expects an "IdentityInput" object, which contains the identity object, the id-use-data, and the identity provider's information. - -### Construct from user-cli output: - -Below is an example of how to construct the identityInput, with a plaintext id-use-data.json from the [user-cli guide](https://github.com/Concordium/concordium-base/blob/main/rust-bins/docs/user-cli.md#generate-a-request-for-the-identity-object), and an id-object file. - -```js -// First we load the files. We assume here that they are available as local files. -const rawIdUseData = fs.readFileSync( - 'path/to/id-use-data.json', - 'utf8' -); -const rawIdObject = fs.readFileSync( - 'path/to/id-object.json', - 'utf8' -); - -// Then we parse them. We assume here that they are both version 0. -const idUseData = JSON.parse(rawIdUseData).value; -const identityObject = JSON.parse(rawIdObject).value; - -// Finally we construct the IdentityInput: -const identityInput: IdentityInput = { - identityObject, - identityProvider: { - ipInfo: idUseData.ipInfo, - arsInfos: idUseData.ars, - }, - idCredSecret: idUseData.idUseData.aci.credentialHolderInformation.idCredSecret, - prfKey: idUseData.idUseData.aci.prfKey, - randomness: idUseData.idUseData.randomness, -}; -``` - -### Construct from mobile wallet export: - -The following is an example of how to construct the identityInput for the _i_-th identity from a mobile wallet export: - -```js -// We assume the export is available as a local file: -const rawData = fs.readFileSync( - 'path/to/export.concordiumwallet', - 'utf8' -); -const mobileWalletExport: EncryptedData = JSON.parse(rawData); -const decrypted: MobileWalletExport = decryptMobileWalletExport( - mobileWalletExport, - password -); -const identity = decrypted.value.identities[i]; -const identityInput: IdentityInput = { - identityObject: identity.identityObject, - identityProvider: identity.identityProvider, - idCredSecret: identity.privateIdObjectData.aci.credentialHolderInformation.idCredSecret, - prfKey: identity.privateIdObjectData.aci.prfKey, - randomness: identity.privateIdObjectData.randomness, -}; -``` - -## getAccountInfo -Retrieves information about an account. The function must be provided an account address or a credential registration id. -If a credential registration id is provided, then the node returns the information of the account, -which the corresponding credential is (or was) deployed to. -If there is no account that matches the address or credential id at the provided -block, then undefined will be returned. -```js -const accountAddress = new AccountAddress("3sAHwfehRNEnXk28W7A3XB3GzyBiuQkXLNRmDwDGPUe8JsoAcU"); -const blockHash = "6b01f2043d5621192480f4223644ef659dd5cda1e54a78fc64ad642587c73def"; -const accountInfo: AccountInfo = await client.getAccountInfo(accountAddress, blockHash); -const amount: bigint = accountInfo.accountAmount; - -// Nationality for the account creator, if the information has been revealed. -const nationality: string = accountInfo.accountCredentials[0].value.contents.policy.revealedAttributes["nationality"]; -``` - -To check if the account is a baker or a delegator, one can use the functions `isDelegatorAccount` and `isBakerAccount`. -```js -... -const accountInfo: AccountInfo = await client.getAccountInfo(accountAddress, blockHash); -if (isDelegatorAccount(accountInfo)) { - const delegationDetails = accountInfo.accountDelegation; - ... -} else if (isBakerAccount(accountInfo) { - const bakingDetails = accountInfo.accountBaker; - ... -} else { - // Neither a baker nor a delegator -} -``` -Furthermore there are different versions, based on Protocol version, of a baker's accountInfo. -In protocol version 4 the concept of baker pools was introduced, so to get baker pool information one should confirm the version with `isBakerAccountV0` or `isBakerAccountV1`. - -```js -... -const accountInfo: AccountInfo = await client.getAccountInfo(accountAddress, blockHash); -if (isBakerAccountV1(accountInfo)) { - const bakerPoolInfo = accountInfo.accountBaker.bakerPoolInfo; - ... -} else if (isBakerAccountV0(accountInfo) { - // accountInfo is from protocol version < 4, so it will not contain bakerPoolInfo - ... -} -``` - -## getNextAccountNonce -Retrieves the next account nonce, i.e. the nonce that must be set in the account transaction -header for the next transaction submitted by that account. Along with the nonce there is a boolean -that indicates whether all transactions are finalized. If this is true, then the nonce is reliable, -if not then the next nonce might be off. -```js -const accountAddress = new AccountAddress("3VwCfvVskERFAJ3GeJy2mNFrzfChqUymSJJCvoLAP9rtAwMGYt"); -const nextAccountNonce: NextAccountNonce = await client.getNextAccountNonce(accountAddress); -const nonce: bigint = nextAccountNonce.nonce; -const allFinal: boolean = nextAccountNonce.allFinal; -if (allFinal) { - // nonce is reliable -} -``` - -## getTransactionStatus -Retrieves status information about a transaction. -```js -const transactionHash = "f1f5f966e36b95d5474e6b85b85c273c81bac347c38621a0d8fefe68b69a430f"; -const transactionStatus: TransactionStatus = await client.getTransactionStatus(transactionHash); -const isFinalized = transactionStatus.status === TransactionStatusEnum.Finalized; -... -``` -Note that there will be no outcomes for a transaction that has only been received: -```js -if (transactionStatus.status === TransactionStatusEnum.Received) { - const outcomes = Object.values(transactionStatus.outcomes); - // outcomes.length === 0. -} -``` -If the transaction has been finalized, then there is exactly one outcome: -```js -if (transactionStatus.status === TransactionStatusEnum.Finalized) { - const outcomes = Object.values(transactionStatus.outcomes); - // outcomes.length === 1. -} -``` -A transaction was successful if it is finalized and it has a successful outcome: -```js -if (transactionStatus.status === TransactionStatusEnum.Finalized) { - const event = Object.values(response.outcomes)[0]; - if (event.result.outcome === "success") { - // transaction was successful. - } -} -``` - -## getBlockSummary -Retrives a summary for a specific block. The summary contains information about finalization, the -current chain parameters, a list of the governance keys, information about any queued chain parameter -updates and a summary of any transactions within the block. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8761d08554756f42bf268a42749"; -const blockSummary: BlockSummary = await client.getBlockSummary(blockHash); -const numberOfFinalizers = blockSummary.finalizationData.finalizers.length; -... -``` - -Blocks before protocol version 4 have a different type than those from higher protocol versions. -To determine the version, use `isBlockSummaryV1` and `isBlockSummaryV0`: - -```js -... -const blockSummary: BlockSummary = await client.getBlockSummary(blockHash); -if (isBlockSummaryV0(blockSummary)) { - // This block is from protocol version <= 3, and so the summary has version 0 structure - ... -} else if (isBlockSummaryV1(blockSummary) { - // This block is from protocol version >= 4, and so the summary has version 1 structure - ... -} else { - // Must be a future version of a blockSummary (or the given object is not a blockSummary) -} -``` - -There are also type checks for specific fields in the summary, which can be found in [blockSummaryHelpers](../common/src/blockSummaryHelpers.ts). - -## getBlockInfo -Retrieves information about a specific block. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8761d08554756f42bf268a42749"; -const blockInfo: BlockInfo = await client.getBlockInfo(blockHash); -const transactionsCount = blockInfo.transactionCount; -... -``` - -## getBlocksAtHeight -Retrieves the hashes of blocks at a specific height. -```js -const blockHeight: bigint = 5310n; -const blocksAtHeight: string[] = await client.getBlocksAtHeight(blockHeight); -``` - -## getConsensusStatus -Retrieves the current consensus status from the node. -```js -const consensusStatus: ConsensusStatus = await client.getConsensusStatus(); -const bestBlock = consensusStatus.bestBlock; -... -``` - -## getCryptographicParameters -Retrieves the global cryptographic parameters for the blockchain at a specific block. -These are a required input for e.g. creating credentials. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8761d08554756f42bf268a42749" -const cryptographicParameters = await client.getCryptographicParameters(blockHash); -... -``` +To create a client, the function `createConcordiumClient` can be used. It requires the address and port of the node. +It also requires credentials to be specified. These can be used for create either an insecure connection or a TLS connection. In the following example the credentials are created for a TLS connection: -## getIdentityProviders -Retrieves the list of identity providers at a specific block. ```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const identityProviders = await client.getIdentityProviders(blockHash); +import { credentials } from '@grpc/grpc-js/'; ... -``` - -## getAnonymityRevokers -Retrieves the list of anonymity revokers at a specific block. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const identityProviders = await client.getAnonymityRevokers(blockHash); -... -``` - -## getPeerList -Retrieves the list of peers that the node is connected to, including some -connection information about them. A boolean parameter determines if this -should include bootstrapper nodes or not. -```js -const peerListResponse = await client.getPeerList(false); -const peersList = peerListResponse.getPeersList(); -... -``` - -## getBakerList -Retrieves the list of ID's for registered bakers on the network at a specific block. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const bakerIds = await client.getBakerList(blockHash); -... -``` - -## getPoolStatus -Retrieves the status of a pool (either a specific baker or passive delegation) at a specific block. -If a baker ID is specified, the status of that baker is returned. To get the status of passive delegation, baker ID should be left undefined. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const bakerId = BigInt(1); - -const bakerStatus = await client.getPoolStatus(blockHash, bakerId); -const passiveDelegationStatus = await client.getPoolStatus(blockHash); -... -``` - -## getRewardStatus -Retrieves the current amount of funds in the system at a specific block, and the state of the special accounts. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; - -const rewardStatus = await client.getRewardStatus(blockHash); -``` - -Protocol version 4 expanded the amount of information in the response, so one should check the type to access that. -This information includes information about the payday and total amount of funds staked. -```js -if (isRewardStatusV1(rewardStatus)) { - const nextPaydayTime = rewardStatus.nextPaydayTime; - ... -} -``` - -## Check block for transfers with memo -The following example demonstrates how to check and parse a block -for transfers with a memo. -```js -const blockHash = "b49bb1c06c697b7d6539c987082c5a0dc6d86d91208874517ab17da752472edf"; -const blockSummary = await client.getBlockSummary(blockHash); -const transactionSummaries = blockSummary.transactionSummaries; - -for (const transactionSummary of transactionSummaries) { - if (transactionSummary.result.outcome === 'success') { - if (instanceOfTransferWithMemoTransactionSummary(transactionSummary)) { - const [transferredEvent, memoEvent] = transactionSummary.result.events; - - const toAddress = transferredEvent.to.address; - const amount = transferredEvent.amount; - const memo = memoEvent.memo; - - // Apply business logic to toAddress, amount and memo... - } - } -} -``` - -## getInstances -Used to get the full list of contract instances on the chain at a specific block. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; - -const instances = await client.getInstances(blockHash); -... -``` - -## getInstanceInfo -Used to get information about a specific contract instance, at a specific block. - -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const contractAddress = { index: 1n, subindex: 0n }; - -const instanceInfo = await client.getInstanceInfo(contractAddress, blockHash); -const name = instanceInfo.name; -... -``` - -Note that only version 0 contracts returns the model. (use `isInstanceInfoV0`/`isInstanceInfoV1` to check the version) - -## invokeContract -Used to simulate a contract update, and to trigger view functions. - -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const contractAddress = { index: 1n, subindex: 0n }; -const invoker = new AccountAddress('3tXiu8d4CWeuC12irAB7YVb1hzp3YxsmmmNzzkdujCPqQ9EjDm'); -const result = await client.invokeContract( - { - invoker: invoker, - contract: contractAddress, - method: 'PiggyBank.smash', - amount: undefined, - parameter: undefined, - energy: 30000n, - }, - blockHash +return createConcordiumClientV2( + address, + port, + credentials.createSsl(), + { timeout: 15000 } ); - -if (!result) { - // The node could not attempt the invocation, most likely the contract doesn't exist. -} - -if (result.tag === 'failure') { - // Invoke was unsuccesful - const rejectReason = result.reason; // Describes why the update failed; - ... -} else { - const events = result.events; // a list of events that would be generated by the update - const returnValue = result.returnValue; // If the invoked method has return value - ... -} ``` -Note that some of the parts of the context are optional: - - amount: defaults to 0 - - energy: defaults to 10 million - - parameter: defaults to no parameters - - invoker: uses the zero account address, which can be used instead of finding a random address. - -## getModuleSource -This commands gets the source of a module on the chain. +The fourth argument is additional options. In the example above we sat the timeout for a call to the node to 15 seconds. The options allowed here are those allowed by the [grpc-transport](https://www.npmjs.com/package/@protobuf-ts/grpc-transport). -Note that this returns the raw bytes of the source, as a buffer. -```js -const blockHash = "7f7409679e53875567e2ae812c9fcefe90ced8961d08554756f42bf268a42749"; -const moduleReference = "c0e51cd55ccbff4fa8da9bb76c9917e83ae8286d86b47647104bf715b4821c1a"; -const source = await client.getModuleSource(moduleReference, blockHash); -if (!source) { - // the blockHash is unknown or the module doesn't exist at the given blockHash -} -``` +The connection to a node can be either an insecure connection or a TLS connection. Note that the node that you are trying to connect to must support TLS, for a TLS connection to work. Otherwise an insecure connection can be created by using `credentials.createInsecure()` instead. + +To see the documentation for the deprecated v1 client, [check here](../../docs/grpc-v1.md). +For an overview of how to migrate from the v1 client to the v2 client, [check here](../../docs/grpc-migration.md). # Build diff --git a/packages/nodejs/package.json b/packages/nodejs/package.json index bcd9b7535..fac16de5e 100644 --- a/packages/nodejs/package.json +++ b/packages/nodejs/package.json @@ -4,7 +4,8 @@ "description": "Helpers for interacting with the Concordium node", "repository": { "type": "git", - "url": "https://github.com/Concordium/concordium-node-sdk-js" + "url": "https://github.com/Concordium/concordium-node-sdk-js", + "directory": "packages/nodejs" }, "author": { "name": "Concordium Software", @@ -58,7 +59,7 @@ "build": "rm -rf grpc; mkdir -p grpc; yarn generate && tsc" }, "dependencies": { - "@concordium/common-sdk": "6.2.0", + "@concordium/common-sdk": "6.3.0", "@grpc/grpc-js": "^1.3.4", "@protobuf-ts/grpc-transport": "^2.8.2", "buffer": "^6.0.3", diff --git a/packages/nodejs/src/clientV2.ts b/packages/nodejs/src/clientV2.ts index 47c6fd363..315c34bb5 100644 --- a/packages/nodejs/src/clientV2.ts +++ b/packages/nodejs/src/clientV2.ts @@ -1,17 +1,24 @@ import { ChannelCredentials } from '@grpc/grpc-js'; -import { GrpcTransport } from '@protobuf-ts/grpc-transport'; +import { GrpcOptions, GrpcTransport } from '@protobuf-ts/grpc-transport'; import ConcordiumGRPCClient from '@concordium/common-sdk/lib/GRPCClient'; +/** + * Initialize a gRPC client for a specific concordium node. + * @param address the ip address of the node, e.g. http://127.0.0.1 + * @param port the port to use when econnecting to the node + * @param credentials channel credentials for communicating with the node + * @param options optional options for the grpc transport + */ export default function createConcordiumClient( address: string, port: number, credentials: ChannelCredentials, - options?: Record + options?: Partial ): ConcordiumGRPCClient { const grpcTransport = new GrpcTransport({ host: `${address}:${port}`, channelCredentials: credentials, - options, + ...options, }); return new ConcordiumGRPCClient(grpcTransport); } diff --git a/packages/nodejs/test/testHelpers.ts b/packages/nodejs/test/testHelpers.ts index dace3a5a2..2dd1fe803 100644 --- a/packages/nodejs/test/testHelpers.ts +++ b/packages/nodejs/test/testHelpers.ts @@ -40,7 +40,7 @@ export function getNodeClientV2( address, port, credentials.createInsecure(), - { timeout: 15000 } + { timeout: 1 } ); } diff --git a/packages/rust-bindings/CHANGELOG.md b/packages/rust-bindings/CHANGELOG.md index 97598efdf..a1b9d1b48 100644 --- a/packages/rust-bindings/CHANGELOG.md +++ b/packages/rust-bindings/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.10.0 +## 0.10.0 2023-2-7 ### Added diff --git a/packages/rust-bindings/package.json b/packages/rust-bindings/package.json index 99373dad8..0b491ed6f 100644 --- a/packages/rust-bindings/package.json +++ b/packages/rust-bindings/package.json @@ -1,10 +1,15 @@ { "name": "@concordium/rust-bindings", - "version": "0.9.0", + "version": "0.10.0", "license": "Apache-2.0", "engines": { "node": ">=14.16.0" }, + "repository": { + "type": "git", + "url": "https://github.com/Concordium/concordium-node-sdk-js", + "directory": "packages/rust-bindings" + }, "main": "pkg/node/concordium_rust_bindings.js", "browser": "pkg/bundler/concordium_rust_bindings.js", "types": "pkg/bundler/concordium_rust_bindings.d.ts", diff --git a/packages/web/CHANGELOG.md b/packages/web/CHANGELOG.md index 6447173f8..f5a4ae195 100644 --- a/packages/web/CHANGELOG.md +++ b/packages/web/CHANGELOG.md @@ -1,14 +1,14 @@ # Changelog -## Unreleased +## 3.3.0 2023-2-7 ### Added -- Added a `createConcordiumClient` function to create the GRPCv2 client. +- Added a `createConcordiumClient` function to create the gRPC v2 client. ### Changed -- Bumped @concordium/common-sdk to 6.3.0. (Adds the initial version of GRPCv2 client) +- Bumped @concordium/common-sdk to 6.3.0. (Adds the initial version of gRPC v2 client) ## 3.2.0 2022-1-4 diff --git a/packages/web/README.md b/packages/web/README.md index 42c41e443..944964d62 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -21,27 +21,29 @@ Wrappers for interacting with the Concordium node, for the web environment. - [Building for a release](#building-for-a-release) - [Publishing a release](#publishing-a-release) -# JSON-RPC client -The SDK provides a JSON-RPC client, which can interact with the [Concordium JSON-RPC server](https://github.com/Concordium/concordium-json-rpc) +# ConcordiumNodeClient +The SDK provides a gRPC client, which can interact with the [Concordium Node](https://github.com/Concordium/concordium-node) using gRPC-web. + +For an overview of the endpoints, [check here](../../docs/gRPC.md). -## Creating a client -To create a client, one needs a provider, which handles sending and receiving over a specific protocol. Currently the only one available is the HTTP provider. -The HTTP provider needs the URL to the JSON-RPC server. The following example demonstrates how to create a client that connects to a local server on port 9095: +To create a client, the function `createConcordiumClient` can be used. It requires the address and port of the concordium node. ```js -const client = new JsonRpcClient(new HttpProvider("http://localhost:9095")); +... +return createConcordiumClient( + address, + port, + { timeout: 15000 } +); ``` -## API Entrypoints -Currently the client only supports the following entrypoints, with the same interface as the node client: - -- [sendTransaction](../nodejs#send-account-transaction) -- [getTransactionStatus](../nodejs#gettransactionstatus) -- [getInstanceInfo](../nodejs#getInstanceInfo) -- [getConsensusStatus](../nodejs#getconsensusstatus) -- [getAccountInfo](../nodejs#getAccountInfo) -- [getCryptographicParameters](../nodejs#getcryptographicparameters) -- [invokeContract](../nodejs#invokecontract) -- [getModuleSource](../nodejs#getModuleSource) +The third argument is additional options. In the example above we sat the timeout for a call to the node to 15 seconds. The options allowed here are those allowed by the [grpcweb-transport](https://www.npmjs.com/package/@protobuf-ts/grpcweb-transport). + + +# JSON-RPC client +> :warning: **The JSON-RPC client has been deprecated**: the gRPC client should be used instead to communicate directly with a node +> To migrate, the migration guide from the v1 client to the v2 client [can be found here](../../docs/grpc-migration.md), as the JSON-RPC's endpoints shares interface with the equivalents in the v1 gRPC cient + +The SDK also provides a JSON-RPC client, [check here for the documentation](../../docs/JSON-RPC.md). # Creating buffers Some of the functions in the SDK expects buffers as input. diff --git a/packages/web/package.json b/packages/web/package.json index 87066881c..93254b4d4 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "@concordium/web-sdk", - "version": "3.2.0", + "version": "3.3.0", "license": "Apache-2.0", "browser": "lib/concordium.min.js", "types": "lib/index.d.ts", @@ -10,7 +10,8 @@ ], "repository": { "type": "git", - "url": "https://github.com/Concordium/concordium-node-sdk-js" + "url": "https://github.com/Concordium/concordium-node-sdk-js", + "directory": "packages/web" }, "author": { "name": "Concordium Software", @@ -48,8 +49,8 @@ "webpack-cli": "^4.9.2" }, "dependencies": { - "@concordium/common-sdk": "6.2.0", - "@concordium/rust-bindings": "0.9.0", + "@concordium/common-sdk": "6.3.0", + "@concordium/rust-bindings": "0.10.0", "@grpc/grpc-js": "^1.3.4", "@protobuf-ts/grpcweb-transport": "^2.8.2", "buffer": "^6.0.3", diff --git a/yarn.lock b/yarn.lock index 9eaa77315..ef9b7d421 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1312,11 +1312,11 @@ __metadata: languageName: node linkType: hard -"@concordium/common-sdk@6.2.0, @concordium/common-sdk@workspace:packages/common": +"@concordium/common-sdk@6.3.0, @concordium/common-sdk@workspace:packages/common": version: 0.0.0-use.local resolution: "@concordium/common-sdk@workspace:packages/common" dependencies: - "@concordium/rust-bindings": 0.9.0 + "@concordium/rust-bindings": 0.10.0 "@grpc/grpc-js": ^1.3.4 "@noble/ed25519": ^1.7.1 "@protobuf-ts/plugin": 2.8.1 @@ -1354,7 +1354,7 @@ __metadata: version: 0.0.0-use.local resolution: "@concordium/node-sdk@workspace:packages/nodejs" dependencies: - "@concordium/common-sdk": 6.2.0 + "@concordium/common-sdk": 6.3.0 "@grpc/grpc-js": ^1.3.4 "@noble/ed25519": ^1.7.1 "@protobuf-ts/grpc-transport": ^2.8.2 @@ -1383,7 +1383,7 @@ __metadata: languageName: unknown linkType: soft -"@concordium/rust-bindings@0.9.0, @concordium/rust-bindings@workspace:packages/rust-bindings": +"@concordium/rust-bindings@0.10.0, @concordium/rust-bindings@workspace:packages/rust-bindings": version: 0.0.0-use.local resolution: "@concordium/rust-bindings@workspace:packages/rust-bindings" languageName: unknown @@ -1393,8 +1393,8 @@ __metadata: version: 0.0.0-use.local resolution: "@concordium/web-sdk@workspace:packages/web" dependencies: - "@concordium/common-sdk": 6.2.0 - "@concordium/rust-bindings": 0.9.0 + "@concordium/common-sdk": 6.3.0 + "@concordium/rust-bindings": 0.10.0 "@grpc/grpc-js": ^1.3.4 "@protobuf-ts/grpcweb-transport": ^2.8.2 "@typescript-eslint/eslint-plugin": ^4.28.1 From 43bb1f21e02ff17ceef8e0d317f146dcaab33406 Mon Sep 17 00:00:00 2001 From: Shjorty <201505261@post.au.dk> Date: Tue, 7 Feb 2023 16:24:42 +0100 Subject: [PATCH 2/5] reverse test timeout change --- packages/nodejs/test/testHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodejs/test/testHelpers.ts b/packages/nodejs/test/testHelpers.ts index 2dd1fe803..dace3a5a2 100644 --- a/packages/nodejs/test/testHelpers.ts +++ b/packages/nodejs/test/testHelpers.ts @@ -40,7 +40,7 @@ export function getNodeClientV2( address, port, credentials.createInsecure(), - { timeout: 1 } + { timeout: 15000 } ); } From e91441bce7a26fc9f0d3bde67e545a50fc15c5cb Mon Sep 17 00:00:00 2001 From: Hjort Date: Fri, 17 Feb 2023 11:28:03 +0100 Subject: [PATCH 3/5] gRPC v2 invokeContract energy now defaults to 10 mil + document it --- docs/gRPC.md | 3 ++- packages/common/src/GRPCClient.ts | 3 ++- packages/common/src/constants.ts | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 packages/common/src/constants.ts diff --git a/docs/gRPC.md b/docs/gRPC.md index 802e3f932..c7b76759b 100644 --- a/docs/gRPC.md +++ b/docs/gRPC.md @@ -386,6 +386,7 @@ if (result.tag === 'failure') { Note that some of the parts of the context are optional: - blockHash: defaults to last finalized block + - energy: defaults to 10,000,000 NRG. ## getModuleSource This commands gets the source of a module on the chain. @@ -899,4 +900,4 @@ If a blockhash is not supplied it will pick the latest finalized block. ```js const blockHash = "fe88ff35454079c3df11d8ae13d5777babd61f28be58494efe51b6593e30716e"; const pendingUpdates: BlockFinalizationSummary = await this.client.getBlockSpecialEvents(blockHash); -``` \ No newline at end of file +``` diff --git a/packages/common/src/GRPCClient.ts b/packages/common/src/GRPCClient.ts index b01a48289..95513ae1e 100644 --- a/packages/common/src/GRPCClient.ts +++ b/packages/common/src/GRPCClient.ts @@ -23,6 +23,7 @@ import { } from './serialization'; import { BlockItemStatus, BlockItemSummary } from './types/blockItemSummary'; import { ModuleReference } from './types/moduleReference'; +import { DEFAULT_INVOKE_ENERGY } from './constants'; /** * A concordium-node specific gRPC client wrapper. @@ -208,7 +209,7 @@ export default class ConcordiumNodeClient { amount: { value: context.amount?.microCcdAmount || 0n }, entrypoint: { value: context.method }, parameter: { value: context.parameter || Buffer.alloc(0) }, - energy: { value: context.energy || 0n }, + energy: { value: context.energy || DEFAULT_INVOKE_ENERGY }, }; const response = await this.client.invokeInstance(invokeInstanceRequest) diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts new file mode 100644 index 000000000..5a1c42b4e --- /dev/null +++ b/packages/common/src/constants.ts @@ -0,0 +1 @@ +export const DEFAULT_INVOKE_ENERGY = 10000000n; From be5c1fc3951077744e0f369843e75076b9524a08 Mon Sep 17 00:00:00 2001 From: Shjorty <201505261@post.au.dk> Date: Mon, 20 Feb 2023 10:50:22 +0100 Subject: [PATCH 4/5] Change gRPC v2 invokeContract default energy to 1 mil --- docs/gRPC.md | 2 +- packages/common/src/constants.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/gRPC.md b/docs/gRPC.md index c7b76759b..7d5373839 100644 --- a/docs/gRPC.md +++ b/docs/gRPC.md @@ -386,7 +386,7 @@ if (result.tag === 'failure') { Note that some of the parts of the context are optional: - blockHash: defaults to last finalized block - - energy: defaults to 10,000,000 NRG. + - energy: defaults to 1,000,000 NRG. ## getModuleSource This commands gets the source of a module on the chain. diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index 5a1c42b4e..a57ac1bca 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -1 +1 @@ -export const DEFAULT_INVOKE_ENERGY = 10000000n; +export const DEFAULT_INVOKE_ENERGY = 1000000n; From 3f6c4f145d6558ff9251f65d4cc8d3d084c79524 Mon Sep 17 00:00:00 2001 From: Hjort Date: Thu, 23 Feb 2023 11:47:04 +0100 Subject: [PATCH 5/5] Remove duplicate tag --- packages/common/src/types/rejectReason.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/common/src/types/rejectReason.ts b/packages/common/src/types/rejectReason.ts index 4f31d594a..b28dac017 100644 --- a/packages/common/src/types/rejectReason.ts +++ b/packages/common/src/types/rejectReason.ts @@ -140,7 +140,6 @@ export type AccountAddressRejectReasonTag = export type StringRejectReasonTag = | ModuleRefRejectReasonTag | AccountAddressRejectReasonTag - | RejectReasonTag.NonExistentCredentialID | RejectReasonTag.DuplicateAggregationKey; export interface StringRejectReason {