Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Get block items #80

Merged
merged 25 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c9058fb
Added type for `PendingUpdate`
rasmus-kirk Oct 13, 2023
3c9e39a
Working on adding account transactions
rasmus-kirk Oct 26, 2023
1901ee8
Merge remote-tracking branch 'origin/more-grpc-endpoints' into get-bl…
rasmus-kirk Oct 26, 2023
b7d47a5
Worked on adding credential deployment
rasmus-kirk Oct 26, 2023
cf13b89
Did more stuff
rasmus-kirk Nov 2, 2023
c34ce3b
Implemented serialization+deserialization for `deployModule`
rasmus-kirk Nov 7, 2023
44b5841
Fixed DeployModule equality and cleanup
rasmus-kirk Nov 14, 2023
8860563
Added deserialization of TransferWithMemo
rasmus-kirk Nov 14, 2023
134a4ea
Deserial now works for `TransferWithMemo` and `RegisterData`
rasmus-kirk Nov 21, 2023
a6dda78
Moved `BlockItem` and added RawPayload
rasmus-kirk Nov 21, 2023
7404cb6
Merge branch 'main' into get-block-items
rasmus-kirk Nov 28, 2023
30838ed
Self-review
rasmus-kirk Nov 28, 2023
b659229
Pleased formatter
rasmus-kirk Nov 28, 2023
193d694
Added changelog
rasmus-kirk Nov 28, 2023
55f386a
Merge branch 'main' into get-block-items
rasmus-kirk Nov 28, 2023
6b773df
Fixed example
rasmus-kirk Nov 28, 2023
2e8fd9a
Addressed some comments
rasmus-kirk Dec 5, 2023
6864086
handle "impossible" serialization cases with exception
rasmus-kirk Dec 5, 2023
ed878ad
updated changelog and example
rasmus-kirk Dec 5, 2023
c3f3e6f
Addressed comment: Fixed PayloadSize calculation
rasmus-kirk Dec 12, 2023
f6dd00b
Merge remote-tracking branch 'origin/main' into get-block-items
rasmus-kirk Dec 12, 2023
eefd1fd
Addressed comments
rasmus-kirk Dec 19, 2023
466ce49
Addressed comments
rasmus-kirk Jan 23, 2024
126206f
Made `Helpers.HashCode.GetHashCodeByteArray` unchecked
rasmus-kirk Jan 23, 2024
6af2857
Addressed more comments
rasmus-kirk Jan 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
## Unreleased changes
- Added
- New GRPC-endpoint: `GetBlockItems`
- New transaction `DeployModule`
- The function `Prepare` has been removed from the `AccountTransactionPayload` class, but is implemented for all subclasses except `RawPayload`.
- Added serialization and deserialization for all instances of `AccountTransactionPayload`

- Breaking
- The function `GetTransactionSpecificCost` has been removed from the `AccountTransactionPayload` class.

## 4.2.0
- Added
- Deserialization from module schema
- Contract entrypoint messages
- Contract events
Expand Down
18 changes: 18 additions & 0 deletions examples/GetBlockItems/GetBlockItems.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Concordium.Sdk.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
</ItemGroup>

</Project>
50 changes: 50 additions & 0 deletions examples/GetBlockItems/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using CommandLine;
using Concordium.Sdk.Client;
using Concordium.Sdk.Types;

// We disable these warnings since CommandLine needs to set properties in options
// but we don't want to give default values.
#pragma warning disable CS8618

namespace GetBlockItems;

internal sealed class GetBlocksOptions
{
[Option(HelpText = "URL representing the endpoint where the gRPC V2 API is served.",
Default = "http://node.testnet.concordium.com:20000/")]
public string Endpoint { get; set; }
[Option(
'b',
"block-hash",
HelpText = "Block hash of the block. Defaults to LastFinal."
)]
public string BlockHash { get; set; }
}

public static class Program
{
/// <summary>
/// Example how to use <see cref="ConcordiumClient.GetBlockItems"/>
/// </summary>
public static async Task Main(string[] args) =>
await Parser.Default
.ParseArguments<GetBlocksOptions>(args)
.WithParsedAsync(Run);

private static async Task Run(GetBlocksOptions o)
{
using var client = new ConcordiumClient(new Uri(o.Endpoint), new ConcordiumClientOptions());

IBlockHashInput bi = o.BlockHash != null ? new Given(BlockHash.From(o.BlockHash)) : new LastFinal();

var blockItems = await client.GetBlockItems(bi);

Console.WriteLine($"All block items in block {blockItems.BlockHash}: [");
await foreach (var item in blockItems.Response)
{
Console.WriteLine($"{item},");
}
Console.WriteLine("]");
}
}

1 change: 0 additions & 1 deletion examples/GetBlockPendingUpdates/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ private static async Task Run(GetBlockPendingUpdatesOptions o)
await foreach (var update in updates.Response)
{
Console.WriteLine($"Pending update: {update}");

}
}
}
17 changes: 17 additions & 0 deletions src/Client/ConcordiumClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using BakerId = Concordium.Sdk.Types.BakerId;
using BlockHash = Concordium.Sdk.Types.BlockHash;
using BlockInfo = Concordium.Sdk.Types.BlockInfo;
using BlockItem = Concordium.Sdk.Transactions.BlockItem;
using BlockItemSummary = Concordium.Sdk.Types.BlockItemSummary;
using Branch = Concordium.Sdk.Types.Branch;
using ConsensusInfo = Concordium.Sdk.Types.ConsensusInfo;
Expand Down Expand Up @@ -742,5 +743,21 @@ await Task.WhenAll(response.ResponseHeadersAsync, response.ResponseAsync)
.ConfigureAwait(false);
}

/// <summary>
/// Get the items of a block.
/// </summary>
/// <param name="blockHashInput">Identifies what block to get the information from.</param>
/// <param name="token">Cancellation token</param>
/// <returns>A stream of block items.</returns>
/// <exception cref="RpcException">
/// RPC error occurred, access <see cref="RpcException.StatusCode"/> for more information.
/// <see cref="StatusCode.Unimplemented"/> indicates that this endpoint is disabled in the node.
/// </exception>
public Task<QueryResponse<IAsyncEnumerable<BlockItem>>> GetBlockItems(IBlockHashInput blockHashInput, CancellationToken token = default)
{
var response = this.Raw.GetBlockItems(blockHashInput.Into(), token);
return QueryResponse<IAsyncEnumerable<BlockItem>>.From(response, BlockItem.From, token);
}

public void Dispose() => this.Raw.Dispose();
}
23 changes: 23 additions & 0 deletions src/Exceptions/DeserialException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Concordium.Sdk.Exceptions;

/// <summary>
/// Thrown when deserialization fails and is explicitly meant not to.
/// </summary>
public class DeserialException : Exception
{
internal DeserialException(string errorMessage) :
base($"Deserialization error: {errorMessage}")
{ }
}

/// <summary>
/// Thrown when deserialization fails but no error message is present. This
/// should, by construction, be impossible.
/// </summary>
public sealed class DeserialNullException : DeserialException
{
internal DeserialNullException() :
base($"Deserialization error: The parsed output is null, but no error was found. This should not be possible.")
rasmus-kirk marked this conversation as resolved.
Show resolved Hide resolved
{ }
}

11 changes: 11 additions & 0 deletions src/Exceptions/UnexpectedNodeResponseException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Concordium.Sdk.Exceptions;

/// <summary>
/// Thrown if the node sends an invalid response.
/// </summary>
public class UnexpectedNodeResponseException : Exception
{
internal UnexpectedNodeResponseException() :
base($"Unexpected node response received.")
{ }
}
15 changes: 9 additions & 6 deletions src/Helpers/HashCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ public static class HashCode
{
public static int GetHashCodeByteArray(byte[] array)
{
var hashValue = 31;

foreach (var value in array)
unchecked
{
hashValue = (37 * hashValue) + value.GetHashCode();
}
var hashValue = 31;

return hashValue;
foreach (var value in array)
{
hashValue = (37 * hashValue) + value.GetHashCode();
}

return hashValue;
}
}
}
106 changes: 106 additions & 0 deletions src/Transactions/AccountSignatureMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,110 @@ public Grpc.V2.AccountSignatureMap ToProto()
}
return accountSignatureMap;
}

internal static AccountSignatureMap From(Grpc.V2.AccountSignatureMap map)
{
var dict = new Dictionary<AccountKeyIndex, byte[]>();
foreach (var s in map.Signatures)
{
dict.Add(new AccountKeyIndex((byte)s.Key), s.Value.Value.ToByteArray());
rasmus-kirk marked this conversation as resolved.
Show resolved Hide resolved
}
return Create(dict);
}

/// <summary>Check for equality.</summary>
public bool Equals(AccountSignatureMap? other) => other != null &&
other.GetType().Equals(this.GetType()) &&
this.Signatures.Count == other.Signatures.Count &&
this.Signatures.All(p => p.Value == other.Signatures[p.Key]);

/// <summary>Gets hash code.</summary>
public override int GetHashCode()
{
// Based on https://stackoverflow.com/questions/1646807/quick-and-simple-hash-code-combinations
unchecked
{
var hash = 17;
foreach (var (key, val) in this.Signatures)
{
hash = (hash * 31) + key.GetHashCode();
hash = (hash * 31) + Helpers.HashCode.GetHashCodeByteArray(val);
}
rasmus-kirk marked this conversation as resolved.
Show resolved Hide resolved
return hash;
}
}
}


/// <summary>Index of a key in an authorizations update payload.</summary>
public sealed record UpdateKeysIndex(byte Value);

/// <summary>A map from <see cref="UpdateKeysIndex"/> to signatures.</summary>
public sealed record UpdateInstructionSignatureMap
rasmus-kirk marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>Internal representation of the map.</summary>
public ImmutableDictionary<UpdateKeysIndex, byte[]> Signatures { get; init; }

/// <summary>Initializes a new instance of the <see cref="UpdateInstructionSignatureMap"/> class.</summary>
/// <param name="signatures">A map from update key indices to signatures.</param>
private UpdateInstructionSignatureMap(Dictionary<UpdateKeysIndex, byte[]> signatures) => this.Signatures = signatures.ToImmutableDictionary();

/// <summary>Creates a new instance of the <see cref="UpdateInstructionSignatureMap"/> class.</summary>
/// <param name="signatures">A map from update key indices to signatures.</param>
/// <exception cref="ArgumentException">A signature is not 64 bytes.</exception>
public static UpdateInstructionSignatureMap Create(Dictionary<UpdateKeysIndex, byte[]> signatures)
{
// Signatures are 64-byte ed25519 signatures and therefore 64 bytes.
if (signatures.Values.Any(signature => signature.Length != 64))
{
throw new ArgumentException($"Signature should be {64} bytes.");
}
return new UpdateInstructionSignatureMap(signatures);
}

/// <summary>Converts the update signature map to its corresponding protocol buffer message instance.</summary>
public Grpc.V2.SignatureMap ToProto()
{
var signatureMap = new Grpc.V2.SignatureMap();
foreach (var s in this.Signatures)
{
signatureMap.Signatures.Add(
s.Key.Value,
new Grpc.V2.Signature() { Value = Google.Protobuf.ByteString.CopyFrom(s.Value) }
);
}
return signatureMap;
}

internal static UpdateInstructionSignatureMap From(Grpc.V2.SignatureMap map)
{
var dict = new Dictionary<UpdateKeysIndex, byte[]>();
foreach (var s in map.Signatures)
{
dict.Add(new UpdateKeysIndex((byte)s.Key), s.Value.Value.ToByteArray());
}
return Create(dict);
}

/// <summary>Check for equality.</summary>
public bool Equals(UpdateInstructionSignatureMap? other) => other != null &&
other.GetType().Equals(this.GetType()) &&
this.Signatures.Count == other.Signatures.Count &&
this.Signatures.All(p => p.Value == other.Signatures[p.Key]);

/// <summary>Gets hash code.</summary>
public override int GetHashCode()
{
// Based on https://stackoverflow.com/questions/1646807/quick-and-simple-hash-code-combinations
unchecked
{
var hash = 17;
foreach (var (key, val) in this.Signatures)
{
hash = (hash * 31) + key.GetHashCode();
hash = (hash * 31) + Helpers.HashCode.GetHashCodeByteArray(val);
}
rasmus-kirk marked this conversation as resolved.
Show resolved Hide resolved
return hash;
}
}
}
11 changes: 11 additions & 0 deletions src/Transactions/AccountTransactionHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,15 @@ public Grpc.V2.AccountTransactionHeader ToProto() =>
Expiry = this.Expiry.ToProto(),
EnergyAmount = new Grpc.V2.Energy() { Value = this.MaxEnergyCost.Value }
};

/// <summary>
/// Creates an account transaction header from its corresponding protocol buffer message instance.
/// </summary>
internal static AccountTransactionHeader From(Grpc.V2.AccountTransactionHeader accountTransactionHeader, PayloadSize payloadSize) => new(
AccountAddress.From(accountTransactionHeader.Sender),
AccountSequenceNumber.From(accountTransactionHeader.SequenceNumber),
Expiry.From(accountTransactionHeader.Expiry.Value),
EnergyAmount.From(accountTransactionHeader.EnergyAmount),
payloadSize
);
}
Loading
Loading