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

Map missing chain parameter updates #141

Merged
27 commits merged into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b5e2239
add missing chain update types to mapping
Nov 23, 2023
543fb2d
Updated frontend
Nov 23, 2023
cb86fb4
updated chain log and bumped version
Nov 23, 2023
0d6403e
fix data corruption
Nov 27, 2023
2b4c574
Merge branch 'main' into cbw-1484/fix-data-corruption
Nov 28, 2023
56a26f2
refactor JobRepository
Nov 28, 2023
2955c9f
factor to use common
Nov 28, 2023
3f06e2d
remove unused generic
Nov 28, 2023
31ecaf3
refactored health and added resilience to job
Nov 28, 2023
70c05a8
added metric
Nov 28, 2023
3cd5a00
added test
Nov 29, 2023
92e9c9e
use default source class enricher from serilog
Nov 29, 2023
76e7314
Merge branch 'cbw-1484/fix-data-corruption' into cbw-1505/map-missing…
Nov 29, 2023
5595a13
Add job to migrate events
Nov 29, 2023
28f1f12
add test
Nov 29, 2023
5aa3747
add tests
Nov 29, 2023
f2b37a2
fix naming and add job
Nov 29, 2023
49b74e0
update log format
Nov 29, 2023
35a3347
add log statement
Nov 29, 2023
2aded51
Merge branch 'cbw-1484/fix-data-corruption' into cbw-1505/map-missing…
Nov 29, 2023
f25f7a2
Update ImportWriteController.cs
Nov 29, 2023
bae35ca
bump version
Nov 29, 2023
fba2d21
Merge branch 'cbw-1484/fix-data-corruption' into cbw-1505/map-missing…
Nov 29, 2023
7c583ad
Merge branch 'main' into cbw-1505/map-missing-chain-update-types
Nov 29, 2023
db32565
Fix job name
Nov 29, 2023
a5bf31d
Merge branch 'cbw-1484/fix-job-name' into cbw-1505/map-missing-chain-…
Nov 29, 2023
e37a1d1
Merge branch 'main' into cbw-1505/map-missing-chain-update-types
Nov 30, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ public class ChainUpdatePayloadConverter : PolymorphicJsonConverter<ChainUpdateP
{ typeof(PoolParametersChainUpdatePayload), 15 },
{ typeof(TimeParametersChainUpdatePayload), 16 },
{ typeof(MintDistributionV1ChainUpdatePayload), 17 },
{ typeof(GasRewardsCpv2Update), 18 },
{ typeof(BlockEnergyLimitUpdate), 19 },
{ typeof(FinalizationCommitteeParametersUpdate), 20 },
{ typeof(TimeoutParametersUpdate), 21 },
{ typeof(MinBlockTimeUpdate), 22 },
};

public ChainUpdatePayloadConverter() : base(SerializeMap)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using Application.Configurations;
using Application.Database;
using Application.Database.MigrationJobs;
using Application.Entities;
using Application.Import;
using Application.Jobs;
using Application.Observability;
Expand Down
6 changes: 6 additions & 0 deletions backend/Application/Api/GraphQL/Ratio.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Application.Api.GraphQL;

public readonly record struct Ratio(ulong Numerator, ulong Denominator)
{
internal static Ratio From(Concordium.Sdk.Types.Ratio ratio) => new(ratio.Numerator, ratio.Denominator);
}
74 changes: 62 additions & 12 deletions backend/Application/Api/GraphQL/Transactions/ChainUpdatePayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ namespace Application.Api.GraphQL.Transactions;
[UnionType]
public abstract record ChainUpdatePayload
{
internal static bool TryFrom(IUpdatePayload payload, out ChainUpdatePayload? chainUpdatePayload)
{
chainUpdatePayload = payload switch
internal static ChainUpdatePayload From(IUpdatePayload payload) =>
payload switch
{
AddAnonymityRevokerUpdate addAnonymityRevokerUpdate => AddAnonymityRevokerChainUpdatePayload.From(addAnonymityRevokerUpdate),
AddIdentityProviderUpdate addIdentityProviderUpdate => AddIdentityProviderChainUpdatePayload.From(addIdentityProviderUpdate),
Expand All @@ -30,16 +29,67 @@ internal static bool TryFrom(IUpdatePayload payload, out ChainUpdatePayload? cha
RootUpdate rootUpdate => RootKeysChainUpdatePayload.From(rootUpdate),
TimeParametersCpv1Update timeParametersCpv1Update => TimeParametersChainUpdatePayload.From(timeParametersCpv1Update),
TransactionFeeDistributionUpdate transactionFeeDistributionUpdate => TransactionFeeDistributionChainUpdatePayload.From(transactionFeeDistributionUpdate),
GasRewardsCpv2Update => null,
BlockEnergyLimitUpdate => null,
FinalizationCommitteeParametersUpdate => null,
TimeoutParametersUpdate => null,
MinBlockTimeUpdate => null,
Concordium.Sdk.Types.GasRewardsCpv2Update update => GasRewardsCpv2Update.From(update),
Concordium.Sdk.Types.BlockEnergyLimitUpdate update => BlockEnergyLimitUpdate.From(update),
Concordium.Sdk.Types.FinalizationCommitteeParametersUpdate update => FinalizationCommitteeParametersUpdate.From(update),
Concordium.Sdk.Types.TimeoutParametersUpdate update => TimeoutParametersUpdate.From(update),
Concordium.Sdk.Types.MinBlockTimeUpdate update => MinBlockTimeUpdate.From(update),
_ => throw new ArgumentOutOfRangeException(nameof(payload))
};

return chainUpdatePayload != null;
}
}

public sealed record MinBlockTimeUpdate(ulong DurationSeconds) : ChainUpdatePayload
{
internal static MinBlockTimeUpdate From(Concordium.Sdk.Types.MinBlockTimeUpdate update) =>
new((ulong)update.Duration.TotalSeconds);

}

public sealed record TimeoutParametersUpdate(
ulong DurationSeconds, Ratio Increase, Ratio Decrease
) : ChainUpdatePayload
{
internal static TimeoutParametersUpdate From(Concordium.Sdk.Types.TimeoutParametersUpdate update) =>
new(
(ulong)update.TimeoutParameters.Duration.TotalSeconds,
Ratio.From(update.TimeoutParameters.Increase),
Ratio.From(update.TimeoutParameters.Decrease)
);
}

public sealed record FinalizationCommitteeParametersUpdate(
uint MinFinalizers,
uint MaxFinalizers,
decimal FinalizersRelativeStakeThreshold
) : ChainUpdatePayload
{
internal static FinalizationCommitteeParametersUpdate From(
Concordium.Sdk.Types.FinalizationCommitteeParametersUpdate update) =>
new FinalizationCommitteeParametersUpdate(
update.FinalizationCommitteeParameters.MinFinalizers,
update.FinalizationCommitteeParameters.MaxFinalizers,
update.FinalizationCommitteeParameters.FinalizersRelativeStakeThreshold.AsDecimal()
);
}

public sealed record BlockEnergyLimitUpdate(
ulong EnergyLimit) : ChainUpdatePayload
{
internal static BlockEnergyLimitUpdate From(Concordium.Sdk.Types.BlockEnergyLimitUpdate update) =>
new(update.EnergyLimit.Value);
}

public sealed record GasRewardsCpv2Update(
decimal Baker,
decimal AccountCreation,
decimal ChainUpdate) : ChainUpdatePayload
{
internal static GasRewardsCpv2Update From(Concordium.Sdk.Types.GasRewardsCpv2Update update) =>
new(
update.Baker.AsDecimal(),
update.AccountCreation.AsDecimal(),
update.ChainUpdate.AsDecimal()
);
}

public record ProtocolChainUpdatePayload(
Expand Down Expand Up @@ -274,4 +324,4 @@ internal static MintDistributionV1ChainUpdatePayload From(MintDistributionCpv1Up
);
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,7 @@ internal static IEnumerable<TransactionResultEvent> ToIter(IBlockItemSummaryDeta

break;
case UpdateDetails updateDetails:
if (ChainUpdateEnqueued.TryFrom(updateDetails, blockSlotTime, out var chainUpdateEnqueued))
{
yield return chainUpdateEnqueued!;
}
yield return ChainUpdateEnqueued.From(updateDetails, blockSlotTime);
break;
}
}
Expand Down Expand Up @@ -878,20 +875,14 @@ public record ChainUpdateEnqueued(
bool EffectiveImmediately,
ChainUpdatePayload Payload) : TransactionResultEvent
{
internal static bool TryFrom(
internal static ChainUpdateEnqueued From(
UpdateDetails updateDetails,
DateTimeOffset blockSlotTime,
out ChainUpdateEnqueued? chainUpdateEnqueued)
DateTimeOffset blockSlotTime)
{
if (!ChainUpdatePayload.TryFrom(updateDetails.Payload, out var chainUpdatePayload))
{
chainUpdateEnqueued = null;
return false;
}
var chainUpdatePayload = ChainUpdatePayload.From(updateDetails.Payload);
var isEffectiveImmediately = updateDetails.EffectiveTime.ToUnixTimeSeconds() == 0;
var effectiveTime = isEffectiveImmediately ? blockSlotTime : updateDetails.EffectiveTime;
chainUpdateEnqueued = new ChainUpdateEnqueued(effectiveTime, isEffectiveImmediately, chainUpdatePayload!);
return true;
return new ChainUpdateEnqueued(effectiveTime, isEffectiveImmediately, chainUpdatePayload!);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public class _00_UpdateValidatorCommissionRates : IMainMigrationJob {
/// WARNING - Do not change this if job already executed on environment, since it will trigger rerun of job.
/// </summary>
private const string JobName = "_00_UpdateValidatorCommissionRates";

private readonly IDbContextFactory<GraphQlDbContext> _contextFactory;
private readonly IConcordiumNodeClient _client;
private readonly JobHealthCheck _jobHealthCheck;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System.Threading;
using System.Threading.Tasks;
using Application.Api.GraphQL.EfCore;
using Application.Api.GraphQL.Import;
using Application.Api.GraphQL.Transactions;
using Application.Exceptions;
using Application.Import.ConcordiumNode;
using Application.Observability;
using Application.Resilience;
using Concordium.Sdk.Types;
using Dapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Serilog.Context;

namespace Application.Database.MigrationJobs;

/// <summary>
/// Some transaction events hasn't been mapped to the database. Those missing are
/// <see cref="Concordium.Sdk.Types.UpdateType"/> of one of below values
/// - <see cref="Concordium.Sdk.Types.UpdateType.GasRewardsCpv2Update"/>
/// - <see cref="Concordium.Sdk.Types.UpdateType.TimeoutParametersUpdate"/>
/// - <see cref="Concordium.Sdk.Types.UpdateType.MinBlockTimeUpdate"/>
/// - <see cref="Concordium.Sdk.Types.UpdateType.BlockEnergyLimitUpdate"/>
/// - <see cref="Concordium.Sdk.Types.UpdateType.FinalizationCommitteeParametersUpdate"/>
///
/// The events are mapped to <see cref="ChainUpdateEnqueued"/>. This migration job adds missing events.
///
/// Even though transaction events for these cases hasn't been mapped the event type has been set on the
/// <see cref="Transaction"/> entity on property <see cref="Transaction.TransactionType"/>. The jobs starts by
/// querying the transaction table for those transaction with is of one of the missing event types.
///
/// The mapping between the <see cref="Transaction.TransactionType"/> and the string value stored in the database
/// is present at <see cref="Application.Api.GraphQL.EfCore.Converters.EfCore.TransactionTypeToStringConverter"/>.
///
/// There is an one-to-one relation between transaction and events when the transaction is of type
/// <see cref="Concordium.Sdk.Types.UpdateDetails"/>. Hence the job is idempotent because it checks if any transaction
/// event for the given transaction already exist, and only generates an event if none is present.
/// </summary>
public class _01_AddMissingChainUpdateEvents : IMainMigrationJob {
/// <summary>
/// WARNING - Do not change this if job already executed on environment, since it will trigger rerun of job.
/// </summary>
private const string JobName = "_01_AddMissingChainUpdateEvents";

/// <summary>
/// The mapping between the <see cref="Transaction.TransactionType"/> and the string value stored in the database
/// is present at <see cref="Application.Api.GraphQL.EfCore.Converters.EfCore.TransactionTypeToStringConverter"/>.
/// </summary>
private const string AffectedTransactionTypesSql = @"
SELECT id as Id, block_id as BlockId, index as TransactionIndex, transaction_hash as TransactionHash
FROM graphql_transactions
WHERE transaction_type IN ('2.22', '2.21', '2.20', '2.19', '2.18');
";

private readonly IDbContextFactory<GraphQlDbContext> _contextFactory;
private readonly IConcordiumNodeClient _client;
private readonly JobHealthCheck _jobHealthCheck;
private readonly ILogger _logger;
private readonly MainMigrationJobOptions _mainMigrationJobOptions;

public _01_AddMissingChainUpdateEvents(
IDbContextFactory<GraphQlDbContext> contextFactory,
IConcordiumNodeClient client,
JobHealthCheck jobHealthCheck,
IOptions<MainMigrationJobOptions> options
)
{
_contextFactory = contextFactory;
_client = client;
_jobHealthCheck = jobHealthCheck;
_logger = Log.ForContext<_00_UpdateValidatorCommissionRates>();
_mainMigrationJobOptions = options.Value;
}

/// <summary>
/// Start import of missing transaction events.
/// </summary>
/// <exception cref="JobException">If the transaction fetched from the node isn't
/// <see cref="TransactionStatusFinalized"/> or the transaction isn't of type <see cref="UpdateDetails"/>
/// </exception>
public async Task StartImport(CancellationToken token)
{
using var _ = TraceContext.StartActivity(GetUniqueIdentifier());
using var __ = LogContext.PushProperty("Job", GetUniqueIdentifier());

try
{
await Policies.GetTransientPolicy(GetUniqueIdentifier(), _logger, _mainMigrationJobOptions.RetryCount, _mainMigrationJobOptions.RetryDelay)
.ExecuteAsync(async () =>
{
await using var context = await _contextFactory.CreateDbContextAsync(token);
var connection = context.Database.GetDbConnection();

var transactions = await connection.QueryAsync<Transaction>(AffectedTransactionTypesSql);

foreach (var transaction in transactions)
{
var count = await context.TransactionResultEvents
.Where(te => te.TransactionId == transaction.Id)
.CountAsync(cancellationToken: token);
// If a transaction event exist the event has already been generated.
if (count > 0)
{
continue;
}

var blockItemStatus = await _client.GetBlockItemStatusAsync(TransactionHash.From(transaction.TransactionHash), token);

var finalized = blockItemStatus.GetFinalizedBlockItemSummary();

if (finalized.Details is not UpdateDetails updateDetails)
{
throw JobException.Create(GetUniqueIdentifier(),
$"Transaction details was of wrong type {finalized.Details.GetType()}");
}

var block = await context
.Blocks
.SingleAsync(b => b.Id == transaction.BlockId, cancellationToken: token);

var chainUpdateEnqueued = ChainUpdateEnqueued.From(updateDetails, block.BlockSlotTime);

var transactionRelated = new TransactionRelated<TransactionResultEvent>(transaction.Id, 0, chainUpdateEnqueued);
await context.TransactionResultEvents.AddAsync(transactionRelated, token);
await context.SaveChangesAsync(token);
}
});
}
catch (Exception e)
{
_jobHealthCheck.AddUnhealthyJobWithMessage(GetUniqueIdentifier(), "Job stopped due to exception.");
_logger.Fatal(e, $"{GetUniqueIdentifier()} stopped due to exception.");
throw;
}
}

public string GetUniqueIdentifier() => JobName;

public bool ShouldNodeImportAwait() => false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Application.Exceptions;

public sealed class ConcordiumClientWrapperException : Exception
{
public ConcordiumClientWrapperException(string message) : base(message)
{}
}
12 changes: 12 additions & 0 deletions backend/Application/Exceptions/JobException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Application.Exceptions;

internal sealed class JobException : Exception
{
private JobException(string message) : base(message)
{}

internal static JobException Create(string identifier, string message)
{
return new JobException($"Job {identifier} encountered error: {message}");
}
}
3 changes: 2 additions & 1 deletion backend/Application/Extensions/StartupExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal static void AddMainMigrationJobs(this IServiceCollection collection, IC
collection.AddSingleton<IJobRepository<MainMigrationJob>, JobRepository<MainMigrationJob>>();

collection.AddTransient<IMainMigrationJob, _00_UpdateValidatorCommissionRates>();
collection.AddSingleton<IConcordiumNodeClient, ConcordiumNodeClient>();
collection.AddTransient<IMainMigrationJob, _01_AddMissingChainUpdateEvents>();
}

internal static void AddConcordiumClient(this IServiceCollection services, IConfiguration configuration)
Expand All @@ -40,6 +40,7 @@ internal static void AddConcordiumClient(this IServiceCollection services, IConf
var concordiumClientOptions = configuration.GetSection("ConcordiumNodeGrpc").Get<ConcordiumClientOptions>();
var uri = new Uri(grpcNodeClientSettings.Address);
services.AddSingleton(new ConcordiumClient(uri, concordiumClientOptions));
services.AddSingleton<IConcordiumNodeClient, ConcordiumNodeClient>();
}

internal static void AddDefaultHealthChecks(this IServiceCollection services)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Application.Exceptions;
using Concordium.Sdk.Types;

namespace Application.Import.ConcordiumNode.ConcordiumClientWrappers;

public interface IBlockItemSummaryWrapper
{
ITransactionStatus GetTransactionStatus();
BlockItemSummary GetFinalizedBlockItemSummary();
}

public class BlockItemSummaryWrapper : IBlockItemSummaryWrapper
{
private readonly ITransactionStatus _transactionStatus;

public BlockItemSummaryWrapper(ITransactionStatus transactionStatus)
{
_transactionStatus = transactionStatus;
}
public ITransactionStatus GetTransactionStatus() => _transactionStatus;

/// <summary>
/// Get block item summary for finalized transaction.
/// </summary>
/// <exception cref="ConcordiumClientWrapperException">Throws exception if the transaction
/// it not finalized.
/// </exception>
public BlockItemSummary GetFinalizedBlockItemSummary()
{
if (_transactionStatus is not TransactionStatusFinalized finalized)
{
throw new ConcordiumClientWrapperException($"Transaction was of wrong type {_transactionStatus.GetType()}");
}

return finalized.State.Summary;
}
}
Loading
Loading