Skip to content

Commit

Permalink
Smart Contract listing endpoints and GraphQl duration metrics (#97)
Browse files Browse the repository at this point in the history
* Made entities and import flow

* Added test to aggregate which validates events are stored

* Added test for other read cases

* Added smart contract entity

* Added Smart Contract Aggregate to Startup

* Added import job

* Finished repository test

* Fixed aggregate test

* Added data source

* Added awaits on jobs on node import

* Added contract version to contract initialize and update events

* Added tests which validates uniqueness contrains

* Remove todos and added simple resilience in import loop

* Fixed dependency injection issue

* Fix issues when testing job

* Small updates

* Updated changelog

* Moved classes to own files

* Added logic to Smart Contract Repository which can fetch batch

* Made batch import instead of processing single rows

* Clean up unused code

* Fix limit on smart contract database job

* Remove unnecessary batch

* Added duration- and height metrics

* Added health states to application

* Fixed health output

* Adding temp CI

* Updating change log

* Remove some debugging and added nugets

* Fix test

* Refactor

* Clean up after merge

* Added smart contract paging query

* Added metrics to graphql endpoints

* Updated changelog

* Added contract address to Smart Contract query

* Updated naming from Smart Contract to Contract

* Renamed test and tables

* Fix naming conflicts

* Resolve conflicts after merge

* Updated with add and remove link entities

* Fixed off by one error

* Update Program.cs

* Using activity and better exception handling in diagnostic handler

* Enable rewind of request body

* Added tracing to logs

* Updated log formats

* remove extra from console

* Corrected naming

* Made repository for contract jobs

* Migrated to options for feature flags

* Moved interfaces to same file as classes where only one implementation is expected.

* Changed to date time offset

* Added warnings not to change job identifier

* Update range function with comments

* Follow standards from EF regarding CS8618 compiler warning for C# 10

https://learn.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types#non-nullable-properties-and-initialization

* Refactored Contract Aggregate Node Import job to be more readable

* Resolve comments after merge

* Resolve issues after merge

* Resolve issues after merge

* Resolve comments

* Minor refactoring after final review

* Updates after merge

* Fix exceptions handling in diagnostics

* Fix missing events when money CCD was transferred between account

* Remove unused exception

* Added block slot time to all events

* Updated graphql schema
  • Loading branch information
Søren Schwartz authored Sep 11, 2023
1 parent af21113 commit 4150a04
Show file tree
Hide file tree
Showing 38 changed files with 902 additions and 150 deletions.
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,5 @@ $RECYCLE.BIN/
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

/Logs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Application.Aggregates.Contract.Jobs;
using Application.Observability;
using Application.Configurations;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -33,6 +34,8 @@ IOptions<FeatureFlagOptions> featureFlagsOptions

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var _ = TraceContext.StartActivity(nameof(ContractJobsBackgroundService));

if (!_featureFlags.ConcordiumNodeImportEnabled)
{
_logger.Information("Import data from Concordium node is disabled. This controller will not run!");
Expand Down Expand Up @@ -73,4 +76,4 @@ private async Task RunJob(IContractJob job, CancellationToken token)
throw;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Application.Aggregates.Contract.Jobs;
using Application.Aggregates.Contract.Observability;
using Application.Api.GraphQL.EfCore;
using Application.Observability;
using Application.Configurations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -46,6 +47,8 @@ public ContractNodeImportBackgroundService(

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var _ = TraceContext.StartActivity(nameof(ContractNodeImportBackgroundService));

if (!_featureFlags.ConcordiumNodeImportEnabled)
{
_logger.Information("Import data from Concordium node is disabled. This controller will not run!");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Application.Aggregates.Contract.Entities;
using Application.Api.GraphQL.EfCore.Converters.EfCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
Expand All @@ -11,9 +12,6 @@ public void Configure(EntityTypeBuilder<Entities.Contract> builder)
builder.ToTable("graphql_contracts");
builder.HasKey(x => new
{
x.BlockHeight,
x.TransactionIndex,
x.EventIndex,
x.ContractAddressIndex,
x.ContractAddressSubIndex
});
Expand All @@ -34,7 +32,14 @@ public void Configure(EntityTypeBuilder<Entities.Contract> builder)
.HasConversion<AccountAddressConverter>();
builder.Property(x => x.Source)
.HasColumnName("source");
builder.Property(x => x.BlockSlotTime)
.HasColumnName("block_slot_time");
builder.Property(x => x.CreatedAt)
.HasColumnName("created_at");
.HasColumnName("created_at");

builder
.HasMany<ContractEvent>(sm => sm.ContractEvents)
.WithOne()
.HasForeignKey(sme => new { sme.ContractAddressIndex, sme.ContractAddressSubIndex });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ public void Configure(EntityTypeBuilder<ContractEvent> builder)
.HasConversion<TransactionResultEventToJsonConverter>();
builder.Property(x => x.Source)
.HasColumnName("source");
builder.Property(x => x.BlockSlotTime)
.HasColumnName("block_slot_time");
builder.Property(x => x.CreatedAt)
.HasColumnName("created_at");
.HasColumnName("created_at");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public void Configure(EntityTypeBuilder<ModuleReferenceContractLinkEvent> builde
x.EventIndex,
x.ModuleReference,
x.ContractAddressIndex,
x.ContractAddressSubIndex
x.ContractAddressSubIndex,
x.LinkAction
});
builder.Property(x => x.BlockHeight)
.HasColumnName("block_height");
Expand All @@ -36,6 +37,8 @@ public void Configure(EntityTypeBuilder<ModuleReferenceContractLinkEvent> builde
.HasColumnName("source");
builder.Property(x => x.LinkAction)
.HasColumnName("link_action");
builder.Property(x => x.BlockSlotTime)
.HasColumnName("block_slot_time");
builder.Property(x => x.CreatedAt)
.HasColumnName("created_at");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ public void Configure(EntityTypeBuilder<ModuleReferenceEvent> builder)
builder.ToTable("graphql_module_reference_events");
builder.HasKey(x => new
{
x.BlockHeight,
x.TransactionIndex,
x.EventIndex,
x.ModuleReference
});
builder.Property(x => x.BlockHeight)
Expand All @@ -28,6 +25,8 @@ public void Configure(EntityTypeBuilder<ModuleReferenceEvent> builder)
.HasColumnName("module_reference");
builder.Property(x => x.Source)
.HasColumnName("source");
builder.Property(x => x.BlockSlotTime)
.HasColumnName("block_slot_time");
builder.Property(x => x.CreatedAt)
.HasColumnName("created_at");
}
Expand Down
97 changes: 73 additions & 24 deletions backend/Application/Aggregates/Contract/ContractAggregate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Application.Aggregates.Contract.Observability;
using Application.Aggregates.Contract.Types;
using Application.Api.GraphQL.Transactions;
using Application.Observability;
using Concordium.Sdk.Types;
using AccountAddress = Application.Api.GraphQL.Accounts.AccountAddress;
using ContractAddress = Application.Api.GraphQL.ContractAddress;
Expand All @@ -19,6 +20,8 @@ internal sealed class ContractAggregate
private readonly IContractRepositoryFactory _repositoryFactory;
private readonly ContractAggregateOptions _options;
private readonly ILogger _logger;
private const string NodeImportJobActivity = "NodeImportJobActivity";
private const string NodeImportJobLoopActivity = "NodeImportJobLoopActivity";

public ContractAggregate(
IContractRepositoryFactory repositoryFactory,
Expand All @@ -32,6 +35,8 @@ ContractAggregateOptions options

internal async Task NodeImportJob(IContractNodeClient client, CancellationToken token = default)
{
using var _ = TraceContext.StartActivity(NodeImportJobActivity);

var retryCount = 0;
while (!token.IsCancellationRequested)
{
Expand Down Expand Up @@ -102,6 +107,7 @@ await StoreEvent(
transactionResultEvent,
AccountAddress.From(details.Sender),
blockInfo.BlockHeight,
blockInfo.BlockSlotTime,
transactionHash,
blockItemSummary.Index,
eventIndex
Expand Down Expand Up @@ -144,6 +150,7 @@ internal static async Task StoreEvent(
TransactionResultEvent transactionResultEvent,
AccountAddress sender,
ulong blockHeight,
DateTimeOffset blockSlotTime,
string transactionHash,
ulong transactionIndex,
uint eventIndex
Expand All @@ -159,7 +166,8 @@ await repository.AddAsync(new Entities.Contract(
eventIndex,
contractInitialized.ContractAddress,
sender,
source
source,
blockSlotTime
));
await repository
.AddAsync(new ContractEvent(
Expand All @@ -169,7 +177,8 @@ await repository
eventIndex,
contractInitialized.ContractAddress,
contractInitialized,
source
source,
blockSlotTime
));
await repository
.AddAsync(new ModuleReferenceContractLinkEvent(
Expand All @@ -180,7 +189,8 @@ await repository
contractInitialized.ModuleRef,
contractInitialized.ContractAddress,
source,
ModuleReferenceContractLinkEvent.ModuleReferenceContractLinkAction.Added
ModuleReferenceContractLinkEvent.ModuleReferenceContractLinkAction.Added,
blockSlotTime
));
break;
case ContractInterrupted contractInterrupted:
Expand All @@ -192,7 +202,8 @@ await repository
eventIndex,
contractInterrupted.ContractAddress,
contractInterrupted,
source
source,
blockSlotTime
));
break;
case ContractResumed contractResumed:
Expand All @@ -204,7 +215,8 @@ await repository
eventIndex,
contractResumed.ContractAddress,
contractResumed,
source
source,
blockSlotTime
));
break;
case ContractUpdated contractUpdated:
Expand All @@ -216,8 +228,27 @@ await repository
eventIndex,
contractUpdated.ContractAddress,
contractUpdated,
source
));
source,
blockSlotTime
));
if (contractUpdated.Instigator is ContractAddress contractInstigator && contractUpdated.Amount != 0)
{
await repository
.AddAsync(new ContractEvent(
blockHeight,
transactionHash,
transactionIndex,
eventIndex,
contractInstigator,
new Transferred(
contractUpdated.Amount,
contractInstigator,
contractUpdated.ContractAddress
),
source,
blockSlotTime
));
}
break;
case ContractUpgraded contractUpgraded:
await repository
Expand All @@ -228,7 +259,8 @@ await repository
eventIndex,
contractUpgraded.ContractAddress,
contractUpgraded,
source
source,
blockSlotTime
));
await repository
.AddAsync(new ModuleReferenceContractLinkEvent(
Expand All @@ -239,7 +271,8 @@ await repository
contractUpgraded.To,
contractUpgraded.ContractAddress,
source,
ModuleReferenceContractLinkEvent.ModuleReferenceContractLinkAction.Added
ModuleReferenceContractLinkEvent.ModuleReferenceContractLinkAction.Added,
blockSlotTime
));
await repository
.AddAsync(new ModuleReferenceContractLinkEvent(
Expand All @@ -250,25 +283,39 @@ await repository
contractUpgraded.From,
contractUpgraded.ContractAddress,
source,
ModuleReferenceContractLinkEvent.ModuleReferenceContractLinkAction.Removed
ModuleReferenceContractLinkEvent.ModuleReferenceContractLinkAction.Removed,
blockSlotTime
));
break;
case Transferred transferred:
if (transferred.From is not ContractAddress contractAddress ||
transferred.To is not AccountAddress)
if (transferred.From is ContractAddress contractAddressFrom)
{
break;
await repository
.AddAsync(new ContractEvent(
blockHeight,
transactionHash,
transactionIndex,
eventIndex,
contractAddressFrom,
transferred,
source,
blockSlotTime
));
}
if (transferred.To is ContractAddress contractAddressTo)
{
await repository
.AddAsync(new ContractEvent(
blockHeight,
transactionHash,
transactionIndex,
eventIndex,
contractAddressTo,
transferred,
source,
blockSlotTime
));
}
await repository
.AddAsync(new ContractEvent(
blockHeight,
transactionHash,
transactionIndex,
eventIndex,
contractAddress,
transferred,
source
));
break;
case ContractModuleDeployed contractModuleDeployed:
await repository
Expand All @@ -278,7 +325,8 @@ await repository
transactionIndex,
eventIndex,
contractModuleDeployed.ModuleRef,
source
source,
blockSlotTime
));
break;
}
Expand All @@ -294,6 +342,7 @@ private async Task NodeImportRange(IContractNodeClient client, ulong fromBlockHe
{
for (var height = fromBlockHeight; height <= toBlockHeight; height++)
{
using var __ = TraceContext.StartActivity(NodeImportJobLoopActivity);
using var durationMetric = new ContractMetrics.DurationMetric(ImportSource.NodeImport);
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public async Task<IList<TransactionResultEventDto>> FromBlockHeightRangeGetContr
const string sql = @"
SELECT
gb.block_height as BlockHeight,
gb.block_slot_time as BlockSlotTime,
gt.transaction_type as TransactionType,
gt.sender as TransactionSender,
gt.transaction_hash as TransactionHash,
Expand All @@ -81,7 +82,7 @@ graphql_transaction_events te
graphql_blocks gb ON gt.block_id = gb.id
WHERE
gb.block_height >= @FromHeight AND gb.block_height <= @ToHeight
AND te.event->>'tag' IN ('1', '16', '18', '17', '34', '35', '36');
AND te.event->>'tag' IN ('1', '16', '17', '18', '34', '35', '36');
";
var queryAsync = await _context.Database.GetDbConnection()
.QueryAsync<TransactionResultEventDto>(sql, new { FromHeight = (long)heightFrom, ToHeight = (long)heightTo });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Application.Aggregates.Contract.Dto;
public class TransactionResultEventDto
{
public int BlockHeight { get; init; }
public DateTimeOffset BlockSlotTime { get; init; }
public TransactionTypeUnion TransactionType { get; init; }
public AccountAddress? TransactionSender { get; init; }
public string TransactionHash { get; init; }
Expand Down
Loading

0 comments on commit 4150a04

Please sign in to comment.