diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f192db4..516c220e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ## Unreleased changes +- Added + - New GRPC-endpoints: `GetBlocks`, `GetFinalizedBlocks`, `GetBranches`, `GetAncestors`, `GetBlockPendingUpdates` ## 4.1.0 - Bugfix diff --git a/ConcordiumNetSdk.sln b/ConcordiumNetSdk.sln index 57522607..b291f93c 100644 --- a/ConcordiumNetSdk.sln +++ b/ConcordiumNetSdk.sln @@ -67,6 +67,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetModuleSource", "examples EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetInstanceInfo", "examples\GetInstanceInfo\GetInstanceInfo.csproj", "{60BB9915-06F0-48FC-ABCD-271B6A361A01}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetAncestors", "examples\GetAncestors\GetAncestors.csproj", "{F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetBlockPendingUpdates", "examples\GetBlockPendingUpdates\GetBlockPendingUpdates.csproj", "{C5BEBE65-EA24-431D-BB53-DA3673EF7E60}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetBlocks", "examples\GetBlocks\GetBlocks.csproj", "{79E97788-D084-487E-8F34-0BA1911C452A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetBranches", "examples\GetBranches\GetBranches.csproj", "{26417CD7-2897-47BA-BA9B-C4475187331A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetFinalizedBlocks", "examples\GetFinalizedBlocks\GetFinalizedBlocks.csproj", "{E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -197,6 +207,26 @@ Global {60BB9915-06F0-48FC-ABCD-271B6A361A01}.Debug|Any CPU.Build.0 = Debug|Any CPU {60BB9915-06F0-48FC-ABCD-271B6A361A01}.Release|Any CPU.ActiveCfg = Release|Any CPU {60BB9915-06F0-48FC-ABCD-271B6A361A01}.Release|Any CPU.Build.0 = Release|Any CPU + {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}.Release|Any CPU.Build.0 = Release|Any CPU + {C5BEBE65-EA24-431D-BB53-DA3673EF7E60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5BEBE65-EA24-431D-BB53-DA3673EF7E60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5BEBE65-EA24-431D-BB53-DA3673EF7E60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5BEBE65-EA24-431D-BB53-DA3673EF7E60}.Release|Any CPU.Build.0 = Release|Any CPU + {79E97788-D084-487E-8F34-0BA1911C452A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79E97788-D084-487E-8F34-0BA1911C452A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79E97788-D084-487E-8F34-0BA1911C452A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79E97788-D084-487E-8F34-0BA1911C452A}.Release|Any CPU.Build.0 = Release|Any CPU + {26417CD7-2897-47BA-BA9B-C4475187331A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26417CD7-2897-47BA-BA9B-C4475187331A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26417CD7-2897-47BA-BA9B-C4475187331A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26417CD7-2897-47BA-BA9B-C4475187331A}.Release|Any CPU.Build.0 = Release|Any CPU + {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -234,5 +264,10 @@ Global {E6530630-82A0-4AB2-A600-A86DC619CB88} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D} {FFF88CB0-DC68-4B00-8632-770A270946F5} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D} {60BB9915-06F0-48FC-ABCD-271B6A361A01} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D} + {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D} + {C5BEBE65-EA24-431D-BB53-DA3673EF7E60} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D} + {79E97788-D084-487E-8F34-0BA1911C452A} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D} + {26417CD7-2897-47BA-BA9B-C4475187331A} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D} + {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D} EndGlobalSection EndGlobal diff --git a/examples/GetAncestors/GetAncestors.csproj b/examples/GetAncestors/GetAncestors.csproj new file mode 100644 index 00000000..f9d1b111 --- /dev/null +++ b/examples/GetAncestors/GetAncestors.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + diff --git a/examples/GetAncestors/Program.cs b/examples/GetAncestors/Program.cs new file mode 100644 index 00000000..5a2b48ea --- /dev/null +++ b/examples/GetAncestors/Program.cs @@ -0,0 +1,54 @@ +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 GetAncestors; + +internal sealed class GetAncestorsOptions +{ + [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( + 'm', + "max-ancestors", + HelpText = "The maximum number of ancestors returned.", + Required = true + )] + public ulong MaxAncestors { get; set; } + [Option( + 'b', + "block-hash", + HelpText = "Block hash of the block. Defaults to LastFinal." + )] + public string BlockHash { get; set; } +} + +public static class Program +{ + /// + /// Example how to use + /// + public static async Task Main(string[] args) => + await Parser.Default + .ParseArguments(args) + .WithParsedAsync(Run); + + private static async Task Run(GetAncestorsOptions 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 ancestors = await client.GetAncestors(bi, o.MaxAncestors); + + await foreach (var ancestor in ancestors.Response) + { + Console.WriteLine($"Ancestor: {ancestor}"); + } + } +} diff --git a/examples/GetBlockPendingUpdates/GetBlockPendingUpdates.csproj b/examples/GetBlockPendingUpdates/GetBlockPendingUpdates.csproj new file mode 100644 index 00000000..f9d1b111 --- /dev/null +++ b/examples/GetBlockPendingUpdates/GetBlockPendingUpdates.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + diff --git a/examples/GetBlockPendingUpdates/Program.cs b/examples/GetBlockPendingUpdates/Program.cs new file mode 100644 index 00000000..53e11786 --- /dev/null +++ b/examples/GetBlockPendingUpdates/Program.cs @@ -0,0 +1,49 @@ +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 GetBlockPendingUpdates; + +internal sealed class GetBlockPendingUpdatesOptions +{ + [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 +{ + /// + /// Example how to use + /// + public static async Task Main(string[] args) => + await Parser.Default + .ParseArguments(args) + .WithParsedAsync(Run); + + private static async Task Run(GetBlockPendingUpdatesOptions 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 updates = await client.GetBlockPendingUpdates(bi); + + Console.WriteLine($"Updates:"); + await foreach (var update in updates.Response) + { + Console.WriteLine($"Pending update: {update}"); + + } + } +} diff --git a/examples/GetBlocks/GetBlocks.csproj b/examples/GetBlocks/GetBlocks.csproj new file mode 100644 index 00000000..f9d1b111 --- /dev/null +++ b/examples/GetBlocks/GetBlocks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + diff --git a/examples/GetBlocks/Program.cs b/examples/GetBlocks/Program.cs new file mode 100644 index 00000000..ed840f1a --- /dev/null +++ b/examples/GetBlocks/Program.cs @@ -0,0 +1,40 @@ +using CommandLine; +using Concordium.Sdk.Client; + +// 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 GetBlocks; + +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; } +} + +public static class Program +{ + /// + /// Example how to use + /// + public static async Task Main(string[] args) => + await Parser.Default + .ParseArguments(args) + .WithParsedAsync(Run); + + private static async Task Run(GetBlocksOptions options) + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using var client = new ConcordiumClient(new Uri(options.Endpoint), new ConcordiumClientOptions()); + + var blocks = client.GetBlocks(); + + await foreach (var block in blocks) + { + Console.WriteLine($"Block arrived: {block}"); + } + } +} diff --git a/examples/GetBranches/GetBranches.csproj b/examples/GetBranches/GetBranches.csproj new file mode 100644 index 00000000..ea5fc94a --- /dev/null +++ b/examples/GetBranches/GetBranches.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + enable + enable + GetNodeInfo + + + + + + + + + + + diff --git a/examples/GetBranches/Program.cs b/examples/GetBranches/Program.cs new file mode 100644 index 00000000..e1fbd81a --- /dev/null +++ b/examples/GetBranches/Program.cs @@ -0,0 +1,47 @@ +using CommandLine; +using Concordium.Sdk.Client; +using Branch = Concordium.Sdk.Types.Branch; + +// 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 GetBranches; + +internal sealed class GetBranchesOptions +{ + [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; } +} + + +public static class Program +{ + /// + /// Example how to use + /// s + public static async Task Main(string[] args) => + await Parser.Default + .ParseArguments(args) + .WithParsedAsync(Run); + + private static async Task Run(GetBranchesOptions options) + { + using var client = new ConcordiumClient(new Uri(options.Endpoint), new ConcordiumClientOptions()); + + var branch = await client.GetBranchesAsync(); + + PrintBranchesAsTree(0, branch); + } + + private static void PrintBranchesAsTree(uint depth, Branch branch) + { + for (var i = 0; i < depth; i++) + { + Console.Write("--"); + } + Console.WriteLine(branch.BlockHash); + branch.Children.ForEach(x => PrintBranchesAsTree(depth + 1, x)); + } +} diff --git a/examples/GetFinalizedBlocks/GetFinalizedBlocks.csproj b/examples/GetFinalizedBlocks/GetFinalizedBlocks.csproj new file mode 100644 index 00000000..f9d1b111 --- /dev/null +++ b/examples/GetFinalizedBlocks/GetFinalizedBlocks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + diff --git a/examples/GetFinalizedBlocks/Program.cs b/examples/GetFinalizedBlocks/Program.cs new file mode 100644 index 00000000..2808d64b --- /dev/null +++ b/examples/GetFinalizedBlocks/Program.cs @@ -0,0 +1,40 @@ +using CommandLine; +using Concordium.Sdk.Client; + +// 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 GetFinalizedBlocks; + +internal sealed class GetFinalizedBlocksOptions +{ + [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; } +} + +public static class Program +{ + /// + /// Example how to use + /// + public static async Task Main(string[] args) => + await Parser.Default + .ParseArguments(args) + .WithParsedAsync(Run); + + private static async Task Run(GetFinalizedBlocksOptions options) + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using var client = new ConcordiumClient(new Uri(options.Endpoint), new ConcordiumClientOptions()); + + var blocks = client.GetFinalizedBlocks(); + + await foreach (var block in blocks) + { + Console.WriteLine($"Finalized block arrived: {block}"); + } + } +} diff --git a/src/Client/ConcordiumClient.cs b/src/Client/ConcordiumClient.cs index 0aa4e2d8..82915bbe 100644 --- a/src/Client/ConcordiumClient.cs +++ b/src/Client/ConcordiumClient.cs @@ -5,15 +5,19 @@ using Grpc.Net.Client; using AccountAddress = Concordium.Sdk.Types.AccountAddress; using AccountInfo = Concordium.Sdk.Types.AccountInfo; +using ArrivedBlockInfo = Concordium.Sdk.Types.ArrivedBlockInfo; using BakerId = Concordium.Sdk.Types.BakerId; using BlockHash = Concordium.Sdk.Types.BlockHash; using BlockInfo = Concordium.Sdk.Types.BlockInfo; using BlockItemSummary = Concordium.Sdk.Types.BlockItemSummary; +using Branch = Concordium.Sdk.Types.Branch; using ConsensusInfo = Concordium.Sdk.Types.ConsensusInfo; using ContractAddress = Concordium.Sdk.Types.ContractAddress; using FinalizationSummary = Concordium.Sdk.Types.FinalizationSummary; +using FinalizedBlockInfo = Concordium.Sdk.Types.FinalizedBlockInfo; using IpInfo = Concordium.Sdk.Types.IpInfo; using NodeInfo = Concordium.Sdk.Types.NodeInfo; +using PendingUpdate = Concordium.Sdk.Types.PendingUpdate; using TransactionHash = Concordium.Sdk.Types.TransactionHash; using VersionedModuleSource = Concordium.Sdk.Types.VersionedModuleSource; @@ -545,6 +549,112 @@ await Task.WhenAll(response.ResponseHeadersAsync, response.ResponseAsync) .ConfigureAwait(false); } + /// + /// Return a stream of blocks that arrive from the time the query is made onward. + /// This can be used to listen for incoming blocks. + /// + /// Cancellation token + /// A stream of blocks that arrive from the time the query is made onward. + /// + /// RPC error occurred, access for more information. + /// indicates that this endpoint is disabled in the node. + /// + public IAsyncEnumerable GetBlocks(CancellationToken token = default) + { + var response = this.Raw.GetBlocks(token); + return response.ResponseStream.ReadAllAsync(token).Select(ArrivedBlockInfo.From); + } + + /// + /// Return a stream of blocks that are finalized from the time the query is + /// made onward. This can be used to listen for newly finalized blocks. Note + /// that there is no guarantee that blocks will not be skipped if the client is + /// too slow in processing the stream, however blocks will always be sent by + /// increasing block height. + /// + /// Cancellation token + /// A stream of finalized blocks that arrive from the time the query is made onward. + /// + /// RPC error occurred, access for more information. + /// indicates that this endpoint is disabled in the node. + /// + public IAsyncEnumerable GetFinalizedBlocks(CancellationToken token = default) + { + var response = this.Raw.GetFinalizedBlocks(token); + return response.ResponseStream.ReadAllAsync(token).Select(FinalizedBlockInfo.From); + } + + /// + /// Get the current branches of blocks starting from and including the last finalized block. + /// + /// Cancellation token + /// The current branches of blocks. + /// + /// RPC error occurred, access for more information. + /// indicates that this endpoint is disabled in the node. + /// + public async Task GetBranchesAsync(CancellationToken token = default) + { + var response = await this.Raw.GetBranchesAsync(token).ConfigureAwait(false); + return Branch.From(response); + } + + /// + /// Get the current branches of blocks starting from and including the last finalized block. + /// + /// Cancellation token + /// The current branches of blocks. + /// + /// RPC error occurred, access for more information. + /// indicates that this endpoint is disabled in the node. + /// + public Branch GetBranches(CancellationToken token = default) + { + var response = this.Raw.GetBranches(token); + return Branch.From(response); + } + + /// + /// Get the pending updates to chain parameters at the end of a given block. + /// The stream will end when all the pending updates for a given block have been returned. + /// + /// The block to get ancestors of + /// Cancellation token + /// The pending updates. + /// + /// RPC error occurred, access for more information. + /// indicates that this endpoint is disabled in the node. + /// + public Task>> GetBlockPendingUpdates(IBlockHashInput blockHash, CancellationToken token = default) + { + var responses = this.Raw.GetBlockPendingUpdates(blockHash.Into(), token); + return QueryResponse>.From(responses, PendingUpdate.From, token); + } + + /// + /// Get a stream of ancestors for the provided block. + /// Starting with the provided block itself, moving backwards until no more + /// ancestors or the requested number of ancestors has been returned. + /// + /// The block to get ancestors of + /// The maximum number of ancestors returned + /// Cancellation token + /// A stream of ancestors. + /// + /// RPC error occurred, access for more information. + /// indicates that this endpoint is disabled in the node. + /// + public Task>> GetAncestors(IBlockHashInput blockHash, ulong limit, CancellationToken token = default) + { + var req = new AncestorsRequest() + { + BlockHash = blockHash.Into(), + Amount = limit + }; + var response = this.Raw.GetAncestors(req, token); + return QueryResponse>.From(response, BlockHash.From, token); + } + /// /// Get all smart contract modules that exist at the end of a given block. /// diff --git a/src/Types/ArrivedBlockInfo.cs b/src/Types/ArrivedBlockInfo.cs new file mode 100644 index 00000000..dabdfa36 --- /dev/null +++ b/src/Types/ArrivedBlockInfo.cs @@ -0,0 +1,15 @@ +namespace Concordium.Sdk.Types; + +/// +/// Information about an arrived block that is part of the streaming response. +/// +/// Hash of the block. +/// Absolute height of the block, height 0 is the genesis block. +public sealed record ArrivedBlockInfo(BlockHash BlockHash, AbsoluteHeight BlockHeight) +{ + internal static ArrivedBlockInfo From(Grpc.V2.ArrivedBlockInfo info) => + new( + BlockHash.From(info.Hash), + AbsoluteHeight.From(info.Height) + ); +} diff --git a/src/Types/BlockHeight.cs b/src/Types/BlockHeight.cs index 9eac8335..7cdf6d07 100644 --- a/src/Types/BlockHeight.cs +++ b/src/Types/BlockHeight.cs @@ -16,6 +16,8 @@ public interface IBlockHeight /// Height from the beginning of the chain. public sealed record AbsoluteHeight(ulong Height) : IBlockHeight { + internal static AbsoluteHeight From(AbsoluteBlockHeight blockHeight) => new(blockHeight.Value); + public BlocksAtHeightRequest Into() => new() { diff --git a/src/Types/Branch.cs b/src/Types/Branch.cs new file mode 100644 index 00000000..1fd1ceec --- /dev/null +++ b/src/Types/Branch.cs @@ -0,0 +1,15 @@ +namespace Concordium.Sdk.Types; + +/// +/// Response type for GetBranches. +/// +/// The hash of the block. +/// Further blocks branching of this block. +public sealed record Branch(BlockHash BlockHash, List Children) +{ + internal static Branch From(Grpc.V2.Branch info) => + new( + BlockHash.From(info.BlockHash), + info.Children.Select(From).ToList() + ); +} diff --git a/src/Types/ElectionDifficulty.cs b/src/Types/ElectionDifficulty.cs new file mode 100644 index 00000000..7a9669d3 --- /dev/null +++ b/src/Types/ElectionDifficulty.cs @@ -0,0 +1,9 @@ +namespace Concordium.Sdk.Types; + +/// Election difficulty parameter. +/// The election difficulty. +public sealed record ElectionDifficulty(AmountFraction Difficulty) +{ + internal static ElectionDifficulty From(Grpc.V2.ElectionDifficulty electionDifficulty) => + new(AmountFraction.From(electionDifficulty.Value)); +} diff --git a/src/Types/FinalizedBlockInfo.cs b/src/Types/FinalizedBlockInfo.cs new file mode 100644 index 00000000..e22588a1 --- /dev/null +++ b/src/Types/FinalizedBlockInfo.cs @@ -0,0 +1,15 @@ +namespace Concordium.Sdk.Types; + +/// +/// Information about a finalized block that is part of the streaming response. +/// +/// Hash of the block. +/// Absolute height of the block, height 0 is the genesis block. +public sealed record FinalizedBlockInfo(BlockHash BlockHash, AbsoluteHeight BlockHeight) +{ + internal static FinalizedBlockInfo From(Grpc.V2.FinalizedBlockInfo info) => + new( + BlockHash.From(info.Hash), + AbsoluteHeight.From(info.Height) + ); +} diff --git a/src/Types/PendingUpdate.cs b/src/Types/PendingUpdate.cs new file mode 100644 index 00000000..8af2a1c3 --- /dev/null +++ b/src/Types/PendingUpdate.cs @@ -0,0 +1,106 @@ +using Concordium.Sdk.Exceptions; +using GrpcEffect = Concordium.Grpc.V2.PendingUpdate.EffectOneofCase; + +namespace Concordium.Sdk.Types; +/// +/// Minimum stake needed to become a baker. This only applies to protocol version 1-3. +/// +/// Minimum threshold required for registering as a baker. +public record BakerStakeThreshold(CcdAmount MinimumThresholdForBaking) +{ + internal static BakerStakeThreshold From(Grpc.V2.BakerStakeThreshold bakerStakeThreshold) => new(CcdAmount.From(bakerStakeThreshold.BakerStakeThreshold_)); +}; + +/// +/// A pending update. +/// +/// The effective time of the update. +/// The effect of the update. +public sealed record PendingUpdate(TransactionTime EffectiveTime, IEffect Effect) +{ + internal static PendingUpdate From(Grpc.V2.PendingUpdate pendingUpdate) => + new( + TransactionTime.From(pendingUpdate.EffectiveTime), + pendingUpdate.EffectCase switch + { + GrpcEffect.RootKeys => new EffectRootKeys(RootKeys.From(pendingUpdate.RootKeys)), + GrpcEffect.Level1Keys => new EffectLevel1Keys(Level1Keys.From(pendingUpdate.Level1Keys)), + GrpcEffect.Level2KeysCpv0 => new EffectLevel2KeysCpv0(AuthorizationsV0.From(pendingUpdate.Level2KeysCpv0)), + GrpcEffect.Level2KeysCpv1 => new EffectLevel2KeysCpv1(AuthorizationsV1.From(pendingUpdate.Level2KeysCpv1)), + GrpcEffect.Protocol => new EffectProtocol(ProtocolUpdate.From(pendingUpdate.Protocol)), + GrpcEffect.ElectionDifficulty => new EffectElectionDifficulty(ElectionDifficulty.From(pendingUpdate.ElectionDifficulty)), + GrpcEffect.EuroPerEnergy => new EffectEuroPerEnergy(ExchangeRate.From(pendingUpdate.EuroPerEnergy)), + GrpcEffect.MicroCcdPerEuro => new EffectMicroCcdPerEnergy(ExchangeRate.From(pendingUpdate.MicroCcdPerEuro)), + GrpcEffect.FoundationAccount => new EffectFoundationAccount(AccountAddress.From(pendingUpdate.FoundationAccount)), + GrpcEffect.MintDistributionCpv0 => new EffectMintDistributionCpv0(MintDistributionCpv0.From(pendingUpdate.MintDistributionCpv0)), + GrpcEffect.MintDistributionCpv1 => new EffectMintDistributionCpv1(MintDistributionCpv1.From(pendingUpdate.MintDistributionCpv1)), + GrpcEffect.TransactionFeeDistribution => new EffectTransactionFeeDistribution(TransactionFeeDistribution.From(pendingUpdate.TransactionFeeDistribution)), + GrpcEffect.GasRewards => new EffectGasRewards(GasRewards.From(pendingUpdate.GasRewards)), + GrpcEffect.PoolParametersCpv0 => new EffectPoolParametersCpv0(BakerStakeThreshold.From(pendingUpdate.PoolParametersCpv0)), + GrpcEffect.PoolParametersCpv1 => new EffectPoolParametersCpv1(PoolParameters.From(pendingUpdate.PoolParametersCpv1)), + GrpcEffect.AddAnonymityRevoker => new EffectAddAnonymityRevoker(ArInfo.From(pendingUpdate.AddAnonymityRevoker)), + GrpcEffect.AddIdentityProvider => new EffectAddIdentityProvider(IpInfo.From(pendingUpdate.AddIdentityProvider)), + GrpcEffect.CooldownParameters => new EffectCooldownParameters(CooldownParameters.From(pendingUpdate.CooldownParameters)), + GrpcEffect.TimeParameters => new EffectTimeParameters(TimeParameters.From(pendingUpdate.TimeParameters)), + GrpcEffect.GasRewardsCpv2 => new EffectGasRewardsCpv2(GasRewardsCpv2.From(pendingUpdate.GasRewardsCpv2)), + GrpcEffect.TimeoutParameters => new EffectTimeoutParameters(TimeoutParameters.From(pendingUpdate.TimeoutParameters)), + GrpcEffect.MinBlockTime => new EffectMinBlockTime(TimeSpan.FromMilliseconds(pendingUpdate.MinBlockTime.Value)), + GrpcEffect.BlockEnergyLimit => new EffectBlockEnergyLimit(EnergyAmount.From(pendingUpdate.BlockEnergyLimit)), + GrpcEffect.FinalizationCommitteeParameters => new EffectFinalizationCommitteeParameters(FinalizationCommitteeParameters.From(pendingUpdate.FinalizationCommitteeParameters)), + GrpcEffect.None => throw new NotImplementedException(), + _ => throw new MissingEnumException(pendingUpdate.EffectCase), + } + ); +} + +/// The effect of the update. +public interface IEffect { }; + +/// Updates to the root keys. +public sealed record EffectRootKeys(RootKeys RootKeys) : IEffect; +/// Updates to the level 1 keys. +public sealed record EffectLevel1Keys(Level1Keys Level1Keys) : IEffect; +/// Updates to the level 2 keys. +public sealed record EffectLevel2KeysCpv0(AuthorizationsV0 Level2KeysUpdateV0) : IEffect; +/// Updates to the level 2 keys. +public sealed record EffectLevel2KeysCpv1(AuthorizationsV1 Level2KeysUpdateV1) : IEffect; +/// Protocol updates. +public sealed record EffectProtocol(ProtocolUpdate ProtocolUpdate) : IEffect; +/// Updates to the election difficulty parameter. +public sealed record EffectElectionDifficulty(ElectionDifficulty ElectionDifficulty) : IEffect; +/// Updates to the euro:energy exchange rate. +public sealed record EffectEuroPerEnergy(ExchangeRate EuroPerEnergy) : IEffect; +/// Updates to the CCD:EUR exchange rate. +public sealed record EffectMicroCcdPerEnergy(ExchangeRate MicroCcdPerEnergy) : IEffect; +/// Updates to the foundation account. +public sealed record EffectFoundationAccount(AccountAddress FoundationAccount) : IEffect; +/// Updates to the mint distribution. Is only relevant prior to protocol version 4. +public sealed record EffectMintDistributionCpv0(MintDistributionCpv0 MintDistributionCpv0) : IEffect; +/// The mint distribution was updated. Introduced in protocol version 4. +public sealed record EffectMintDistributionCpv1(MintDistributionCpv1 MintDistributionCpv1) : IEffect; +/// Updates to the transaction fee distribution. +public sealed record EffectTransactionFeeDistribution(TransactionFeeDistribution TransactionFeeDistribution) : IEffect; +/// Updates to the GAS rewards. +public sealed record EffectGasRewards(GasRewards GasRewards) : IEffect; +/// Updates baker stake threshold. Is only relevant prior to protocol version 4. +public sealed record EffectPoolParametersCpv0(BakerStakeThreshold BakerParameters) : IEffect; +/// Updates pool parameters. Introduced in protocol version 4. +public sealed record EffectPoolParametersCpv1(PoolParameters PoolParameters) : IEffect; +/// Adds a new anonymity revoker. +public sealed record EffectAddAnonymityRevoker(ArInfo AddAnonymityRevoker) : IEffect; +/// Adds a new identity provider. +public sealed record EffectAddIdentityProvider(IpInfo AddIdentityProvider) : IEffect; +/// Updates to cooldown parameters for chain parameters version 1 introduced in protocol version 4. +public sealed record EffectCooldownParameters(CooldownParameters CooldownParameters) : IEffect; +/// Updates to time parameters for chain parameters version 1 introduced in protocol version 4. +public sealed record EffectTimeParameters(TimeParameters TimeParameters) : IEffect; +/// Updates to the GAS rewards effective from protocol version 6 (chain parameters version 2). +public sealed record EffectGasRewardsCpv2(GasRewardsCpv2 GasRewardsCpv2) : IEffect; +/// Updates to the consensus timeouts for chain parameters version 2. +public sealed record EffectTimeoutParameters(TimeoutParameters TimeoutParameters) : IEffect; +/// Updates to the the minimum time between blocks for chain parameters version 2. +public sealed record EffectMinBlockTime(TimeSpan MinBlockTime) : IEffect; +/// Updates to the block energy limit for chain parameters version 2. +public sealed record EffectBlockEnergyLimit(EnergyAmount BlockEnergyLimit) : IEffect; +/// Updates to the finalization committee for for chain parameters version 2. +public sealed record EffectFinalizationCommitteeParameters(FinalizationCommitteeParameters FinalizationCommitteeParameters) : IEffect; diff --git a/src/Types/TransactionTime.cs b/src/Types/TransactionTime.cs new file mode 100644 index 00000000..e3e516c2 --- /dev/null +++ b/src/Types/TransactionTime.cs @@ -0,0 +1,24 @@ +namespace Concordium.Sdk.Types; + +/// Transaction time specified as seconds since unix epoch. +/// Seconds since the unix epoch. +public sealed record TransactionTime(ulong SecondsSinceUnixEpoch) +{ + internal static TransactionTime From(Grpc.V2.TransactionTime transactionTime) => + new(transactionTime.Value); + + /// Convert the TransactionTime to a DateTimeOffset. + public DateTimeOffset ToDateTimeOffset() + { + if (this.SecondsSinceUnixEpoch > long.MaxValue) + { + throw new ArgumentOutOfRangeException( + $"The timestamp has a value of {this.SecondsSinceUnixEpoch} which exceeds the maximum value of supported by DateTimeOffset." + ); + } + else + { + return DateTimeOffset.FromUnixTimeSeconds((long)this.SecondsSinceUnixEpoch); + } + } +}