Skip to content

Commit

Permalink
Cis2 (#304)
Browse files Browse the repository at this point in the history
  • Loading branch information
MilkywayPirate authored Feb 14, 2024
1 parent 47576a4 commit 20c837d
Show file tree
Hide file tree
Showing 50 changed files with 2,119 additions and 180 deletions.
25 changes: 16 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
# Changelog

# Unreleased changes
- Cleanup the API a bit for configure baker transaction by using `PartsPerHundredThousands` for determining the commission rates.
- Fix serialization of `ConfigureDelegation` transaction
- Remove `AccountNonce` in favor of just using the `Nonce` type across the API.
- Fix a bug in the serialization of `AccountIndex`
- Fix a bug that caused `getAccountInfo` to fail for delegator and baker accounts if they had no stake pending changes.
This change is also propagated to the type level such that `Baker` and `AccountDelegation` retains an `Optional<PendingChange>`
as opposed to just `PendingChange`.
- Fix .equals() for AccountInfo such that all fields are used to deduce equality.<
## Unreleased changes
- Make the `energy` parameter for invoking an instance `Optional`.
- Parse the underlying reject reasons into `AccountTransactionDetails`.
- Introduced Cis2Client for interfacing with CIS2 compliant smart contracts.
- Support for deserializing contract update transactions.
- Fix a bug where contract invocations used the wrong format for parameters.
- Fix a bug in the serialization of `AccountIndex`
- Fix a bug that caused `getAccountInfo` to fail for delegator and baker accounts if they had no stake pending changes.
- Cleanup the API a bit for configure baker transaction by using `PartsPerHundredThousands` for determining the commission rates.
- Fix serialization of `ConfigureDelegation` transaction
- Remove `AccountNonce` in favor of just using the `Nonce` type across the API.
- Fix a bug in the serialization of `AccountIndex`
- Fix a bug that caused `getAccountInfo` to fail for delegator and baker accounts if they had no stake pending changes.
This change is also propagated to the type level such that `Baker` and `AccountDelegation` retains an `Optional<PendingChange>`
as opposed to just `PendingChange`.
- Fix .equals() for AccountInfo such that all fields are used to deduce equality.

## 6.1.0
- Purge remaining usages of V1 GRPC API.
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ android:


clean:
cd $(PATH_CRYPTO) && cargo clean
rm -rf $(PATH_JAVA_NATIVE_RESOURCES)*
rm -rf $(PATH_ANDROID_NATIVE_RESOURCES)*
5 changes: 5 additions & 0 deletions concordium-android-sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.concordium.sdk.examples;

import com.concordium.sdk.ClientV2;
import com.concordium.sdk.Connection;
import com.concordium.sdk.cis2.BalanceQuery;
import com.concordium.sdk.cis2.Cis2Client;
import com.concordium.sdk.cis2.TokenAmount;
import com.concordium.sdk.cis2.TokenId;
import com.concordium.sdk.exceptions.ClientInitializationException;
import com.concordium.sdk.requests.BlockQuery;
import com.concordium.sdk.transactions.Hash;
import com.concordium.sdk.types.AbstractAddress;
import com.concordium.sdk.types.AccountAddress;
import com.concordium.sdk.types.ContractAddress;
import lombok.val;
import picocli.CommandLine;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;

/**
* Example usage of the CIS2 client
*/
@CommandLine.Command(name = "Cis2", mixinStandardHelpOptions = true)
public class Cis2 implements Callable<Integer> {

@CommandLine.Option(
names = {"--endpoint"},
description = "GRPC interface of the node.",
defaultValue = "http://localhost:20000")
private String endpoint;

@CommandLine.Option(
names = {"--index"},
description = "Index of the contract.",
defaultValue = "9390")
private long contractIndex;

@Override
public Integer call() throws ClientInitializationException, MalformedURLException {
URL endpointUrl = new URL(this.endpoint);
val client = Cis2Client.newClient(ClientV2.from(Connection.newBuilder()
.host(endpointUrl.getHost())
.port(endpointUrl.getPort())
.build()), ContractAddress.from(contractIndex, 0));

val eventsForBlock = client.getEventsFor(BlockQuery.HASH(Hash.from("cfd9a3de1b7de2d2942f80b102135bcc8553f472c53c6c8074110aba38bca43c")));
while (eventsForBlock.hasNext()) {
System.out.println(eventsForBlock.next());
}

val balances = client.balanceOf(new BalanceQuery(TokenId.min(), AccountAddress.from("3rXssmPErqhHvDMByFLCEydYAJwot7ZkL7xcu8y296iMJxwNGC")));
for (BalanceQuery balanceQuery : balances.keySet()) {
TokenId tokenId = balanceQuery.getTokenId();
AbstractAddress owner = balanceQuery.getAddress();
TokenAmount balance = balances.get(balanceQuery);
System.out.println("TokenId: " + tokenId + " Owner: " + owner + " Balance " + balance);
}

return 0;
}

public static void main(String[] args) {
int exitCode = new CommandLine(new Cis2()).execute(args);
System.exit(exitCode);
}
}
5 changes: 5 additions & 0 deletions concordium-sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@
<version>3.3.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
</dependencies>

<profiles>
Expand Down
57 changes: 31 additions & 26 deletions concordium-sdk/src/main/java/com/concordium/sdk/ClientV2.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -141,7 +142,6 @@ public Iterator<BlockIdentifier> getBlocks(int timeoutMillis) {
*
* @param timeoutMillis Timeout for the request in Milliseconds.
* @return {@link Iterator<BlockIdentifier>}
*
*/
public Iterator<BlockIdentifier> getFinalizedBlocks(int timeoutMillis) {
val grpcOutput = this.server(timeoutMillis)
Expand Down Expand Up @@ -323,8 +323,8 @@ public Nonce getNextAccountSequenceNumber(AccountAddress address) {
* @param transactionHash The transaction {@link Hash}
* @return The {@link BlockItemStatus}
* @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}: <ul>
* <li> {@link io.grpc.Status#NOT_FOUND} if the transaction is not known to the node.
* </ul>
* <li> {@link io.grpc.Status#NOT_FOUND} if the transaction is not known to the node.
* </ul>
*/
public BlockItemStatus getBlockItemStatus(Hash transactionHash) {
val grpcOutput = this.server()
Expand Down Expand Up @@ -793,9 +793,10 @@ public InvokeInstanceResult invokeInstance(InvokeInstanceRequest request) {
grpcRequest.setInstance(to(request.getInstance()))
.setAmount(to(request.getAmount()))
.setEntrypoint(to(request.getEntrypoint()))
.setParameter(to(request.getParameter()))
.setEnergy(com.concordium.grpc.v2.Energy.newBuilder().setValue(request.getEnergy().getValue().getValue()));

.setParameter(to(request.getParameter()));
if (request.getEnergy().isPresent()) {
grpcRequest.setEnergy(com.concordium.grpc.v2.Energy.newBuilder().setValue(request.getEnergy().get().getValue().getValue()));
}
val grpcResponse = this.server().invokeInstance(grpcRequest.build());
return InvokeInstanceResult.parse(grpcResponse);
}
Expand All @@ -807,7 +808,7 @@ public InvokeInstanceResult invokeInstance(InvokeInstanceRequest request) {
* @param input The block to query.
* @return {@link ImmutableList} with the {@link BakerRewardPeriodInfo} of all the bakers in the block.
* @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}:
* <ul><li>{@link io.grpc.Status.Code#UNIMPLEMENTED} if the protocol does not support the endpoint.</ul>
* <ul><li>{@link io.grpc.Status.Code#UNIMPLEMENTED} if the protocol does not support the endpoint.</ul>
*/
public ImmutableList<BakerRewardPeriodInfo> getBakersRewardPeriod(BlockQuery input) {
val response = this.server().getBakersRewardPeriod(to(input));
Expand All @@ -824,9 +825,9 @@ public ImmutableList<BakerRewardPeriodInfo> getBakersRewardPeriod(BlockQuery inp
* @param block The block to query
* @return {@link BlockCertificates} of the block.
* @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}:<ul>
* <li>{@link io.grpc.Status.Code#UNIMPLEMENTED} if the endpoint is not enabled by the node.
* <li>{@link io.grpc.Status.Code#INVALID_ARGUMENT} if the block being pointed to is not a product of ConcordiumBFT, i.e. created before protocol version 6.
* </ul>
* <li>{@link io.grpc.Status.Code#UNIMPLEMENTED} if the endpoint is not enabled by the node.
* <li>{@link io.grpc.Status.Code#INVALID_ARGUMENT} if the block being pointed to is not a product of ConcordiumBFT, i.e. created before protocol version 6.
* </ul>
*/
public BlockCertificates getBlockCertificates(BlockQuery block) {
val res = this.server().getBlockCertificates(to(block));
Expand All @@ -835,7 +836,7 @@ public BlockCertificates getBlockCertificates(BlockQuery block) {

/**
* Get the projected earliest time at which a particular baker will be required to bake a block. <p>
*
* <p>
* If the baker is not a baker for the current reward period, this returns a timestamp at the
* start of the next reward period. <p>
* If the baker is a baker for the current reward period, the
Expand All @@ -848,10 +849,11 @@ public BlockCertificates getBlockCertificates(BlockQuery block) {
* epoch. This is because the seed for the leader election is updated at the epoch boundary, and
* so the winners cannot be predicted beyond that. <p>
* Note that in some circumstances the returned timestamp can be in the past, especially at the end of an epoch.
*
* @param bakerId id of the baker to query.
* @return {@link Timestamp} as described in the method documentation.
* @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}:
* <ul><li>{@link io.grpc.Status.Code#UNIMPLEMENTED} if the current consensus version is 0, as the endpoint is only supported by consensus version 1.</ul>
* <ul><li>{@link io.grpc.Status.Code#UNIMPLEMENTED} if the current consensus version is 0, as the endpoint is only supported by consensus version 1.</ul>
*/
public Timestamp getBakerEarliestWinTime(BakerId bakerId) {
val res = this.server().getBakerEarliestWinTime(to(bakerId));
Expand All @@ -864,12 +866,12 @@ public Timestamp getBakerEarliestWinTime(BakerId bakerId) {
* @param epochQuery {@link EpochQuery} representing the specific epoch to query.
* @return {@link Hash} of the first finalized block in the epoch.
* @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}: <ul>
* <li> {@link io.grpc.Status#NOT_FOUND} if the query specifies an unknown block.
* <li> {@link io.grpc.Status#UNAVAILABLE} if the query is for an epoch that is not finalized in the current genesis index, or is for a future genesis index.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the query is for an epoch with no finalized blocks for a past genesis index.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the input {@link EpochQuery} is malformed.
* <li> {@link io.grpc.Status#UNIMPLEMENTED} if the endpoint is disabled on the node.
* </ul>
* <li> {@link io.grpc.Status#NOT_FOUND} if the query specifies an unknown block.
* <li> {@link io.grpc.Status#UNAVAILABLE} if the query is for an epoch that is not finalized in the current genesis index, or is for a future genesis index.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the query is for an epoch with no finalized blocks for a past genesis index.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the input {@link EpochQuery} is malformed.
* <li> {@link io.grpc.Status#UNIMPLEMENTED} if the endpoint is disabled on the node.
* </ul>
*/
public Hash getFirstBlockEpoch(EpochQuery epochQuery) {
val res = this.server().getFirstBlockEpoch(to(epochQuery));
Expand All @@ -880,16 +882,17 @@ public Hash getFirstBlockEpoch(EpochQuery epochQuery) {
* Get the list of bakers that won the lottery in a particular historical epoch (i.e. the last finalized block is in a later epoch). <p>
* This lists the winners for each round in the epoch, starting from the round after the last block in the previous epoch, running to the round before the first block in the next epoch. <p>
* It also indicates if a block in each round was included in the finalized chain.
*
* @param epochQuery {@link EpochQuery} representing the specific epoch to query.
* @return {@link ImmutableList} of bakers that won the lottery in the specified epoch.
* @throws io.grpc.StatusRuntimeException with {@link io.grpc.Status.Code}: <ul>
* <li> {@link io.grpc.Status#NOT_FOUND} if the query specifies an unknown block.
* <li> {@link io.grpc.Status#UNAVAILABLE} if the query is for an epoch that is not finalized in the current genesis index, or is for a future genesis index.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the query is for an epoch that is not finalized for a past genesis index.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the query is for a genesis index at consensus version 0.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the input {@link EpochQuery} is malformed.
* <li> {@link io.grpc.Status#UNIMPLEMENTED} if the endpoint is disabled on the node.
* </ul>
* <li> {@link io.grpc.Status#NOT_FOUND} if the query specifies an unknown block.
* <li> {@link io.grpc.Status#UNAVAILABLE} if the query is for an epoch that is not finalized in the current genesis index, or is for a future genesis index.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the query is for an epoch that is not finalized for a past genesis index.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the query is for a genesis index at consensus version 0.
* <li> {@link io.grpc.Status#INVALID_ARGUMENT} if the input {@link EpochQuery} is malformed.
* <li> {@link io.grpc.Status#UNIMPLEMENTED} if the endpoint is disabled on the node.
* </ul>
*/
public ImmutableList<WinningBaker> getWinningBakersEpoch(EpochQuery epochQuery) {
val res = this.server().getWinningBakersEpoch(to(epochQuery));
Expand All @@ -904,8 +907,9 @@ public ImmutableList<WinningBaker> getWinningBakersEpoch(EpochQuery epochQuery)
* Waits until a given transaction is finalized and returns the corresponding {@link Optional<FinalizedBlockItem>}.
* If the transaction is unknown to the node or not finalized, the client starts listening for newly finalized blocks,
* and returns the corresponding {@link Optional<FinalizedBlockItem>} once the transaction is finalized.
*
* @param transactionHash the {@link Hash} of the transaction to wait for.
* @param timeoutMillis the number of milliseconds to listen for newly finalized blocks.
* @param timeoutMillis the number of milliseconds to listen for newly finalized blocks.
* @return {@link Optional<FinalizedBlockItem>} of the transaction if it was finalized before exceeding the timeout, Empty otherwise.
*/
public Optional<FinalizedBlockItem> waitUntilFinalized(Hash transactionHash, int timeoutMillis) {
Expand Down Expand Up @@ -939,6 +943,7 @@ public Optional<FinalizedBlockItem> waitUntilFinalized(Hash transactionHash, int

/**
* Helper function for {@link ClientV2#waitUntilFinalized(Hash, int)}. Retrieves the {@link Optional<FinalizedBlockItem>} of the transaction if it is finalized.
*
* @param transactionHash the {@link Hash} of the transaction to wait for.
* @return {@link Optional<FinalizedBlockItem>} of the transaction if it is finalized, Empty otherwise.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1649,7 +1649,7 @@ static com.concordium.grpc.v2.ReceiveName to(ReceiveName receiveName) {

static com.concordium.grpc.v2.Parameter to(Parameter parameter) {
return com.concordium.grpc.v2.Parameter.newBuilder()
.setValue(ByteString.copyFrom(parameter.getBytes()))
.setValue(ByteString.copyFrom(parameter.getBytesForContractInvocation()))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.concordium.sdk.cis2;

import com.concordium.sdk.types.AbstractAddress;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

/**
* An object used for querying token balances.
* See <a href="https://proposals.concordium.software/CIS/cis-2.html#balanceof">here</a> for the specification.
*/
@Getter
@ToString
@EqualsAndHashCode
public class BalanceQuery {

/**
* The token id to query
*/
private final TokenId tokenId;

/**
* The address to query the balance of
*/
private final AbstractAddress address;

public BalanceQuery(TokenId tokenId, AbstractAddress address) {
this.tokenId = tokenId;
this.address = address;
}

}
Loading

0 comments on commit 20c837d

Please sign in to comment.