diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76ab70f3c..0a2c677ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,22 +12,23 @@ jobs: runs-on: windows-latest if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} steps: - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v3 with: - java-version: 1.11 - - uses: actions/checkout@v2 + java-version: 17 + distribution: 'zulu' # Alternative distribution options are available. + - uses: actions/checkout@v3 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Cache SonarCloud packages - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~\sonar\cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache SonarCloud scanner id: cache-sonar-scanner - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: .\.sonar\scanner key: ${{ runner.os }}-sonar-scanner @@ -44,6 +45,6 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: powershell run: | - .\.sonar\scanner\dotnet-sonarscanner begin /k:"baking-bad_tzkt" /o:"baking-bad" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" + .\.sonar\scanner\dotnet-sonarscanner begin /k:"baking-bad_tzkt" /o:"baking-bad" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" dotnet build - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/Makefile b/Makefile index d527d69cb..531a89df7 100644 --- a/Makefile +++ b/Makefile @@ -109,4 +109,7 @@ mumbai-stop: docker-compose -f docker-compose.mumbai.yml down mumbai-db-start: + docker-compose -f docker-compose.mumbai.yml up -d mumbai-db +reset: + docker-compose -f docker-compose.mumbai.yml down --volumes docker-compose -f docker-compose.mumbai.yml up -d mumbai-db \ No newline at end of file diff --git a/Tzkt.Api/Controllers/TicketsController.cs b/Tzkt.Api/Controllers/TicketsController.cs new file mode 100644 index 000000000..c894915ca --- /dev/null +++ b/Tzkt.Api/Controllers/TicketsController.cs @@ -0,0 +1,309 @@ +using Microsoft.AspNetCore.Mvc; +using Tzkt.Api.Models; +using Tzkt.Api.Repositories; +using Tzkt.Api.Services; +using Tzkt.Api.Services.Cache; + +namespace Tzkt.Api.Controllers +{ + [ApiController] + [Route("v1/tickets")] + public class TicketsController : ControllerBase + { + readonly TicketsRepository Tickets; + readonly StateCache State; + readonly ResponseCacheService ResponseCache; + + public TicketsController(TicketsRepository tickets, StateCache state, ResponseCacheService responseCache) + { + Tickets = tickets; + State = state; + ResponseCache = responseCache; + } + + #region tickets + /// + /// Get tickets count + /// + /// + /// Returns a total number of tickets. + /// + /// Filter + /// + [HttpGet("count")] + public async Task> GetTicketsCount([FromQuery] TicketFilter filter) + { + if (filter.Empty) + return Ok(State.Current.TicketsCount); + + var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter)); + + if (ResponseCache.TryGet(query, out var cached)) + return this.Bytes(cached); + + var res = await Tickets.GetTicketsCount(filter); + cached = ResponseCache.Set(query, res); + return this.Bytes(cached); + } + + /// + /// Get tickets + /// + /// + /// Returns a list of tickets. + /// + /// Filter + /// Pagination + /// Selection + /// + [HttpGet] + public async Task>> GetTickets( + [FromQuery] TicketFilter filter, + [FromQuery] Pagination pagination, + [FromQuery] Selection selection) + { + var query = ResponseCacheService.BuildKey(Request.Path.Value, + ("filter", filter), ("pagination", pagination), ("selection", selection)); + + if (ResponseCache.TryGet(query, out var cached)) + return this.Bytes(cached); + + object res; + if (selection.select == null) + { + res = await Tickets.GetTickets(filter, pagination); + } + else + { + res = new SelectionResponse + { + Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(), + Rows = await Tickets.GetTickets(filter, pagination, selection.select.Fields ?? selection.select.Values) + }; + } + cached = ResponseCache.Set(query, res); + return this.Bytes(cached); + } + #endregion + + #region ticket balances + /// + /// Get ticket balances count + /// + /// + /// Returns a total number of ticket balances. + /// + /// Filter + /// + [HttpGet("balances/count")] + public async Task> GetTicketBalancesCount([FromQuery] TicketBalanceFilter filter) + { + if (filter.Empty) + return Ok(State.Current.TicketBalancesCount); + + #region optimizations + if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull)) + return Ok(0); + #endregion + + var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter)); + + if (ResponseCache.TryGet(query, out var cached)) + return this.Bytes(cached); + + var res = await Tickets.GetTicketBalancesCount(filter); + cached = ResponseCache.Set(query, res); + return this.Bytes(cached); + } + + /// + /// Get ticket balances + /// + /// + /// Returns a list of ticket balances. + /// + /// Filter + /// Pagination + /// Selection + /// + [HttpGet("balances")] + public async Task>> GetTicketBalances( + [FromQuery] TicketBalanceFilter filter, + [FromQuery] Pagination pagination, + [FromQuery] Selection selection) + { + #region optimizations + if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull)) + return Ok(Enumerable.Empty()); + #endregion + + var query = ResponseCacheService.BuildKey(Request.Path.Value, + ("filter", filter), ("pagination", pagination), ("selection", selection)); + + if (ResponseCache.TryGet(query, out var cached)) + return this.Bytes(cached); + + object res; + if (selection.select == null) + { + res = await Tickets.GetTicketBalances(filter, pagination); + } + else + { + res = new SelectionResponse + { + Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(), + Rows = await Tickets.GetTicketBalances(filter, pagination, selection.select.Fields ?? selection.select.Values) + }; + } + cached = ResponseCache.Set(query, res); + return this.Bytes(cached); + } + #endregion + + #region ticket transfers + /// + /// Get ticket transfers count + /// + /// + /// Returns the total number of ticket transfers. + /// + /// Filter + /// + [HttpGet("transfers/count")] + public async Task> GetTicketTransfersCount([FromQuery] TicketTransferFilter filter) + { + if (filter.Empty) + return Ok(State.Current.TicketTransfersCount); + + #region optimizations + if (filter.from != null && (filter.from.Eq == -1 || filter.from.In?.Count == 0 && !filter.from.InHasNull)) + return Ok(0); + + if (filter.to != null && (filter.to.Eq == -1 || filter.to.In?.Count == 0 && !filter.to.InHasNull)) + return Ok(0); + + if (filter.anyof != null && (filter.anyof.Eq == -1 || filter.anyof.In?.Count == 0 && !filter.anyof.InHasNull)) + return Ok(0); + #endregion + + var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter)); + + if (ResponseCache.TryGet(query, out var cached)) + return this.Bytes(cached); + + var res = await Tickets.GetTicketTransfersCount(filter); + cached = ResponseCache.Set(query, res); + return this.Bytes(cached); + } + + /// + /// Get ticket transfers + /// + /// + /// Returns a list of ticket transfers. + /// + /// Filter + /// Pagination + /// Selection + /// + [HttpGet("transfers")] + public async Task>> GetTicketTransfers( + [FromQuery] TicketTransferFilter filter, + [FromQuery] Pagination pagination, + [FromQuery] Selection selection) + { + #region optimizations + if (filter.from != null && (filter.from.Eq == -1 || filter.from.In?.Count == 0 && !filter.from.InHasNull)) + return Ok(Enumerable.Empty()); + + if (filter.to != null && (filter.to.Eq == -1 || filter.to.In?.Count == 0 && !filter.to.InHasNull)) + return Ok(Enumerable.Empty()); + + if (filter.anyof != null && (filter.anyof.Eq == -1 || filter.anyof.In?.Count == 0 && !filter.anyof.InHasNull)) + return Ok(Enumerable.Empty()); + #endregion + + var query = ResponseCacheService.BuildKey(Request.Path.Value, + ("filter", filter), ("pagination", pagination), ("selection", selection)); + + if (ResponseCache.TryGet(query, out var cached)) + return this.Bytes(cached); + + object res; + if (selection.select == null) + { + res = await Tickets.GetTicketTransfers(filter, pagination); + } + else + { + res = new SelectionResponse + { + Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(), + Rows = await Tickets.GetTicketTransfers(filter, pagination, selection.select.Fields ?? selection.select.Values) + }; + } + cached = ResponseCache.Set(query, res); + return this.Bytes(cached); + } + #endregion + + #region historical balances + /// + /// Get historical ticket balances + /// + /// + /// Returns a list of ticket balances at the end of the specified block. + /// Note, this endpoint is quite heavy, therefore at least one of the filters + /// (`account`, `ticket.id`, `ticket.ticketer`) must be specified. + /// + /// Level of the block at the end of which historical balances must be calculated + /// Filter + /// Pagination + /// Selection + /// + [HttpGet("historical_balances/{level:int}")] + public async Task>> GetHistoricalTicketBalances(int level, + [FromQuery] TicketBalanceShortFilter filter, + [FromQuery] Pagination pagination, + [FromQuery] Selection selection) + { + + if (filter.account?.Eq == null && + filter.account?.In == null && + filter.ticket.id?.Eq == null && + filter.ticket.id?.In == null && + filter.ticket.ticketer?.Eq == null && + filter.ticket.ticketer?.In == null) + return new BadRequest("query", "At least one of the filters (`account`, `ticket.id`, `ticket.ticketer`) must be specified"); + + #region optimizations + if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull)) + return Ok(Enumerable.Empty()); + #endregion + + var query = ResponseCacheService.BuildKey(Request.Path.Value, + ("filter", filter), ("pagination", pagination), ("selection", selection)); + + if (ResponseCache.TryGet(query, out var cached)) + return this.Bytes(cached); + + object res; + if (selection.select == null) + { + res = await Tickets.GetHistoricalTicketBalances(level, filter, pagination); + } + else + { + res = new SelectionResponse + { + Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(), + Rows = await Tickets.GetHistoricalTicketBalances(level, filter, pagination, selection.select.Fields ?? selection.select.Values) + }; + } + cached = ResponseCache.Set(query, res); + return this.Bytes(cached); + } + #endregion + } +} diff --git a/Tzkt.Api/Extensions/ModelBindingContextExtension.cs b/Tzkt.Api/Extensions/ModelBindingContextExtension.cs index 2cc11532c..883317f22 100644 --- a/Tzkt.Api/Extensions/ModelBindingContextExtension.cs +++ b/Tzkt.Api/Extensions/ModelBindingContextExtension.cs @@ -4,6 +4,7 @@ using System.Text.Json; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Netezos.Encoding; namespace Tzkt.Api { @@ -189,6 +190,68 @@ public static bool TryGetInt64List(this ModelBindingContext bindingContext, stri return true; } + public static bool TryGetMicheline(this ModelBindingContext bindingContext, string name, ref bool hasValue, out IMicheline result) + { + result = null; + var valueObject = bindingContext.ValueProvider.GetValue(name); + + if (valueObject != ValueProviderResult.None) + { + bindingContext.ModelState.SetModelValue(name, valueObject); + if (!string.IsNullOrEmpty(valueObject.FirstValue)) + { + try + { + result = Micheline.FromJson(valueObject.FirstValue); + hasValue = true; + } + catch + { + bindingContext.ModelState.TryAddModelError(name, "Invalid Micheline value."); + return false; + } + } + } + + return true; + } + + public static bool TryGetMichelineList(this ModelBindingContext bindingContext, string name, ref bool hasValue, out List result) + { + result = null; + var valueObject = bindingContext.ValueProvider.GetValue(name); + + if (valueObject != ValueProviderResult.None) + { + bindingContext.ModelState.SetModelValue(name, valueObject); + if (!string.IsNullOrEmpty(valueObject.FirstValue)) + { + try + { + var json = valueObject.FirstValue.Trim(); + if (json[0] != '[') json = $"[{json}]"; + var values = JsonSerializer.Deserialize>(json); + + if (values.Count == 0) + { + bindingContext.ModelState.TryAddModelError(name, "List should contain at least one item."); + return false; + } + + hasValue = true; + result = values; + } + catch + { + bindingContext.ModelState.TryAddModelError(name, "List contains invalid Micheline value(s)."); + return false; + } + } + } + + return true; + } + public static bool TryGetDateTime(this ModelBindingContext bindingContext, string name, ref bool hasValue, out DateTime? result) { result = null; diff --git a/Tzkt.Api/Models/Accounts/Contract.cs b/Tzkt.Api/Models/Accounts/Contract.cs index 1b51ac6c6..9f11fc27f 100644 --- a/Tzkt.Api/Models/Accounts/Contract.cs +++ b/Tzkt.Api/Models/Accounts/Contract.cs @@ -76,14 +76,14 @@ public class Contract : Account public int NumContracts { get; set; } /// - /// Number of account tokens with non-zero balances. + /// Number of tokens minted in the contract. /// - public int ActiveTokensCount { get; set; } + public int TokensCount { get; set; } /// - /// Number of tokens minted in the contract. + /// Number of account tokens with non-zero balances. /// - public int TokensCount { get; set; } + public int ActiveTokensCount { get; set; } /// /// Number of tokens the account ever had. @@ -95,6 +95,26 @@ public class Contract : Account /// public int TokenTransfersCount { get; set; } + /// + /// Number of tickets minted in the contract. + /// + public int TicketsCount { get; set; } + + /// + /// Number of tickets the account owns. + /// + public int ActiveTicketsCount { get; set; } + + /// + /// Number of tickets the account ever owned. + /// + public int TicketBalancesCount { get; set; } + + /// + /// Number of ticket transfers from/to the account. + /// + public int TicketTransfersCount { get; set; } + /// /// Number of delegation operations of the contract. /// diff --git a/Tzkt.Api/Models/Accounts/Delegate.cs b/Tzkt.Api/Models/Accounts/Delegate.cs index fb69d4fff..5aefb6d8d 100644 --- a/Tzkt.Api/Models/Accounts/Delegate.cs +++ b/Tzkt.Api/Models/Accounts/Delegate.cs @@ -130,6 +130,21 @@ public class Delegate : Account /// public int TokenTransfersCount { get; set; } + /// + /// Number of tickets the account owns. + /// + public int ActiveTicketsCount { get; set; } + + /// + /// Number of tickets the account ever owned. + /// + public int TicketBalancesCount { get; set; } + + /// + /// Number of ticket transfers from/to the account. + /// + public int TicketTransfersCount { get; set; } + /// /// Number of current delegators (accounts, delegated their funds) of the delegate (baker) /// diff --git a/Tzkt.Api/Models/Accounts/Ghost.cs b/Tzkt.Api/Models/Accounts/Ghost.cs index 0f8c5559f..5241758ee 100644 --- a/Tzkt.Api/Models/Accounts/Ghost.cs +++ b/Tzkt.Api/Models/Accounts/Ghost.cs @@ -40,6 +40,21 @@ public class Ghost : Account /// public int TokenTransfersCount { get; set; } + /// + /// Number of tickets the account owns. + /// + public int ActiveTicketsCount { get; set; } + + /// + /// Number of tickets the account ever owned. + /// + public int TicketBalancesCount { get; set; } + + /// + /// Number of ticket transfers from/to the account. + /// + public int TicketTransfersCount { get; set; } + /// /// Block height at which the ghost contract appeared first time /// diff --git a/Tzkt.Api/Models/Accounts/Rollup.cs b/Tzkt.Api/Models/Accounts/Rollup.cs index 3f31d52c1..a4ce343c8 100644 --- a/Tzkt.Api/Models/Accounts/Rollup.cs +++ b/Tzkt.Api/Models/Accounts/Rollup.cs @@ -50,6 +50,21 @@ public class Rollup : Account /// public int TokenTransfersCount { get; set; } + /// + /// Number of tickets the account owns. + /// + public int ActiveTicketsCount { get; set; } + + /// + /// Number of tickets the account ever owned. + /// + public int TicketBalancesCount { get; set; } + + /// + /// Number of ticket transfers from/to the account. + /// + public int TicketTransfersCount { get; set; } + /// /// Number of transaction operations related to the account /// diff --git a/Tzkt.Api/Models/Accounts/SmartRollup.cs b/Tzkt.Api/Models/Accounts/SmartRollup.cs index 0090c2c7e..1ace94ef8 100644 --- a/Tzkt.Api/Models/Accounts/SmartRollup.cs +++ b/Tzkt.Api/Models/Accounts/SmartRollup.cs @@ -104,6 +104,21 @@ public class SmartRollup : Account /// public int TokenTransfersCount { get; set; } + /// + /// Number of tickets the account owns. + /// + public int ActiveTicketsCount { get; set; } + + /// + /// Number of tickets the account ever owned. + /// + public int TicketBalancesCount { get; set; } + + /// + /// Number of ticket transfers from/to the account. + /// + public int TicketTransfersCount { get; set; } + /// /// Number of transaction operations related to the account /// diff --git a/Tzkt.Api/Models/Accounts/User.cs b/Tzkt.Api/Models/Accounts/User.cs index 7b10d81f9..14c6ebf74 100644 --- a/Tzkt.Api/Models/Accounts/User.cs +++ b/Tzkt.Api/Models/Accounts/User.cs @@ -98,6 +98,21 @@ public class User : Account /// public int TokenTransfersCount { get; set; } + /// + /// Number of tickets the account owns. + /// + public int ActiveTicketsCount { get; set; } + + /// + /// Number of tickets the account ever owned. + /// + public int TicketBalancesCount { get; set; } + + /// + /// Number of ticket transfers from/to the account. + /// + public int TicketTransfersCount { get; set; } + /// /// Number of account activation operations. Are used to activate accounts that were recommended allocations of /// tezos tokens for donations to the Tezos Foundation’s fundraiser diff --git a/Tzkt.Api/Models/Operations/SmartRollupExecuteOperation.cs b/Tzkt.Api/Models/Operations/SmartRollupExecuteOperation.cs index 53388b87e..0b995288e 100644 --- a/Tzkt.Api/Models/Operations/SmartRollupExecuteOperation.cs +++ b/Tzkt.Api/Models/Operations/SmartRollupExecuteOperation.cs @@ -91,6 +91,11 @@ public class SmartRollupExecuteOperation : Operation /// public List Errors { get; set; } + /// + /// Number of ticket transfers produced by the operation, or `null` if there are no transfers + /// + public int? TicketTransfersCount { get; set; } + #region injecting /// /// Injected historical quote at the time of operation diff --git a/Tzkt.Api/Models/Operations/TransactionOperation.cs b/Tzkt.Api/Models/Operations/TransactionOperation.cs index 0945057a2..35b31d6a1 100644 --- a/Tzkt.Api/Models/Operations/TransactionOperation.cs +++ b/Tzkt.Api/Models/Operations/TransactionOperation.cs @@ -150,6 +150,11 @@ public class TransactionOperation : Operation /// public int? TokenTransfersCount { get; set; } + /// + /// Number of ticket transfers produced by the operation, or `null` if there are no transfers + /// + public int? TicketTransfersCount { get; set; } + /// /// Number of events produced by the operation, or `null` if there are no events /// diff --git a/Tzkt.Api/Models/Operations/TransferTicketOperation.cs b/Tzkt.Api/Models/Operations/TransferTicketOperation.cs index 804db3c3c..95d544ec2 100644 --- a/Tzkt.Api/Models/Operations/TransferTicketOperation.cs +++ b/Tzkt.Api/Models/Operations/TransferTicketOperation.cs @@ -95,6 +95,11 @@ public class TransferTicketOperation : Operation /// public string Entrypoint { get; set; } + /// + /// Number of ticket transfers produced by the operation, or `null` if there are no transfers + /// + public int? TicketTransfersCount { get; set; } + /// /// Micheline type of the content /// diff --git a/Tzkt.Api/Models/Tickets/Ticket.cs b/Tzkt.Api/Models/Tickets/Ticket.cs new file mode 100644 index 000000000..b8ba4670f --- /dev/null +++ b/Tzkt.Api/Models/Tickets/Ticket.cs @@ -0,0 +1,103 @@ +using Netezos.Encoding; + +namespace Tzkt.Api.Models +{ + public class Ticket + { + /// + /// Internal TzKT id. + /// **[sortable]** + /// + public long Id { get; set; } + + /// + /// Contract, issued the ticket. + /// + public Alias Ticketer { get; set; } + + /// + /// Ticket content type in Micheline format. + /// + public IMicheline RawType { get; set; } + + /// + /// Ticket content in Micheline format. + /// + public IMicheline RawContent { get; set; } + + /// + /// Ticket content in JSON format. + /// + public RawJson Content { get; set; } + + /// + /// 32-bit hash of the ticket content type. + /// + public int TypeHash { get; set; } + + /// + /// 32-bit hash of the ticket content. + /// + public int ContentHash { get; set; } + + /// + /// Account, minted the ticket first. + /// + public Alias FirstMinter { get; set; } + + /// + /// Level of the block where the ticket was first seen. + /// **[sortable]** + /// + public int FirstLevel { get; set; } + + /// + /// Timestamp of the block where the ticket was first seen. + /// + public DateTime FirstTime { get; set; } + + /// + /// Level of the block where the ticket was last seen. + /// **[sortable]** + /// + public int LastLevel { get; set; } + + /// + /// Timestamp of the block where the ticket was last seen. + /// + public DateTime LastTime { get; set; } + + /// + /// Total number of transfers. + /// **[sortable]** + /// + public int TransfersCount { get; set; } + + /// + /// Total number of holders ever seen. + /// **[sortable]** + /// + public int BalancesCount { get; set; } + + /// + /// Total number of current holders. + /// **[sortable]** + /// + public int HoldersCount { get; set; } + + /// + /// Total amount minted. + /// + public string TotalMinted { get; set; } + + /// + /// Total amount burned. + /// + public string TotalBurned { get; set; } + + /// + /// Total amount exists. + /// + public string TotalSupply { get; set; } + } +} diff --git a/Tzkt.Api/Models/Tickets/TicketBalance.cs b/Tzkt.Api/Models/Tickets/TicketBalance.cs new file mode 100644 index 000000000..7fa4850b8 --- /dev/null +++ b/Tzkt.Api/Models/Tickets/TicketBalance.cs @@ -0,0 +1,57 @@ +namespace Tzkt.Api.Models +{ + public class TicketBalance + { + /// + /// Internal TzKT id. + /// **[sortable]** + /// + public long Id { get; set; } + + /// + /// Ticket info. + /// Click on the field to expand more details. + /// + public TicketInfo Ticket { get; set; } + + /// + /// Owner account. + /// Click on the field to expand more details. + /// + public Alias Account { get; set; } + + /// + /// Balance. + /// **[sortable]** + /// + public string Balance { get; set; } + + /// + /// Total number of transfers, affecting the ticket balance. + /// **[sortable]** + /// + public int TransfersCount { get; set; } + + /// + /// Level of the block where the ticket balance was first changed. + /// **[sortable]** + /// + public int FirstLevel { get; set; } + + /// + /// Timestamp of the block where the ticket balance was first changed. + /// + public DateTime FirstTime { get; set; } + + /// + /// Level of the block where the ticket balance was last changed. + /// **[sortable]** + /// + public int LastLevel { get; set; } + + /// + /// Timestamp of the block where the ticket balance was last changed. + /// + public DateTime LastTime { get; set; } + } +} diff --git a/Tzkt.Api/Models/Tickets/TicketBalanceShort.cs b/Tzkt.Api/Models/Tickets/TicketBalanceShort.cs new file mode 100644 index 000000000..8116d6c7e --- /dev/null +++ b/Tzkt.Api/Models/Tickets/TicketBalanceShort.cs @@ -0,0 +1,28 @@ +namespace Tzkt.Api.Models +{ + public class TicketBalanceShort + { + /// + /// Internal TzKT id. + /// **[sortable]** + /// + public long Id { get; set; } + + /// + /// Ticket info. + /// Click on the field to expand more details. + /// + public TicketInfoShort Ticket { get; set; } + + /// + /// Owner account. + /// Click on the field to expand more details. + /// + public Alias Account { get; set; } + + /// + /// Balance. + /// + public string Balance { get; set; } + } +} diff --git a/Tzkt.Api/Models/Tickets/TicketInfo.cs b/Tzkt.Api/Models/Tickets/TicketInfo.cs new file mode 100644 index 000000000..d35fb23ae --- /dev/null +++ b/Tzkt.Api/Models/Tickets/TicketInfo.cs @@ -0,0 +1,47 @@ +using Netezos.Encoding; + +namespace Tzkt.Api.Models +{ + public class TicketInfo + { + /// + /// Internal TzKT id. + /// + public long Id { get; set; } + + /// + /// Contract, issued the ticket. + /// + public Alias Ticketer { get; set; } + + /// + /// Ticket content type in Micheline format. + /// + public IMicheline RawType { get; set; } + + /// + /// Ticket content in Micheline format. + /// + public IMicheline RawContent { get; set; } + + /// + /// Ticket content in JSON format. + /// + public RawJson Content { get; set; } + + /// + /// 32-bit hash of the ticket content type. + /// + public int TypeHash { get; set; } + + /// + /// 32-bit hash of the ticket content. + /// + public int ContentHash { get; set; } + + /// + /// Total amount exists. + /// + public string TotalSupply { get; set; } + } +} diff --git a/Tzkt.Api/Models/Tickets/TicketInfoShort.cs b/Tzkt.Api/Models/Tickets/TicketInfoShort.cs new file mode 100644 index 000000000..aca108074 --- /dev/null +++ b/Tzkt.Api/Models/Tickets/TicketInfoShort.cs @@ -0,0 +1,46 @@ +using Netezos.Encoding; +using NJsonSchema.Annotations; + +namespace Tzkt.Api.Models +{ + public class TicketInfoShort + { + /// + /// Internal TzKT id. + /// + public long Id { get; set; } + + /// + /// Contract, issued the ticket. + /// + public Alias Ticketer { get; set; } + + /// + /// Ticket content type in Micheline format. + /// + public IMicheline RawType { get; set; } + + /// + /// Ticket content in Micheline format. + /// + public IMicheline RawContent { get; set; } + + /// + /// Ticket content in JSON format. + /// + [JsonSchemaType(typeof(object), IsNullable = true)] + public RawJson Content { get; set; } + + /// + /// 32-bit hash of the ticket content type. + /// This field can be used for searching similar tickets (which have the same type). + /// + public int TypeHash { get; set; } + + /// + /// 32-bit hash of the ticket content. + /// This field can be used for searching same tickets (which have the same content). + /// + public int ContentHash { get; set; } + } +} diff --git a/Tzkt.Api/Models/Tickets/TicketTransfer.cs b/Tzkt.Api/Models/Tickets/TicketTransfer.cs new file mode 100644 index 000000000..34713d958 --- /dev/null +++ b/Tzkt.Api/Models/Tickets/TicketTransfer.cs @@ -0,0 +1,61 @@ +namespace Tzkt.Api.Models +{ + public class TicketTransfer + { + /// + /// Internal TzKT id. + /// **[sortable]** + /// + public long Id { get; set; } + + /// + /// Level of the block, at which the transfer was made. + /// **[sortable]** + /// + public int Level { get; set; } + + /// + /// Timestamp of the block, at which the transfer was made. + /// + public DateTime Timestamp { get; set; } + + /// + /// Ticket info. + /// Click on the field to expand more details. + /// + public TicketInfo Ticket { get; set; } + + /// + /// Sender account. + /// Click on the field to expand more details. + /// + public Alias From { get; set; } + + /// + /// Recepient account. + /// Click on the field to expand more details. + /// + public Alias To { get; set; } + + /// + /// Amount of tickets transferred. + /// **[sortable]** + /// + public string Amount { get; set; } + + /// + /// Internal TzKT id of the transaction operation, caused the ticket transfer. + /// + public long? TransactionId { get; set; } + + /// + /// Internal TzKT id of the transfer_ticket operation, caused the ticket transfer. + /// + public long? TransferTicketId { get; set; } + + /// + /// Internal TzKT id of the smart_rollup_execute operation, caused the ticket transfer. + /// + public long? SmartRollupExecuteId { get; set; } + } +} diff --git a/Tzkt.Api/Parameters/Binders/MichelineBinder.cs b/Tzkt.Api/Parameters/Binders/MichelineBinder.cs new file mode 100644 index 000000000..67789b7e4 --- /dev/null +++ b/Tzkt.Api/Parameters/Binders/MichelineBinder.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Tzkt.Api +{ + public class MichelineBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var model = bindingContext.ModelName; + var hasValue = false; + + if (!bindingContext.TryGetMicheline($"{model}", ref hasValue, out var value)) + return Task.CompletedTask; + + if (!bindingContext.TryGetMicheline($"{model}.eq", ref hasValue, out var eq)) + return Task.CompletedTask; + + if (!bindingContext.TryGetMicheline($"{model}.ne", ref hasValue, out var ne)) + return Task.CompletedTask; + + if (!bindingContext.TryGetMichelineList($"{model}.in", ref hasValue, out var @in)) + return Task.CompletedTask; + + if (!bindingContext.TryGetMichelineList($"{model}.ni", ref hasValue, out var ni)) + return Task.CompletedTask; + + if (!hasValue) + { + bindingContext.Result = ModelBindingResult.Success(null); + return Task.CompletedTask; + } + + bindingContext.Result = ModelBindingResult.Success(new MichelineParameter + { + Eq = value ?? eq, + Ne = ne, + In = @in, + Ni = ni + }); + + return Task.CompletedTask; + } + } +} diff --git a/Tzkt.Api/Parameters/MichelineParameter.cs b/Tzkt.Api/Parameters/MichelineParameter.cs new file mode 100644 index 000000000..e11152880 --- /dev/null +++ b/Tzkt.Api/Parameters/MichelineParameter.cs @@ -0,0 +1,71 @@ +using System.Text; +using Microsoft.AspNetCore.Mvc; +using Netezos.Encoding; +using NJsonSchema.Annotations; + +namespace Tzkt.Api +{ + [ModelBinder(BinderType = typeof(MichelineBinder))] + [JsonSchemaExtensionData("x-tzkt-extension", "query-parameter")] + public class MichelineParameter : INormalizable + { + /// + /// **Equal** filter mode (optional, i.e. `param.eq=123` is the same as `param=123`). \ + /// Specify Micheline JSON value to get items where the specified field is equal to the specified value. + /// + /// Example: `?type={"prim":"string"}`. + /// + public IMicheline Eq { get; set; } + + /// + /// **Not equal** filter mode. \ + /// Specify Micheline JSON value to get items where the specified field is not equal to the specified value. + /// + /// Example: `?balance.ne={"prim":"string"}`. + /// + public IMicheline Ne { get; set; } + + /// + /// **In list** (any of) filter mode. \ + /// Specify a comma-separated list of Micheline JSON values where the specified field is equal to one of the specified values. + /// + /// Example: `?type.in={"prim":"string"},{"prim":"nat"}`. + /// + public List In { get; set; } + + /// + /// **Not in list** (none of) filter mode. \ + /// Specify a comma-separated list of Micheline JSON values where the specified field is not equal to all the specified values. + /// + /// Example: `?type.ni={"prim":"string"},{"prim":"nat"}`. + /// + public List Ni { get; set; } + + public string Normalize(string name) + { + var sb = new StringBuilder(); + + if (Eq != null) + { + sb.Append($"{name}.eq={Hex.Convert(Eq.ToBytes())}&"); + } + + if (Ne != null) + { + sb.Append($"{name}.ne={Hex.Convert(Ne.ToBytes())}&"); + } + + if (In?.Count > 0) + { + sb.Append($"{name}.in={string.Join(",", In.Select(x => Hex.Convert(x.ToBytes())).OrderBy(x => x))}&"); + } + + if (Ni?.Count > 0) + { + sb.Append($"{name}.ni={string.Join(",", Ni.Select(x => Hex.Convert(x.ToBytes())).OrderBy(x => x))}&"); + } + + return sb.ToString(); + } + } +} diff --git a/Tzkt.Api/Parameters/TicketBalanceFilter.cs b/Tzkt.Api/Parameters/TicketBalanceFilter.cs new file mode 100644 index 000000000..c5bba356a --- /dev/null +++ b/Tzkt.Api/Parameters/TicketBalanceFilter.cs @@ -0,0 +1,81 @@ +using NSwag.Annotations; +using Tzkt.Api.Services; + +namespace Tzkt.Api +{ + public class TicketBalanceFilter : INormalizable + { + /// + /// Filter by internal TzKT id. + /// Click on the parameter to expand more details. + /// + public Int64Parameter id { get; set; } + + /// + /// Filter by ticket. + /// Click on the parameter to expand more details. + /// + public TicketInfoFilter ticket { get; set; } + + /// + /// Filter by account address. + /// Click on the parameter to expand more details. + /// + public AccountParameter account { get; set; } + + /// + /// Filter by balance. + /// Click on the parameter to expand more details. + /// + public NatParameter balance { get; set; } + + /// + /// Filter by number of transfers. + /// Click on the parameter to expand more details. + /// + public Int32Parameter transfersCount { get; set; } + + /// + /// Filter by level of the block where the balance was first changed. + /// Click on the parameter to expand more details. + /// + public Int32Parameter firstLevel { get; set; } + + /// + /// Filter by timestamp (ISO 8601) of the block where the balance was first changed. + /// Click on the parameter to expand more details. + /// + public TimestampParameter firstTime { get; set; } + + /// + /// Filter by level of the block where the balance was last changed. + /// Click on the parameter to expand more details. + /// + public Int32Parameter lastLevel { get; set; } + + /// + /// Filter by timestamp (ISO 8601) of the block where the balance was last changed. + /// Click on the parameter to expand more details. + /// + public TimestampParameter lastTime { get; set; } + + [OpenApiIgnore] + public bool Empty => + id == null && + (ticket == null || ticket.Empty) && + account == null && + balance == null && + transfersCount == null && + firstLevel == null && + firstTime == null && + lastLevel == null && + lastTime == null; + + public string Normalize(string name) + { + return ResponseCacheService.BuildKey("", + ("id", id), ("ticket", ticket), ("account", account), ("balance", balance), ("transfersCount", transfersCount), + ("firstLevel", firstLevel), ("firstTime", firstTime), ("lastLevel", lastLevel), ("lastTime", lastTime)); + } + } +} diff --git a/Tzkt.Api/Parameters/TicketBalanceShortFilter.cs b/Tzkt.Api/Parameters/TicketBalanceShortFilter.cs new file mode 100644 index 000000000..042a647ef --- /dev/null +++ b/Tzkt.Api/Parameters/TicketBalanceShortFilter.cs @@ -0,0 +1,45 @@ +using NSwag.Annotations; +using Tzkt.Api.Services; + +namespace Tzkt.Api +{ + public class TicketBalanceShortFilter : INormalizable + { + /// + /// Filter by internal TzKT id. + /// Click on the parameter to expand more details. + /// + public Int64Parameter id { get; set; } + + /// + /// Filter by ticket. + /// Click on the parameter to expand more details. + /// + public TicketInfoShortFilter ticket { get; set; } + + /// + /// Filter by account address. + /// Click on the parameter to expand more details. + /// + public AccountParameter account { get; set; } + + /// + /// Filter by balance. + /// Click on the parameter to expand more details. + /// + public NatParameter balance { get; set; } + + [OpenApiIgnore] + public bool Empty => + id == null && + (ticket == null || ticket.Empty) && + account == null && + balance == null; + + public string Normalize(string name) + { + return ResponseCacheService.BuildKey("", + ("id", id), ("ticket", ticket), ("account", account), ("balance", balance)); + } + } +} diff --git a/Tzkt.Api/Parameters/TicketFilter.cs b/Tzkt.Api/Parameters/TicketFilter.cs new file mode 100644 index 000000000..a06ea9184 --- /dev/null +++ b/Tzkt.Api/Parameters/TicketFilter.cs @@ -0,0 +1,105 @@ +using NSwag.Annotations; +using Tzkt.Api.Services; + +namespace Tzkt.Api +{ + public class TicketFilter : INormalizable + { + /// + /// Filter by internal TzKT id. + /// Click on the parameter to expand more details. + /// + public Int64Parameter id { get; set; } + + /// + /// Filter by ticketer address. + /// Click on the parameter to expand more details. + /// + public AccountParameter ticketer { get; set; } + + /// + /// Filter by ticket content type in Micheline format. + /// Click on the parameter to expand more details. + /// + public MichelineParameter rawType { get; set; } + + /// + /// Filter by ticket content in Micheline format. + /// Click on the parameter to expand more details. + /// + public MichelineParameter rawContent { get; set; } + + /// + /// Filter by ticket content in JSON format. + /// Note, this parameter supports the following format: `content{.path?}{.mode?}=...`, + /// so you can specify a path to a particular field to filter by (for example, `?content.color.in=red,green`). + /// Click on the parameter to expand more details. + /// + public JsonParameter content { get; set; } + + /// + /// Filter by 32-bit hash of ticket content type. + /// Click on the parameter to expand more details. + /// + public Int32Parameter typeHash { get; set; } + + /// + /// Filter by 32-bit hash of ticket content. + /// Click on the parameter to expand more details. + /// + public Int32Parameter contentHash { get; set; } + + /// + /// Filter by address of the first minter. + /// Click on the parameter to expand more details. + /// + public AccountParameter firstMinter { get; set; } + + /// + /// Filter by level of the block where the ticket was first seen. + /// Click on the parameter to expand more details. + /// + public Int32Parameter firstLevel { get; set; } + + /// + /// Filter by timestamp (ISO 8601) of the block where the ticket was first seen. + /// Click on the parameter to expand more details. + /// + public TimestampParameter firstTime { get; set; } + + /// + /// Filter by level of the block where the ticket was last seen. + /// Click on the parameter to expand more details. + /// + public Int32Parameter lastLevel { get; set; } + + /// + /// Filter by timestamp (ISO 8601) of the block where the ticket was last seen. + /// Click on the parameter to expand more details. + /// + public TimestampParameter lastTime { get; set; } + + [OpenApiIgnore] + public bool Empty => + id == null && + ticketer == null && + rawType == null && + rawContent == null && + content == null && + typeHash == null && + contentHash == null && + firstMinter == null && + firstLevel == null && + firstTime == null && + lastLevel == null && + lastTime == null; + + public string Normalize(string name) + { + return ResponseCacheService.BuildKey("", + ("id", id), ("ticketer", ticketer), ("rawType", rawType), ("rawContent", rawContent), ("content", content), + ("typeHash", typeHash), ("contentHash", contentHash), ("firstMinter", firstMinter), ("firstLevel", firstLevel), + ("firstTime", firstTime), ("lastLevel", lastLevel), ("lastTime", lastTime)); + } + } +} diff --git a/Tzkt.Api/Parameters/TicketInfoFilter.cs b/Tzkt.Api/Parameters/TicketInfoFilter.cs new file mode 100644 index 000000000..0c8defed4 --- /dev/null +++ b/Tzkt.Api/Parameters/TicketInfoFilter.cs @@ -0,0 +1,69 @@ +using NSwag.Annotations; +using Tzkt.Api.Services; + +namespace Tzkt.Api +{ + public class TicketInfoFilter : INormalizable + { + /// + /// Filter by internal TzKT id. + /// Click on the parameter to expand more details. + /// + public Int64Parameter id { get; set; } + + /// + /// Filter by ticketer address. + /// Click on the parameter to expand more details. + /// + public AccountParameter ticketer { get; set; } + + /// + /// Filter by ticket content type in Micheline format. + /// Click on the parameter to expand more details. + /// + public MichelineParameter rawType { get; set; } + + /// + /// Filter by ticket content in Micheline format. + /// Click on the parameter to expand more details. + /// + public MichelineParameter rawContent { get; set; } + + /// + /// Filter by ticket content in JSON format. + /// Note, this parameter supports the following format: `content{.path?}{.mode?}=...`, + /// so you can specify a path to a particular field to filter by (for example, `?content.color.in=red,green`). + /// Click on the parameter to expand more details. + /// + public JsonParameter content { get; set; } + + /// + /// Filter by 32-bit hash of ticket content type. + /// Click on the parameter to expand more details. + /// + public Int32Parameter typeHash { get; set; } + + /// + /// Filter by 32-bit hash of ticket content. + /// Click on the parameter to expand more details. + /// + public Int32Parameter contentHash { get; set; } + + [OpenApiIgnore] + public bool Empty => + id == null && + ticketer == null && + rawType == null && + rawContent == null && + content == null && + typeHash == null && + contentHash == null; + + public string Normalize(string name) + { + return ResponseCacheService.BuildKey("", + ("id", id), ("ticketer", ticketer), ("rawType", rawType), ("rawContent", rawContent), + ("content", content), ("typeHash", typeHash), ("contentHash", contentHash)); + } + } +} diff --git a/Tzkt.Api/Parameters/TicketInfoShortFilter.cs b/Tzkt.Api/Parameters/TicketInfoShortFilter.cs new file mode 100644 index 000000000..0812f01d7 --- /dev/null +++ b/Tzkt.Api/Parameters/TicketInfoShortFilter.cs @@ -0,0 +1,31 @@ +using NSwag.Annotations; +using Tzkt.Api.Services; + +namespace Tzkt.Api +{ + public class TicketInfoShortFilter : INormalizable + { + /// + /// Filter by internal TzKT id. + /// Click on the parameter to expand more details. + /// + public Int64Parameter id { get; set; } + + /// + /// Filter by ticketer address. + /// Click on the parameter to expand more details. + /// + public AccountParameter ticketer { get; set; } + + [OpenApiIgnore] + public bool Empty => + id == null && + ticketer == null; + + public string Normalize(string name) + { + return ResponseCacheService.BuildKey("", + ("id", id), ("ticketer", ticketer)); + } + } +} diff --git a/Tzkt.Api/Parameters/TicketTransferFilter.cs b/Tzkt.Api/Parameters/TicketTransferFilter.cs new file mode 100644 index 000000000..ec026bd50 --- /dev/null +++ b/Tzkt.Api/Parameters/TicketTransferFilter.cs @@ -0,0 +1,97 @@ +using NSwag.Annotations; +using Tzkt.Api.Services; + +namespace Tzkt.Api +{ + public class TicketTransferFilter : INormalizable + { + /// + /// Filter by internal TzKT id. + /// Click on the parameter to expand more details. + /// + public Int64Parameter id { get; set; } + + /// + /// Filter by level of the block where the transfer was made. + /// Click on the parameter to expand more details. + /// + public Int32Parameter level { get; set; } + + /// + /// Filter by timestamp (ISO 8601) of the block where the transfer was made. + /// Click on the parameter to expand more details. + /// + public TimestampParameter timestamp { get; set; } + + /// + /// Filter by ticket. + /// Click on the parameter to expand more details. + /// + public TicketInfoFilter ticket { get; set; } + + /// + /// Filter by any of the specified fields (`from` or `to`). + /// Example: `anyof.from.to=tz1...` will return transfers where `from` OR `to` is equal to the specified value. + /// This parameter is useful when you need to get both incoming and outgoing transfers of the account at once. + /// Click on the parameter to expand more details. + /// + public AnyOfParameter anyof { get; set; } + + /// + /// Filter by sender address. + /// Click on the parameter to expand more details. + /// + public AccountParameter from { get; set; } + + /// + /// Filter by recepient address. + /// Click on the parameter to expand more details. + /// + public AccountParameter to { get; set; } + + /// + /// Filter by amount. + /// Click on the parameter to expand more details. + /// + public NatParameter amount { get; set; } + + /// + /// Filter by id of the transaction, caused the ticket transfer. + /// Click on the parameter to expand more details. + /// + public Int64NullParameter transactionId { get; set; } + + /// + /// Filter by id of the transfer_ticket operation, caused the ticket transfer. + /// Click on the parameter to expand more details. + /// + public Int64NullParameter transferTicketId { get; set; } + + /// + /// Filter by id of the smart_rollup_execute operation, caused the ticket transfer. + /// Click on the parameter to expand more details. + /// + public Int64NullParameter smartRollupExecuteId { get; set; } + + [OpenApiIgnore] + public bool Empty => + id == null && + level == null && + timestamp == null && + (ticket == null || ticket.Empty) && + anyof == null && + from == null && + to == null && + amount == null && + transactionId == null && + transferTicketId == null && + smartRollupExecuteId == null; + + public string Normalize(string name) + { + return ResponseCacheService.BuildKey("", + ("id", id), ("level", level), ("timestamp", timestamp), ("ticket", ticket), ("anyof", anyof), ("from", from), ("to", to), + ("amount", amount), ("transactionId", transactionId), ("transferTicketId", transferTicketId), ("smartRollupExecuteId", smartRollupExecuteId)); + } + } +} diff --git a/Tzkt.Api/Program.cs b/Tzkt.Api/Program.cs index 5f5d68cb7..9c7877c96 100644 --- a/Tzkt.Api/Program.cs +++ b/Tzkt.Api/Program.cs @@ -65,6 +65,7 @@ builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); +builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); @@ -120,6 +121,12 @@ builder.Services.AddTransient>(); builder.Services.AddTransient>(); + builder.Services.AddTransient>(); + builder.Services.AddTransient>(); + + builder.Services.AddTransient>(); + builder.Services.AddTransient>(); + builder.Services.AddTransient>(); builder.Services.AddTransient>(); diff --git a/Tzkt.Api/Repositories/AccountRepository.Bakers.cs b/Tzkt.Api/Repositories/AccountRepository.Bakers.cs index 5c4ffb947..6f6b9d048 100644 --- a/Tzkt.Api/Repositories/AccountRepository.Bakers.cs +++ b/Tzkt.Api/Repositories/AccountRepository.Bakers.cs @@ -59,6 +59,9 @@ public partial class AccountRepository : DbConnection ActiveTokensCount = delegat.ActiveTokensCount, TokenBalancesCount = delegat.TokenBalancesCount, TokenTransfersCount = delegat.TokenTransfersCount, + ActiveTicketsCount = delegat.ActiveTicketsCount, + TicketBalancesCount = delegat.TicketBalancesCount, + TicketTransfersCount = delegat.TicketTransfersCount, NumDelegators = delegat.DelegatorsCount, NumBlocks = delegat.BlocksCount, NumDelegations = delegat.DelegationsCount, @@ -171,6 +174,9 @@ public async Task GetDelegatesCount(BoolParameter active) ActiveTokensCount = row.ActiveTokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, NumDelegators = row.DelegatorsCount, NumBlocks = row.BlocksCount, NumDelegations = row.DelegationsCount, @@ -245,6 +251,9 @@ public async Task GetDelegates( case "activeTokensCount": columns.Add(@"""ActiveTokensCount"""); break; case "tokenBalancesCount": columns.Add(@"""TokenBalancesCount"""); break; case "tokenTransfersCount": columns.Add(@"""TokenTransfersCount"""); break; + case "activeTicketsCount": columns.Add(@"""ActiveTicketsCount"""); break; + case "ticketBalancesCount": columns.Add(@"""TicketBalancesCount"""); break; + case "ticketTransfersCount": columns.Add(@"""TicketTransfersCount"""); break; case "numDelegators": columns.Add(@"""DelegatorsCount"""); break; case "numBlocks": columns.Add(@"""BlocksCount"""); break; case "numDelegations": columns.Add(@"""DelegationsCount"""); break; @@ -429,6 +438,18 @@ public async Task GetDelegates( foreach (var row in rows) result[j++][i] = row.TokenTransfersCount; break; + case "activeTicketsCount": + foreach (var row in rows) + result[j++][i] = row.ActiveTicketsCount; + break; + case "ticketBalancesCount": + foreach (var row in rows) + result[j++][i] = row.TicketBalancesCount; + break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++][i] = row.TicketTransfersCount; + break; case "numDelegators": foreach (var row in rows) result[j++][i] = row.DelegatorsCount; @@ -653,6 +674,9 @@ public async Task GetDelegates( case "activeTokensCount": columns.Add(@"""ActiveTokensCount"""); break; case "tokenBalancesCount": columns.Add(@"""TokenBalancesCount"""); break; case "tokenTransfersCount": columns.Add(@"""TokenTransfersCount"""); break; + case "activeTicketsCount": columns.Add(@"""ActiveTicketsCount"""); break; + case "ticketBalancesCount": columns.Add(@"""TicketBalancesCount"""); break; + case "ticketTransfersCount": columns.Add(@"""TicketTransfersCount"""); break; case "numDelegators": columns.Add(@"""DelegatorsCount"""); break; case "numBlocks": columns.Add(@"""BlocksCount"""); break; case "numDelegations": columns.Add(@"""DelegationsCount"""); break; @@ -833,6 +857,18 @@ public async Task GetDelegates( foreach (var row in rows) result[j++] = row.TokenTransfersCount; break; + case "activeTicketsCount": + foreach (var row in rows) + result[j++] = row.ActiveTicketsCount; + break; + case "ticketBalancesCount": + foreach (var row in rows) + result[j++] = row.TicketBalancesCount; + break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++] = row.TicketTransfersCount; + break; case "numDelegators": foreach (var row in rows) result[j++] = row.DelegatorsCount; diff --git a/Tzkt.Api/Repositories/AccountRepository.Contracts.cs b/Tzkt.Api/Repositories/AccountRepository.Contracts.cs index 06337a204..acf45d54d 100644 --- a/Tzkt.Api/Repositories/AccountRepository.Contracts.cs +++ b/Tzkt.Api/Repositories/AccountRepository.Contracts.cs @@ -45,6 +45,10 @@ async Task> QueryContractsAsync(bool includeStorage, Contra case "tokensCount": columns.Add(@"c.""TokensCount"""); break; case "tokenBalancesCount": columns.Add(@"c.""TokenBalancesCount"""); break; case "tokenTransfersCount": columns.Add(@"c.""TokenTransfersCount"""); break; + case "ticketsCount": columns.Add(@"c.""TicketsCount"""); break; + case "activeTicketsCount": columns.Add(@"c.""ActiveTicketsCount"""); break; + case "ticketBalancesCount": columns.Add(@"c.""TicketBalancesCount"""); break; + case "ticketTransfersCount": columns.Add(@"c.""TicketTransfersCount"""); break; case "numDelegations": columns.Add(@"c.""DelegationsCount"""); break; case "numOriginations": columns.Add(@"c.""OriginationsCount"""); break; case "numTransactions": columns.Add(@"c.""TransactionsCount"""); break; @@ -162,6 +166,10 @@ public async Task GetContract(string address, bool legacy) ActiveTokensCount = contract.ActiveTokensCount, TokenBalancesCount = contract.TokenBalancesCount, TokenTransfersCount = contract.TokenTransfersCount, + TicketsCount = contract.TicketsCount, + ActiveTicketsCount = contract.ActiveTicketsCount, + TicketBalancesCount = contract.TicketBalancesCount, + TicketTransfersCount = contract.TicketTransfersCount, NumDelegations = contract.DelegationsCount, NumOriginations = contract.OriginationsCount, NumReveals = contract.RevealsCount, @@ -251,6 +259,10 @@ public async Task> GetContracts(bool includeStorage, Contr ActiveTokensCount = row.ActiveTokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + TicketsCount = row.TicketsCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, NumDelegations = row.DelegationsCount, NumOriginations = row.OriginationsCount, NumReveals = row.RevealsCount, @@ -402,6 +414,22 @@ public async Task GetContracts(bool includeStorage, ContractFilter f foreach (var row in rows) result[j++][i] = row.TokenTransfersCount; break; + case "ticketsCount": + foreach (var row in rows) + result[j++][i] = row.TicketsCount; + break; + case "activeTicketsCount": + foreach (var row in rows) + result[j++][i] = row.ActiveTicketsCount; + break; + case "ticketBalancesCount": + foreach (var row in rows) + result[j++][i] = row.TicketBalancesCount; + break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++][i] = row.TicketTransfersCount; + break; case "numDelegations": foreach (var row in rows) result[j++][i] = row.DelegationsCount; diff --git a/Tzkt.Api/Repositories/AccountRepository.cs b/Tzkt.Api/Repositories/AccountRepository.cs index c4a1bc585..64b1327e5 100644 --- a/Tzkt.Api/Repositories/AccountRepository.cs +++ b/Tzkt.Api/Repositories/AccountRepository.cs @@ -87,6 +87,9 @@ public async Task Get(string address, bool legacy) ActiveTokensCount = delegat.ActiveTokensCount, TokenBalancesCount = delegat.TokenBalancesCount, TokenTransfersCount = delegat.TokenTransfersCount, + ActiveTicketsCount = delegat.ActiveTicketsCount, + TicketBalancesCount = delegat.TicketBalancesCount, + TicketTransfersCount = delegat.TicketTransfersCount, NumDelegators = delegat.DelegatorsCount, NumBlocks = delegat.BlocksCount, NumDelegations = delegat.DelegationsCount, @@ -165,6 +168,9 @@ public async Task Get(string address, bool legacy) ActiveTokensCount = user.ActiveTokensCount, TokenBalancesCount = user.TokenBalancesCount, TokenTransfersCount = user.TokenTransfersCount, + ActiveTicketsCount = user.ActiveTicketsCount, + TicketBalancesCount = user.TicketBalancesCount, + TicketTransfersCount = user.TicketTransfersCount, NumDelegations = user.DelegationsCount, NumOriginations = user.OriginationsCount, NumReveals = user.RevealsCount, @@ -236,6 +242,10 @@ public async Task Get(string address, bool legacy) TokensCount = contract.TokensCount, TokenBalancesCount = contract.TokenBalancesCount, TokenTransfersCount = contract.TokenTransfersCount, + TicketsCount = contract.TicketsCount, + ActiveTicketsCount = contract.ActiveTicketsCount, + TicketBalancesCount = contract.TicketBalancesCount, + TicketTransfersCount = contract.TicketTransfersCount, NumDelegations = contract.DelegationsCount, NumOriginations = contract.OriginationsCount, NumReveals = contract.RevealsCount, @@ -273,6 +283,9 @@ public async Task Get(string address, bool legacy) ActiveTokensCount = rollup.ActiveTokensCount, TokenBalancesCount = rollup.TokenBalancesCount, TokenTransfersCount = rollup.TokenTransfersCount, + ActiveTicketsCount = rollup.ActiveTicketsCount, + TicketBalancesCount = rollup.TicketBalancesCount, + TicketTransfersCount = rollup.TicketTransfersCount, NumTransactions = rollup.TransactionsCount, Metadata = legacy ? rollup.Profile : null, Extras = legacy ? null : rollup.Extras @@ -295,6 +308,9 @@ public async Task Get(string address, bool legacy) ActiveTokensCount = rollup.ActiveTokensCount, TokenBalancesCount = rollup.TokenBalancesCount, TokenTransfersCount = rollup.TokenTransfersCount, + ActiveTicketsCount = rollup.ActiveTicketsCount, + TicketBalancesCount = rollup.TicketBalancesCount, + TicketTransfersCount = rollup.TicketTransfersCount, NumTransactions = rollup.TransactionsCount, SmartRollupCementCount = rollup.SmartRollupCementCount, SmartRollupExecuteCount = rollup.SmartRollupExecuteCount, @@ -328,6 +344,9 @@ public async Task Get(string address, bool legacy) ActiveTokensCount = ghost.ActiveTokensCount, TokenBalancesCount = ghost.TokenBalancesCount, TokenTransfersCount = ghost.TokenTransfersCount, + ActiveTicketsCount = ghost.ActiveTicketsCount, + TicketBalancesCount = ghost.TicketBalancesCount, + TicketTransfersCount = ghost.TicketTransfersCount, FirstActivity = ghost.FirstLevel, FirstActivityTime = Time[ghost.FirstLevel], LastActivity = ghost.LastLevel, @@ -439,6 +458,9 @@ public async Task> Get( ActiveTokensCount = row.ActiveTokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, NumDelegations = row.DelegationsCount, NumOriginations = row.OriginationsCount, NumReveals = row.RevealsCount, @@ -505,6 +527,9 @@ public async Task> Get( ActiveTokensCount = row.ActiveTokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, NumDelegators = row.DelegatorsCount, NumBlocks = row.BlocksCount, NumDelegations = row.DelegationsCount, @@ -587,6 +612,10 @@ public async Task> Get( TokensCount = row.TokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + TicketsCount = row.TicketsCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, NumDelegations = row.DelegationsCount, NumOriginations = row.OriginationsCount, NumReveals = row.RevealsCount, @@ -608,6 +637,9 @@ public async Task> Get( ActiveTokensCount = row.ActiveTokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, FirstActivity = row.FirstLevel, FirstActivityTime = Time[row.FirstLevel], LastActivity = row.LastLevel, @@ -640,6 +672,9 @@ public async Task> Get( ActiveTokensCount = row.ActiveTokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, NumTransactions = row.TransactionsCount }); break; @@ -661,6 +696,9 @@ public async Task> Get( ActiveTokensCount = row.ActiveTokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, NumTransactions = row.TransactionsCount, SmartRollupCementCount = row.SmartRollupCementCount, SmartRollupExecuteCount = row.SmartRollupExecuteCount, @@ -736,6 +774,9 @@ public async Task Get( case "activeTokensCount": columns.Add(@"""ActiveTokensCount"""); break; case "tokenBalancesCount": columns.Add(@"""TokenBalancesCount"""); break; case "tokenTransfersCount": columns.Add(@"""TokenTransfersCount"""); break; + case "activeTicketsCount": columns.Add(@"""ActiveTicketsCount"""); break; + case "ticketBalancesCount": columns.Add(@"""TicketBalancesCount"""); break; + case "ticketTransfersCount": columns.Add(@"""TicketTransfersCount"""); break; case "numDelegators": columns.Add(@"""DelegatorsCount"""); break; case "numBlocks": columns.Add(@"""BlocksCount"""); break; case "numDelegations": columns.Add(@"""DelegationsCount"""); break; @@ -792,6 +833,7 @@ public async Task Get( case "manager": columns.Add(@"""ManagerId"""); break; case "tokensCount": columns.Add(@"""TokensCount"""); break; case "eventsCount": columns.Add(@"""EventsCount"""); break; + case "ticketsCount": columns.Add(@"""TicketsCount"""); break; case "pvmKind": columns.Add(@"""PvmKind"""); break; case "genesisCommitment": columns.Add(@"""GenesisCommitment"""); break; @@ -949,6 +991,18 @@ public async Task Get( foreach (var row in rows) result[j++][i] = row.TokenTransfersCount; break; + case "activeTicketsCount": + foreach (var row in rows) + result[j++][i] = row.ActiveTicketsCount; + break; + case "ticketBalancesCount": + foreach (var row in rows) + result[j++][i] = row.TicketBalancesCount; + break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++][i] = row.TicketTransfersCount; + break; case "numDelegators": foreach (var row in rows) result[j++][i] = row.DelegatorsCount; @@ -1188,6 +1242,10 @@ public async Task Get( foreach (var row in rows) result[j++][i] = row.EventsCount; break; + case "ticketsCount": + foreach (var row in rows) + result[j++][i] = row.TicketsCount; + break; case "pvmKind": foreach (var row in rows) result[j++][i] = PvmKinds.ToString((int)row.PvmKind); @@ -1282,6 +1340,9 @@ public async Task Get( case "activeTokensCount": columns.Add(@"""ActiveTokensCount"""); break; case "tokenBalancesCount": columns.Add(@"""TokenBalancesCount"""); break; case "tokenTransfersCount": columns.Add(@"""TokenTransfersCount"""); break; + case "activeTicketsCount": columns.Add(@"""ActiveTicketsCount"""); break; + case "ticketBalancesCount": columns.Add(@"""TicketBalancesCount"""); break; + case "ticketTransfersCount": columns.Add(@"""TicketTransfersCount"""); break; case "numDelegators": columns.Add(@"""DelegatorsCount"""); break; case "numBlocks": columns.Add(@"""BlocksCount"""); break; case "numDelegations": columns.Add(@"""DelegationsCount"""); break; @@ -1338,6 +1399,7 @@ public async Task Get( case "manager": columns.Add(@"""ManagerId"""); break; case "tokensCount": columns.Add(@"""TokensCount"""); break; case "eventsCount": columns.Add(@"""EventsCount"""); break; + case "ticketsCount": columns.Add(@"""TicketsCount"""); break; case "pvmKind": columns.Add(@"""PvmKind"""); break; case "genesisCommitment": columns.Add(@"""GenesisCommitment"""); break; @@ -1491,6 +1553,18 @@ public async Task Get( foreach (var row in rows) result[j++] = row.TokenTransfersCount; break; + case "activeTicketsCount": + foreach (var row in rows) + result[j++] = row.ActiveTicketsCount; + break; + case "ticketBalancesCount": + foreach (var row in rows) + result[j++] = row.TicketBalancesCount; + break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++] = row.TicketTransfersCount; + break; case "numDelegators": foreach (var row in rows) result[j++] = row.DelegatorsCount; @@ -1730,6 +1804,10 @@ public async Task Get( foreach (var row in rows) result[j++] = row.EventsCount; break; + case "ticketsCount": + foreach (var row in rows) + result[j++] = row.TicketsCount; + break; case "pvmKind": foreach (var row in rows) result[j++] = PvmKinds.ToString((int)row.PvmKind); diff --git a/Tzkt.Api/Repositories/OperationRepository.SmartRollupExecuteOperation.cs b/Tzkt.Api/Repositories/OperationRepository.SmartRollupExecuteOperation.cs index 855883e99..1a29224e3 100644 --- a/Tzkt.Api/Repositories/OperationRepository.SmartRollupExecuteOperation.cs +++ b/Tzkt.Api/Repositories/OperationRepository.SmartRollupExecuteOperation.cs @@ -47,7 +47,7 @@ async Task> QuerySmartRollupExecuteOps(SrOperationFilter fi o."SmartRollupId", o."CommitmentId" as "cId", o."Errors", - o."Level", + o."TicketTransfers", c."InitiatorId" as "cInitiatorId", c."InboxLevel" as "cInboxLevel", @@ -105,6 +105,7 @@ async Task> QuerySmartRollupExecuteOps(SrOperationFilter fi } break; case "errors": columns.Add(@"o.""Errors"""); break; + case "ticketTransfersCount": columns.Add(@"o.""TicketTransfers"""); break; case "quote": columns.Add(@"o.""Level"""); break; } } @@ -163,6 +164,7 @@ public async Task> GetSmartRollupExecut FirstTime = Times[row.cFirstLevel], }, Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, + TicketTransfersCount = row.TicketTransfers, Quote = Quotes.Get(quote, row.Level) }); } @@ -313,6 +315,10 @@ public async Task GetSmartRollupExecuteOps(SrOperationFilter filter, foreach (var row in rows) result[j++][i] = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null; break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++][i] = row.TicketTransfers; + break; case "quote": foreach (var row in rows) result[j++][i] = Quotes.Get(quote, row.Level); diff --git a/Tzkt.Api/Repositories/OperationRepository.Transactions.cs b/Tzkt.Api/Repositories/OperationRepository.Transactions.cs index 4c419c13c..c8d6cb002 100644 --- a/Tzkt.Api/Repositories/OperationRepository.Transactions.cs +++ b/Tzkt.Api/Repositories/OperationRepository.Transactions.cs @@ -115,6 +115,7 @@ INNER JOIN ""Blocks"" as b Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, HasInternals = row.InternalOperations > 0, TokenTransfersCount = row.TokenTransfers, + TicketTransfersCount = row.TicketTransfers, EventsCount = row.EventsCount, Quote = Quotes.Get(quote, row.Level) }); @@ -190,6 +191,7 @@ INNER JOIN ""Blocks"" as b Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, HasInternals = row.InternalOperations > 0, TokenTransfersCount = row.TokenTransfers, + TicketTransfersCount = row.TicketTransfers, EventsCount = row.EventsCount, Quote = Quotes.Get(quote, row.Level) }); @@ -265,6 +267,7 @@ INNER JOIN ""Blocks"" as b Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, HasInternals = row.InternalOperations > 0, TokenTransfersCount = row.TokenTransfers, + TicketTransfersCount = row.TicketTransfers, EventsCount = row.EventsCount, Quote = Quotes.Get(quote, row.Level) }); @@ -319,6 +322,7 @@ public async Task> GetTransactions(Block block Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, HasInternals = row.InternalOperations > 0, TokenTransfersCount = row.TokenTransfers, + TicketTransfersCount = row.TicketTransfers, EventsCount = row.EventsCount, Quote = Quotes.Get(quote, block.Level) }); @@ -448,6 +452,7 @@ INNER JOIN ""Blocks"" as b Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, HasInternals = row.InternalOperations > 0, TokenTransfersCount = row.TokenTransfers, + TicketTransfersCount = row.TicketTransfers, EventsCount = row.EventsCount, Quote = Quotes.Get(quote, row.Level) }); @@ -522,6 +527,7 @@ public async Task GetTransactions( case "errors": columns.Add(@"o.""Errors"""); break; case "hasInternals": columns.Add(@"o.""InternalOperations"""); break; case "tokenTransfersCount": columns.Add(@"o.""TokenTransfers"""); break; + case "ticketTransfersCount": columns.Add(@"o.""TicketTransfers"""); break; case "eventsCount": columns.Add(@"o.""EventsCount"""); break; case "block": columns.Add(@"b.""Hash"""); @@ -713,6 +719,10 @@ public async Task GetTransactions( foreach (var row in rows) result[j++][i] = row.TokenTransfers; break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++][i] = row.TicketTransfers; + break; case "eventsCount": foreach (var row in rows) result[j++][i] = row.EventsCount; @@ -799,6 +809,7 @@ public async Task GetTransactions( case "errors": columns.Add(@"o.""Errors"""); break; case "hasInternals": columns.Add(@"o.""InternalOperations"""); break; case "tokenTransfersCount": columns.Add(@"o.""TokenTransfers"""); break; + case "ticketTransfersCount": columns.Add(@"o.""TicketTransfers"""); break; case "eventsCount": columns.Add(@"o.""EventsCount"""); break; case "block": columns.Add(@"b.""Hash"""); @@ -987,6 +998,10 @@ public async Task GetTransactions( foreach (var row in rows) result[j++] = row.TokenTransfers; break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++] = row.TicketTransfers; + break; case "eventsCount": foreach (var row in rows) result[j++] = row.EventsCount; diff --git a/Tzkt.Api/Repositories/OperationRepository.TransferTicket.cs b/Tzkt.Api/Repositories/OperationRepository.TransferTicket.cs index 35ec97bc5..6e93e1cc3 100644 --- a/Tzkt.Api/Repositories/OperationRepository.TransferTicket.cs +++ b/Tzkt.Api/Repositories/OperationRepository.TransferTicket.cs @@ -72,6 +72,7 @@ INNER JOIN ""Blocks"" as b }, Status = OpStatuses.ToString(row.Status), Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, + TicketTransfersCount = row.TicketTransfers, Quote = Quotes.Get(quote, row.Level) }); } @@ -119,6 +120,7 @@ INNER JOIN ""Blocks"" as b }, Status = OpStatuses.ToString(row.Status), Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, + TicketTransfersCount = row.TicketTransfers, Quote = Quotes.Get(quote, row.Level) }); } @@ -164,6 +166,7 @@ SELECT o.* }, Status = OpStatuses.ToString(row.Status), Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, + TicketTransfersCount = row.TicketTransfers, Quote = Quotes.Get(quote, row.Level) }); } @@ -240,6 +243,7 @@ INNER JOIN ""Blocks"" as b }, Status = OpStatuses.ToString(row.Status), Errors = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null, + TicketTransfersCount = row.TicketTransfers, Quote = Quotes.Get(quote, row.Level) }); } @@ -299,6 +303,7 @@ public async Task GetTransferTicketOps( columns.Add(@"b.""Hash"""); joins.Add(@"INNER JOIN ""Blocks"" as b ON b.""Level"" = o.""Level"""); break; + case "ticketTransfersCount": columns.Add(@"o.""TicketTransfers"""); break; case "quote": columns.Add(@"o.""Level"""); break; } } @@ -433,6 +438,10 @@ public async Task GetTransferTicketOps( foreach (var row in rows) result[j++][i] = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null; break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++][i] = row.TicketTransfers; + break; case "quote": foreach (var row in rows) result[j++][i] = Quotes.Get(quote, row.Level); @@ -496,6 +505,7 @@ public async Task GetTransferTicketOps( columns.Add(@"b.""Hash"""); joins.Add(@"INNER JOIN ""Blocks"" as b ON b.""Level"" = o.""Level"""); break; + case "ticketTransfersCount": columns.Add(@"o.""TicketTransfers"""); break; case "quote": columns.Add(@"o.""Level"""); break; } @@ -627,6 +637,10 @@ public async Task GetTransferTicketOps( foreach (var row in rows) result[j++] = row.Errors != null ? OperationErrorSerializer.Deserialize(row.Errors) : null; break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++] = row.TicketTransfers; + break; case "quote": foreach (var row in rows) result[j++] = Quotes.Get(quote, row.Level); diff --git a/Tzkt.Api/Repositories/SmartRollupsRepository.cs b/Tzkt.Api/Repositories/SmartRollupsRepository.cs index d0fcec40b..402c043c9 100644 --- a/Tzkt.Api/Repositories/SmartRollupsRepository.cs +++ b/Tzkt.Api/Repositories/SmartRollupsRepository.cs @@ -40,6 +40,9 @@ async Task> QuerySmartRollupsAsync(SrFilter filter, Paginat r."ActiveTokensCount", r."TokenBalancesCount", r."TokenTransfersCount", + r."ActiveTicketsCount", + r."TicketBalancesCount", + r."TicketTransfersCount", r."TransactionsCount", r."TransferTicketCount", r."SmartRollupCementCount", @@ -80,6 +83,9 @@ async Task> QuerySmartRollupsAsync(SrFilter filter, Paginat case "activeTokensCount": columns.Add(@"r.""ActiveTokensCount"""); break; case "tokenBalancesCount": columns.Add(@"r.""TokenBalancesCount"""); break; case "tokenTransfersCount": columns.Add(@"r.""TokenTransfersCount"""); break; + case "activeTicketsCount": columns.Add(@"r.""ActiveTicketsCount"""); break; + case "ticketBalancesCount": columns.Add(@"r.""TicketBalancesCount"""); break; + case "ticketTransfersCount": columns.Add(@"r.""TicketTransfersCount"""); break; case "numTransactions": columns.Add(@"r.""TransactionsCount"""); break; case "transferTicketCount": columns.Add(@"r.""TransferTicketCount"""); break; case "smartRollupCementCount": columns.Add(@"r.""SmartRollupCementCount"""); break; @@ -178,6 +184,9 @@ public async Task GetSmartRollup(string address) ActiveTokensCount = rollup.ActiveTokensCount, TokenBalancesCount = rollup.TokenBalancesCount, TokenTransfersCount = rollup.TokenTransfersCount, + ActiveTicketsCount = rollup.ActiveTicketsCount, + TicketBalancesCount = rollup.TicketBalancesCount, + TicketTransfersCount = rollup.TicketTransfersCount, NumTransactions = rollup.TransactionsCount, TransferTicketCount = rollup.TransferTicketCount, SmartRollupCementCount = rollup.SmartRollupCementCount, @@ -220,6 +229,9 @@ public async Task> GetSmartRollups(SrFilter filter, Pag ActiveTokensCount = row.ActiveTokensCount, TokenBalancesCount = row.TokenBalancesCount, TokenTransfersCount = row.TokenTransfersCount, + ActiveTicketsCount = row.ActiveTicketsCount, + TicketBalancesCount = row.TicketBalancesCount, + TicketTransfersCount = row.TicketTransfersCount, NumTransactions = row.TransactionsCount, TransferTicketCount = row.TransferTicketCount, SmartRollupCementCount = row.SmartRollupCementCount, @@ -334,6 +346,18 @@ public async Task GetSmartRollups(SrFilter filter, Pagination pagina foreach (var row in rows) result[j++][i] = row.TokenTransfersCount; break; + case "activeTicketsCount": + foreach (var row in rows) + result[j++][i] = row.ActiveTicketsCount; + break; + case "ticketBalancesCount": + foreach (var row in rows) + result[j++][i] = row.TicketBalancesCount; + break; + case "ticketTransfersCount": + foreach (var row in rows) + result[j++][i] = row.TicketTransfersCount; + break; case "numTransactions": foreach (var row in rows) result[j++][i] = row.TransactionsCount; diff --git a/Tzkt.Api/Repositories/TicketsRepository.cs b/Tzkt.Api/Repositories/TicketsRepository.cs new file mode 100644 index 000000000..0714cdca5 --- /dev/null +++ b/Tzkt.Api/Repositories/TicketsRepository.cs @@ -0,0 +1,1048 @@ +using Dapper; +using Netezos.Encoding; +using Tzkt.Api.Models; +using Tzkt.Api.Services.Cache; + +namespace Tzkt.Api.Repositories +{ + public class TicketsRepository : DbConnection + { + readonly AccountsCache Accounts; + readonly TimeCache Times; + + public TicketsRepository(AccountsCache accounts, TimeCache times, IConfiguration config) : base(config) + { + Accounts = accounts; + Times = times; + } + + #region tickets + async Task> QueryTicketsAsync(TicketFilter filter, Pagination pagination, List fields = null) + { + var select = "*"; + if (fields != null) + { + var counter = 0; + var columns = new HashSet(fields.Count); + foreach (var field in fields) + { + switch (field.Field) + { + case "id": columns.Add(@"""Id"""); break; + case "ticketer": columns.Add(@"""TicketerId"""); break; + case "rawType": columns.Add(@"""RawType"""); break; + case "rawContent": columns.Add(@"""RawContent"""); break; + case "content": + if (field.Path == null) + { + columns.Add(@"""JsonContent"""); + } + else + { + field.Column = $"c{counter++}"; + columns.Add($@"""JsonContent"" #> '{{{field.PathString}}}' as {field.Column}"); + } + break; + case "typeHash": columns.Add(@"""TypeHash"""); break; + case "contentHash": columns.Add(@"""ContentHash"""); break; + case "firstMinter": columns.Add(@"""FirstMinterId"""); break; + case "firstLevel": columns.Add(@"""FirstLevel"""); break; + case "firstTime": columns.Add(@"""FirstLevel"""); break; + case "lastLevel": columns.Add(@"""LastLevel"""); break; + case "lastTime": columns.Add(@"""LastLevel"""); break; + case "transfersCount": columns.Add(@"""TransfersCount"""); break; + case "balancesCount": columns.Add(@"""BalancesCount"""); break; + case "holdersCount": columns.Add(@"""HoldersCount"""); break; + case "totalMinted": columns.Add(@"""TotalMinted"""); break; + case "totalBurned": columns.Add(@"""TotalBurned"""); break; + case "totalSupply": columns.Add(@"""TotalSupply"""); break; + } + } + + if (columns.Count == 0) + return Enumerable.Empty(); + + select = string.Join(',', columns); + } + + var sql = new SqlBuilder($@"SELECT {select} FROM ""Tickets""") + .Filter("Id", filter.id) + .Filter("TicketerId", filter.ticketer) + .Filter("RawType", filter.rawType) + .Filter("RawContent", filter.rawContent) + .Filter("JsonContent", filter.content) + .Filter("TypeHash", filter.typeHash) + .Filter("ContentHash", filter.contentHash) + .Filter("FirstMinterId", filter.firstMinter) + .Filter("FirstLevel", filter.firstLevel) + .Filter("FirstLevel", filter.firstTime) + .Filter("LastLevel", filter.lastLevel) + .Filter("LastLevel", filter.lastTime) + .Take(pagination, x => x switch + { + "firstLevel" => (@"""FirstLevel""", @"""FirstLevel"""), + "lastLevel" => (@"""LastLevel""", @"""LastLevel"""), + "transfersCount" => (@"""TransfersCount""", @"""TransfersCount"""), + "balancesCount" => (@"""BalancesCount""", @"""BalancesCount"""), + "holdersCount" => (@"""HoldersCount""", @"""HoldersCount"""), + _ => (@"""Id""", @"""Id""") + }); + + using var db = GetConnection(); + return await db.QueryAsync(sql.Query, sql.Params); + } + + public async Task GetTicketsCount(TicketFilter filter) + { + var sql = new SqlBuilder(@"SELECT COUNT(*) FROM ""Tickets""") + .Filter("Id", filter.id) + .Filter("TicketerId", filter.ticketer) + .Filter("RawType", filter.rawType) + .Filter("RawContent", filter.rawContent) + .Filter("JsonContent", filter.content) + .Filter("TypeHash", filter.typeHash) + .Filter("ContentHash", filter.contentHash) + .Filter("FirstMinterId", filter.firstMinter) + .Filter("FirstLevel", filter.firstLevel) + .Filter("FirstLevel", filter.firstTime) + .Filter("LastLevel", filter.lastLevel) + .Filter("LastLevel", filter.lastTime); + + using var db = GetConnection(); + return await db.QueryFirstAsync(sql.Query, sql.Params); + } + + public async Task> GetTickets(TicketFilter filter, Pagination pagination) + { + var rows = await QueryTicketsAsync(filter, pagination); + return rows.Select(row => new Ticket + { + Id = row.Id, + Ticketer = Accounts.GetAlias(row.TicketerId), + RawType = Micheline.FromBytes((byte[])row.RawType), + RawContent = Micheline.FromBytes((byte[])row.RawContent), + Content = (RawJson)row.JsonContent, + TypeHash = row.TypeHash, + ContentHash = row.ContentHash, + FirstMinter = Accounts.GetAlias(row.FirstMinterId), + FirstLevel = row.FirstLevel, + FirstTime = Times[row.FirstLevel], + LastLevel = row.LastLevel, + LastTime = Times[row.LastLevel], + TransfersCount = row.TransfersCount, + BalancesCount = row.BalancesCount, + HoldersCount = row.HoldersCount, + TotalMinted = row.TotalMinted, + TotalBurned = row.TotalBurned, + TotalSupply = row.TotalSupply + }); + } + + public async Task GetTickets(TicketFilter filter, Pagination pagination, List fields) + { + var rows = await QueryTicketsAsync(filter, pagination, fields); + + var result = new object[rows.Count()][]; + for (int i = 0; i < result.Length; i++) + result[i] = new object[fields.Count]; + + for (int i = 0, j = 0; i < fields.Count; j = 0, i++) + { + switch (fields[i].Full) + { + case "id": + foreach (var row in rows) + result[j++][i] = row.Id; + break; + case "ticketer": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.TicketerId); + break; + case "ticketer.alias": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.TicketerId).Name; + break; + case "ticketer.address": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.TicketerId).Address; + break; + case "rawType": + foreach (var row in rows) + result[j++][i] = Micheline.FromBytes((byte[])row.RawType); + break; + case "rawContent": + foreach (var row in rows) + result[j++][i] = Micheline.FromBytes((byte[])row.RawContent); + break; + case "content": + foreach (var row in rows) + result[j++][i] = (RawJson)row.JsonContent; + break; + case "typeHash": + foreach (var row in rows) + result[j++][i] = row.TypeHash; + break; + case "contentHash": + foreach (var row in rows) + result[j++][i] = row.ContentHash; + break; + case "firstMinter": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.FirstMinterId); + break; + case "firstMinter.alias": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.FirstMinterId).Name; + break; + case "firstMinter.address": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.FirstMinterId).Address; + break; + case "firstLevel": + foreach (var row in rows) + result[j++][i] = row.FirstLevel; + break; + case "firstTime": + foreach (var row in rows) + result[j++][i] = Times[row.FirstLevel]; + break; + case "lastLevel": + foreach (var row in rows) + result[j++][i] = row.LastLevel; + break; + case "lastTime": + foreach (var row in rows) + result[j++][i] = Times[row.LastLevel]; + break; + case "transfersCount": + foreach (var row in rows) + result[j++][i] = row.TransfersCount; + break; + case "balancesCount": + foreach (var row in rows) + result[j++][i] = row.BalancesCount; + break; + case "holdersCount": + foreach (var row in rows) + result[j++][i] = row.HoldersCount; + break; + case "totalMinted": + foreach (var row in rows) + result[j++][i] = row.TotalMinted; + break; + case "totalBurned": + foreach (var row in rows) + result[j++][i] = row.TotalBurned; + break; + case "totalSupply": + foreach (var row in rows) + result[j++][i] = row.TotalSupply; + break; + } + } + + return result; + } + #endregion + + #region ticket balances + async Task> QueryTicketBalancesAsync(TicketBalanceFilter filter, Pagination pagination, List fields = null) + { + var select = @" + tb.""Id"", + tb.""AccountId"", + tb.""Balance"", + tb.""TransfersCount"", + tb.""FirstLevel"", + tb.""LastLevel"", + tb.""TicketId"" as ""tId"", + tb.""TicketerId"" as ""tTicketerId"", + t.""RawType"" as ""tRawType"", + t.""RawContent"" as ""tRawContent"", + t.""JsonContent"" as ""tJsonContent"", + t.""TypeHash"" as ""tTypeHash"", + t.""ContentHash"" as ""tContentHash"", + t.""TotalSupply"" as ""tTotalSupply"""; + if (fields != null) + { + var counter = 0; + var columns = new HashSet(fields.Count); + foreach (var field in fields) + { + switch (field.Field) + { + case "id": columns.Add(@"tb.""Id"""); break; + case "ticket": + if (field.Path == null) + { + columns.Add(@"tb.""TicketId"" as ""tId"""); + columns.Add(@"tb.""TicketerId"" as ""tTicketerId"""); + columns.Add(@"t.""RawType"" as ""tRawType"""); + columns.Add(@"t.""RawContent"" as ""tRawContent"""); + columns.Add(@"t.""JsonContent"" as ""tJsonContent"""); + columns.Add(@"t.""TypeHash"" as ""tTypeHash"""); + columns.Add(@"t.""ContentHash"" as ""tContentHash"""); + columns.Add(@"t.""TotalSupply"" as ""tTotalSupply"""); + } + else + { + var subField = field.SubField(); + switch (subField.Field) + { + case "id": columns.Add(@"tb.""TicketId"" as ""tId"""); break; + case "ticketer": columns.Add(@"tb.""TicketerId"" as ""tTicketerId"""); break; + case "rawType": columns.Add(@"t.""RawType"" as ""tRawType"""); break; + case "rawContent": columns.Add(@"t.""RawContent"" as ""tRawContent"""); break; + case "content": + if (subField.Path == null) + { + columns.Add(@"t.""JsonContent"" as ""tJsonContent"""); + } + else + { + field.Column = $"c{counter++}"; + columns.Add($@"t.""JsonContent"" #> '{{{subField.PathString}}}' as {field.Column}"); + } + break; + case "typeHash": columns.Add(@"t.""TypeHash"" as ""tTypeHash"""); break; + case "contentHash": columns.Add(@"t.""ContentHash"" as ""tContentHash"""); break; + case "totalSupply": columns.Add(@"t.""TotalSupply"" as ""tTotalSupply"""); break; + } + } + break; + case "account": columns.Add(@"tb.""AccountId"""); break; + case "balance": columns.Add(@"tb.""Balance"""); break; + case "transfersCount": columns.Add(@"tb.""TransfersCount"""); break; + case "firstLevel": columns.Add(@"tb.""FirstLevel"""); break; + case "firstTime": columns.Add(@"tb.""FirstLevel"""); break; + case "lastLevel": columns.Add(@"tb.""LastLevel"""); break; + case "lastTime": columns.Add(@"tb.""LastLevel"""); break; + } + } + + if (columns.Count == 0) + return Enumerable.Empty(); + + select = string.Join(',', columns); + } + + var sql = new SqlBuilder($@" + SELECT {select} FROM ""TicketBalances"" as tb + INNER JOIN ""Tickets"" AS t ON t.""Id"" = tb.""TicketId""") + .FilterA(@"tb.""Id""", filter.id) + .FilterA(@"tb.""AccountId""", filter.account) + .FilterA(@"tb.""Balance""", filter.balance) + .FilterA(@"tb.""TransfersCount""", filter.transfersCount) + .FilterA(@"tb.""FirstLevel""", filter.firstLevel) + .FilterA(@"tb.""FirstLevel""", filter.firstTime) + .FilterA(@"tb.""LastLevel""", filter.lastLevel) + .FilterA(@"tb.""LastLevel""", filter.lastTime) + .FilterA(@"tb.""TicketId""", filter.ticket.id) + .FilterA(@"tb.""TicketerId""", filter.ticket.ticketer) + .FilterA(@"t.""RawType""", filter.ticket.rawType) + .FilterA(@"t.""RawContent""", filter.ticket.rawContent) + .FilterA(@"t.""JsonContent""", filter.ticket.content) + .FilterA(@"t.""TypeHash""", filter.ticket.typeHash) + .FilterA(@"t.""ContentHash""", filter.ticket.contentHash) + .Take(pagination, x => x switch + { + "balance" => (@"tb.""Balance""::numeric", @"tb.""Balance""::numeric"), + "transfersCount" => (@"tb.""TransfersCount""", @"tb.""TransfersCount"""), + "firstLevel" => (@"tb.""FirstLevel""", @"tb.""FirstLevel"""), + "lastLevel" => (@"tb.""LastLevel""", @"tb.""LastLevel"""), + _ => (@"tb.""Id""", @"tb.""Id""") + }, @"tb.""Id"""); + + using var db = GetConnection(); + return await db.QueryAsync(sql.Query, sql.Params); + } + + public async Task GetTicketBalancesCount(TicketBalanceFilter filter) + { + var sql = new SqlBuilder(@" + SELECT COUNT(*) FROM ""TicketBalances"" as tb + INNER JOIN ""Tickets"" AS t ON t.""Id"" = tb.""TicketId""") + .FilterA(@"tb.""Id""", filter.id) + .FilterA(@"tb.""AccountId""", filter.account) + .FilterA(@"tb.""Balance""", filter.balance) + .FilterA(@"tb.""TransfersCount""", filter.transfersCount) + .FilterA(@"tb.""FirstLevel""", filter.firstLevel) + .FilterA(@"tb.""FirstLevel""", filter.firstTime) + .FilterA(@"tb.""LastLevel""", filter.lastLevel) + .FilterA(@"tb.""LastLevel""", filter.lastTime) + .FilterA(@"tb.""TicketId""", filter.ticket.id) + .FilterA(@"tb.""TicketerId""", filter.ticket.ticketer) + .FilterA(@"t.""RawType""", filter.ticket.rawType) + .FilterA(@"t.""RawContent""", filter.ticket.rawContent) + .FilterA(@"t.""JsonContent""", filter.ticket.content) + .FilterA(@"t.""TypeHash""", filter.ticket.typeHash) + .FilterA(@"t.""ContentHash""", filter.ticket.contentHash); + + using var db = GetConnection(); + return await db.QueryFirstAsync(sql.Query, sql.Params); + } + + public async Task> GetTicketBalances(TicketBalanceFilter filter, Pagination pagination) + { + var rows = await QueryTicketBalancesAsync(filter, pagination); + return rows.Select(row => new TicketBalance + { + Id = row.Id, + Ticket = new TicketInfo + { + Id = row.tId, + Ticketer = Accounts.GetAlias(row.tTicketerId), + RawType = Micheline.FromBytes((byte[])row.tRawType), + RawContent = Micheline.FromBytes((byte[])row.tRawContent), + Content = (RawJson)row.tJsonContent, + TypeHash = row.tTypeHash, + ContentHash = row.tContentHash, + TotalSupply = row.tTotalSupply + }, + Account = Accounts.GetAlias(row.AccountId), + Balance = row.Balance, + TransfersCount = row.TransfersCount, + FirstLevel = row.FirstLevel, + FirstTime = Times[row.FirstLevel], + LastLevel = row.LastLevel, + LastTime = Times[row.LastLevel] + }); + } + + public async Task GetTicketBalances(TicketBalanceFilter filter, Pagination pagination, List fields) + { + var rows = await QueryTicketBalancesAsync(filter, pagination, fields); + + var result = new object[rows.Count()][]; + for (int i = 0; i < result.Length; i++) + result[i] = new object[fields.Count]; + + for (int i = 0, j = 0; i < fields.Count; j = 0, i++) + { + switch (fields[i].Full) + { + case "id": + foreach (var row in rows) + result[j++][i] = row.Id; + break; + case "account": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.AccountId); + break; + case "account.alias": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.AccountId).Name; + break; + case "account.address": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.AccountId).Address; + break; + case "balance": + foreach (var row in rows) + result[j++][i] = row.Balance; + break; + case "transfersCount": + foreach (var row in rows) + result[j++][i] = row.TransfersCount; + break; + case "firstLevel": + foreach (var row in rows) + result[j++][i] = row.FirstLevel; + break; + case "firstTime": + foreach (var row in rows) + result[j++][i] = Times[row.FirstLevel]; + break; + case "lastLevel": + foreach (var row in rows) + result[j++][i] = row.LastLevel; + break; + case "lastTime": + foreach (var row in rows) + result[j++][i] = Times[row.LastLevel]; + break; + case "ticket": + foreach (var row in rows) + result[j++][i] = new TicketInfo + { + Id = row.tId, + Ticketer = Accounts.GetAlias(row.tTicketerId), + RawType = Micheline.FromBytes((byte[])row.tRawType), + RawContent = Micheline.FromBytes((byte[])row.tRawContent), + Content = (RawJson)row.tJsonContent, + TypeHash = row.tTypeHash, + ContentHash = row.tContentHash, + TotalSupply = row.tTotalSupply + }; + break; + case "ticket.id": + foreach (var row in rows) + result[j++][i] = row.tId; + break; + case "ticket.ticketer": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId); + break; + case "ticket.ticketer.alias": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId).Alias; + break; + case "ticket.ticketer.address": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId).Address; + break; + case "ticket.rawType": + foreach (var row in rows) + result[j++][i] = Micheline.FromBytes((byte[])row.tRawType); + break; + case "ticket.rawContent": + foreach (var row in rows) + result[j++][i] = Micheline.FromBytes((byte[])row.tRawContent); + break; + case "ticket.content": + foreach (var row in rows) + result[j++][i] = (RawJson)row.tJsonContent; + break; + case "ticket.typeHash": + foreach (var row in rows) + result[j++][i] = row.tTypeHash; + break; + case "ticket.contentHash": + foreach (var row in rows) + result[j++][i] = row.tContentHash; + break; + case "ticket.totalSupply": + foreach (var row in rows) + result[j++][i] = row.tTotalSupply; + break; + default: + if (fields[i].Full.StartsWith("ticket.content.")) + foreach (var row in rows) + result[j++][i] = (RawJson)((row as IDictionary)[fields[i].Column] as string); + break; + } + } + + return result; + } + #endregion + + #region ticket transfers + async Task> QueryTicketTransfersAsync(TicketTransferFilter filter, Pagination pagination, List fields = null) + { + var select = @" + tr.""Id"", + tr.""Level"", + tr.""FromId"", + tr.""ToId"", + tr.""Amount"", + tr.""TransactionId"", + tr.""TransferTicketId"", + tr.""SmartRollupExecuteId"", + tr.""TicketId"" as ""tId"", + tr.""TicketerId"" as ""tTicketerId"", + t.""RawType"" as ""tRawType"", + t.""RawContent"" as ""tRawContent"", + t.""JsonContent"" as ""tJsonContent"", + t.""TypeHash"" as ""tTypeHash"", + t.""ContentHash"" as ""tContentHash"", + t.""TotalSupply"" as ""tTotalSupply"""; + if (fields != null) + { + var counter = 0; + var columns = new HashSet(fields.Count); + foreach (var field in fields) + { + switch (field.Field) + { + case "id": columns.Add(@"tr.""Id"""); break; + case "level": columns.Add(@"tr.""Level"""); break; + case "timestamp": columns.Add(@"tr.""Level"""); break; + case "ticket": + if (field.Path == null) + { + columns.Add(@"tr.""TicketId"" as ""tId"""); + columns.Add(@"tr.""TicketerId"" as ""tTicketerId"""); + columns.Add(@"t.""RawType"" as ""tRawType"""); + columns.Add(@"t.""RawContent"" as ""tRawContent"""); + columns.Add(@"t.""JsonContent"" as ""tJsonContent"""); + columns.Add(@"t.""TypeHash"" as ""tTypeHash"""); + columns.Add(@"t.""ContentHash"" as ""tContentHash"""); + columns.Add(@"t.""TotalSupply"" as ""tTotalSupply"""); + } + else + { + var subField = field.SubField(); + switch (subField.Field) + { + case "id": columns.Add(@"tr.""TicketId"" as ""tId"""); break; + case "ticketer": columns.Add(@"tr.""TicketerId"" as ""tTicketerId"""); break; + case "rawType": columns.Add(@"t.""RawType"" as ""tRawType"""); break; + case "rawContent": columns.Add(@"t.""RawContent"" as ""tRawContent"""); break; + case "content": + if (subField.Path == null) + { + columns.Add(@"t.""JsonContent"" as ""tJsonContent"""); + } + else + { + field.Column = $"c{counter++}"; + columns.Add($@"t.""JsonContent"" #> '{{{subField.PathString}}}' as {field.Column}"); + } + break; + case "typeHash": columns.Add(@"t.""TypeHash"" as ""tTypeHash"""); break; + case "contentHash": columns.Add(@"t.""ContentHash"" as ""tContentHash"""); break; + case "totalSupply": columns.Add(@"t.""TotalSupply"" as ""tTotalSupply"""); break; + } + } + break; + case "from": columns.Add(@"tr.""FromId"""); break; + case "to": columns.Add(@"tr.""ToId"""); break; + case "amount": columns.Add(@"tr.""Amount"""); break; + case "transactionId": columns.Add(@"tr.""TransactionId"""); break; + case "transferTicketId": columns.Add(@"tr.""TransferTicketId"""); break; + case "smartRollupExecuteId": columns.Add(@"tr.""SmartRollupExecuteId"""); break; + } + } + + if (columns.Count == 0) + return Enumerable.Empty(); + + select = string.Join(',', columns); + } + + var sql = new SqlBuilder($@" + SELECT {select} FROM ""TicketTransfers"" as tr + INNER JOIN ""Tickets"" AS t ON t.""Id"" = tr.""TicketId""") + .FilterA(@"tr.""Id""", filter.id) + .FilterA(@"tr.""Level""", filter.level) + .FilterA(@"tr.""Level""", filter.timestamp) + .FilterA(filter.anyof, x => x == "from" ? @"tr.""FromId""" : @"tr.""ToId""") + .FilterA(@"tr.""FromId""", filter.from) + .FilterA(@"tr.""ToId""", filter.to) + .FilterA(@"tr.""Amount""", filter.amount) + .FilterA(@"tr.""TransactionId""", filter.transactionId) + .FilterA(@"tr.""TransferTicketId""", filter.transferTicketId) + .FilterA(@"tr.""SmartRollupExecuteId""", filter.smartRollupExecuteId) + .FilterA(@"tr.""TicketId""", filter.ticket.id) + .FilterA(@"tr.""TicketerId""", filter.ticket.ticketer) + .FilterA(@"t.""RawType""", filter.ticket.rawType) + .FilterA(@"t.""RawContent""", filter.ticket.rawContent) + .FilterA(@"t.""JsonContent""", filter.ticket.content) + .FilterA(@"t.""TypeHash""", filter.ticket.typeHash) + .FilterA(@"t.""ContentHash""", filter.ticket.contentHash) + .Take(pagination, x => x switch + { + "level" => (@"tr.""Level""", @"tr.""Level"""), + "amount" => (@"tr.""Amount""::numeric", @"tr.""Amount""::numeric"), + _ => (@"tr.""Id""", @"tr.""Id""") + }, @"tr.""Id"""); + + using var db = GetConnection(); + return await db.QueryAsync(sql.Query, sql.Params); + } + + public async Task GetTicketTransfersCount(TicketTransferFilter filter) + { + var sql = new SqlBuilder(@" + SELECT COUNT(*) FROM ""TicketTransfers"" as tr + INNER JOIN ""Tickets"" AS t ON t.""Id"" = tr.""TicketId""") + .FilterA(@"tr.""Id""", filter.id) + .FilterA(@"tr.""Level""", filter.level) + .FilterA(@"tr.""Level""", filter.timestamp) + .FilterA(filter.anyof, x => x == "from" ? @"tr.""FromId""" : @"tr.""ToId""") + .FilterA(@"tr.""FromId""", filter.from) + .FilterA(@"tr.""ToId""", filter.to) + .FilterA(@"tr.""Amount""", filter.amount) + .FilterA(@"tr.""TransactionId""", filter.transactionId) + .FilterA(@"tr.""TransferTicketId""", filter.transferTicketId) + .FilterA(@"tr.""SmartRollupExecuteId""", filter.smartRollupExecuteId) + .FilterA(@"tr.""TicketId""", filter.ticket.id) + .FilterA(@"tr.""TicketerId""", filter.ticket.ticketer) + .FilterA(@"t.""RawType""", filter.ticket.rawType) + .FilterA(@"t.""RawContent""", filter.ticket.rawContent) + .FilterA(@"t.""JsonContent""", filter.ticket.content) + .FilterA(@"t.""TypeHash""", filter.ticket.typeHash) + .FilterA(@"t.""ContentHash""", filter.ticket.contentHash); + + using var db = GetConnection(); + return await db.QueryFirstAsync(sql.Query, sql.Params); + } + + public async Task> GetTicketTransfers(TicketTransferFilter filter, Pagination pagination) + { + var rows = await QueryTicketTransfersAsync(filter, pagination); + return rows.Select(row => new TicketTransfer + { + Id = row.Id, + Level = row.Level, + Timestamp = Times[row.Level], + Ticket = new TicketInfo + { + Id = row.tId, + Ticketer = Accounts.GetAlias(row.tTicketerId), + RawType = Micheline.FromBytes((byte[])row.tRawType), + RawContent = Micheline.FromBytes((byte[])row.tRawContent), + Content = (RawJson)row.tJsonContent, + TypeHash = row.tTypeHash, + ContentHash = row.tContentHash, + TotalSupply = row.tTotalSupply + }, + From = row.FromId == null ? null : Accounts.GetAlias(row.FromId), + To = row.ToId == null ? null : Accounts.GetAlias(row.ToId), + Amount = row.Amount, + TransactionId = row.TransactionId, + TransferTicketId = row.TransferTicketId, + SmartRollupExecuteId = row.SmartRollupExecuteId + }); + } + + public async Task GetTicketTransfers(TicketTransferFilter filter, Pagination pagination, List fields) + { + var rows = await QueryTicketTransfersAsync(filter, pagination, fields); + + var result = new object[rows.Count()][]; + for (int i = 0; i < result.Length; i++) + result[i] = new object[fields.Count]; + + for (int i = 0, j = 0; i < fields.Count; j = 0, i++) + { + switch (fields[i].Full) + { + case "id": + foreach (var row in rows) + result[j++][i] = row.Id; + break; + case "level": + foreach (var row in rows) + result[j++][i] = row.Level; + break; + case "timestamp": + foreach (var row in rows) + result[j++][i] = Times[row.Level]; + break; + case "from": + foreach (var row in rows) + result[j++][i] = row.FromId == null ? null : Accounts.GetAlias(row.FromId); + break; + case "from.alias": + foreach (var row in rows) + result[j++][i] = row.FromId == null ? null : Accounts.GetAlias(row.FromId).Name; + break; + case "from.address": + foreach (var row in rows) + result[j++][i] = row.FromId == null ? null : Accounts.GetAlias(row.FromId).Address; + break; + case "to": + foreach (var row in rows) + result[j++][i] = row.ToId == null ? null : Accounts.GetAlias(row.ToId); + break; + case "to.alias": + foreach (var row in rows) + result[j++][i] = row.ToId == null ? null : Accounts.GetAlias(row.ToId).Name; + break; + case "to.address": + foreach (var row in rows) + result[j++][i] = row.ToId == null ? null : Accounts.GetAlias(row.ToId).Address; + break; + case "amount": + foreach (var row in rows) + result[j++][i] = row.Amount; + break; + case "transactionId": + foreach (var row in rows) + result[j++][i] = row.TransactionId; + break; + case "transferTicketId": + foreach (var row in rows) + result[j++][i] = row.OriginationId; + break; + case "smartRollupExecuteId": + foreach (var row in rows) + result[j++][i] = row.MigrationId; + break; + case "ticket": + foreach (var row in rows) + result[j++][i] = new TicketInfo + { + Id = row.tId, + Ticketer = Accounts.GetAlias(row.tTicketerId), + RawType = Micheline.FromBytes((byte[])row.tRawType), + RawContent = Micheline.FromBytes((byte[])row.tRawContent), + Content = (RawJson)row.tJsonContent, + TypeHash = row.tTypeHash, + ContentHash = row.tContentHash, + TotalSupply = row.tTotalSupply + }; + break; + case "ticket.id": + foreach (var row in rows) + result[j++][i] = row.tId; + break; + case "ticket.ticketer": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId); + break; + case "ticket.ticketer.alias": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId).Alias; + break; + case "ticket.ticketer.address": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId).Address; + break; + case "ticket.rawType": + foreach (var row in rows) + result[j++][i] = Micheline.FromBytes((byte[])row.tRawType); + break; + case "ticket.rawContent": + foreach (var row in rows) + result[j++][i] = Micheline.FromBytes((byte[])row.tRawContent); + break; + case "ticket.content": + foreach (var row in rows) + result[j++][i] = (RawJson)row.tJsonContent; + break; + case "ticket.typeHash": + foreach (var row in rows) + result[j++][i] = row.tTypeHash; + break; + case "ticket.contentHash": + foreach (var row in rows) + result[j++][i] = row.tContentHash; + break; + case "ticket.totalSupply": + foreach (var row in rows) + result[j++][i] = row.tTotalSupply; + break; + default: + if (fields[i].Full.StartsWith("ticket.content.")) + foreach (var row in rows) + result[j++][i] = (RawJson)((row as IDictionary)[fields[i].Column] as string); + break; + } + } + + return result; + } + #endregion + + #region historical balances + async Task> QueryHistoricalTicketBalancesAsync(int level, TicketBalanceShortFilter filter, Pagination pagination, List fields = null) + { + var select = @" + tb.""Id"", + tb.""AccountId"", + tb.""Balance"", + tb.""TicketId"" as ""tId"", + tb.""TicketerId"" as ""tTicketerId"", + t.""RawType"" as ""tRawType"", + t.""RawContent"" as ""tRawContent"", + t.""JsonContent"" as ""tJsonContent"", + t.""TypeHash"" as ""tTypeHash"", + t.""ContentHash"" as ""tContentHash"""; + if (fields != null) + { + var counter = 0; + var columns = new HashSet(fields.Count); + foreach (var field in fields) + { + switch (field.Field) + { + case "id": columns.Add(@"tb.""Id"""); break; + case "ticket": + if (field.Path == null) + { + columns.Add(@"tb.""TicketId"" as ""tId"""); + columns.Add(@"tb.""TicketerId"" as ""tTicketerId"""); + columns.Add(@"t.""RawType"" as ""tRawType"""); + columns.Add(@"t.""RawContent"" as ""tRawContent"""); + columns.Add(@"t.""JsonContent"" as ""tJsonContent"""); + columns.Add(@"t.""TypeHash"" as ""tTypeHash"""); + columns.Add(@"t.""ContentHash"" as ""tContentHash"""); + } + else + { + var subField = field.SubField(); + switch (subField.Field) + { + case "id": columns.Add(@"tb.""TicketId"" as ""tId"""); break; + case "ticketer": columns.Add(@"tb.""TicketerId"" as ""tTicketerId"""); break; + case "rawType": columns.Add(@"t.""RawType"" as ""tRawType"""); break; + case "rawContent": columns.Add(@"t.""RawContent"" as ""tRawContent"""); break; + case "content": + if (subField.Path == null) + { + columns.Add(@"t.""JsonContent"" as ""tJsonContent"""); + } + else + { + field.Column = $"c{counter++}"; + columns.Add($@"t.""JsonContent"" #> '{{{subField.PathString}}}' as {field.Column}"); + } + break; + case "typeHash": columns.Add(@"t.""TypeHash"" as ""tTypeHash"""); break; + case "contentHash": columns.Add(@"t.""ContentHash"" as ""tContentHash"""); break; + } + } + break; + case "account": columns.Add(@"tb.""AccountId"""); break; + case "balance": columns.Add(@"tb.""Balance"""); break; + } + } + + if (columns.Count == 0) + return Enumerable.Empty(); + + select = string.Join(',', columns); + } + + var sql = new SqlBuilder() + .Append($@"SELECT {select} FROM (") + .Append(@"SELECT ROW_NUMBER() over (ORDER BY ""TicketId"", ""AccountId"") as ""Id"", ""TicketId"", ""TicketerId"", ""AccountId"", SUM(""Amount"")::text AS ""Balance"" FROM (") + + .Append(@"SELECT ""TicketId"", ""TicketerId"", ""FromId"" AS ""AccountId"", -""Amount""::numeric AS ""Amount"" FROM ""TicketTransfers""") + .Filter($@"""Level"" <= {level}") + .Filter($@"""FromId"" IS NOT NULL") + .FilterA(@"""FromId""", filter.account) + .FilterA(@"""TicketId""", filter.ticket.id) + .FilterA(@"""TicketerId""", filter.ticket.ticketer) + .ResetFilters() + + .Append("UNION ALL") + + .Append(@"SELECT ""TicketId"", ""TicketerId"", ""ToId"" AS ""AccountId"", ""Amount""::numeric AS ""Amount"" FROM ""TicketTransfers""") + .Filter($@"""Level"" <= {level}") + .Filter($@"""ToId"" IS NOT NULL") + .FilterA(@"""ToId""", filter.account) + .FilterA(@"""TicketId""", filter.ticket.id) + .FilterA(@"""TicketerId""", filter.ticket.ticketer) + .ResetFilters() + + .Append(") as tb") + .Append(@"GROUP BY tb.""TicketId"", tb.""TicketerId"", tb.""AccountId""") + .Append(") as tb") + .Append(@"INNER JOIN ""Tickets"" AS t ON t.""Id"" = tb.""TicketId""") + .FilterA(@"tb.""Id""", filter.id) + .FilterA(@"tb.""Balance""", filter.balance) + .Take(pagination, _ => (@"tb.""Id""", @"tb.""Id"""), @"tb.""Id"""); + + using var db = GetConnection(); + return await db.QueryAsync(sql.Query, sql.Params); + } + + public async Task> GetHistoricalTicketBalances(int level, TicketBalanceShortFilter filter, Pagination pagination) + { + var rows = await QueryHistoricalTicketBalancesAsync(level, filter, pagination); + return rows.Select(row => new TicketBalanceShort + { + Id = row.Id, + Account = Accounts.GetAlias(row.AccountId), + Balance = row.Balance, + Ticket = new TicketInfoShort + { + Id = row.tId, + Ticketer = Accounts.GetAlias(row.tTicketerId), + RawType = Micheline.FromBytes((byte[])row.tRawType), + RawContent = Micheline.FromBytes((byte[])row.tRawContent), + Content = (RawJson)row.tJsonContent, + TypeHash = row.tTypeHash, + ContentHash = row.tContentHash + } + }); + } + + public async Task GetHistoricalTicketBalances(int level, TicketBalanceShortFilter filter, Pagination pagination, List fields) + { + var rows = await QueryHistoricalTicketBalancesAsync(level, filter, pagination, fields); + + var result = new object[rows.Count()][]; + for (int i = 0; i < result.Length; i++) + result[i] = new object[fields.Count]; + + for (int i = 0, j = 0; i < fields.Count; j = 0, i++) + { + switch (fields[i].Full) + { + case "id": + foreach (var row in rows) + result[j++][i] = row.Id; + break; + case "account": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.AccountId); + break; + case "account.alias": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.AccountId).Name; + break; + case "account.address": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.AccountId).Address; + break; + case "balance": + foreach (var row in rows) + result[j++][i] = row.Balance; + break; + case "ticket": + foreach (var row in rows) + result[j++][i] = new TicketInfoShort + { + Id = row.tId, + Ticketer = Accounts.GetAlias(row.tTicketerId), + RawType = Micheline.FromBytes((byte[])row.tRawType), + RawContent = Micheline.FromBytes((byte[])row.tRawContent), + Content = (RawJson)row.tJsonContent, + TypeHash = row.tTypeHash, + ContentHash = row.tContentHash + }; + break; + case "ticket.id": + foreach (var row in rows) + result[j++][i] = row.tId; + break; + case "ticket.ticketer": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId); + break; + case "ticket.ticketer.alias": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId).Alias; + break; + case "ticket.ticketer.address": + foreach (var row in rows) + result[j++][i] = Accounts.GetAlias(row.tTicketerId).Address; + break; + case "ticket.rawType": + foreach (var row in rows) + result[j++][i] = Micheline.FromBytes((byte[])row.tRawType); + break; + case "ticket.rawContent": + foreach (var row in rows) + result[j++][i] = Micheline.FromBytes((byte[])row.tRawContent); + break; + case "ticket.content": + foreach (var row in rows) + result[j++][i] = (RawJson)row.tJsonContent; + break; + case "ticket.typeHash": + foreach (var row in rows) + result[j++][i] = row.tTypeHash; + break; + case "ticket.contentHash": + foreach (var row in rows) + result[j++][i] = row.tContentHash; + break; + default: + if (fields[i].Full.StartsWith("ticket.content.")) + foreach (var row in rows) + result[j++][i] = (RawJson)((row as IDictionary)[fields[i].Column] as string); + break; + } + } + + return result; + } + #endregion + } +} diff --git a/Tzkt.Api/Services/Cache/Accounts/Models/RawAccount.cs b/Tzkt.Api/Services/Cache/Accounts/Models/RawAccount.cs index c952d3b8c..5f3fdff23 100644 --- a/Tzkt.Api/Services/Cache/Accounts/Models/RawAccount.cs +++ b/Tzkt.Api/Services/Cache/Accounts/Models/RawAccount.cs @@ -20,6 +20,9 @@ public class RawAccount public int ActiveTokensCount { get; set; } public int TokenBalancesCount { get; set; } public int TokenTransfersCount { get; set; } + public int ActiveTicketsCount { get; set; } + public int TicketBalancesCount { get; set; } + public int TicketTransfersCount { get; set; } public int DelegationsCount { get; set; } public int OriginationsCount { get; set; } diff --git a/Tzkt.Api/Services/Cache/Accounts/Models/RawContract.cs b/Tzkt.Api/Services/Cache/Accounts/Models/RawContract.cs index 0ea6a9c73..cc06a53bd 100644 --- a/Tzkt.Api/Services/Cache/Accounts/Models/RawContract.cs +++ b/Tzkt.Api/Services/Cache/Accounts/Models/RawContract.cs @@ -7,6 +7,7 @@ public class RawContract : RawAccount public int CodeHash { get; set; } public int Tags { get; set; } public int TokensCount { get; set; } + public int TicketsCount { get; set; } public int EventsCount { get; set; } public int? CreatorId { get; set; } diff --git a/Tzkt.Api/Services/Cache/State/RawModels/RawState.cs b/Tzkt.Api/Services/Cache/State/RawModels/RawState.cs index a98343d38..49e3cac44 100644 --- a/Tzkt.Api/Services/Cache/State/RawModels/RawState.cs +++ b/Tzkt.Api/Services/Cache/State/RawModels/RawState.cs @@ -76,6 +76,9 @@ public class RawState public int TokensCount { get; set; } public int TokenBalancesCount { get; set; } public int TokenTransfersCount { get; set; } + public int TicketsCount { get; set; } + public int TicketBalancesCount { get; set; } + public int TicketTransfersCount { get; set; } public int EventsCount { get; set; } #endregion diff --git a/Tzkt.Api/Swagger/Swagger.cs b/Tzkt.Api/Swagger/Swagger.cs index 8a69f39bc..0d9e00d3f 100644 --- a/Tzkt.Api/Swagger/Swagger.cs +++ b/Tzkt.Api/Swagger/Swagger.cs @@ -13,6 +13,8 @@ public static void AddOpenApiDocument(this IServiceCollection services) options.OperationProcessors.Add(new TzktExtensionProcessor()); options.OperationProcessors.Add(new AnyOfExtensionProcessor("Tokens_GetTokenTransfers", "from,to")); options.OperationProcessors.Add(new AnyOfExtensionProcessor("Tokens_GetTokenTransfersCount", "from,to")); + options.OperationProcessors.Add(new AnyOfExtensionProcessor("Tickets_GetTicketTransfers", "from,to")); + options.OperationProcessors.Add(new AnyOfExtensionProcessor("Tickets_GetTicketTransfersCount", "from,to")); options.PostProcess = document => { document.Info.Title = "TzKT API"; diff --git a/Tzkt.Api/Utils/RawJson.cs b/Tzkt.Api/Utils/RawJson.cs index 377f06c0b..00beb536a 100644 --- a/Tzkt.Api/Utils/RawJson.cs +++ b/Tzkt.Api/Utils/RawJson.cs @@ -1,10 +1,11 @@ -using System; -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; +using NJsonSchema.Annotations; namespace Tzkt.Api { [JsonConverter(typeof(RawJsonConverter))] + [JsonSchemaType(typeof(object), IsNullable = true)] public class RawJson { string Json { get; } diff --git a/Tzkt.Api/Utils/SqlBuilder.cs b/Tzkt.Api/Utils/SqlBuilder.cs index 86b00bb46..237a6ee82 100644 --- a/Tzkt.Api/Utils/SqlBuilder.cs +++ b/Tzkt.Api/Utils/SqlBuilder.cs @@ -4,6 +4,7 @@ using System.Text; using System.Text.RegularExpressions; using Dapper; +using Netezos.Encoding; using Tzkt.Api.Utils; namespace Tzkt.Api @@ -1626,6 +1627,44 @@ public SqlBuilder FilterA(string column, BoolParameter value, Func map = null) + { + if (value == null) return this; + + if (value.Eq != null) + AppendFilter($@"""{column}"" = {Param(value.Eq.ToBytes())}"); + + if (value.Ne != null) + AppendFilter($@"""{column}"" != {Param(value.Ne.ToBytes())}"); + + if (value.In != null) + AppendFilter($@"""{column}"" = ANY ({Param(value.In.Select(x => x.ToBytes()).ToArray())})"); + + if (value.Ni != null) + AppendFilter($@"NOT (""{column}"" = ANY ({Param(value.Ni.Select(x => x.ToBytes()).ToArray())}))"); + + return this; + } + + public SqlBuilder FilterA(string column, MichelineParameter value, Func map = null) + { + if (value == null) return this; + + if (value.Eq != null) + AppendFilter($"{column} = {Param(value.Eq.ToBytes())}"); + + if (value.Ne != null) + AppendFilter($"{column} != {Param(value.Ne.ToBytes())}"); + + if (value.In != null) + AppendFilter($"{column} = ANY ({Param(value.In.Select(x => x.ToBytes()).ToArray())})"); + + if (value.Ni != null) + AppendFilter($"NOT ({column} = ANY ({Param(value.Ni.Select(x => x.ToBytes()).ToArray())}))"); + + return this; + } + public SqlBuilder Filter(string column, DateTimeParameter value, Func map = null) { if (value == null) return this; diff --git a/Tzkt.Api/Websocket/Hubs/DefaultHub.cs b/Tzkt.Api/Websocket/Hubs/DefaultHub.cs index d80c24494..6dc013ce8 100644 --- a/Tzkt.Api/Websocket/Hubs/DefaultHub.cs +++ b/Tzkt.Api/Websocket/Hubs/DefaultHub.cs @@ -15,8 +15,10 @@ public class DefaultHub : BaseHub readonly OperationsProcessor Operations; readonly BigMapsProcessor BigMaps; readonly EventsProcessor Events; - readonly TokenBalancesProcessor Balances; - readonly TokenTransfersProcessor Transfers; + readonly TokenBalancesProcessor TokenBalances; + readonly TokenTransfersProcessor TokenTransfers; + readonly TicketBalancesProcessor TicketBalances; + readonly TicketTransfersProcessor TicketTransfers; readonly AccountsProcessor Accounts; public DefaultHub( @@ -26,8 +28,10 @@ public DefaultHub( OperationsProcessor operations, BigMapsProcessor bigMaps, EventsProcessor events, - TokenBalancesProcessor balances, - TokenTransfersProcessor transfers, + TokenBalancesProcessor tokenBalances, + TokenTransfersProcessor tokenTransfers, + TicketBalancesProcessor ticketBalances, + TicketTransfersProcessor ticketTransfers, AccountsProcessor accounts, ILogger logger, IMetrics metrics, @@ -39,8 +43,10 @@ public DefaultHub( Operations = operations; BigMaps = bigMaps; Events = events; - Balances = balances; - Transfers = transfers; + TokenBalances = tokenBalances; + TokenTransfers = tokenTransfers; + TicketBalances = ticketBalances; + TicketTransfers = ticketTransfers; Accounts = accounts; } @@ -86,14 +92,28 @@ public Task SubscribeToTokenBalances(TokenTransfersParameter parameters) { parameters ??= new(); parameters.EnsureValid(); - return Balances.Subscribe(Clients.Caller, Context.ConnectionId, parameters); + return TokenBalances.Subscribe(Clients.Caller, Context.ConnectionId, parameters); } public Task SubscribeToTokenTransfers(TokenTransfersParameter parameters) { parameters ??= new(); parameters.EnsureValid(); - return Transfers.Subscribe(Clients.Caller, Context.ConnectionId, parameters); + return TokenTransfers.Subscribe(Clients.Caller, Context.ConnectionId, parameters); + } + + public Task SubscribeToTicketBalances(TicketTransfersParameter parameters) + { + parameters ??= new(); + parameters.EnsureValid(); + return TicketBalances.Subscribe(Clients.Caller, Context.ConnectionId, parameters); + } + + public Task SubscribeToTicketTransfers(TicketTransfersParameter parameters) + { + parameters ??= new(); + parameters.EnsureValid(); + return TicketTransfers.Subscribe(Clients.Caller, Context.ConnectionId, parameters); } public Task SubscribeToAccounts(AccountsParameter parameters) @@ -109,8 +129,10 @@ public override Task OnDisconnectedAsync(Exception exception) Operations.Unsubscribe(Context.ConnectionId); BigMaps.Unsubscribe(Context.ConnectionId); Events.Unsubscribe(Context.ConnectionId); - Balances.Unsubscribe(Context.ConnectionId); - Transfers.Unsubscribe(Context.ConnectionId); + TokenBalances.Unsubscribe(Context.ConnectionId); + TokenTransfers.Unsubscribe(Context.ConnectionId); + TicketBalances.Unsubscribe(Context.ConnectionId); + TicketTransfers.Unsubscribe(Context.ConnectionId); Accounts.Unsubscribe(Context.ConnectionId); return base.OnDisconnectedAsync(exception); } diff --git a/Tzkt.Api/Websocket/Parameters/TicketTransfersParameter.cs b/Tzkt.Api/Websocket/Parameters/TicketTransfersParameter.cs new file mode 100644 index 000000000..b68612834 --- /dev/null +++ b/Tzkt.Api/Websocket/Parameters/TicketTransfersParameter.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.SignalR; + +namespace Tzkt.Api.Websocket +{ + public class TicketTransfersParameter + { + public string Account { get; set; } + public string Ticketer { get; set; } + + public void EnsureValid() + { + if (Account != null && !Regex.IsMatch(Account, "^[0-9A-Za-z]{36,37}$")) + throw new HubException("Invalid account address"); + + if (Ticketer != null && !Regex.IsMatch(Ticketer, "^KT1[0-9A-Za-z]{33}$")) + throw new HubException("Invalid contract address"); + } + } +} \ No newline at end of file diff --git a/Tzkt.Api/Websocket/Processors/TicketBalancesProcessor.cs b/Tzkt.Api/Websocket/Processors/TicketBalancesProcessor.cs new file mode 100644 index 000000000..69721e121 --- /dev/null +++ b/Tzkt.Api/Websocket/Processors/TicketBalancesProcessor.cs @@ -0,0 +1,381 @@ +using Microsoft.AspNetCore.SignalR; +using Tzkt.Api.Models; +using Tzkt.Api.Repositories; +using Tzkt.Api.Services.Cache; + +namespace Tzkt.Api.Websocket.Processors +{ + public class TicketBalancesProcessor : IHubProcessor where T : Hub + { + #region static + const string Group = "ticket_balances"; + const string Channel = "ticket_balances"; + static readonly SemaphoreSlim Sema = new(1, 1); + + static readonly HashSet AllSubs = new(); + static readonly Dictionary AccountSubs = new(); + static readonly Dictionary TicketerSubs = new(); + + static readonly Dictionary Limits = new(); + + class AccountSub + { + public HashSet All { get; set; } + public Dictionary Ticketers { get; set; } + + public bool Empty => All == null && Ticketers == null; + } + class TicketerSub + { + public HashSet All { get; set; } + + public bool Empty => All == null; + } + #endregion + + readonly StateCache State; + readonly TicketsRepository Repo; + readonly IHubContext Context; + readonly WebsocketConfig Config; + readonly ILogger Logger; + + public TicketBalancesProcessor(StateCache state, TicketsRepository tickets, + IHubContext hubContext, IConfiguration config, ILogger> logger) + { + State = state; + Repo = tickets; + Context = hubContext; + Config = config.GetWebsocketConfig(); + Logger = logger; + } + + public async Task OnStateChanged() + { + var sendings = new List(); + try + { + await Sema.WaitAsync(); + + if (Limits.Count == 0) + { + Logger.LogDebug("No ticket balances subs"); + return; + } + + #region check reorg + if (State.Reorganized) + { + Logger.LogDebug("Sending reorg message with state {state}", State.ValidLevel); + sendings.Add(Context.Clients + .Group(Group) + .SendReorg(Channel, State.ValidLevel)); + } + #endregion + + if (State.ValidLevel == State.Current.Level) + { + Logger.LogDebug("No ticket balances to send"); + return; + } + + #region load ticket balances + Logger.LogDebug("Fetching ticket balances from {valid} to {current}", State.ValidLevel, State.Current.Level); + + var param = new TicketBalanceFilter + { + lastLevel = State.Current.Level == State.ValidLevel + 1 + ? new Int32Parameter + { + Eq = State.Current.Level + } + : new Int32Parameter + { + Gt = State.ValidLevel, + Le = State.Current.Level + }, + account = new(), + ticket = new() + }; + var limit = 1_000_000; + + var balances = await Repo.GetTicketBalances(param, new() { limit = limit }); + var count = balances.Count(); + + Logger.LogDebug("{cnt} ticket balances fetched", count); + #endregion + + #region prepare to send + var toSend = new Dictionary>(); + + void Add(HashSet subs, TicketBalance balance) + { + foreach (var clientId in subs) + { + if (!toSend.TryGetValue(clientId, out var list)) + { + list = new(); + toSend.Add(clientId, list); + } + list.Add(balance); + } + } + + foreach (var balance in balances) + { + #region all subs + Add(AllSubs, balance); + #endregion + + #region account subs + if (AccountSubs.TryGetValue(balance.Account.Address, out var accountSubs)) + { + if (accountSubs.All != null) + Add(accountSubs.All, balance); + + if (accountSubs.Ticketers != null) + { + if (accountSubs.Ticketers.TryGetValue(balance.Ticket.Ticketer.Address, out var accountTicketerSubs)) + { + if (accountTicketerSubs.All != null) + Add(accountTicketerSubs.All, balance); + } + } + } + #endregion + + #region ticketer subs + if (TicketerSubs.TryGetValue(balance.Ticket.Ticketer.Address, out var ticketerSubs)) + { + if (ticketerSubs.All != null) + Add(ticketerSubs.All, balance); + } + #endregion + } + #endregion + + #region send + foreach (var (connectionId, balancesList) in toSend.Where(x => x.Value.Count > 0)) + { + var data = balancesList.Count > 1 + ? Distinct(balancesList).OrderBy(x => x.Id) + : (IEnumerable)balancesList; + + sendings.Add(Context.Clients + .Client(connectionId) + .SendData(Channel, data, State.Current.Level)); + + Logger.LogDebug("{cnt} ticket balances sent to {id}", balancesList.Count, connectionId); + } + + Logger.LogDebug("{cnt} ticket balances sent", count); + #endregion + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to process state change"); + } + finally + { + Sema.Release(); + #region await sendings + try + { + await Task.WhenAll(sendings); + } + catch (Exception ex) + { + // should never get here + Logger.LogCritical(ex, "Sendings failed"); + } + #endregion + } + } + + public async Task Subscribe(IClientProxy client, string connectionId, TicketTransfersParameter parameter) + { + Task sending = Task.CompletedTask; + try + { + await Sema.WaitAsync(); + Logger.LogDebug("New subscription..."); + + #region check limits + if (Limits.TryGetValue(connectionId, out var cnt) && cnt >= Config.MaxTicketBalancesSubscriptions) + throw new HubException($"Subscriptions limit exceeded"); + + if (cnt > 0) // reuse already allocated string + connectionId = Limits.Keys.First(x => x == connectionId); + #endregion + + #region add to subs + if (parameter.Account != null) + { + if (!AccountSubs.TryGetValue(parameter.Account, out var accountSub)) + { + accountSub = new(); + AccountSubs.Add(parameter.Account, accountSub); + } + + if (parameter.Ticketer != null) + { + accountSub.Ticketers ??= new(4); + if (!accountSub.Ticketers.TryGetValue(parameter.Ticketer, out var ticketerSub)) + { + ticketerSub = new(); + accountSub.Ticketers.Add(parameter.Ticketer, ticketerSub); + } + + ticketerSub.All ??= new(4); + TryAdd(ticketerSub.All, connectionId); + } + else + { + accountSub.All ??= new(4); + TryAdd(accountSub.All, connectionId); + } + } + else if (parameter.Ticketer != null) + { + if (!TicketerSubs.TryGetValue(parameter.Ticketer, out var ticketerSub)) + { + ticketerSub = new(); + TicketerSubs.Add(parameter.Ticketer, ticketerSub); + } + + ticketerSub.All ??= new(); + TryAdd(ticketerSub.All, connectionId); + } + else + { + TryAdd(AllSubs, connectionId); + } + #endregion + + #region add to group + await Context.Groups.AddToGroupAsync(connectionId, Group); + #endregion + + sending = client.SendState(Channel, State.Current.Level); + + Logger.LogDebug("Client {id} subscribed with state {state}", connectionId, State.Current.Level); + return State.Current.Level; + } + catch (HubException) + { + throw; + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to add subscription"); + return 0; + } + finally + { + Sema.Release(); + try + { + await sending; + } + catch (Exception ex) + { + // should never get here + Logger.LogCritical(ex, "Sending failed"); + } + } + } + + public void Unsubscribe(string connectionId) + { + try + { + Sema.Wait(); + if (!Limits.ContainsKey(connectionId)) return; + Logger.LogDebug("Remove subscription..."); + + #region all subs + TryRemove(AllSubs, connectionId); + #endregion + + #region account subs + var emptyAccountSubs = new List(); + foreach (var (account, accountSub) in AccountSubs) + { + accountSub.All = TryRemove(accountSub.All, connectionId); + if (accountSub.Ticketers != null) + { + var emptyAccountTicketerSubs = new List(); + foreach (var (ticketer, ticketerSub) in accountSub.Ticketers) + { + ticketerSub.All = TryRemove(ticketerSub.All, connectionId); + + if (ticketerSub.Empty) + emptyAccountTicketerSubs.Add(ticketer); + } + foreach (var ticketer in emptyAccountTicketerSubs) + accountSub.Ticketers.Remove(ticketer); + + if (accountSub.Ticketers.Count == 0) + accountSub.Ticketers = null; + } + if (accountSub.Empty) + emptyAccountSubs.Add(account); + } + foreach (var account in emptyAccountSubs) + AccountSubs.Remove(account); + #endregion + + #region ticketer subs + var emptyTicketerSubs = new List(); + foreach (var (ticketer, ticketerSub) in TicketerSubs) + { + ticketerSub.All = TryRemove(ticketerSub.All, connectionId); + + if (ticketerSub.Empty) + emptyTicketerSubs.Add(ticketer); + } + foreach (var ticketer in emptyTicketerSubs) + TicketerSubs.Remove(ticketer); + #endregion + + if (Limits[connectionId] != 0) + Logger.LogCritical("Failed to unsibscribe {id}: {cnt} subs left", connectionId, Limits[connectionId]); + Limits.Remove(connectionId); + + Logger.LogDebug("Client {id} unsubscribed", connectionId); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to remove subscription"); + } + finally + { + Sema.Release(); + } + } + + private static void TryAdd(HashSet set, string connectionId) + { + if (set.Add(connectionId)) + Limits[connectionId] = Limits.GetValueOrDefault(connectionId) + 1; + } + + private static HashSet TryRemove(HashSet set, string connectionId) + { + if (set == null) return null; + if (set.Remove(connectionId)) + { + Limits[connectionId]--; + if (set.Count == 0) return null; + } + return set; + } + + private static IEnumerable Distinct(List items) + { + var set = new HashSet(items.Count); + foreach (var item in items) + if (set.Add(item.Id)) + yield return item; + } + } +} diff --git a/Tzkt.Api/Websocket/Processors/TicketTransfersProcessor.cs b/Tzkt.Api/Websocket/Processors/TicketTransfersProcessor.cs new file mode 100644 index 000000000..884bef3d2 --- /dev/null +++ b/Tzkt.Api/Websocket/Processors/TicketTransfersProcessor.cs @@ -0,0 +1,401 @@ +using Microsoft.AspNetCore.SignalR; +using Tzkt.Api.Models; +using Tzkt.Api.Repositories; +using Tzkt.Api.Services.Cache; + +namespace Tzkt.Api.Websocket.Processors +{ + public class TicketTransfersProcessor : IHubProcessor where T : Hub + { + #region static + const string TicketTransfersGroup = "ticket_transfers"; + const string TicketTransfersChannel = "ticket_transfers"; + static readonly SemaphoreSlim Sema = new(1, 1); + + static readonly HashSet AllSubs = new(); + static readonly Dictionary AccountSubs = new(); + static readonly Dictionary TicketerSubs = new(); + + static readonly Dictionary Limits = new(); + + class AccountSub + { + public HashSet All { get; set; } + public Dictionary Ticketers { get; set; } + + public bool Empty => All == null && Ticketers == null; + } + class TicketerSub + { + public HashSet All { get; set; } + + public bool Empty => All == null; + } + #endregion + + readonly StateCache State; + readonly TicketsRepository Repo; + readonly IHubContext Context; + readonly WebsocketConfig Config; + readonly ILogger Logger; + + public TicketTransfersProcessor(StateCache state, TicketsRepository tickets, + IHubContext hubContext, IConfiguration config, ILogger> logger) + { + State = state; + Repo = tickets; + Context = hubContext; + Config = config.GetWebsocketConfig(); + Logger = logger; + } + + public async Task OnStateChanged() + { + var sendings = new List(); + try + { + await Sema.WaitAsync(); + + if (Limits.Count == 0) + { + Logger.LogDebug("No ticket transfers subs"); + return; + } + + #region check reorg + if (State.Reorganized) + { + Logger.LogDebug("Sending reorg message with state {state}", State.ValidLevel); + sendings.Add(Context.Clients + .Group(TicketTransfersGroup) + .SendReorg(TicketTransfersChannel, State.ValidLevel)); + } + #endregion + + if (State.ValidLevel == State.Current.Level) + { + Logger.LogDebug("No ticket transfers to send"); + return; + } + + #region load ticket transfers + Logger.LogDebug("Fetching ticket transfers from block {valid} to block {current}", State.ValidLevel, State.Current.Level); + + var param = new TicketTransferFilter + { + level = State.Current.Level == State.ValidLevel + 1 + ? new Int32Parameter + { + Eq = State.Current.Level + } + : new Int32Parameter + { + Gt = State.ValidLevel, + Le = State.Current.Level + }, + ticket = new() + }; + var limit = 1_000_000; + + + var transfers = await Repo.GetTicketTransfers(param, new() { limit = limit }); + var count = transfers.Count(); + + Logger.LogDebug("{cnt} ticket transfers fetched", count); + #endregion + + #region prepare to send + var toSend = new Dictionary>(); + + void Add(HashSet subs, TicketTransfer transfer) + { + foreach (var clientId in subs) + { + if (!toSend.TryGetValue(clientId, out var list)) + { + list = new(); + toSend.Add(clientId, list); + } + list.Add(transfer); + } + } + + foreach (var transfer in transfers) + { + #region all subs + Add(AllSubs, transfer); + #endregion + + #region account subs + if (transfer.From != null) + { + if (AccountSubs.TryGetValue(transfer.From.Address, out var accountSubs)) + { + if (accountSubs.All != null) + Add(accountSubs.All, transfer); + + if (accountSubs.Ticketers != null) + { + if (accountSubs.Ticketers.TryGetValue(transfer.Ticket.Ticketer.Address, out var accountTicketerSubs)) + { + if (accountTicketerSubs.All != null) + Add(accountTicketerSubs.All, transfer); + } + } + } + } + if (transfer.To != null) + { + if (AccountSubs.TryGetValue(transfer.To.Address, out var accountSubs)) + { + if (accountSubs.All != null) + Add(accountSubs.All, transfer); + + if (accountSubs.Ticketers != null) + { + if (accountSubs.Ticketers.TryGetValue(transfer.Ticket.Ticketer.Address, out var accountTicketerSubs)) + { + if (accountTicketerSubs.All != null) + Add(accountTicketerSubs.All, transfer); + } + } + } + } + #endregion + + #region ticketer subs + if (TicketerSubs.TryGetValue(transfer.Ticket.Ticketer.Address, out var ticketerSubs)) + { + if (ticketerSubs.All != null) + Add(ticketerSubs.All, transfer); + } + #endregion + } + #endregion + + #region send + foreach (var (connectionId, transfersList) in toSend.Where(x => x.Value.Count > 0)) + { + var data = transfersList.Count > 1 + ? Distinct(transfersList).OrderBy(x => x.Id) + : (IEnumerable)transfersList; + + sendings.Add(Context.Clients + .Client(connectionId) + .SendData(TicketTransfersChannel, data, State.Current.Level)); + + Logger.LogDebug("{cnt} ticket transfers sent to {id}", transfersList.Count, connectionId); + } + + Logger.LogDebug("{cnt} ticket transfers sent", count); + #endregion + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to process state change"); + } + finally + { + Sema.Release(); + #region await sendings + try + { + await Task.WhenAll(sendings); + } + catch (Exception ex) + { + // should never get here + Logger.LogCritical(ex, "Sendings failed"); + } + #endregion + } + } + + public async Task Subscribe(IClientProxy client, string connectionId, TicketTransfersParameter parameter) + { + Task sending = Task.CompletedTask; + try + { + await Sema.WaitAsync(); + Logger.LogDebug("New subscription..."); + + #region check limits + if (Limits.TryGetValue(connectionId, out var cnt) && cnt >= Config.MaxTicketTransfersSubscriptions) + throw new HubException($"Subscriptions limit exceeded"); + + if (cnt > 0) // reuse already allocated string + connectionId = Limits.Keys.First(x => x == connectionId); + #endregion + + #region add to subs + if (parameter.Account != null) + { + if (!AccountSubs.TryGetValue(parameter.Account, out var accountSub)) + { + accountSub = new(); + AccountSubs.Add(parameter.Account, accountSub); + } + + if (parameter.Ticketer != null) + { + accountSub.Ticketers ??= new(4); + if (!accountSub.Ticketers.TryGetValue(parameter.Ticketer, out var ticketerSub)) + { + ticketerSub = new(); + accountSub.Ticketers.Add(parameter.Ticketer, ticketerSub); + } + + ticketerSub.All ??= new(4); + TryAdd(ticketerSub.All, connectionId); + } + else + { + accountSub.All ??= new(4); + TryAdd(accountSub.All, connectionId); + } + } + else if (parameter.Ticketer != null) + { + if (!TicketerSubs.TryGetValue(parameter.Ticketer, out var ticketerSub)) + { + ticketerSub = new(); + TicketerSubs.Add(parameter.Ticketer, ticketerSub); + } + + ticketerSub.All ??= new(); + TryAdd(ticketerSub.All, connectionId); + } + else + { + TryAdd(AllSubs, connectionId); + } + #endregion + + #region add to group + await Context.Groups.AddToGroupAsync(connectionId, TicketTransfersGroup); + #endregion + + sending = client.SendState(TicketTransfersChannel, State.Current.Level); + + Logger.LogDebug("Client {id} subscribed with state {state}", connectionId, State.Current.Level); + return State.Current.Level; + } + catch (HubException) + { + throw; + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to add subscription"); + return 0; + } + finally + { + Sema.Release(); + try + { + await sending; + } + catch (Exception ex) + { + // should never get here + Logger.LogCritical(ex, "Sending failed"); + } + } + } + + public void Unsubscribe(string connectionId) + { + try + { + Sema.Wait(); + if (!Limits.ContainsKey(connectionId)) return; + Logger.LogDebug("Remove subscription..."); + + #region all subs + TryRemove(AllSubs, connectionId); + #endregion + + #region account subs + var emptyAccountSubs = new List(); + foreach (var (account, accountSub) in AccountSubs) + { + accountSub.All = TryRemove(accountSub.All, connectionId); + if (accountSub.Ticketers != null) + { + var emptyAccountTicketerSubs = new List(); + foreach (var (ticketer, ticketerSub) in accountSub.Ticketers) + { + ticketerSub.All = TryRemove(ticketerSub.All, connectionId); + + if (ticketerSub.Empty) + emptyAccountTicketerSubs.Add(ticketer); + } + foreach (var ticketer in emptyAccountTicketerSubs) + accountSub.Ticketers.Remove(ticketer); + + if (accountSub.Ticketers.Count == 0) + accountSub.Ticketers = null; + } + if (accountSub.Empty) + emptyAccountSubs.Add(account); + } + foreach (var account in emptyAccountSubs) + AccountSubs.Remove(account); + #endregion + + #region ticketer subs + var emptyTicketerSubs = new List(); + foreach (var (ticketer, ticketerSub) in TicketerSubs) + { + ticketerSub.All = TryRemove(ticketerSub.All, connectionId); + + if (ticketerSub.Empty) + emptyTicketerSubs.Add(ticketer); + } + foreach (var ticketer in emptyTicketerSubs) + TicketerSubs.Remove(ticketer); + #endregion + + if (Limits[connectionId] != 0) + Logger.LogCritical("Failed to unsubscribe {id}: {cnt} subs left", connectionId, Limits[connectionId]); + Limits.Remove(connectionId); + + Logger.LogDebug("Client {id} unsubscribed", connectionId); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to remove subscription"); + } + finally + { + Sema.Release(); + } + } + + private static void TryAdd(HashSet set, string connectionId) + { + if (set.Add(connectionId)) + Limits[connectionId] = Limits.GetValueOrDefault(connectionId) + 1; + } + + private static HashSet TryRemove(HashSet set, string connectionId) + { + if (set == null) return null; + if (set.Remove(connectionId)) + { + Limits[connectionId]--; + if (set.Count == 0) return null; + } + return set; + } + + private static IEnumerable Distinct(List items) + { + var set = new HashSet(items.Count); + foreach (var item in items) + if (set.Add(item.Id)) + yield return item; + } + } +} diff --git a/Tzkt.Api/Websocket/WebsocketConfig.cs b/Tzkt.Api/Websocket/WebsocketConfig.cs index 7b57428de..27141d41d 100644 --- a/Tzkt.Api/Websocket/WebsocketConfig.cs +++ b/Tzkt.Api/Websocket/WebsocketConfig.cs @@ -12,6 +12,8 @@ public class WebsocketConfig public int MaxAccountsSubscriptions { get; set; } = 50; public int MaxTokenBalancesSubscriptions { get; set; } = 50; public int MaxTokenTransfersSubscriptions { get; set; } = 50; + public int MaxTicketBalancesSubscriptions { get; set; } = 50; + public int MaxTicketTransfersSubscriptions { get; set; } = 50; } public static class CacheConfigExt diff --git a/Tzkt.Api/appsettings.json b/Tzkt.Api/appsettings.json index 9cff53740..0d2063e76 100644 --- a/Tzkt.Api/appsettings.json +++ b/Tzkt.Api/appsettings.json @@ -17,7 +17,9 @@ "MaxEventSubscriptions": 50, "MaxAccountsSubscriptions": 50, "MaxTokenBalancesSubscriptions": 50, - "MaxTokenTransfersSubscriptions": 50 + "MaxTokenTransfersSubscriptions": 50, + "MaxTicketBalancesSubscriptions": 50, + "MaxTicketTransfersSubscriptions": 50 }, "RpcHelpers": { "Enabled": false, diff --git a/Tzkt.Data/Migrations/20230425183139_Nairobi.Designer.cs b/Tzkt.Data/Migrations/20230425183139_Nairobi.Designer.cs index 0d4207510..19dca4a2e 100644 --- a/Tzkt.Data/Migrations/20230425183139_Nairobi.Designer.cs +++ b/Tzkt.Data/Migrations/20230425183139_Nairobi.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Text.Json; using Microsoft.EntityFrameworkCore; @@ -6049,4 +6049,4 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) #pragma warning restore 612, 618 } } -} +} \ No newline at end of file diff --git a/Tzkt.Data/Migrations/20230425183139_Nairobi.cs b/Tzkt.Data/Migrations/20230425183139_Nairobi.cs index e5e425504..cf63c2ef9 100644 --- a/Tzkt.Data/Migrations/20230425183139_Nairobi.cs +++ b/Tzkt.Data/Migrations/20230425183139_Nairobi.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/Tzkt.Data/Migrations/20230823140932_Tickets.Designer.cs b/Tzkt.Data/Migrations/20230823140932_Tickets.Designer.cs new file mode 100644 index 000000000..18b26e24e --- /dev/null +++ b/Tzkt.Data/Migrations/20230823140932_Tickets.Designer.cs @@ -0,0 +1,6290 @@ +// +using System; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Tzkt.Data; + +#nullable disable + +namespace Tzkt.Data.Migrations +{ + [DbContext(typeof(TzktContext))] + [Migration("20230823140932_Tickets")] + partial class Tickets + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Tzkt.Data.Models.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveRefutationGamesCount") + .HasColumnType("integer"); + + b.Property("ActiveTicketsCount") + .HasColumnType("integer"); + + b.Property("ActiveTokensCount") + .HasColumnType("integer"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(37) + .HasColumnType("character varying(37)"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("ContractsCount") + .HasColumnType("integer"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("DelegateId") + .HasColumnType("integer"); + + b.Property("DelegationLevel") + .HasColumnType("integer"); + + b.Property("DelegationsCount") + .HasColumnType("integer"); + + b.Property("DrainDelegateCount") + .HasColumnType("integer"); + + b.Property("Extras") + .HasColumnType("jsonb"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("IncreasePaidStorageCount") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("Metadata") + .HasColumnType("jsonb"); + + b.Property("MigrationsCount") + .HasColumnType("integer"); + + b.Property("OriginationsCount") + .HasColumnType("integer"); + + b.Property("RefutationGamesCount") + .HasColumnType("integer"); + + b.Property("RevealsCount") + .HasColumnType("integer"); + + b.Property("RollupBonds") + .HasColumnType("bigint"); + + b.Property("RollupsCount") + .HasColumnType("integer"); + + b.Property("SmartRollupAddMessagesCount") + .HasColumnType("integer"); + + b.Property("SmartRollupBonds") + .HasColumnType("bigint"); + + b.Property("SmartRollupCementCount") + .HasColumnType("integer"); + + b.Property("SmartRollupExecuteCount") + .HasColumnType("integer"); + + b.Property("SmartRollupOriginateCount") + .HasColumnType("integer"); + + b.Property("SmartRollupPublishCount") + .HasColumnType("integer"); + + b.Property("SmartRollupRecoverBondCount") + .HasColumnType("integer"); + + b.Property("SmartRollupRefuteCount") + .HasColumnType("integer"); + + b.Property("SmartRollupsCount") + .HasColumnType("integer"); + + b.Property("Staked") + .HasColumnType("boolean"); + + b.Property("TicketBalancesCount") + .HasColumnType("integer"); + + b.Property("TicketTransfersCount") + .HasColumnType("integer"); + + b.Property("TokenBalancesCount") + .HasColumnType("integer"); + + b.Property("TokenTransfersCount") + .HasColumnType("integer"); + + b.Property("TransactionsCount") + .HasColumnType("integer"); + + b.Property("TransferTicketCount") + .HasColumnType("integer"); + + b.Property("TxRollupCommitCount") + .HasColumnType("integer"); + + b.Property("TxRollupDispatchTicketsCount") + .HasColumnType("integer"); + + b.Property("TxRollupFinalizeCommitmentCount") + .HasColumnType("integer"); + + b.Property("TxRollupOriginationCount") + .HasColumnType("integer"); + + b.Property("TxRollupRejectionCount") + .HasColumnType("integer"); + + b.Property("TxRollupRemoveCommitmentCount") + .HasColumnType("integer"); + + b.Property("TxRollupReturnBondCount") + .HasColumnType("integer"); + + b.Property("TxRollupSubmitBatchCount") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UpdateConsensusKeyCount") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique(); + + b.HasIndex("DelegateId"); + + b.HasIndex("Extras"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Extras"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("Extras"), new[] { "jsonb_path_ops" }); + + b.HasIndex("FirstLevel"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Metadata"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Metadata"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("Metadata"), new[] { "jsonb_path_ops" }); + + b.HasIndex("Staked"); + + b.HasIndex("Type"); + + b.ToTable("Accounts"); + + b.HasDiscriminator("Type").HasValue((byte)3); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Tzkt.Data.Models.ActivationOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .IsUnique(); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.ToTable("ActivationOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.AppState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountCounter") + .HasColumnType("integer"); + + b.Property("AccountsCount") + .HasColumnType("integer"); + + b.Property("ActivationOpsCount") + .HasColumnType("integer"); + + b.Property("BallotOpsCount") + .HasColumnType("integer"); + + b.Property("BigMapCounter") + .HasColumnType("integer"); + + b.Property("BigMapKeyCounter") + .HasColumnType("integer"); + + b.Property("BigMapUpdateCounter") + .HasColumnType("integer"); + + b.Property("BlocksCount") + .HasColumnType("integer"); + + b.Property("Chain") + .HasColumnType("text"); + + b.Property("ChainId") + .HasColumnType("text"); + + b.Property("CommitmentsCount") + .HasColumnType("integer"); + + b.Property("ConstantsCount") + .HasColumnType("integer"); + + b.Property("Cycle") + .HasColumnType("integer"); + + b.Property("CyclesCount") + .HasColumnType("integer"); + + b.Property("DelegationOpsCount") + .HasColumnType("integer"); + + b.Property("DomainsLevel") + .HasColumnType("integer"); + + b.Property("DomainsNameRegistry") + .HasColumnType("text"); + + b.Property("DoubleBakingOpsCount") + .HasColumnType("integer"); + + b.Property("DoubleEndorsingOpsCount") + .HasColumnType("integer"); + + b.Property("DoublePreendorsingOpsCount") + .HasColumnType("integer"); + + b.Property("DrainDelegateOpsCount") + .HasColumnType("integer"); + + b.Property("EndorsementOpsCount") + .HasColumnType("integer"); + + b.Property("EndorsingRewardOpsCount") + .HasColumnType("integer"); + + b.Property("EventCounter") + .HasColumnType("integer"); + + b.Property("EventsCount") + .HasColumnType("integer"); + + b.Property("Extras") + .HasColumnType("jsonb"); + + b.Property("Hash") + .HasColumnType("text"); + + b.Property("InboxMessageCounter") + .HasColumnType("integer"); + + b.Property("IncreasePaidStorageOpsCount") + .HasColumnType("integer"); + + b.Property("KnownHead") + .HasColumnType("integer"); + + b.Property("LastSync") + .HasColumnType("timestamp with time zone"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("ManagerCounter") + .HasColumnType("integer"); + + b.Property("MigrationOpsCount") + .HasColumnType("integer"); + + b.Property("NextProtocol") + .HasColumnType("text"); + + b.Property("NonceRevelationOpsCount") + .HasColumnType("integer"); + + b.Property("OperationCounter") + .HasColumnType("bigint"); + + b.Property("OriginationOpsCount") + .HasColumnType("integer"); + + b.Property("PreendorsementOpsCount") + .HasColumnType("integer"); + + b.Property("ProposalOpsCount") + .HasColumnType("integer"); + + b.Property("ProposalsCount") + .HasColumnType("integer"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("ProtocolsCount") + .HasColumnType("integer"); + + b.Property("QuoteBtc") + .HasColumnType("double precision"); + + b.Property("QuoteCny") + .HasColumnType("double precision"); + + b.Property("QuoteEth") + .HasColumnType("double precision"); + + b.Property("QuoteEur") + .HasColumnType("double precision"); + + b.Property("QuoteGbp") + .HasColumnType("double precision"); + + b.Property("QuoteJpy") + .HasColumnType("double precision"); + + b.Property("QuoteKrw") + .HasColumnType("double precision"); + + b.Property("QuoteLevel") + .HasColumnType("integer"); + + b.Property("QuoteUsd") + .HasColumnType("double precision"); + + b.Property("RefutationGameCounter") + .HasColumnType("integer"); + + b.Property("RegisterConstantOpsCount") + .HasColumnType("integer"); + + b.Property("RevealOpsCount") + .HasColumnType("integer"); + + b.Property("RevelationPenaltyOpsCount") + .HasColumnType("integer"); + + b.Property("ScriptCounter") + .HasColumnType("integer"); + + b.Property("SetDepositsLimitOpsCount") + .HasColumnType("integer"); + + b.Property("SmartRollupAddMessagesOpsCount") + .HasColumnType("integer"); + + b.Property("SmartRollupCementOpsCount") + .HasColumnType("integer"); + + b.Property("SmartRollupCommitmentCounter") + .HasColumnType("integer"); + + b.Property("SmartRollupExecuteOpsCount") + .HasColumnType("integer"); + + b.Property("SmartRollupOriginateOpsCount") + .HasColumnType("integer"); + + b.Property("SmartRollupPublishOpsCount") + .HasColumnType("integer"); + + b.Property("SmartRollupRecoverBondOpsCount") + .HasColumnType("integer"); + + b.Property("SmartRollupRefuteOpsCount") + .HasColumnType("integer"); + + b.Property("StorageCounter") + .HasColumnType("integer"); + + b.Property("TicketBalancesCount") + .HasColumnType("integer"); + + b.Property("TicketTransfersCount") + .HasColumnType("integer"); + + b.Property("TicketsCount") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("TokenBalancesCount") + .HasColumnType("integer"); + + b.Property("TokenTransfersCount") + .HasColumnType("integer"); + + b.Property("TokensCount") + .HasColumnType("integer"); + + b.Property("TransactionOpsCount") + .HasColumnType("integer"); + + b.Property("TransferTicketOpsCount") + .HasColumnType("integer"); + + b.Property("TxRollupCommitOpsCount") + .HasColumnType("integer"); + + b.Property("TxRollupDispatchTicketsOpsCount") + .HasColumnType("integer"); + + b.Property("TxRollupFinalizeCommitmentOpsCount") + .HasColumnType("integer"); + + b.Property("TxRollupOriginationOpsCount") + .HasColumnType("integer"); + + b.Property("TxRollupRejectionOpsCount") + .HasColumnType("integer"); + + b.Property("TxRollupRemoveCommitmentOpsCount") + .HasColumnType("integer"); + + b.Property("TxRollupReturnBondOpsCount") + .HasColumnType("integer"); + + b.Property("TxRollupSubmitBatchOpsCount") + .HasColumnType("integer"); + + b.Property("UpdateConsensusKeyOpsCount") + .HasColumnType("integer"); + + b.Property("VdfRevelationOpsCount") + .HasColumnType("integer"); + + b.Property("VotingEpoch") + .HasColumnType("integer"); + + b.Property("VotingPeriod") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("AppState"); + + b.HasData( + new + { + Id = -1, + AccountCounter = 0, + AccountsCount = 0, + ActivationOpsCount = 0, + BallotOpsCount = 0, + BigMapCounter = 0, + BigMapKeyCounter = 0, + BigMapUpdateCounter = 0, + BlocksCount = 0, + CommitmentsCount = 0, + ConstantsCount = 0, + Cycle = -1, + CyclesCount = 0, + DelegationOpsCount = 0, + DomainsLevel = 0, + DoubleBakingOpsCount = 0, + DoubleEndorsingOpsCount = 0, + DoublePreendorsingOpsCount = 0, + DrainDelegateOpsCount = 0, + EndorsementOpsCount = 0, + EndorsingRewardOpsCount = 0, + EventCounter = 0, + EventsCount = 0, + Hash = "", + InboxMessageCounter = 0, + IncreasePaidStorageOpsCount = 0, + KnownHead = 0, + LastSync = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Level = -1, + ManagerCounter = 0, + MigrationOpsCount = 0, + NextProtocol = "", + NonceRevelationOpsCount = 0, + OperationCounter = 0L, + OriginationOpsCount = 0, + PreendorsementOpsCount = 0, + ProposalOpsCount = 0, + ProposalsCount = 0, + Protocol = "", + ProtocolsCount = 0, + QuoteBtc = 0.0, + QuoteCny = 0.0, + QuoteEth = 0.0, + QuoteEur = 0.0, + QuoteGbp = 0.0, + QuoteJpy = 0.0, + QuoteKrw = 0.0, + QuoteLevel = -1, + QuoteUsd = 0.0, + RefutationGameCounter = 0, + RegisterConstantOpsCount = 0, + RevealOpsCount = 0, + RevelationPenaltyOpsCount = 0, + ScriptCounter = 0, + SetDepositsLimitOpsCount = 0, + SmartRollupAddMessagesOpsCount = 0, + SmartRollupCementOpsCount = 0, + SmartRollupCommitmentCounter = 0, + SmartRollupExecuteOpsCount = 0, + SmartRollupOriginateOpsCount = 0, + SmartRollupPublishOpsCount = 0, + SmartRollupRecoverBondOpsCount = 0, + SmartRollupRefuteOpsCount = 0, + StorageCounter = 0, + TicketBalancesCount = 0, + TicketTransfersCount = 0, + TicketsCount = 0, + Timestamp = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + TokenBalancesCount = 0, + TokenTransfersCount = 0, + TokensCount = 0, + TransactionOpsCount = 0, + TransferTicketOpsCount = 0, + TxRollupCommitOpsCount = 0, + TxRollupDispatchTicketsOpsCount = 0, + TxRollupFinalizeCommitmentOpsCount = 0, + TxRollupOriginationOpsCount = 0, + TxRollupRejectionOpsCount = 0, + TxRollupRemoveCommitmentOpsCount = 0, + TxRollupReturnBondOpsCount = 0, + TxRollupSubmitBatchOpsCount = 0, + UpdateConsensusKeyOpsCount = 0, + VdfRevelationOpsCount = 0, + VotingEpoch = -1, + VotingPeriod = -1 + }); + }); + + modelBuilder.Entity("Tzkt.Data.Models.BakerCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveStake") + .HasColumnType("bigint"); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("BlockFees") + .HasColumnType("bigint"); + + b.Property("BlockRewards") + .HasColumnType("bigint"); + + b.Property("Blocks") + .HasColumnType("integer"); + + b.Property("Cycle") + .HasColumnType("integer"); + + b.Property("DelegatedBalance") + .HasColumnType("bigint"); + + b.Property("DelegatorsCount") + .HasColumnType("integer"); + + b.Property("DoubleBakingLosses") + .HasColumnType("bigint"); + + b.Property("DoubleBakingRewards") + .HasColumnType("bigint"); + + b.Property("DoubleEndorsingLosses") + .HasColumnType("bigint"); + + b.Property("DoubleEndorsingRewards") + .HasColumnType("bigint"); + + b.Property("DoublePreendorsingLosses") + .HasColumnType("bigint"); + + b.Property("DoublePreendorsingRewards") + .HasColumnType("bigint"); + + b.Property("EndorsementRewards") + .HasColumnType("bigint"); + + b.Property("Endorsements") + .HasColumnType("integer"); + + b.Property("ExpectedBlocks") + .HasColumnType("double precision"); + + b.Property("ExpectedEndorsements") + .HasColumnType("double precision"); + + b.Property("FutureBlockRewards") + .HasColumnType("bigint"); + + b.Property("FutureBlocks") + .HasColumnType("integer"); + + b.Property("FutureEndorsementRewards") + .HasColumnType("bigint"); + + b.Property("FutureEndorsements") + .HasColumnType("integer"); + + b.Property("MissedBlockFees") + .HasColumnType("bigint"); + + b.Property("MissedBlockRewards") + .HasColumnType("bigint"); + + b.Property("MissedBlocks") + .HasColumnType("integer"); + + b.Property("MissedEndorsementRewards") + .HasColumnType("bigint"); + + b.Property("MissedEndorsements") + .HasColumnType("integer"); + + b.Property("RevelationLosses") + .HasColumnType("bigint"); + + b.Property("RevelationRewards") + .HasColumnType("bigint"); + + b.Property("SelectedStake") + .HasColumnType("bigint"); + + b.Property("StakingBalance") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BakerId"); + + b.HasIndex("Cycle"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Cycle", "BakerId") + .IsUnique(); + + b.ToTable("BakerCycles"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.BakingRight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("Cycle") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Round") + .HasColumnType("integer"); + + b.Property("Slots") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("Cycle"); + + b.HasIndex("Level"); + + b.HasIndex("Cycle", "BakerId"); + + b.ToTable("BakingRights"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.BallotOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Epoch") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("ProposalId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Vote") + .HasColumnType("integer"); + + b.Property("VotingPower") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Epoch"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("Period"); + + b.HasIndex("ProposalId"); + + b.HasIndex("SenderId"); + + b.ToTable("BallotOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.BigMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("ActiveKeys") + .HasColumnType("integer"); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("KeyType") + .HasColumnType("bytea"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("Ptr") + .HasColumnType("integer"); + + b.Property("StoragePath") + .HasColumnType("text"); + + b.Property("Tags") + .HasColumnType("integer"); + + b.Property("TotalKeys") + .HasColumnType("integer"); + + b.Property("Updates") + .HasColumnType("integer"); + + b.Property("ValueType") + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasAlternateKey("Ptr"); + + b.HasIndex("ContractId"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Ptr") + .IsUnique(); + + b.ToTable("BigMaps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.BigMapKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("BigMapPtr") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("JsonKey") + .HasColumnType("jsonb"); + + b.Property("JsonValue") + .HasColumnType("jsonb"); + + b.Property("KeyHash") + .HasMaxLength(54) + .HasColumnType("character varying(54)"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("RawKey") + .HasColumnType("bytea"); + + b.Property("RawValue") + .HasColumnType("bytea"); + + b.Property("Updates") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BigMapPtr"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("JsonKey"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonKey"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonKey"), new[] { "jsonb_path_ops" }); + + b.HasIndex("JsonValue"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonValue"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonValue"), new[] { "jsonb_path_ops" }); + + b.HasIndex("LastLevel"); + + b.HasIndex("BigMapPtr", "Active") + .HasFilter("\"Active\" = true"); + + b.HasIndex("BigMapPtr", "KeyHash"); + + b.ToTable("BigMapKeys"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.BigMapUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer"); + + b.Property("BigMapKeyId") + .HasColumnType("integer"); + + b.Property("BigMapPtr") + .HasColumnType("integer"); + + b.Property("JsonValue") + .HasColumnType("jsonb"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("MigrationId") + .HasColumnType("bigint"); + + b.Property("OriginationId") + .HasColumnType("bigint"); + + b.Property("RawValue") + .HasColumnType("bytea"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BigMapKeyId") + .HasFilter("\"BigMapKeyId\" is not null"); + + b.HasIndex("BigMapPtr"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Level"); + + b.HasIndex("MigrationId") + .HasFilter("\"MigrationId\" is not null"); + + b.HasIndex("OriginationId") + .HasFilter("\"OriginationId\" is not null"); + + b.HasIndex("TransactionId") + .HasFilter("\"TransactionId\" is not null"); + + b.ToTable("BigMapUpdates"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Block", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BlockRound") + .HasColumnType("integer"); + + b.Property("Bonus") + .HasColumnType("bigint"); + + b.Property("Cycle") + .HasColumnType("integer"); + + b.Property("Deposit") + .HasColumnType("bigint"); + + b.Property("Events") + .HasColumnType("integer"); + + b.Property("Extras") + .HasColumnType("jsonb"); + + b.Property("Fees") + .HasColumnType("bigint"); + + b.Property("Hash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("LBToggle") + .HasColumnType("boolean"); + + b.Property("LBToggleEma") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Operations") + .HasColumnType("bigint"); + + b.Property("PayloadRound") + .HasColumnType("integer"); + + b.Property("ProducerId") + .HasColumnType("integer"); + + b.Property("ProposerId") + .HasColumnType("integer"); + + b.Property("ProtoCode") + .HasColumnType("integer"); + + b.Property("ResetBakerDeactivation") + .HasColumnType("integer"); + + b.Property("ResetProposerDeactivation") + .HasColumnType("integer"); + + b.Property("RevelationId") + .HasColumnType("bigint"); + + b.Property("Reward") + .HasColumnType("bigint"); + + b.Property("SoftwareId") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Validations") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("Level") + .IsUnique(); + + b.HasIndex("ProducerId"); + + b.HasIndex("ProposerId"); + + b.HasIndex("ProtoCode"); + + b.HasIndex("RevelationId") + .IsUnique(); + + b.HasIndex("SoftwareId"); + + b.ToTable("Blocks"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Commitment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(37) + .HasColumnType("character(37)") + .IsFixedLength(); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("Level") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique(); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Commitments"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.ContractEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ContractCodeHash") + .HasColumnType("integer"); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("JsonPayload") + .HasColumnType("jsonb"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("RawPayload") + .HasColumnType("bytea"); + + b.Property("Tag") + .HasColumnType("text"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("ContractCodeHash"); + + b.HasIndex("ContractId"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("JsonPayload"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonPayload"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonPayload"), new[] { "jsonb_path_ops" }); + + b.HasIndex("Level"); + + b.HasIndex("Tag"); + + b.HasIndex("TransactionId"); + + b.HasIndex("ContractCodeHash", "Tag"); + + b.HasIndex("ContractId", "Tag"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Cycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("Seed") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("bytea") + .IsFixedLength(); + + b.Property("SelectedBakers") + .HasColumnType("integer"); + + b.Property("SelectedStake") + .HasColumnType("bigint"); + + b.Property("SnapshotIndex") + .HasColumnType("integer"); + + b.Property("SnapshotLevel") + .HasColumnType("integer"); + + b.Property("TotalBakers") + .HasColumnType("integer"); + + b.Property("TotalDelegated") + .HasColumnType("bigint"); + + b.Property("TotalDelegators") + .HasColumnType("integer"); + + b.Property("TotalStaking") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasAlternateKey("Index"); + + b.HasIndex("Index") + .IsUnique(); + + b.ToTable("Cycles"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DelegationOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("DelegateId") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("InitiatorId") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Nonce") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("PrevDelegateId") + .HasColumnType("integer"); + + b.Property("ResetDeactivation") + .HasColumnType("integer"); + + b.Property("SenderCodeHash") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DelegateId"); + + b.HasIndex("InitiatorId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("PrevDelegateId"); + + b.HasIndex("SenderCodeHash") + .HasFilter("\"SenderCodeHash\" IS NOT NULL"); + + b.HasIndex("SenderId"); + + b.ToTable("DelegationOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DelegatorCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("Cycle") + .HasColumnType("integer"); + + b.Property("DelegatorId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Cycle"); + + b.HasIndex("DelegatorId"); + + b.HasIndex("Cycle", "BakerId"); + + b.HasIndex("Cycle", "DelegatorId") + .IsUnique(); + + b.ToTable("DelegatorCycles"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("Data") + .HasColumnType("jsonb"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Owner") + .HasColumnType("text"); + + b.Property("Reverse") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("FirstLevel"); + + b.HasIndex("LastLevel"); + + b.HasIndex("Level"); + + b.HasIndex("Name"); + + b.HasIndex("Owner"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DoubleBakingOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccusedLevel") + .HasColumnType("integer"); + + b.Property("AccuserId") + .HasColumnType("integer"); + + b.Property("AccuserReward") + .HasColumnType("bigint"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OffenderId") + .HasColumnType("integer"); + + b.Property("OffenderLoss") + .HasColumnType("bigint"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AccuserId"); + + b.HasIndex("Level"); + + b.HasIndex("OffenderId"); + + b.HasIndex("OpHash"); + + b.ToTable("DoubleBakingOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DoubleEndorsingOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccusedLevel") + .HasColumnType("integer"); + + b.Property("AccuserId") + .HasColumnType("integer"); + + b.Property("AccuserReward") + .HasColumnType("bigint"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OffenderId") + .HasColumnType("integer"); + + b.Property("OffenderLoss") + .HasColumnType("bigint"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AccuserId"); + + b.HasIndex("Level"); + + b.HasIndex("OffenderId"); + + b.HasIndex("OpHash"); + + b.ToTable("DoubleEndorsingOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DoublePreendorsingOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccusedLevel") + .HasColumnType("integer"); + + b.Property("AccuserId") + .HasColumnType("integer"); + + b.Property("AccuserReward") + .HasColumnType("bigint"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OffenderId") + .HasColumnType("integer"); + + b.Property("OffenderLoss") + .HasColumnType("bigint"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AccuserId"); + + b.HasIndex("Level"); + + b.HasIndex("OffenderId"); + + b.HasIndex("OpHash"); + + b.ToTable("DoublePreendorsingOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DrainDelegateOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("DelegateId") + .HasColumnType("integer"); + + b.Property("Fee") + .HasColumnType("bigint"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("TargetId") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DelegateId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("TargetId"); + + b.ToTable("DrainDelegateOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.EndorsementOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelegateId") + .HasColumnType("integer"); + + b.Property("Deposit") + .HasColumnType("bigint"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("ResetDeactivation") + .HasColumnType("integer"); + + b.Property("Reward") + .HasColumnType("bigint"); + + b.Property("Slots") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DelegateId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.ToTable("EndorsementOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.EndorsingRewardOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("Expected") + .HasColumnType("bigint"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Received") + .HasColumnType("bigint"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("BakerId"); + + b.HasIndex("Level"); + + b.ToTable("EndorsingRewardOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.FreezerUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("Change") + .HasColumnType("bigint"); + + b.Property("Cycle") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Cycle"); + + b.ToTable("FreezerUpdates"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.InboxMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OperationId") + .HasColumnType("bigint"); + + b.Property("Payload") + .HasColumnType("bytea"); + + b.Property("PredecessorLevel") + .HasColumnType("integer"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OperationId"); + + b.HasIndex("Type", "Id"); + + b.ToTable("InboxMessages"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.IncreasePaidStorageOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("Amount") + .IsRequired() + .HasColumnType("text"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.ToTable("IncreasePaidStorageOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.MigrationOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("BalanceChange") + .HasColumnType("bigint"); + + b.Property("BigMapUpdates") + .HasColumnType("integer"); + + b.Property("Kind") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("ScriptId") + .HasColumnType("integer"); + + b.Property("StorageId") + .HasColumnType("integer"); + + b.Property("SubIds") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("TokenTransfers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("Level"); + + b.HasIndex("ScriptId"); + + b.HasIndex("StorageId"); + + b.ToTable("MigrationOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.NonceRevelationOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Nonce") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("bytea") + .IsFixedLength(); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RevealedCycle") + .HasColumnType("integer"); + + b.Property("RevealedLevel") + .HasColumnType("integer"); + + b.Property("Reward") + .HasColumnType("bigint"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasAlternateKey("RevealedLevel"); + + b.HasIndex("BakerId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RevealedCycle"); + + b.HasIndex("SenderId"); + + b.ToTable("NonceRevelationOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.OriginationOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("BigMapUpdates") + .HasColumnType("integer"); + + b.Property("ContractCodeHash") + .HasColumnType("integer"); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("DelegateId") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("InitiatorId") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("ManagerId") + .HasColumnType("integer"); + + b.Property("Nonce") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("ScriptId") + .HasColumnType("integer"); + + b.Property("SenderCodeHash") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageId") + .HasColumnType("integer"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("SubIds") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("TokenTransfers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ContractCodeHash") + .HasFilter("\"ContractCodeHash\" IS NOT NULL"); + + b.HasIndex("ContractId"); + + b.HasIndex("DelegateId"); + + b.HasIndex("InitiatorId"); + + b.HasIndex("Level"); + + b.HasIndex("ManagerId"); + + b.HasIndex("OpHash"); + + b.HasIndex("ScriptId"); + + b.HasIndex("SenderCodeHash") + .HasFilter("\"SenderCodeHash\" IS NOT NULL"); + + b.HasIndex("SenderId"); + + b.HasIndex("StorageId"); + + b.ToTable("OriginationOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.PreendorsementOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelegateId") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("ResetDeactivation") + .HasColumnType("integer"); + + b.Property("Slots") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DelegateId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.ToTable("PreendorsementOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Proposal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Epoch") + .HasColumnType("integer"); + + b.Property("Extras") + .HasColumnType("jsonb"); + + b.Property("FirstPeriod") + .HasColumnType("integer"); + + b.Property("Hash") + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("InitiatorId") + .HasColumnType("integer"); + + b.Property("LastPeriod") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Upvotes") + .HasColumnType("integer"); + + b.Property("VotingPower") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Epoch"); + + b.HasIndex("Hash"); + + b.ToTable("Proposals"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.ProposalOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Duplicated") + .HasColumnType("boolean"); + + b.Property("Epoch") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("ProposalId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("VotingPower") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Epoch"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("Period"); + + b.HasIndex("ProposalId"); + + b.HasIndex("SenderId"); + + b.ToTable("ProposalOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Protocol", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BallotQuorumMax") + .HasColumnType("integer"); + + b.Property("BallotQuorumMin") + .HasColumnType("integer"); + + b.Property("BlockDeposit") + .HasColumnType("bigint"); + + b.Property("BlockReward0") + .HasColumnType("bigint"); + + b.Property("BlockReward1") + .HasColumnType("bigint"); + + b.Property("BlocksPerCommitment") + .HasColumnType("integer"); + + b.Property("BlocksPerCycle") + .HasColumnType("integer"); + + b.Property("BlocksPerSnapshot") + .HasColumnType("integer"); + + b.Property("BlocksPerVoting") + .HasColumnType("integer"); + + b.Property("ByteCost") + .HasColumnType("integer"); + + b.Property("Code") + .HasColumnType("integer"); + + b.Property("ConsensusThreshold") + .HasColumnType("integer"); + + b.Property("Dictator") + .HasColumnType("text"); + + b.Property("DoubleBakingPunishment") + .HasColumnType("bigint"); + + b.Property("DoubleEndorsingPunishmentDenominator") + .HasColumnType("integer"); + + b.Property("DoubleEndorsingPunishmentNumerator") + .HasColumnType("integer"); + + b.Property("EndorsementDeposit") + .HasColumnType("bigint"); + + b.Property("EndorsementReward0") + .HasColumnType("bigint"); + + b.Property("EndorsementReward1") + .HasColumnType("bigint"); + + b.Property("EndorsersPerBlock") + .HasColumnType("integer"); + + b.Property("Extras") + .HasColumnType("jsonb"); + + b.Property("FirstCycle") + .HasColumnType("integer"); + + b.Property("FirstCycleLevel") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("FrozenDepositsPercentage") + .HasColumnType("integer"); + + b.Property("HardBlockGasLimit") + .HasColumnType("integer"); + + b.Property("HardOperationGasLimit") + .HasColumnType("integer"); + + b.Property("HardOperationStorageLimit") + .HasColumnType("integer"); + + b.Property("Hash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("LBSubsidy") + .HasColumnType("integer"); + + b.Property("LBToggleThreshold") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("MaxBakingReward") + .HasColumnType("bigint"); + + b.Property("MaxEndorsingReward") + .HasColumnType("bigint"); + + b.Property("MaxSlashingPeriod") + .HasColumnType("integer"); + + b.Property("MinParticipationDenominator") + .HasColumnType("integer"); + + b.Property("MinParticipationNumerator") + .HasColumnType("integer"); + + b.Property("NoRewardCycles") + .HasColumnType("integer"); + + b.Property("OriginationSize") + .HasColumnType("integer"); + + b.Property("PreservedCycles") + .HasColumnType("integer"); + + b.Property("ProposalQuorum") + .HasColumnType("integer"); + + b.Property("RampUpCycles") + .HasColumnType("integer"); + + b.Property("RevelationReward") + .HasColumnType("bigint"); + + b.Property("SmartRollupChallengeWindow") + .HasColumnType("integer"); + + b.Property("SmartRollupCommitmentPeriod") + .HasColumnType("integer"); + + b.Property("SmartRollupOriginationSize") + .HasColumnType("integer"); + + b.Property("SmartRollupStakeAmount") + .HasColumnType("bigint"); + + b.Property("SmartRollupTimeoutPeriod") + .HasColumnType("integer"); + + b.Property("TimeBetweenBlocks") + .HasColumnType("integer"); + + b.Property("TokensPerRoll") + .HasColumnType("bigint"); + + b.Property("TxRollupCommitmentBond") + .HasColumnType("bigint"); + + b.Property("TxRollupOriginationSize") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Protocols"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Btc") + .HasColumnType("double precision"); + + b.Property("Cny") + .HasColumnType("double precision"); + + b.Property("Eth") + .HasColumnType("double precision"); + + b.Property("Eur") + .HasColumnType("double precision"); + + b.Property("Gbp") + .HasColumnType("double precision"); + + b.Property("Jpy") + .HasColumnType("double precision"); + + b.Property("Krw") + .HasColumnType("double precision"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Usd") + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.HasIndex("Level") + .IsUnique(); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.RefutationGame", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("InitiatorCommitmentId") + .HasColumnType("integer"); + + b.Property("InitiatorId") + .HasColumnType("integer"); + + b.Property("InitiatorLoss") + .HasColumnType("bigint"); + + b.Property("InitiatorReward") + .HasColumnType("bigint"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("LastMoveId") + .HasColumnType("bigint"); + + b.Property("OpponentCommitmentId") + .HasColumnType("integer"); + + b.Property("OpponentId") + .HasColumnType("integer"); + + b.Property("OpponentLoss") + .HasColumnType("bigint"); + + b.Property("OpponentReward") + .HasColumnType("bigint"); + + b.Property("SmartRollupId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("FirstLevel"); + + b.HasIndex("InitiatorCommitmentId"); + + b.HasIndex("InitiatorId"); + + b.HasIndex("LastLevel"); + + b.HasIndex("OpponentCommitmentId"); + + b.HasIndex("OpponentId"); + + b.HasIndex("SmartRollupId"); + + b.ToTable("RefutationGames"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.RegisterConstantOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(54) + .HasColumnType("character varying(54)"); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("Extras") + .HasColumnType("jsonb"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .HasColumnType("text"); + + b.Property("Refs") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique() + .HasFilter("\"Address\" is not null"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.ToTable("RegisterConstantOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.RevealOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.ToTable("RevealOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.RevelationPenaltyOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Loss") + .HasColumnType("bigint"); + + b.Property("MissedLevel") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("BakerId"); + + b.HasIndex("Level"); + + b.ToTable("RevelationPenaltyOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Script", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CodeHash") + .HasColumnType("integer"); + + b.Property("CodeSchema") + .HasColumnType("bytea"); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("Current") + .HasColumnType("boolean"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("MigrationId") + .HasColumnType("bigint"); + + b.Property("OriginationId") + .HasColumnType("bigint"); + + b.Property("ParameterSchema") + .HasColumnType("bytea"); + + b.Property("StorageSchema") + .HasColumnType("bytea"); + + b.Property("TypeHash") + .HasColumnType("integer"); + + b.Property("Views") + .HasColumnType("bytea[]"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("ContractId", "Current") + .HasFilter("\"Current\" = true"); + + b.ToTable("Scripts"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SetDepositsLimitOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Limit") + .HasColumnType("text"); + + b.Property("OpHash") + .HasColumnType("text"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.ToTable("SetDepositsLimitOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupAddMessagesOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("MessagesCount") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.ToTable("SmartRollupAddMessagesOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupCementOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("CommitmentId") + .HasColumnType("integer"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("SmartRollupId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.ToTable("SmartRollupCementOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupCommitment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveStakers") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("Hash") + .HasColumnType("text"); + + b.Property("InboxLevel") + .HasColumnType("integer"); + + b.Property("InitiatorId") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("PredecessorId") + .HasColumnType("integer"); + + b.Property("SmartRollupId") + .HasColumnType("integer"); + + b.Property("Stakers") + .HasColumnType("integer"); + + b.Property("State") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Successors") + .HasColumnType("integer"); + + b.Property("Ticks") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Hash"); + + b.HasIndex("InboxLevel"); + + b.HasIndex("LastLevel"); + + b.HasIndex("PredecessorId"); + + b.HasIndex("SmartRollupId"); + + b.HasIndex("Hash", "SmartRollupId"); + + b.ToTable("SmartRollupCommitments"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupExecuteOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("CommitmentId") + .HasColumnType("integer"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("SmartRollupId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("SubIds") + .HasColumnType("integer"); + + b.Property("TicketTransfers") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CommitmentId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.HasIndex("SmartRollupId"); + + b.ToTable("SmartRollupExecuteOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupOriginateOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("GenesisCommitment") + .HasColumnType("text"); + + b.Property("Kernel") + .HasColumnType("bytea"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("OriginationProof") + .HasColumnType("bytea"); + + b.Property("ParameterType") + .HasColumnType("bytea"); + + b.Property("PvmKind") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("SmartRollupId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.HasIndex("SmartRollupId"); + + b.ToTable("SmartRollupOriginateOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupPublishOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Bond") + .HasColumnType("bigint"); + + b.Property("BondStatus") + .HasColumnType("integer"); + + b.Property("CommitmentId") + .HasColumnType("integer"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("Flags") + .HasColumnType("integer"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("SmartRollupId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CommitmentId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.HasIndex("SmartRollupId"); + + b.HasIndex("SmartRollupId", "BondStatus", "SenderId") + .HasFilter("\"BondStatus\" IS NOT NULL"); + + b.ToTable("SmartRollupPublishOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupRecoverBondOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Bond") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("SmartRollupId") + .HasColumnType("integer"); + + b.Property("StakerId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.HasIndex("SmartRollupId"); + + b.HasIndex("StakerId"); + + b.ToTable("SmartRollupRecoverBondOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupRefuteOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("DissectionEnd") + .HasColumnType("bigint"); + + b.Property("DissectionStart") + .HasColumnType("bigint"); + + b.Property("DissectionSteps") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("GameStatus") + .HasColumnType("integer"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Move") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("SmartRollupId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.HasIndex("SmartRollupId"); + + b.ToTable("SmartRollupRefuteOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SnapshotBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("DelegateId") + .HasColumnType("integer"); + + b.Property("DelegatedBalance") + .HasColumnType("bigint"); + + b.Property("DelegatorsCount") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("StakingBalance") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Level") + .HasFilter("\"DelegateId\" IS NULL"); + + b.ToTable("SnapshotBalances"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Software", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BlocksCount") + .HasColumnType("integer"); + + b.Property("Extras") + .HasColumnType("jsonb"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("ShortHash") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character(8)") + .IsFixedLength(); + + b.HasKey("Id"); + + b.ToTable("Software"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Statistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Cycle") + .HasColumnType("integer"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("TotalActivated") + .HasColumnType("bigint"); + + b.Property("TotalBanished") + .HasColumnType("bigint"); + + b.Property("TotalBootstrapped") + .HasColumnType("bigint"); + + b.Property("TotalBurned") + .HasColumnType("bigint"); + + b.Property("TotalCommitments") + .HasColumnType("bigint"); + + b.Property("TotalCreated") + .HasColumnType("bigint"); + + b.Property("TotalFrozen") + .HasColumnType("bigint"); + + b.Property("TotalRollupBonds") + .HasColumnType("bigint"); + + b.Property("TotalSmartRollupBonds") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Cycle") + .IsUnique() + .HasFilter("\"Cycle\" IS NOT NULL"); + + b.HasIndex("Date") + .IsUnique() + .HasFilter("\"Date\" IS NOT NULL"); + + b.HasIndex("Level") + .IsUnique(); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Storage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("Current") + .HasColumnType("boolean"); + + b.Property("JsonValue") + .HasColumnType("jsonb"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("MigrationId") + .HasColumnType("bigint"); + + b.Property("OriginationId") + .HasColumnType("bigint"); + + b.Property("RawValue") + .HasColumnType("bytea"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Level"); + + b.HasIndex("ContractId", "Current") + .HasFilter("\"Current\" = true"); + + b.ToTable("Storages"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BalancesCount") + .HasColumnType("integer"); + + b.Property("ContentHash") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("FirstMinterId") + .HasColumnType("integer"); + + b.Property("HoldersCount") + .HasColumnType("integer"); + + b.Property("JsonContent") + .HasColumnType("jsonb"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("RawContent") + .HasColumnType("bytea"); + + b.Property("RawType") + .HasColumnType("bytea"); + + b.Property("TicketerId") + .HasColumnType("integer"); + + b.Property("TotalBurned") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalMinted") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalSupply") + .IsRequired() + .HasColumnType("text"); + + b.Property("TransfersCount") + .HasColumnType("integer"); + + b.Property("TypeHash") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ContentHash"); + + b.HasIndex("FirstLevel"); + + b.HasIndex("FirstMinterId"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("JsonContent"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonContent"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonContent"), new[] { "jsonb_path_ops" }); + + b.HasIndex("LastLevel"); + + b.HasIndex("TicketerId"); + + b.HasIndex("TypeHash"); + + b.HasIndex("TicketerId", "TypeHash", "ContentHash"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TicketBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("Balance") + .IsRequired() + .HasColumnType("text"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("TicketId") + .HasColumnType("bigint"); + + b.Property("TicketerId") + .HasColumnType("integer"); + + b.Property("TransfersCount") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("FirstLevel"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LastLevel"); + + b.HasIndex("TicketId"); + + b.HasIndex("TicketerId"); + + b.HasIndex("AccountId", "TicketId") + .IsUnique(); + + b.HasIndex("AccountId", "TicketerId"); + + b.ToTable("TicketBalances"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TicketTransfer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromId") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("SmartRollupExecuteId") + .HasColumnType("bigint"); + + b.Property("TicketId") + .HasColumnType("bigint"); + + b.Property("TicketerId") + .HasColumnType("integer"); + + b.Property("ToId") + .HasColumnType("integer"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("TransferTicketId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FromId"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Level"); + + b.HasIndex("SmartRollupExecuteId") + .HasFilter("\"SmartRollupExecuteId\" is not null"); + + b.HasIndex("TicketId"); + + b.HasIndex("TicketerId"); + + b.HasIndex("ToId"); + + b.HasIndex("TransactionId") + .HasFilter("\"TransactionId\" is not null"); + + b.HasIndex("TransferTicketId") + .HasFilter("\"TransferTicketId\" is not null"); + + b.ToTable("TicketTransfers"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Token", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BalancesCount") + .HasColumnType("integer"); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("FirstMinterId") + .HasColumnType("integer"); + + b.Property("HoldersCount") + .HasColumnType("integer"); + + b.Property("IndexedAt") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("Metadata") + .HasColumnType("jsonb"); + + b.Property("OwnerId") + .HasColumnType("integer"); + + b.Property("Tags") + .HasColumnType("integer"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBurned") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalMinted") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalSupply") + .IsRequired() + .HasColumnType("text"); + + b.Property("TransfersCount") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("FirstMinterId"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("IndexedAt") + .HasFilter("\"IndexedAt\" is not null"); + + b.HasIndex("LastLevel"); + + b.HasIndex("Metadata"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Metadata"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("Metadata"), new[] { "jsonb_path_ops" }); + + b.HasIndex("ContractId", "TokenId") + .IsUnique(); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TokenBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("Balance") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("IndexedAt") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("TokenId") + .HasColumnType("bigint"); + + b.Property("TransfersCount") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AccountId") + .HasFilter("\"Balance\" != '0'"); + + b.HasIndex("ContractId") + .HasFilter("\"Balance\" != '0'"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("IndexedAt") + .HasFilter("\"IndexedAt\" is not null"); + + b.HasIndex("LastLevel"); + + b.HasIndex("TokenId") + .HasFilter("\"Balance\" != '0'"); + + b.HasIndex("AccountId", "ContractId"); + + b.HasIndex("AccountId", "TokenId") + .IsUnique(); + + b.ToTable("TokenBalances"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TokenTransfer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContractId") + .HasColumnType("integer"); + + b.Property("FromId") + .HasColumnType("integer"); + + b.Property("IndexedAt") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("MigrationId") + .HasColumnType("bigint"); + + b.Property("OriginationId") + .HasColumnType("bigint"); + + b.Property("ToId") + .HasColumnType("integer"); + + b.Property("TokenId") + .HasColumnType("bigint"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("FromId") + .HasFilter("\"FromId\" is not null"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("IndexedAt") + .HasFilter("\"IndexedAt\" is not null"); + + b.HasIndex("Level"); + + b.HasIndex("MigrationId") + .HasFilter("\"MigrationId\" is not null"); + + b.HasIndex("OriginationId") + .HasFilter("\"OriginationId\" is not null"); + + b.HasIndex("ToId") + .HasFilter("\"ToId\" is not null"); + + b.HasIndex("TokenId"); + + b.HasIndex("TransactionId") + .HasFilter("\"TransactionId\" is not null"); + + b.ToTable("TokenTransfers"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TransactionOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("BigMapUpdates") + .HasColumnType("integer"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Entrypoint") + .HasColumnType("text"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("EventsCount") + .HasColumnType("integer"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("InitiatorId") + .HasColumnType("integer"); + + b.Property("InternalDelegations") + .HasColumnType("smallint"); + + b.Property("InternalOperations") + .HasColumnType("smallint"); + + b.Property("InternalOriginations") + .HasColumnType("smallint"); + + b.Property("InternalTransactions") + .HasColumnType("smallint"); + + b.Property("JsonParameters") + .HasColumnType("jsonb"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Nonce") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RawParameters") + .HasColumnType("bytea"); + + b.Property("ResetDeactivation") + .HasColumnType("integer"); + + b.Property("SenderCodeHash") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageId") + .HasColumnType("integer"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("SubIds") + .HasColumnType("integer"); + + b.Property("TargetCodeHash") + .HasColumnType("integer"); + + b.Property("TargetId") + .HasColumnType("integer"); + + b.Property("TicketTransfers") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("TokenTransfers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("InitiatorId"); + + b.HasIndex("JsonParameters"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonParameters"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonParameters"), new[] { "jsonb_path_ops" }); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderCodeHash") + .HasFilter("\"SenderCodeHash\" IS NOT NULL"); + + b.HasIndex("SenderId"); + + b.HasIndex("StorageId"); + + b.HasIndex("TargetCodeHash") + .HasFilter("\"TargetCodeHash\" IS NOT NULL"); + + b.HasIndex("TargetId"); + + b.ToTable("TransactionOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TransferTicketOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("Amount") + .IsRequired() + .HasColumnType("text"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Entrypoint") + .HasColumnType("text"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("JsonContent") + .HasColumnType("text"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RawContent") + .HasColumnType("bytea"); + + b.Property("RawType") + .HasColumnType("bytea"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("SubIds") + .HasColumnType("integer"); + + b.Property("TargetId") + .HasColumnType("integer"); + + b.Property("TicketTransfers") + .HasColumnType("integer"); + + b.Property("TicketerId") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.HasIndex("TargetId"); + + b.HasIndex("TicketerId"); + + b.ToTable("TransferTicketOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupCommitOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Bond") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RollupId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RollupId"); + + b.HasIndex("SenderId"); + + b.ToTable("TxRollupCommitOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupDispatchTicketsOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RollupId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RollupId"); + + b.HasIndex("SenderId"); + + b.ToTable("TxRollupDispatchTicketsOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupFinalizeCommitmentOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RollupId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RollupId"); + + b.HasIndex("SenderId"); + + b.ToTable("TxRollupFinalizeCommitmentOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupOriginationOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RollupId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RollupId"); + + b.HasIndex("SenderId"); + + b.ToTable("TxRollupOriginationOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupRejectionOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("CommitterId") + .HasColumnType("integer"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Loss") + .HasColumnType("bigint"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("Reward") + .HasColumnType("bigint"); + + b.Property("RollupId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CommitterId"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RollupId"); + + b.HasIndex("SenderId"); + + b.ToTable("TxRollupRejectionOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupRemoveCommitmentOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RollupId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RollupId"); + + b.HasIndex("SenderId"); + + b.ToTable("TxRollupRemoveCommitmentOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupReturnBondOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Bond") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RollupId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RollupId"); + + b.HasIndex("SenderId"); + + b.ToTable("TxRollupReturnBondOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupSubmitBatchOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("RollupId") + .HasColumnType("integer"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("RollupId"); + + b.HasIndex("SenderId"); + + b.ToTable("TxRollupSubmitBatchOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.UpdateConsensusKeyOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActivationCycle") + .HasColumnType("integer"); + + b.Property("AllocationFee") + .HasColumnType("bigint"); + + b.Property("BakerFee") + .HasColumnType("bigint"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("Errors") + .HasColumnType("text"); + + b.Property("GasLimit") + .HasColumnType("integer"); + + b.Property("GasUsed") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("PublicKeyHash") + .HasColumnType("text"); + + b.Property("SenderId") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StorageFee") + .HasColumnType("bigint"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("StorageUsed") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.HasIndex("SenderId"); + + b.ToTable("UpdateConsensusKeyOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.VdfRevelationOperation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("Cycle") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("OpHash") + .IsRequired() + .HasMaxLength(51) + .HasColumnType("character(51)") + .IsFixedLength(); + + b.Property("Proof") + .HasColumnType("bytea"); + + b.Property("Reward") + .HasColumnType("bigint"); + + b.Property("Solution") + .HasColumnType("bytea"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("BakerId"); + + b.HasIndex("Cycle"); + + b.HasIndex("Level"); + + b.HasIndex("OpHash"); + + b.ToTable("VdfRevelationOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.VotingPeriod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BallotsQuorum") + .HasColumnType("integer"); + + b.Property("Dictator") + .HasColumnType("integer"); + + b.Property("Epoch") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("Kind") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("NayBallots") + .HasColumnType("integer"); + + b.Property("NayVotingPower") + .HasColumnType("bigint"); + + b.Property("ParticipationEma") + .HasColumnType("integer"); + + b.Property("PassBallots") + .HasColumnType("integer"); + + b.Property("PassVotingPower") + .HasColumnType("bigint"); + + b.Property("ProposalsCount") + .HasColumnType("integer"); + + b.Property("SingleWinner") + .HasColumnType("boolean"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Supermajority") + .HasColumnType("integer"); + + b.Property("TopUpvotes") + .HasColumnType("integer"); + + b.Property("TopVotingPower") + .HasColumnType("bigint"); + + b.Property("TotalBakers") + .HasColumnType("integer"); + + b.Property("TotalVotingPower") + .HasColumnType("bigint"); + + b.Property("UpvotesQuorum") + .HasColumnType("integer"); + + b.Property("YayBallots") + .HasColumnType("integer"); + + b.Property("YayVotingPower") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasAlternateKey("Index"); + + b.HasIndex("Epoch"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Index") + .IsUnique(); + + b.ToTable("VotingPeriods"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.VotingSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BakerId") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("VotingPower") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Period"); + + b.HasIndex("Period", "BakerId") + .IsUnique(); + + b.ToTable("VotingSnapshots"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Contract", b => + { + b.HasBaseType("Tzkt.Data.Models.Account"); + + b.Property("CodeHash") + .HasColumnType("integer"); + + b.Property("CreatorId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("CreatorId"); + + b.Property("EventsCount") + .HasColumnType("integer"); + + b.Property("Kind") + .HasColumnType("smallint"); + + b.Property("ManagerId") + .HasColumnType("integer"); + + b.Property("Spendable") + .HasColumnType("boolean"); + + b.Property("Tags") + .HasColumnType("integer"); + + b.Property("TicketsCount") + .HasColumnType("integer"); + + b.Property("TokensCount") + .HasColumnType("integer"); + + b.Property("TypeHash") + .HasColumnType("integer"); + + b.Property("WeirdDelegateId") + .HasColumnType("integer"); + + b.HasIndex("CodeHash"); + + b.HasIndex("CreatorId"); + + b.HasIndex("ManagerId"); + + b.HasIndex("TypeHash"); + + b.HasIndex("WeirdDelegateId"); + + b.HasIndex("Type", "Kind") + .HasFilter("\"Type\" = 2"); + + b.HasDiscriminator().HasValue((byte)2); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Rollup", b => + { + b.HasBaseType("Tzkt.Data.Models.Account"); + + b.Property("CreatorId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("CreatorId"); + + b.HasIndex("CreatorId"); + + b.HasDiscriminator().HasValue((byte)4); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollup", b => + { + b.HasBaseType("Tzkt.Data.Models.Account"); + + b.Property("ActiveStakers") + .HasColumnType("integer"); + + b.Property("CementedCommitments") + .HasColumnType("integer"); + + b.Property("CreatorId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("CreatorId"); + + b.Property("ExecutedCommitments") + .HasColumnType("integer"); + + b.Property("GenesisCommitment") + .HasColumnType("text"); + + b.Property("InboxLevel") + .HasColumnType("integer"); + + b.Property("LastCommitment") + .HasColumnType("text"); + + b.Property("OrphanCommitments") + .HasColumnType("integer"); + + b.Property("PendingCommitments") + .HasColumnType("integer"); + + b.Property("PvmKind") + .HasColumnType("integer"); + + b.Property("RefutedCommitments") + .HasColumnType("integer"); + + b.Property("TotalStakers") + .HasColumnType("integer"); + + b.HasIndex("CreatorId"); + + b.HasDiscriminator().HasValue((byte)5); + }); + + modelBuilder.Entity("Tzkt.Data.Models.User", b => + { + b.HasBaseType("Tzkt.Data.Models.Account"); + + b.Property("Activated") + .HasColumnType("boolean"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RegisterConstantsCount") + .HasColumnType("integer"); + + b.Property("Revealed") + .HasColumnType("boolean"); + + b.Property("SetDepositsLimitsCount") + .HasColumnType("integer"); + + b.HasDiscriminator().HasValue((byte)0); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Delegate", b => + { + b.HasBaseType("Tzkt.Data.Models.User"); + + b.Property("ActivationLevel") + .HasColumnType("integer"); + + b.Property("BallotsCount") + .HasColumnType("integer"); + + b.Property("BlocksCount") + .HasColumnType("integer"); + + b.Property("DeactivationLevel") + .HasColumnType("integer"); + + b.Property("DelegatedBalance") + .HasColumnType("bigint"); + + b.Property("DelegatorsCount") + .HasColumnType("integer"); + + b.Property("DoubleBakingCount") + .HasColumnType("integer"); + + b.Property("DoubleEndorsingCount") + .HasColumnType("integer"); + + b.Property("DoublePreendorsingCount") + .HasColumnType("integer"); + + b.Property("EndorsementsCount") + .HasColumnType("integer"); + + b.Property("EndorsingRewardsCount") + .HasColumnType("integer"); + + b.Property("FrozenDeposit") + .HasColumnType("bigint"); + + b.Property("FrozenDepositLimit") + .HasColumnType("bigint"); + + b.Property("NonceRevelationsCount") + .HasColumnType("integer"); + + b.Property("PreendorsementsCount") + .HasColumnType("integer"); + + b.Property("ProposalsCount") + .HasColumnType("integer"); + + b.Property("RevelationPenaltiesCount") + .HasColumnType("integer"); + + b.Property("SoftwareId") + .HasColumnType("integer"); + + b.Property("StakingBalance") + .HasColumnType("bigint"); + + b.Property("VdfRevelationsCount") + .HasColumnType("integer"); + + b.HasIndex("SoftwareId"); + + b.HasIndex("Type", "Staked") + .HasFilter("\"Type\" = 1"); + + b.HasDiscriminator().HasValue((byte)1); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Account", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Delegate") + .WithMany("DelegatedAccounts") + .HasForeignKey("DelegateId"); + + b.HasOne("Tzkt.Data.Models.Block", "FirstBlock") + .WithMany("CreatedAccounts") + .HasForeignKey("FirstLevel") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Delegate"); + + b.Navigation("FirstBlock"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.ActivationOperation", b => + { + b.HasOne("Tzkt.Data.Models.User", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Activations") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Block"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.BallotOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Ballots") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Proposal", "Proposal") + .WithMany() + .HasForeignKey("ProposalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Delegate", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Proposal"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Block", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Proposer") + .WithMany() + .HasForeignKey("ProposerId"); + + b.HasOne("Tzkt.Data.Models.Protocol", "Protocol") + .WithMany() + .HasForeignKey("ProtoCode") + .HasPrincipalKey("Code") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.NonceRevelationOperation", "Revelation") + .WithOne("RevealedBlock") + .HasForeignKey("Tzkt.Data.Models.Block", "RevelationId"); + + b.HasOne("Tzkt.Data.Models.Software", "Software") + .WithMany() + .HasForeignKey("SoftwareId"); + + b.Navigation("Proposer"); + + b.Navigation("Protocol"); + + b.Navigation("Revelation"); + + b.Navigation("Software"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DelegationOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Delegate") + .WithMany() + .HasForeignKey("DelegateId"); + + b.HasOne("Tzkt.Data.Models.Account", "Initiator") + .WithMany() + .HasForeignKey("InitiatorId"); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Delegations") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Delegate", "PrevDelegate") + .WithMany() + .HasForeignKey("PrevDelegateId"); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Delegate"); + + b.Navigation("Initiator"); + + b.Navigation("PrevDelegate"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DoubleBakingOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Accuser") + .WithMany() + .HasForeignKey("AccuserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("DoubleBakings") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Delegate", "Offender") + .WithMany() + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Accuser"); + + b.Navigation("Block"); + + b.Navigation("Offender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DoubleEndorsingOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Accuser") + .WithMany() + .HasForeignKey("AccuserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("DoubleEndorsings") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Delegate", "Offender") + .WithMany() + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Accuser"); + + b.Navigation("Block"); + + b.Navigation("Offender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DoublePreendorsingOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Accuser") + .WithMany() + .HasForeignKey("AccuserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("DoublePreendorsings") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Delegate", "Offender") + .WithMany() + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Accuser"); + + b.Navigation("Block"); + + b.Navigation("Offender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.DrainDelegateOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("DrainDelegateOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.EndorsementOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Delegate") + .WithMany() + .HasForeignKey("DelegateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Endorsements") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Delegate"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.IncreasePaidStorageOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("IncreasePaidStorageOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.MigrationOperation", b => + { + b.HasOne("Tzkt.Data.Models.Account", "Account") + .WithMany() + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Migrations") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Script", "Script") + .WithMany() + .HasForeignKey("ScriptId"); + + b.HasOne("Tzkt.Data.Models.Storage", "Storage") + .WithMany() + .HasForeignKey("StorageId"); + + b.Navigation("Account"); + + b.Navigation("Block"); + + b.Navigation("Script"); + + b.Navigation("Storage"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.NonceRevelationOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Baker") + .WithMany() + .HasForeignKey("BakerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Revelations") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Delegate", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Baker"); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.OriginationOperation", b => + { + b.HasOne("Tzkt.Data.Models.Contract", "Contract") + .WithMany() + .HasForeignKey("ContractId"); + + b.HasOne("Tzkt.Data.Models.Delegate", "Delegate") + .WithMany() + .HasForeignKey("DelegateId"); + + b.HasOne("Tzkt.Data.Models.Account", "Initiator") + .WithMany() + .HasForeignKey("InitiatorId"); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Originations") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.User", "Manager") + .WithMany() + .HasForeignKey("ManagerId"); + + b.HasOne("Tzkt.Data.Models.Script", "Script") + .WithMany() + .HasForeignKey("ScriptId"); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Storage", "Storage") + .WithMany() + .HasForeignKey("StorageId"); + + b.Navigation("Block"); + + b.Navigation("Contract"); + + b.Navigation("Delegate"); + + b.Navigation("Initiator"); + + b.Navigation("Manager"); + + b.Navigation("Script"); + + b.Navigation("Sender"); + + b.Navigation("Storage"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.PreendorsementOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Delegate") + .WithMany() + .HasForeignKey("DelegateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Preendorsements") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Delegate"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.ProposalOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Proposals") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Proposal", "Proposal") + .WithMany() + .HasForeignKey("ProposalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Delegate", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Proposal"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.RegisterConstantOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("RegisterConstants") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.RevealOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Reveals") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.RevelationPenaltyOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Baker") + .WithMany() + .HasForeignKey("BakerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("RevelationPenalties") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Baker"); + + b.Navigation("Block"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SetDepositsLimitOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("SetDepositsLimits") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupAddMessagesOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("SmartRollupAddMessagesOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupCementOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("SmartRollupCementOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupExecuteOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("SmartRollupExecuteOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupOriginateOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("SmartRollupOriginateOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupPublishOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("SmartRollupPublishOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupRecoverBondOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("SmartRollupRecoverBondOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.SmartRollupRefuteOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("SmartRollupRefuteOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TransactionOperation", b => + { + b.HasOne("Tzkt.Data.Models.Account", "Initiator") + .WithMany() + .HasForeignKey("InitiatorId"); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("Transactions") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Storage", "Storage") + .WithMany() + .HasForeignKey("StorageId"); + + b.HasOne("Tzkt.Data.Models.Account", "Target") + .WithMany() + .HasForeignKey("TargetId"); + + b.Navigation("Block"); + + b.Navigation("Initiator"); + + b.Navigation("Sender"); + + b.Navigation("Storage"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TransferTicketOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TransferTicketOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupCommitOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TxRollupCommitOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupDispatchTicketsOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TxRollupDispatchTicketsOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupFinalizeCommitmentOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TxRollupFinalizeCommitmentOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupOriginationOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TxRollupOriginationOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupRejectionOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TxRollupRejectionOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupRemoveCommitmentOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TxRollupRemoveCommitmentOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupReturnBondOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TxRollupReturnBondOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TxRollupSubmitBatchOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("TxRollupSubmitBatchOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.UpdateConsensusKeyOperation", b => + { + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("UpdateConsensusKeyOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Account", "Sender") + .WithMany() + .HasForeignKey("SenderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Block"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.VdfRevelationOperation", b => + { + b.HasOne("Tzkt.Data.Models.Delegate", "Baker") + .WithMany() + .HasForeignKey("BakerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tzkt.Data.Models.Block", "Block") + .WithMany("VdfRevelationOps") + .HasForeignKey("Level") + .HasPrincipalKey("Level") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Baker"); + + b.Navigation("Block"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Contract", b => + { + b.HasOne("Tzkt.Data.Models.Account", "Creator") + .WithMany() + .HasForeignKey("CreatorId"); + + b.HasOne("Tzkt.Data.Models.User", "Manager") + .WithMany() + .HasForeignKey("ManagerId"); + + b.HasOne("Tzkt.Data.Models.User", "WeirdDelegate") + .WithMany() + .HasForeignKey("WeirdDelegateId"); + + b.Navigation("Creator"); + + b.Navigation("Manager"); + + b.Navigation("WeirdDelegate"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Delegate", b => + { + b.HasOne("Tzkt.Data.Models.Software", "Software") + .WithMany() + .HasForeignKey("SoftwareId"); + + b.Navigation("Software"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Block", b => + { + b.Navigation("Activations"); + + b.Navigation("Ballots"); + + b.Navigation("CreatedAccounts"); + + b.Navigation("Delegations"); + + b.Navigation("DoubleBakings"); + + b.Navigation("DoubleEndorsings"); + + b.Navigation("DoublePreendorsings"); + + b.Navigation("DrainDelegateOps"); + + b.Navigation("Endorsements"); + + b.Navigation("IncreasePaidStorageOps"); + + b.Navigation("Migrations"); + + b.Navigation("Originations"); + + b.Navigation("Preendorsements"); + + b.Navigation("Proposals"); + + b.Navigation("RegisterConstants"); + + b.Navigation("Reveals"); + + b.Navigation("RevelationPenalties"); + + b.Navigation("Revelations"); + + b.Navigation("SetDepositsLimits"); + + b.Navigation("SmartRollupAddMessagesOps"); + + b.Navigation("SmartRollupCementOps"); + + b.Navigation("SmartRollupExecuteOps"); + + b.Navigation("SmartRollupOriginateOps"); + + b.Navigation("SmartRollupPublishOps"); + + b.Navigation("SmartRollupRecoverBondOps"); + + b.Navigation("SmartRollupRefuteOps"); + + b.Navigation("Transactions"); + + b.Navigation("TransferTicketOps"); + + b.Navigation("TxRollupCommitOps"); + + b.Navigation("TxRollupDispatchTicketsOps"); + + b.Navigation("TxRollupFinalizeCommitmentOps"); + + b.Navigation("TxRollupOriginationOps"); + + b.Navigation("TxRollupRejectionOps"); + + b.Navigation("TxRollupRemoveCommitmentOps"); + + b.Navigation("TxRollupReturnBondOps"); + + b.Navigation("TxRollupSubmitBatchOps"); + + b.Navigation("UpdateConsensusKeyOps"); + + b.Navigation("VdfRevelationOps"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.NonceRevelationOperation", b => + { + b.Navigation("RevealedBlock"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.Delegate", b => + { + b.Navigation("DelegatedAccounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Tzkt.Data/Migrations/20230823140932_Tickets.cs b/Tzkt.Data/Migrations/20230823140932_Tickets.cs new file mode 100644 index 000000000..399126d52 --- /dev/null +++ b/Tzkt.Data/Migrations/20230823140932_Tickets.cs @@ -0,0 +1,367 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Tzkt.Data.Migrations +{ + /// + public partial class Tickets : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SubIds", + table: "TransferTicketOps", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "TicketTransfers", + table: "TransferTicketOps", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "TicketTransfers", + table: "TransactionOps", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "SubIds", + table: "SmartRollupExecuteOps", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "TicketTransfers", + table: "SmartRollupExecuteOps", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "TicketBalancesCount", + table: "AppState", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "TicketTransfersCount", + table: "AppState", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "TicketsCount", + table: "AppState", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "ActiveTicketsCount", + table: "Accounts", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "TicketBalancesCount", + table: "Accounts", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "TicketTransfersCount", + table: "Accounts", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "TicketsCount", + table: "Accounts", + type: "integer", + nullable: true); + + migrationBuilder.CreateTable( + name: "TicketBalances", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + TicketerId = table.Column(type: "integer", nullable: false), + TicketId = table.Column(type: "bigint", nullable: false), + AccountId = table.Column(type: "integer", nullable: false), + FirstLevel = table.Column(type: "integer", nullable: false), + LastLevel = table.Column(type: "integer", nullable: false), + TransfersCount = table.Column(type: "integer", nullable: false), + Balance = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TicketBalances", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Tickets", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + TicketerId = table.Column(type: "integer", nullable: false), + FirstMinterId = table.Column(type: "integer", nullable: false), + FirstLevel = table.Column(type: "integer", nullable: false), + LastLevel = table.Column(type: "integer", nullable: false), + TransfersCount = table.Column(type: "integer", nullable: false), + BalancesCount = table.Column(type: "integer", nullable: false), + HoldersCount = table.Column(type: "integer", nullable: false), + TotalMinted = table.Column(type: "text", nullable: false), + TotalBurned = table.Column(type: "text", nullable: false), + TotalSupply = table.Column(type: "text", nullable: false), + TypeHash = table.Column(type: "integer", nullable: false), + ContentHash = table.Column(type: "integer", nullable: false), + RawType = table.Column(type: "bytea", nullable: true), + RawContent = table.Column(type: "bytea", nullable: true), + JsonContent = table.Column(type: "jsonb", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tickets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TicketTransfers", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Level = table.Column(type: "integer", nullable: false), + TicketerId = table.Column(type: "integer", nullable: false), + TicketId = table.Column(type: "bigint", nullable: false), + Amount = table.Column(type: "text", nullable: false), + FromId = table.Column(type: "integer", nullable: true), + ToId = table.Column(type: "integer", nullable: true), + TransactionId = table.Column(type: "bigint", nullable: true), + TransferTicketId = table.Column(type: "bigint", nullable: true), + SmartRollupExecuteId = table.Column(type: "bigint", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TicketTransfers", x => x.Id); + }); + + migrationBuilder.UpdateData( + table: "AppState", + keyColumn: "Id", + keyValue: -1, + columns: new[] { "TicketBalancesCount", "TicketTransfersCount", "TicketsCount" }, + values: new object[] { 0, 0, 0 }); + + migrationBuilder.CreateIndex( + name: "IX_TicketBalances_AccountId", + table: "TicketBalances", + column: "AccountId"); + + migrationBuilder.CreateIndex( + name: "IX_TicketBalances_AccountId_TicketerId", + table: "TicketBalances", + columns: new[] { "AccountId", "TicketerId" }); + + migrationBuilder.CreateIndex( + name: "IX_TicketBalances_AccountId_TicketId", + table: "TicketBalances", + columns: new[] { "AccountId", "TicketId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TicketBalances_FirstLevel", + table: "TicketBalances", + column: "FirstLevel"); + + migrationBuilder.CreateIndex( + name: "IX_TicketBalances_Id", + table: "TicketBalances", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TicketBalances_LastLevel", + table: "TicketBalances", + column: "LastLevel"); + + migrationBuilder.CreateIndex( + name: "IX_TicketBalances_TicketerId", + table: "TicketBalances", + column: "TicketerId"); + + migrationBuilder.CreateIndex( + name: "IX_TicketBalances_TicketId", + table: "TicketBalances", + column: "TicketId"); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_ContentHash", + table: "Tickets", + column: "ContentHash"); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_FirstLevel", + table: "Tickets", + column: "FirstLevel"); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_FirstMinterId", + table: "Tickets", + column: "FirstMinterId"); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_Id", + table: "Tickets", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_JsonContent", + table: "Tickets", + column: "JsonContent") + .Annotation("Npgsql:IndexMethod", "gin") + .Annotation("Npgsql:IndexOperators", new[] { "jsonb_path_ops" }); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_LastLevel", + table: "Tickets", + column: "LastLevel"); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_TicketerId", + table: "Tickets", + column: "TicketerId"); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_TicketerId_TypeHash_ContentHash", + table: "Tickets", + columns: new[] { "TicketerId", "TypeHash", "ContentHash" }); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_TypeHash", + table: "Tickets", + column: "TypeHash"); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_FromId", + table: "TicketTransfers", + column: "FromId"); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_Id", + table: "TicketTransfers", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_Level", + table: "TicketTransfers", + column: "Level"); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_SmartRollupExecuteId", + table: "TicketTransfers", + column: "SmartRollupExecuteId", + filter: "\"SmartRollupExecuteId\" is not null"); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_TicketerId", + table: "TicketTransfers", + column: "TicketerId"); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_TicketId", + table: "TicketTransfers", + column: "TicketId"); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_ToId", + table: "TicketTransfers", + column: "ToId"); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_TransactionId", + table: "TicketTransfers", + column: "TransactionId", + filter: "\"TransactionId\" is not null"); + + migrationBuilder.CreateIndex( + name: "IX_TicketTransfers_TransferTicketId", + table: "TicketTransfers", + column: "TransferTicketId", + filter: "\"TransferTicketId\" is not null"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TicketBalances"); + + migrationBuilder.DropTable( + name: "Tickets"); + + migrationBuilder.DropTable( + name: "TicketTransfers"); + + migrationBuilder.DropColumn( + name: "SubIds", + table: "TransferTicketOps"); + + migrationBuilder.DropColumn( + name: "TicketTransfers", + table: "TransferTicketOps"); + + migrationBuilder.DropColumn( + name: "TicketTransfers", + table: "TransactionOps"); + + migrationBuilder.DropColumn( + name: "SubIds", + table: "SmartRollupExecuteOps"); + + migrationBuilder.DropColumn( + name: "TicketTransfers", + table: "SmartRollupExecuteOps"); + + migrationBuilder.DropColumn( + name: "TicketBalancesCount", + table: "AppState"); + + migrationBuilder.DropColumn( + name: "TicketTransfersCount", + table: "AppState"); + + migrationBuilder.DropColumn( + name: "TicketsCount", + table: "AppState"); + + migrationBuilder.DropColumn( + name: "ActiveTicketsCount", + table: "Accounts"); + + migrationBuilder.DropColumn( + name: "TicketBalancesCount", + table: "Accounts"); + + migrationBuilder.DropColumn( + name: "TicketTransfersCount", + table: "Accounts"); + + migrationBuilder.DropColumn( + name: "TicketsCount", + table: "Accounts"); + } + } +} diff --git a/Tzkt.Data/Migrations/TzktContextModelSnapshot.cs b/Tzkt.Data/Migrations/TzktContextModelSnapshot.cs index 4d7ef24f1..e59b72946 100644 --- a/Tzkt.Data/Migrations/TzktContextModelSnapshot.cs +++ b/Tzkt.Data/Migrations/TzktContextModelSnapshot.cs @@ -34,6 +34,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ActiveRefutationGamesCount") .HasColumnType("integer"); + b.Property("ActiveTicketsCount") + .HasColumnType("integer"); + b.Property("ActiveTokensCount") .HasColumnType("integer"); @@ -126,6 +129,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Staked") .HasColumnType("boolean"); + b.Property("TicketBalancesCount") + .HasColumnType("integer"); + + b.Property("TicketTransfersCount") + .HasColumnType("integer"); + b.Property("TokenBalancesCount") .HasColumnType("integer"); @@ -448,6 +457,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("StorageCounter") .HasColumnType("integer"); + b.Property("TicketBalancesCount") + .HasColumnType("integer"); + + b.Property("TicketTransfersCount") + .HasColumnType("integer"); + + b.Property("TicketsCount") + .HasColumnType("integer"); + b.Property("Timestamp") .HasColumnType("timestamp with time zone"); @@ -573,6 +591,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) SmartRollupRecoverBondOpsCount = 0, SmartRollupRefuteOpsCount = 0, StorageCounter = 0, + TicketBalancesCount = 0, + TicketTransfersCount = 0, + TicketsCount = 0, Timestamp = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), TokenBalancesCount = 0, TokenTransfersCount = 0, @@ -3021,6 +3042,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("StorageUsed") .HasColumnType("integer"); + b.Property("SubIds") + .HasColumnType("integer"); + + b.Property("TicketTransfers") + .HasColumnType("integer"); + b.Property("Timestamp") .HasColumnType("timestamp with time zone"); @@ -3550,6 +3577,205 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Storages"); }); + modelBuilder.Entity("Tzkt.Data.Models.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BalancesCount") + .HasColumnType("integer"); + + b.Property("ContentHash") + .HasColumnType("integer"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("FirstMinterId") + .HasColumnType("integer"); + + b.Property("HoldersCount") + .HasColumnType("integer"); + + b.Property("JsonContent") + .HasColumnType("jsonb"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("RawContent") + .HasColumnType("bytea"); + + b.Property("RawType") + .HasColumnType("bytea"); + + b.Property("TicketerId") + .HasColumnType("integer"); + + b.Property("TotalBurned") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalMinted") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalSupply") + .IsRequired() + .HasColumnType("text"); + + b.Property("TransfersCount") + .HasColumnType("integer"); + + b.Property("TypeHash") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ContentHash"); + + b.HasIndex("FirstLevel"); + + b.HasIndex("FirstMinterId"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("JsonContent"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonContent"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonContent"), new[] { "jsonb_path_ops" }); + + b.HasIndex("LastLevel"); + + b.HasIndex("TicketerId"); + + b.HasIndex("TypeHash"); + + b.HasIndex("TicketerId", "TypeHash", "ContentHash"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TicketBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("integer"); + + b.Property("Balance") + .IsRequired() + .HasColumnType("text"); + + b.Property("FirstLevel") + .HasColumnType("integer"); + + b.Property("LastLevel") + .HasColumnType("integer"); + + b.Property("TicketId") + .HasColumnType("bigint"); + + b.Property("TicketerId") + .HasColumnType("integer"); + + b.Property("TransfersCount") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("FirstLevel"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("LastLevel"); + + b.HasIndex("TicketId"); + + b.HasIndex("TicketerId"); + + b.HasIndex("AccountId", "TicketId") + .IsUnique(); + + b.HasIndex("AccountId", "TicketerId"); + + b.ToTable("TicketBalances"); + }); + + modelBuilder.Entity("Tzkt.Data.Models.TicketTransfer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Amount") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromId") + .HasColumnType("integer"); + + b.Property("Level") + .HasColumnType("integer"); + + b.Property("SmartRollupExecuteId") + .HasColumnType("bigint"); + + b.Property("TicketId") + .HasColumnType("bigint"); + + b.Property("TicketerId") + .HasColumnType("integer"); + + b.Property("ToId") + .HasColumnType("integer"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("TransferTicketId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FromId"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Level"); + + b.HasIndex("SmartRollupExecuteId") + .HasFilter("\"SmartRollupExecuteId\" is not null"); + + b.HasIndex("TicketId"); + + b.HasIndex("TicketerId"); + + b.HasIndex("ToId"); + + b.HasIndex("TransactionId") + .HasFilter("\"TransactionId\" is not null"); + + b.HasIndex("TransferTicketId") + .HasFilter("\"TransferTicketId\" is not null"); + + b.ToTable("TicketTransfers"); + }); + modelBuilder.Entity("Tzkt.Data.Models.Token", b => { b.Property("Id") @@ -3867,6 +4093,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TargetId") .HasColumnType("integer"); + b.Property("TicketTransfers") + .HasColumnType("integer"); + b.Property("Timestamp") .HasColumnType("timestamp with time zone"); @@ -3967,9 +4196,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("StorageUsed") .HasColumnType("integer"); + b.Property("SubIds") + .HasColumnType("integer"); + b.Property("TargetId") .HasColumnType("integer"); + b.Property("TicketTransfers") + .HasColumnType("integer"); + b.Property("TicketerId") .HasColumnType("integer"); @@ -4833,6 +5068,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Tags") .HasColumnType("integer"); + b.Property("TicketsCount") + .HasColumnType("integer"); + b.Property("TokensCount") .HasColumnType("integer"); diff --git a/Tzkt.Data/Models/Accounts/Account.cs b/Tzkt.Data/Models/Accounts/Account.cs index 3beb4de51..916424728 100644 --- a/Tzkt.Data/Models/Accounts/Account.cs +++ b/Tzkt.Data/Models/Accounts/Account.cs @@ -23,6 +23,10 @@ public class Account public int ActiveTokensCount { get; set; } public int TokenBalancesCount { get; set; } public int TokenTransfersCount { get; set; } + + public int ActiveTicketsCount { get; set; } + public int TicketBalancesCount { get; set; } + public int TicketTransfersCount { get; set; } public int DelegationsCount { get; set; } public int OriginationsCount { get; set; } diff --git a/Tzkt.Data/Models/Accounts/Contract.cs b/Tzkt.Data/Models/Accounts/Contract.cs index 56b9c42ff..e838c6fdd 100644 --- a/Tzkt.Data/Models/Accounts/Contract.cs +++ b/Tzkt.Data/Models/Accounts/Contract.cs @@ -12,6 +12,7 @@ public class Contract : Account public ContractTags Tags { get; set; } public int TokensCount { get; set; } public int EventsCount { get; set; } + public int TicketsCount { get; set; } public bool? Spendable { get; set; } diff --git a/Tzkt.Data/Models/AppState.cs b/Tzkt.Data/Models/AppState.cs index 67b09f344..c78af5166 100644 --- a/Tzkt.Data/Models/AppState.cs +++ b/Tzkt.Data/Models/AppState.cs @@ -97,6 +97,11 @@ public class AppState public int TokensCount { get; set; } public int TokenBalancesCount { get; set; } public int TokenTransfersCount { get; set; } + + public int TicketsCount { get; set; } + public int TicketBalancesCount { get; set; } + public int TicketTransfersCount { get; set; } + public int EventsCount { get; set; } #endregion diff --git a/Tzkt.Data/Models/Blocks/BlockEvents.cs b/Tzkt.Data/Models/Blocks/BlockEvents.cs index 4252207a3..540471d48 100644 --- a/Tzkt.Data/Models/Blocks/BlockEvents.cs +++ b/Tzkt.Data/Models/Blocks/BlockEvents.cs @@ -5,18 +5,19 @@ namespace Tzkt.Data.Models [Flags] public enum BlockEvents { - None = 0b_0000_0000_0000, - CycleBegin = 0b_0000_0000_0001, - CycleEnd = 0b_0000_0000_0010, - ProtocolBegin = 0b_0000_0000_0100, - ProtocolEnd = 0b_0000_0000_1000, - Deactivations = 0b_0000_0001_0000, - NewAccounts = 0b_0000_0010_0000, - BalanceSnapshot = 0b_0000_0100_0000, - SmartContracts = 0b_0000_1000_0000, - DelegatorContracts = 0b_0001_0000_0000, - Bigmaps = 0b_0010_0000_0000, - Tokens = 0b_0100_0000_0000, - Events = 0b_1000_0000_0000 + None = 0b_0000_0000_0000_0000, + CycleBegin = 0b_0000_0000_0000_0001, + CycleEnd = 0b_0000_0000_0000_0010, + ProtocolBegin = 0b_0000_0000_0000_0100, + ProtocolEnd = 0b_0000_0000_0000_1000, + Deactivations = 0b_0000_0000_0001_0000, + NewAccounts = 0b_0000_0000_0010_0000, + BalanceSnapshot = 0b_0000_0000_0100_0000, + SmartContracts = 0b_0000_0000_1000_0000, + DelegatorContracts = 0b_0000_0001_0000_0000, + Bigmaps = 0b_0000_0010_0000_0000, + Tokens = 0b_0000_0100_0000_0000, + Events = 0b_0000_1000_0000_0000, + Tickets = 0b_0001_0000_0000_0000 } } diff --git a/Tzkt.Data/Models/Operations/SmartRollupExecuteOperation.cs b/Tzkt.Data/Models/Operations/SmartRollupExecuteOperation.cs index 14bc2c5af..e6d3abe4a 100644 --- a/Tzkt.Data/Models/Operations/SmartRollupExecuteOperation.cs +++ b/Tzkt.Data/Models/Operations/SmartRollupExecuteOperation.cs @@ -7,6 +7,9 @@ public class SmartRollupExecuteOperation : ManagerOperation { public int? SmartRollupId { get; set; } public int? CommitmentId { get; set; } + + public int? TicketTransfers { get; set; } + public int? SubIds { get; set; } } public static class SmartRollupExecuteOperationModel diff --git a/Tzkt.Data/Models/Operations/TransactionOperation.cs b/Tzkt.Data/Models/Operations/TransactionOperation.cs index db9e4375a..801d7c0af 100644 --- a/Tzkt.Data/Models/Operations/TransactionOperation.cs +++ b/Tzkt.Data/Models/Operations/TransactionOperation.cs @@ -23,6 +23,7 @@ public class TransactionOperation : ContractOperation public short? InternalTransactions { get; set; } public int? EventsCount { get; set; } + public int? TicketTransfers { get; set; } #region relations [ForeignKey(nameof(TargetId))] diff --git a/Tzkt.Data/Models/Operations/TransferTicketOperation.cs b/Tzkt.Data/Models/Operations/TransferTicketOperation.cs index afe93cba0..c69531335 100644 --- a/Tzkt.Data/Models/Operations/TransferTicketOperation.cs +++ b/Tzkt.Data/Models/Operations/TransferTicketOperation.cs @@ -15,6 +15,9 @@ public class TransferTicketOperation : ManagerOperation public byte[] RawContent { get; set; } public string JsonContent { get; set; } public string Entrypoint { get; set; } + + public int? TicketTransfers { get; set; } + public int? SubIds { get; set; } } public static class TransferTicketOperationModel diff --git a/Tzkt.Data/Models/Scripts/Ticket.cs b/Tzkt.Data/Models/Scripts/Ticket.cs new file mode 100644 index 000000000..846ab3c0c --- /dev/null +++ b/Tzkt.Data/Models/Scripts/Ticket.cs @@ -0,0 +1,97 @@ +using System.Numerics; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Tzkt.Data.Models +{ + public class Ticket + { + public long Id { get; set; } + public int TicketerId { get; set; } + + public int FirstMinterId { get; set; } + public int FirstLevel { get; set; } + public int LastLevel { get; set; } + + public int TransfersCount { get; set; } + public int BalancesCount { get; set; } + public int HoldersCount { get; set; } + + public BigInteger TotalMinted { get; set; } + public BigInteger TotalBurned { get; set; } + public BigInteger TotalSupply { get; set; } + + public int TypeHash { get; set; } + public int ContentHash { get; set; } + + public byte[] RawType { get; set; } + public byte[] RawContent { get; set; } + public string JsonContent { get; set; } + } + + public static class TicketModel + { + public static void BuildTicketModel(this ModelBuilder modelBuilder) + { + #region keys + modelBuilder.Entity() + .HasKey(x => x.Id); + #endregion + + #region props + modelBuilder.Entity() + .Property(x => x.JsonContent) + .HasColumnType("jsonb"); + + // TODO: switch to `numeric` type after migration to .NET 6 + var converter = new ValueConverter( + x => x.ToString(), + x => BigInteger.Parse(x)); + + modelBuilder.Entity() + .Property(x => x.TotalMinted) + .HasConversion(converter); + + modelBuilder.Entity() + .Property(x => x.TotalBurned) + .HasConversion(converter); + + modelBuilder.Entity() + .Property(x => x.TotalSupply) + .HasConversion(converter); + #endregion + + #region indexes + modelBuilder.Entity() + .HasIndex(x => x.Id) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(x => x.TicketerId); + + modelBuilder.Entity() + .HasIndex(x => x.FirstMinterId); + + modelBuilder.Entity() + .HasIndex(x => x.FirstLevel); + + modelBuilder.Entity() + .HasIndex(x => x.LastLevel); + + modelBuilder.Entity() + .HasIndex(x => x.TypeHash); + + modelBuilder.Entity() + .HasIndex(x => x.ContentHash); + + modelBuilder.Entity() + .HasIndex(x => new { x.TicketerId, x.TypeHash, x.ContentHash }); + + modelBuilder.Entity() + .HasIndex(x => x.JsonContent) + .HasMethod("gin") + .HasOperators("jsonb_path_ops"); + #endregion + } + } +} diff --git a/Tzkt.Data/Models/Scripts/TicketBalance.cs b/Tzkt.Data/Models/Scripts/TicketBalance.cs new file mode 100644 index 000000000..518edbf89 --- /dev/null +++ b/Tzkt.Data/Models/Scripts/TicketBalance.cs @@ -0,0 +1,68 @@ +using System.Numerics; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Tzkt.Data.Models +{ + public class TicketBalance + { + public long Id { get; set; } + public int TicketerId { get; set; } + public long TicketId { get; set; } + public int AccountId { get; set; } + public int FirstLevel { get; set; } + public int LastLevel { get; set; } + public int TransfersCount { get; set; } + public BigInteger Balance { get; set; } + } + + public static class TicketBalanceModel + { + public static void BuildTicketBalanceModel(this ModelBuilder modelBuilder) + { + #region keys + modelBuilder.Entity() + .HasKey(x => x.Id); + #endregion + + #region props + // TODO: switch to `numeric` type after migration to .NET 6 + var converter = new ValueConverter( + x => x.ToString(), + x => BigInteger.Parse(x)); + + modelBuilder.Entity() + .Property(x => x.Balance) + .HasConversion(converter); + #endregion + + #region indexes + modelBuilder.Entity() + .HasIndex(x => x.Id) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(x => x.TicketerId); + + modelBuilder.Entity() + .HasIndex(x => x.TicketId); + + modelBuilder.Entity() + .HasIndex(x => x.AccountId); + + modelBuilder.Entity() + .HasIndex(x => new { x.AccountId, x.TicketerId }); + + modelBuilder.Entity() + .HasIndex(x => new { x.AccountId, x.TicketId }) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(x => x.FirstLevel); + + modelBuilder.Entity() + .HasIndex(x => x.LastLevel); + #endregion + } + } +} diff --git a/Tzkt.Data/Models/Scripts/TicketTransfer.cs b/Tzkt.Data/Models/Scripts/TicketTransfer.cs new file mode 100644 index 000000000..b37ff01ae --- /dev/null +++ b/Tzkt.Data/Models/Scripts/TicketTransfer.cs @@ -0,0 +1,77 @@ +using System.Numerics; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Tzkt.Data.Models +{ + public class TicketTransfer + { + public long Id { get; set; } + public int Level { get; set; } + public int TicketerId { get; set; } + public long TicketId { get; set; } + public BigInteger Amount { get; set; } + + public int? FromId { get; set; } + public int? ToId { get; set; } + + public long? TransactionId { get; set; } + public long? TransferTicketId { get; set; } + public long? SmartRollupExecuteId { get; set; } + } + + public static class TicketTransferModel + { + public static void BuildTicketTransferModel(this ModelBuilder modelBuilder) + { + #region keys + modelBuilder.Entity() + .HasKey(x => x.Id); + #endregion + + #region props + // TODO: switch to `numeric` type after migration to .NET 6 + var converter = new ValueConverter( + x => x.ToString(), + x => BigInteger.Parse(x)); + + modelBuilder.Entity() + .Property(x => x.Amount) + .HasConversion(converter); + #endregion + + #region indexes + modelBuilder.Entity() + .HasIndex(x => x.Id) + .IsUnique(); + + modelBuilder.Entity() + .HasIndex(x => x.Level); + + modelBuilder.Entity() + .HasIndex(x => x.TicketerId); + + modelBuilder.Entity() + .HasIndex(x => x.TicketId); + + modelBuilder.Entity() + .HasIndex(x => x.FromId); + + modelBuilder.Entity() + .HasIndex(x => x.ToId); + + modelBuilder.Entity() + .HasIndex(x => x.TransactionId) + .HasFilter($@"""{nameof(TicketTransfer.TransactionId)}"" is not null"); + + modelBuilder.Entity() + .HasIndex(x => x.TransferTicketId) + .HasFilter($@"""{nameof(TicketTransfer.TransferTicketId)}"" is not null"); + + modelBuilder.Entity() + .HasIndex(x => x.SmartRollupExecuteId) + .HasFilter($@"""{nameof(TicketTransfer.SmartRollupExecuteId)}"" is not null"); + #endregion + } + } +} diff --git a/Tzkt.Data/TzktContext.cs b/Tzkt.Data/TzktContext.cs index dc3ef3a05..e9c6db056 100644 --- a/Tzkt.Data/TzktContext.cs +++ b/Tzkt.Data/TzktContext.cs @@ -109,6 +109,12 @@ public class TzktContext : DbContext public DbSet TokenTransfers { get; set; } #endregion + #region tickets + public DbSet Tickets { get; set; } + public DbSet TicketBalances { get; set; } + public DbSet TicketTransfers { get; set; } + #endregion + #region rollups public DbSet SmartRollupCommitments { get; set; } public DbSet RefutationGames { get; set; } @@ -227,6 +233,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.BuildTokenTransferModel(); #endregion + #region tickets + modelBuilder.BuildTicketModel(); + modelBuilder.BuildTicketBalanceModel(); + modelBuilder.BuildTicketTransferModel(); + #endregion + #region rollups modelBuilder.BuildSmartRollupCommitmentModel(); modelBuilder.BuildRefutationGameModel(); diff --git a/Tzkt.Sync.Tests/Database/AppStateTests.cs b/Tzkt.Sync.Tests/Database/AppStateTests.cs index 52f1729a8..8497f3469 100644 --- a/Tzkt.Sync.Tests/Database/AppStateTests.cs +++ b/Tzkt.Sync.Tests/Database/AppStateTests.cs @@ -265,6 +265,15 @@ await db.TransactionOps.CountAsync(x => x.InitiatorId == null) + if (state.TokenTransfersCount != await db.TokenTransfers.CountAsync()) throw new Exception("Invalid AppState.TokenTransfersCount"); + if (state.TicketsCount != await db.Tickets.CountAsync()) + throw new Exception("Invalid AppState.TicketsCount"); + + if (state.TicketBalancesCount != await db.TicketBalances.CountAsync()) + throw new Exception("Invalid AppState.TicketBalancesCount"); + + if (state.TicketTransfersCount != await db.TicketTransfers.CountAsync()) + throw new Exception("Invalid AppState.TicketTransfersCount"); + if (state.EventsCount != await db.Events.CountAsync()) throw new Exception("Invalid AppState.EventsCount"); diff --git a/Tzkt.Sync/Extensions/BytesExtension.cs b/Tzkt.Sync/Extensions/BytesExtension.cs index a4fca77aa..ff8e133f4 100644 --- a/Tzkt.Sync/Extensions/BytesExtension.cs +++ b/Tzkt.Sync/Extensions/BytesExtension.cs @@ -1,6 +1,4 @@ -using System; - -namespace Tzkt.Sync +namespace Tzkt.Sync { static class BytesExtension { @@ -49,6 +47,14 @@ public static bool IsEqual(this byte[] src, byte[] data) return true; } + public static int GetHashCodeExt(this byte[] data) + { + var res = data.Length; + for (int i = 0; i < data.Length; i++) + res ^= data[i] << (8 * (i % 4)); + return res; + } + public static void Flush(this byte[] data) { for (int i = 0; i < data.Length; i++) diff --git a/Tzkt.Sync/Extensions/JsonElementExtension.cs b/Tzkt.Sync/Extensions/JsonElementExtension.cs index 80aa65978..6b32ed447 100644 --- a/Tzkt.Sync/Extensions/JsonElementExtension.cs +++ b/Tzkt.Sync/Extensions/JsonElementExtension.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Numerics; using System.Text.Json; namespace Tzkt.Sync @@ -136,6 +135,12 @@ public static long RequiredInt64(this JsonElement el, string name) : throw new SerializationException($"Missed required long {name}"); } + public static BigInteger RequiredBigInteger(this JsonElement el, string name) + { + return el.TryGetProperty(name, out var prop) && BigInteger.TryParse(prop.GetString(), out var res) ? res + : throw new SerializationException($"Missed required BigInteger {name}"); + } + public static int ParseInt32(this JsonElement el) { return el.ValueKind == JsonValueKind.String diff --git a/Tzkt.Sync/Protocols/Abstract/IRpc.cs b/Tzkt.Sync/Protocols/Abstract/IRpc.cs index 0baaecadc..8710224b2 100644 --- a/Tzkt.Sync/Protocols/Abstract/IRpc.cs +++ b/Tzkt.Sync/Protocols/Abstract/IRpc.cs @@ -22,6 +22,7 @@ public interface IRpc Task GetActiveDelegatesAsync(int level); Task GetDelegateParticipationAsync(int level, string address); Task GetCycleAsync(int level, int cycle); + Task GetTicketBalance(int level, string address, string ticket); #endregion } } diff --git a/Tzkt.Sync/Protocols/Handlers/Genesis/Rpc/Rpc.cs b/Tzkt.Sync/Protocols/Handlers/Genesis/Rpc/Rpc.cs index d532cc094..6c07449bb 100644 --- a/Tzkt.Sync/Protocols/Handlers/Genesis/Rpc/Rpc.cs +++ b/Tzkt.Sync/Protocols/Handlers/Genesis/Rpc/Rpc.cs @@ -52,6 +52,9 @@ public Task GetCycleAsync(int level, int cycle) public Task GetDelegateParticipationAsync(int level, string address) => throw new InvalidOperationException(); + + public Task GetTicketBalance(int level, string address, string ticket) + => throw new InvalidOperationException(); #endregion } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/DelegationsCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/DelegationsCommit.cs index e1b2ca66e..28b48ba7f 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/DelegationsCommit.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/DelegationsCommit.cs @@ -464,6 +464,9 @@ void UpgradeUser(DelegationOperation delegation) ActiveTokensCount = user.ActiveTokensCount, TokenBalancesCount = user.TokenBalancesCount, TokenTransfersCount = user.TokenTransfersCount, + ActiveTicketsCount = user.ActiveTicketsCount, + TicketBalancesCount = user.TicketBalancesCount, + TicketTransfersCount = user.TicketTransfersCount, TransferTicketCount = user.TransferTicketCount, TxRollupCommitCount = user.TxRollupCommitCount, TxRollupDispatchTicketsCount = user.TxRollupDispatchTicketsCount, @@ -721,6 +724,9 @@ void DowngradeDelegate(DelegationOperation delegation) ActiveTokensCount = delegat.ActiveTokensCount, TokenBalancesCount = delegat.TokenBalancesCount, TokenTransfersCount = delegat.TokenTransfersCount, + ActiveTicketsCount = delegat.ActiveTicketsCount, + TicketBalancesCount = delegat.TicketBalancesCount, + TicketTransfersCount = delegat.TicketTransfersCount, TransferTicketCount = delegat.TransferTicketCount, TxRollupCommitCount = delegat.TxRollupCommitCount, TxRollupDispatchTicketsCount = delegat.TxRollupDispatchTicketsCount, diff --git a/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/OriginationsCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/OriginationsCommit.cs index 4a6b17dac..fcb6cb5f8 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/OriginationsCommit.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/OriginationsCommit.cs @@ -64,7 +64,10 @@ public virtual async Task Apply(Block block, JsonElement op, JsonElement content Spendable = GetSpendable(content), ActiveTokensCount = ghost.ActiveTokensCount, TokenBalancesCount = ghost.TokenBalancesCount, - TokenTransfersCount = ghost.TokenTransfersCount + TokenTransfersCount = ghost.TokenTransfersCount, + ActiveTicketsCount = ghost.ActiveTicketsCount, + TicketBalancesCount = ghost.TicketBalancesCount, + TicketTransfersCount = ghost.TicketTransfersCount }; Db.Entry(ghost).State = EntityState.Detached; Db.Entry(contract).State = EntityState.Modified; @@ -262,7 +265,10 @@ public virtual async Task ApplyInternal(Block block, ManagerOperation parent, Js Spendable = GetSpendable(content), ActiveTokensCount = ghost.ActiveTokensCount, TokenBalancesCount = ghost.TokenBalancesCount, - TokenTransfersCount = ghost.TokenTransfersCount + TokenTransfersCount = ghost.TokenTransfersCount, + ActiveTicketsCount = ghost.ActiveTicketsCount, + TicketBalancesCount = ghost.TicketBalancesCount, + TicketTransfersCount = ghost.TicketTransfersCount }; Db.Entry(ghost).State = EntityState.Detached; Db.Entry(contract).State = EntityState.Modified; @@ -473,7 +479,7 @@ public virtual async Task Revert(Block block, OriginationOperation origination) if (contract.Kind > ContractKind.DelegatorContract) await RevertScript(origination); - if (contract.TokenTransfersCount == 0) + if (contract.TokenTransfersCount == 0 && contract.TicketTransfersCount == 0) { Db.Contracts.Remove(contract); Cache.Accounts.Remove(contract); @@ -490,6 +496,9 @@ public virtual async Task Revert(Block block, OriginationOperation origination) ActiveTokensCount = contract.ActiveTokensCount, TokenBalancesCount = contract.TokenBalancesCount, TokenTransfersCount = contract.TokenTransfersCount, + ActiveTicketsCount = contract.ActiveTicketsCount, + TicketBalancesCount = contract.TicketBalancesCount, + TicketTransfersCount = contract.TicketTransfersCount, Type = AccountType.Ghost, }; @@ -598,7 +607,7 @@ public virtual async Task RevertInternal(Block block, OriginationOperation origi if (contract.Kind > ContractKind.DelegatorContract) await RevertScript(origination); - if (contract.TokenTransfersCount == 0) + if (contract.TokenTransfersCount == 0 && contract.TicketTransfersCount == 0) { Db.Contracts.Remove(contract); Cache.Accounts.Remove(contract); @@ -615,6 +624,9 @@ public virtual async Task RevertInternal(Block block, OriginationOperation origi ActiveTokensCount = contract.ActiveTokensCount, TokenBalancesCount = contract.TokenBalancesCount, TokenTransfersCount = contract.TokenTransfersCount, + ActiveTicketsCount = contract.ActiveTicketsCount, + TicketBalancesCount = contract.TicketBalancesCount, + TicketTransfersCount = contract.TicketTransfersCount, Type = AccountType.Ghost, }; diff --git a/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/TransactionsCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/TransactionsCommit.cs index f7affc264..f75dc8297 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/TransactionsCommit.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto1/Commits/Operations/TransactionsCommit.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Numerics; using System.Text.Json; -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; using Netezos.Contracts; using Netezos.Encoding; - using Tzkt.Data.Models; using Tzkt.Data.Models.Base; @@ -17,6 +12,7 @@ class TransactionsCommit : ProtocolCommit { public TransactionOperation Transaction { get; private set; } public IEnumerable BigMapDiffs { get; private set; } + public IEnumerable TicketUpdates { get; private set; } public TransactionsCommit(ProtocolHandler protocol) : base(protocol) { } @@ -145,6 +141,8 @@ public virtual async Task Apply(Block block, JsonElement op, JsonElement content await ProcessStorage(transaction, storage); } + TicketUpdates = ParseTicketUpdates("ticket_updates", result); + if (transaction.Target is SmartRollup) Proto.Inbox.Push(transaction.Id); } @@ -288,6 +286,8 @@ public virtual async Task ApplyInternal(Block block, ManagerOperation parent, Js BigMapDiffs = ParseBigMapDiffs(transaction, result); await ProcessStorage(transaction, storage); } + + TicketUpdates = ParseTicketUpdates("ticket_receipt", result); if (transaction.Target is SmartRollup) Proto.Inbox.Push(transaction.Id); @@ -686,5 +686,69 @@ protected virtual int GetConsumedGas(JsonElement result) { return result.OptionalInt32("consumed_gas") ?? 0; } + + protected virtual IEnumerable ParseTicketUpdates(string property, JsonElement result) + { + if (!result.TryGetProperty(property, out var ticketUpdates)) + return null; + + var res = new List(); + foreach (var updates in ticketUpdates.RequiredArray().EnumerateArray()) + { + var list = new List(); + foreach (var update in updates.RequiredArray("updates").EnumerateArray()) + { + var amount = update.RequiredBigInteger("amount"); + if (amount != BigInteger.Zero) + { + list.Add(new TicketUpdate + { + Account = update.RequiredString("account"), + Amount = amount + }); + } + } + + if (list.Count > 0) + { + var ticketToken = updates.Required("ticket_token"); + var type = Micheline.FromJson(ticketToken.Required("content_type")); + var value = Micheline.FromJson(ticketToken.Required("content")); + var rawType = type.ToBytes(); + + byte[] rawContent; + string jsonContent; + + try + { + var schema = Schema.Create(type as MichelinePrim); + rawContent = schema.Optimize(value).ToBytes(); + jsonContent = schema.Humanize(value); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to parse ticket content"); + rawContent = value.ToBytes(); + jsonContent = null; + } + + res.Add(new TicketUpdates + { + Ticket = new TicketIdentity + { + Ticketer = ticketToken.RequiredString("ticketer"), + RawType = rawType, + RawContent = rawContent, + JsonContent = jsonContent, + TypeHash = Script.GetHash(rawType), + ContentHash = Script.GetHash(rawContent) + }, + Updates = list + }); + } + } + + return res.Count > 0 ? res : null; + } } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto1/Diagnostics/Diagnostics.cs b/Tzkt.Sync/Protocols/Handlers/Proto1/Diagnostics/Diagnostics.cs index c00d42d64..87c487a82 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto1/Diagnostics/Diagnostics.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto1/Diagnostics/Diagnostics.cs @@ -1,9 +1,5 @@ -using System; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; +using System.Text.Json; using Microsoft.EntityFrameworkCore; - using Tzkt.Data; using Tzkt.Data.Models; using Tzkt.Data.Models.Base; @@ -72,6 +68,15 @@ protected virtual async Task RunDiagnostics(int level, int ops = -1) var accounts = entries.Where(x => x.Entity is Account && (x.State == EntityState.Modified || x.State == EntityState.Added)) .Select(x => x.Entity as Account); + + var ticketBalances = entries.Where(x => + x.Entity is TicketBalance && (x.State == EntityState.Modified || x.State == EntityState.Added)) + .Select(x => x.Entity as TicketBalance); + + foreach (var ticketBalance in ticketBalances) + { + await TestTicketBalance(level, ticketBalance); + } await TestGlobalCounter(level, state); @@ -173,6 +178,8 @@ protected virtual async Task TestAccount(int level, Account account) TestAccountDelegate(remote, account); TestAccountCounter(remote, account); } + + protected virtual Task TestTicketBalance(int level, TicketBalance ticketBalance) => Task.CompletedTask; protected virtual void TestAccountDelegate(JsonElement remote, Account local) { diff --git a/Tzkt.Sync/Protocols/Handlers/Proto1/Rpc/Rpc.cs b/Tzkt.Sync/Protocols/Handlers/Proto1/Rpc/Rpc.cs index dbd4ea704..23fe88835 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto1/Rpc/Rpc.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto1/Rpc/Rpc.cs @@ -1,6 +1,4 @@ -using System; -using System.Text.Json; -using System.Threading.Tasks; +using System.Text.Json; using Tzkt.Sync.Services; namespace Tzkt.Sync.Protocols.Proto1 @@ -52,6 +50,9 @@ public virtual Task GetCycleAsync(int level, int cycle) public virtual Task GetDelegateParticipationAsync(int level, string address) => throw new InvalidOperationException(); + + public virtual Task GetTicketBalance(int level, string address, string ticket) + => throw new InvalidOperationException(); #endregion } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto13/Commits/Operations/TransferTicketCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto13/Commits/Operations/TransferTicketCommit.cs index 81d018c32..3a8e0d61a 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto13/Commits/Operations/TransferTicketCommit.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto13/Commits/Operations/TransferTicketCommit.cs @@ -1,8 +1,5 @@ -using System; -using System.Numerics; +using System.Numerics; using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; using Netezos.Contracts; using Netezos.Encoding; using Tzkt.Data.Models; @@ -13,6 +10,8 @@ namespace Tzkt.Sync.Protocols.Proto13 class TransferTicketCommit : ProtocolCommit { public TransferTicketOperation Operation { get; private set; } + public IEnumerable TicketUpdates { get; private set; } + public TransferTicketCommit(ProtocolHandler protocol) : base(protocol) { } @@ -120,6 +119,8 @@ public virtual async Task Apply(Block block, JsonElement op, JsonElement content if (senderDelegate.Id != sender.Id) senderDelegate.DelegatedBalance -= burned; } + + TicketUpdates = ParseTicketUpdates(result); } #endregion @@ -195,5 +196,69 @@ public virtual async Task Revert(Block block, TransferTicketOperation operation) Cache.AppState.ReleaseManagerCounter(); Cache.AppState.ReleaseOperationId(); } + + protected virtual IEnumerable ParseTicketUpdates(JsonElement result) + { + if (!result.TryGetProperty("ticket_updates", out var ticketUpdates)) + return null; + + var res = new List(); + foreach (var updates in ticketUpdates.RequiredArray().EnumerateArray()) + { + var list = new List(); + foreach (var update in updates.RequiredArray("updates").EnumerateArray()) + { + var amount = update.RequiredBigInteger("amount"); + if (amount != BigInteger.Zero) + { + list.Add(new TicketUpdate + { + Account = update.RequiredString("account"), + Amount = amount + }); + } + } + + if (list.Count > 0) + { + var ticketToken = updates.Required("ticket_token"); + var type = Micheline.FromJson(ticketToken.Required("content_type")); + var value = Micheline.FromJson(ticketToken.Required("content")); + var rawType = type.ToBytes(); + + byte[] rawContent; + string jsonContent; + + try + { + var schema = Schema.Create(type as MichelinePrim); + rawContent = schema.Optimize(value).ToBytes(); + jsonContent = schema.Humanize(value); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to parse ticket content"); + rawContent = value.ToBytes(); + jsonContent = null; + } + + res.Add(new TicketUpdates + { + Ticket = new TicketIdentity + { + Ticketer = ticketToken.RequiredString("ticketer"), + RawType = rawType, + RawContent = rawContent, + JsonContent = jsonContent, + TypeHash = Script.GetHash(rawType), + ContentHash = Script.GetHash(rawContent) + }, + Updates = list + }); + } + } + + return res.Count > 0 ? res : null; + } } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto13/Commits/Operations/TxRollupOriginationCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto13/Commits/Operations/TxRollupOriginationCommit.cs index c24d94992..01bcc06f7 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto13/Commits/Operations/TxRollupOriginationCommit.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto13/Commits/Operations/TxRollupOriginationCommit.cs @@ -45,7 +45,10 @@ public virtual async Task Apply(Block block, JsonElement op, JsonElement content Type = AccountType.Rollup, ActiveTokensCount = ghost.ActiveTokensCount, TokenBalancesCount = ghost.TokenBalancesCount, - TokenTransfersCount = ghost.TokenTransfersCount + TokenTransfersCount = ghost.TokenTransfersCount, + ActiveTicketsCount = ghost.ActiveTicketsCount, + TicketBalancesCount = ghost.TicketBalancesCount, + TicketTransfersCount = ghost.TicketTransfersCount }; Db.Entry(ghost).State = EntityState.Detached; Db.Entry(rollup).State = EntityState.Modified; @@ -186,7 +189,7 @@ public virtual async Task Revert(Block block, TxRollupOriginationOperation origi sender.RollupsCount--; - if (rollup.TokenTransfersCount == 0) + if (rollup.TokenTransfersCount == 0 && rollup.TicketTransfersCount == 0) { Db.Rollups.Remove(rollup); Cache.Accounts.Remove(rollup); @@ -203,6 +206,9 @@ public virtual async Task Revert(Block block, TxRollupOriginationOperation origi ActiveTokensCount = rollup.ActiveTokensCount, TokenBalancesCount = rollup.TokenBalancesCount, TokenTransfersCount = rollup.TokenTransfersCount, + ActiveTicketsCount = rollup.ActiveTicketsCount, + TicketBalancesCount = rollup.TicketBalancesCount, + TicketTransfersCount = rollup.TicketTransfersCount, Type = AccountType.Ghost, }; diff --git a/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/Operations/SmartRollupExecuteCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/Operations/SmartRollupExecuteCommit.cs index 38b4a2802..bfd4c6f6a 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/Operations/SmartRollupExecuteCommit.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/Operations/SmartRollupExecuteCommit.cs @@ -1,4 +1,7 @@ -using System.Text.Json; +using System.Numerics; +using System.Text.Json; +using Netezos.Contracts; +using Netezos.Encoding; using Tzkt.Data.Models; using Tzkt.Data.Models.Base; @@ -7,6 +10,7 @@ namespace Tzkt.Sync.Protocols.Proto16 class SmartRollupExecuteCommit : ProtocolCommit { public SmartRollupExecuteOperation Operation { get; private set; } + public IEnumerable TicketUpdates { get; private set; } public SmartRollupExecuteCommit(ProtocolHandler protocol) : base(protocol) { } @@ -104,6 +108,8 @@ public virtual async Task Apply(Block block, JsonElement op, JsonElement content if (senderDelegate.Id != sender.Id) senderDelegate.DelegatedBalance -= burned; } + + TicketUpdates = ParseTicketUpdates(result); rollup.ExecutedCommitments++; @@ -186,5 +192,69 @@ public virtual async Task Revert(Block block, SmartRollupExecuteOperation operat Cache.AppState.ReleaseManagerCounter(); Cache.AppState.ReleaseOperationId(); } + + protected virtual IEnumerable ParseTicketUpdates(JsonElement result) + { + if (!result.TryGetProperty("ticket_updates", out var ticketUpdates)) + return null; + + var res = new List(); + foreach (var updates in ticketUpdates.RequiredArray().EnumerateArray()) + { + var list = new List(); + foreach (var update in updates.RequiredArray("updates").EnumerateArray()) + { + var amount = update.RequiredBigInteger("amount"); + if (amount != BigInteger.Zero) + { + list.Add(new TicketUpdate + { + Account = update.RequiredString("account"), + Amount = amount + }); + } + } + + if (list.Count > 0) + { + var ticketToken = updates.Required("ticket_token"); + var type = Micheline.FromJson(ticketToken.Required("content_type")); + var value = Micheline.FromJson(ticketToken.Required("content")); + var rawType = type.ToBytes(); + + byte[] rawContent; + string jsonContent; + + try + { + var schema = Schema.Create(type as MichelinePrim); + rawContent = schema.Optimize(value).ToBytes(); + jsonContent = schema.Humanize(value); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to parse ticket content"); + rawContent = value.ToBytes(); + jsonContent = null; + } + + res.Add(new TicketUpdates + { + Ticket = new TicketIdentity + { + Ticketer = ticketToken.RequiredString("ticketer"), + RawType = rawType, + RawContent = rawContent, + JsonContent = jsonContent, + TypeHash = Script.GetHash(rawType), + ContentHash = Script.GetHash(rawContent) + }, + Updates = list + }); + } + } + + return res.Count > 0 ? res : null; + } } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/Operations/SmartRollupOriginateCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/Operations/SmartRollupOriginateCommit.cs index e1d613c44..da9381079 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/Operations/SmartRollupOriginateCommit.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/Operations/SmartRollupOriginateCommit.cs @@ -60,7 +60,10 @@ public virtual async Task Apply(Block block, JsonElement op, JsonElement content SmartRollupBonds = 0, ActiveTokensCount = ghost.ActiveTokensCount, TokenBalancesCount = ghost.TokenBalancesCount, - TokenTransfersCount = ghost.TokenTransfersCount + TokenTransfersCount = ghost.TokenTransfersCount, + ActiveTicketsCount = ghost.ActiveTicketsCount, + TicketBalancesCount = ghost.TicketBalancesCount, + TicketTransfersCount = ghost.TicketTransfersCount }; Db.Entry(ghost).State = EntityState.Detached; Db.Entry(rollup).State = EntityState.Modified; @@ -230,7 +233,7 @@ public virtual async Task Revert(Block block, SmartRollupOriginateOperation oper sender.SmartRollupsCount--; - if (rollup.TokenTransfersCount == 0) + if (rollup.TokenTransfersCount == 0 && rollup.TicketTransfersCount == 0) { Db.SmartRollups.Remove(rollup); Cache.Accounts.Remove(rollup); @@ -247,6 +250,9 @@ public virtual async Task Revert(Block block, SmartRollupOriginateOperation oper ActiveTokensCount = rollup.ActiveTokensCount, TokenBalancesCount = rollup.TokenBalancesCount, TokenTransfersCount = rollup.TokenTransfersCount, + ActiveTicketsCount = rollup.ActiveTicketsCount, + TicketBalancesCount = rollup.TicketBalancesCount, + TicketTransfersCount = rollup.TicketTransfersCount, Type = AccountType.Ghost, }; diff --git a/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/TicketsCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/TicketsCommit.cs new file mode 100644 index 000000000..08e2c0b0c --- /dev/null +++ b/Tzkt.Sync/Protocols/Handlers/Proto16/Commits/TicketsCommit.cs @@ -0,0 +1,601 @@ +using System.Numerics; +using Microsoft.EntityFrameworkCore; +using Tzkt.Data.Models; +using Tzkt.Data.Models.Base; + +namespace Tzkt.Sync.Protocols.Proto16 +{ + class TicketsCommit : ProtocolCommit + { + public TicketsCommit(ProtocolHandler protocol) : base(protocol) { } + + readonly Dictionary>> Updates = new(); + + public virtual void Append(ManagerOperation parent, ManagerOperation op, IEnumerable updates) + { + if (!Updates.TryGetValue(parent, out var opUpdates)) + Updates.Add(parent, opUpdates = new()); + + foreach (var update in updates) + { + if (!opUpdates.TryGetValue(update.Ticket, out var ticketUpdates)) + opUpdates.Add(update.Ticket, ticketUpdates = new()); + + ticketUpdates.AddRange(update.Updates.Select(update => (op, update))); + } + } + + public virtual async Task Apply() + { + if (Updates.Count == 0) return; + + #region precache + var accountsSet = new HashSet(); + var ticketsSet = new HashSet<(int, byte[], int, byte[], int)>(); + var balancesSet = new HashSet<(int, long)>(); + + foreach (var (ticket, updates) in Updates.SelectMany(x => x.Value)) + { + accountsSet.Add(ticket.Ticketer); + foreach (var (_, upd) in updates) + accountsSet.Add(upd.Account); + } + + await Cache.Accounts.Preload(accountsSet); + + foreach (var (ticket, _) in Updates.SelectMany(x => x.Value)) + { + if (Cache.Accounts.TryGetCached(ticket.Ticketer, out var ticketer)) + ticketsSet.Add((ticketer.Id, ticket.RawType, ticket.TypeHash, ticket.RawContent, ticket.ContentHash)); + } + + await Cache.Tickets.Preload(ticketsSet); + + foreach (var (ticket, updates) in Updates.SelectMany(x => x.Value)) + { + if (Cache.Accounts.TryGetCached(ticket.Ticketer, out var ticketer)) + { + if (Cache.Tickets.TryGetCached(ticketer.Id, ticket.RawType, ticket.RawContent, out var _ticket)) + { + foreach (var (_, upd) in updates) + { + if (Cache.Accounts.TryGetCached(upd.Account, out var acc)) + balancesSet.Add((acc.Id, _ticket.Id)); + } + } + } + } + + await Cache.TicketBalances.Preload(balancesSet); + #endregion + + Updates.First().Key.Block.Events |= BlockEvents.Tickets; + + foreach (var (parent, opUpdates) in Updates.OrderBy(kv => kv.Key.Id)) + { + foreach (var (ticketIdentity, ticketUpdates) in opUpdates.OrderBy(x => x.Value[0].Op.Id).ThenBy(x => x.Key.ContentHash + x.Key.TypeHash)) + { + var ticketer = GetOrCreateAccount(ticketUpdates[0].Op, ticketIdentity.Ticketer) as Contract; + var ticket = GetOrCreateTicket(ticketUpdates[0].Op, ticketer, ticketIdentity); + + if (ticketUpdates.Count == 1 || ticketUpdates.BigSum(x => x.Update.Amount) != BigInteger.Zero) + { + foreach (var (op, ticketUpdate) in ticketUpdates) + MintOrBurnTickets(op, ticket, ticketUpdate.Account, ticketUpdate.Amount); + } + else if (ticketUpdates.Count(x => x.Update.Amount < BigInteger.Zero) == 1) + { + var (fromOp, fromUpdate) = ticketUpdates.First(x => x.Update.Amount < BigInteger.Zero); + foreach (var (op, ticketUpdate) in ticketUpdates.Where(x => x.Update.Amount > BigInteger.Zero)) + TransferTickets(ticketUpdates[0].Op, ticket, fromUpdate.Account, ticketUpdate.Account, ticketUpdate.Amount); + } + else if (ticketUpdates.Count(x => x.Update.Amount > BigInteger.Zero) == 1) + { + var (toOp, toUpdate) = ticketUpdates.First(x => x.Update.Amount > BigInteger.Zero); + foreach (var (op, ticketUpdate) in ticketUpdates.Where(x => x.Update.Amount < BigInteger.Zero)) + TransferTickets(ticketUpdates[0].Op, ticket, ticketUpdate.Account, toUpdate.Account, -ticketUpdate.Amount); + } + else if (IsTransfersSequence(ticketUpdates)) + { + for (int i = 0; i < ticketUpdates.Count; i += 2) + { + var u1 = ticketUpdates[i].Update; + var u2 = ticketUpdates[i + 1].Update; + + if (u1.Amount < 0) // from u1 to u2 + TransferTickets(ticketUpdates[i].Op, ticket, u1.Account, u2.Account, u2.Amount); + else // from u2 to u1 + TransferTickets(ticketUpdates[i].Op, ticket, u2.Account, u1.Account, u1.Amount); + } + } + else + { + foreach (var (op, ticketUpdate) in ticketUpdates) + MintOrBurnTickets(op, ticket, ticketUpdate.Account, ticketUpdate.Amount); + } + } + } + } + + static bool IsTransfersSequence(List<(ManagerOperation Op, TicketUpdate Update)> updates) + { + if (updates.Count % 2 != 0) + return false; + + for (int i = 0; i < updates.Count; i += 2) + if (updates[i].Update.Amount > 0 || updates[i].Update.Amount != -updates[i + 1].Update.Amount) + return false; + + return true; + } + + Account GetOrCreateAccount(ManagerOperation op, string address) + { + if (!Cache.Accounts.TryGetCached(address, out var account)) + { + account = address[0] == 't' && address[1] == 'z' + ? new User + { + Id = Cache.AppState.NextAccountId(), + Address = address, + FirstLevel = op.Level, + LastLevel = op.Level, + Type = AccountType.User + } + : new Account + { + Id = Cache.AppState.NextAccountId(), + Address = address, + FirstLevel = op.Level, + LastLevel = op.Level, + Type = AccountType.Ghost + }; + + Db.Accounts.Add(account); + Cache.Accounts.Add(account); + } + return account; + } + + Ticket GetOrCreateTicket(ManagerOperation op, Contract ticketer, TicketIdentity ticketToken) + { + if (!Cache.Tickets.TryGetCached(ticketer.Id, ticketToken.RawType, ticketToken.RawContent, out var ticket)) + { + ticket = new Ticket + { + Id = op switch + { + TransactionOperation transaction => Cache.AppState.NextSubId(transaction), + TransferTicketOperation transferTicket => Cache.AppState.NextSubId(transferTicket), + SmartRollupExecuteOperation srExecute => Cache.AppState.NextSubId(srExecute), + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }, + TicketerId = ticketer.Id, + FirstMinterId = op switch + { + TransactionOperation transaction => transaction.InitiatorId ?? transaction.SenderId, + TransferTicketOperation transferTicket => transferTicket.SenderId, + SmartRollupExecuteOperation srExecute => srExecute.SenderId, + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }, + FirstLevel = op.Level, + LastLevel = op.Level, + TotalBurned = BigInteger.Zero, + TotalMinted = BigInteger.Zero, + TotalSupply = BigInteger.Zero, + RawType = ticketToken.RawType, + RawContent = ticketToken.RawContent, + JsonContent = ticketToken.JsonContent, + ContentHash = ticketToken.ContentHash, + TypeHash = ticketToken.TypeHash, + }; + + Db.Tickets.Add(ticket); + Cache.Tickets.Add(ticket); + + Db.TryAttach(ticketer); + ticketer.TicketsCount++; + + var state = Cache.AppState.Get(); + state.TicketsCount++; + } + return ticket; + } + + TicketBalance GetOrCreateTicketBalance(ManagerOperation op, Ticket ticket, Account account) + { + if (!Cache.TicketBalances.TryGet(account.Id, ticket.Id, out var ticketBalance)) + { + ticketBalance = new TicketBalance + { + Id = op switch + { + TransactionOperation transaction => Cache.AppState.NextSubId(transaction), + TransferTicketOperation transferTicket => Cache.AppState.NextSubId(transferTicket), + SmartRollupExecuteOperation srExecute => Cache.AppState.NextSubId(srExecute), + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }, + AccountId = account.Id, + TicketId = ticket.Id, + TicketerId = ticket.TicketerId, + FirstLevel = op.Level, + LastLevel = op.Level, + Balance = BigInteger.Zero + }; + + Db.TicketBalances.Add(ticketBalance); + Cache.TicketBalances.Add(ticketBalance); + + Db.TryAttach(ticket); + ticket.BalancesCount++; + + Db.TryAttach(account); + account.TicketBalancesCount++; + + var state = Cache.AppState.Get(); + state.TicketBalancesCount++; + } + return ticketBalance; + } + + void TransferTickets(ManagerOperation op, Ticket ticket, string fromAddress, string toAddress, BigInteger amount) + { + var from = GetOrCreateAccount(op, fromAddress); + var fromBalance = GetOrCreateTicketBalance(op, ticket, from); + var to = GetOrCreateAccount(op, toAddress); + var toBalance = GetOrCreateTicketBalance(op, ticket, to); + + switch (op) + { + case TransactionOperation transaction: + transaction.TicketTransfers = (transaction.TicketTransfers ?? 0) + 1; + break; + case TransferTicketOperation transferTicket: + transferTicket.TicketTransfers = (transferTicket.TicketTransfers ?? 0) + 1; + break; + case SmartRollupExecuteOperation srExecute: + srExecute.TicketTransfers = (srExecute.TicketTransfers ?? 0) + 1; + break; + } + + Db.TryAttach(from); + from.TicketTransfersCount++; + from.LastLevel = op.Level; + + Db.TryAttach(to); + if (to != from) + { + to.TicketTransfersCount++; + to.LastLevel = op.Level; + } + + Db.TryAttach(fromBalance); + fromBalance.Balance -= amount; + fromBalance.TransfersCount++; + fromBalance.LastLevel = op.Level; + + Db.TryAttach(toBalance); + toBalance.Balance += amount; + if (toBalance != fromBalance) toBalance.TransfersCount++; + toBalance.LastLevel = op.Level; + + Db.TryAttach(ticket); + ticket.TransfersCount++; + ticket.LastLevel = op.Level; + if (amount != BigInteger.Zero && fromBalance != toBalance) + { + if (fromBalance.Balance == BigInteger.Zero) + { + from.ActiveTicketsCount--; + ticket.HoldersCount--; + } + if (toBalance.Balance == amount) + { + to.ActiveTicketsCount++; + ticket.HoldersCount++; + } + } + + var state = Cache.AppState.Get(); + state.TicketTransfersCount++; + + Db.TicketTransfers.Add(new TicketTransfer + { + Id = op switch + { + TransactionOperation transaction => Cache.AppState.NextSubId(transaction), + TransferTicketOperation transferTicket => Cache.AppState.NextSubId(transferTicket), + SmartRollupExecuteOperation srExecute => Cache.AppState.NextSubId(srExecute), + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }, + Amount = amount, + FromId = from.Id, + ToId = to.Id, + Level = op.Level, + TicketId = ticket.Id, + TicketerId = ticket.TicketerId, + TransactionId = (op as TransactionOperation)?.Id, + TransferTicketId = (op as TransferTicketOperation)?.Id, + SmartRollupExecuteId = (op as SmartRollupExecuteOperation)?.Id + }); + } + + void MintOrBurnTickets(ManagerOperation op, Ticket ticket, string address, BigInteger amount) + { + var account = GetOrCreateAccount(op, address); + var balance = GetOrCreateTicketBalance(op, ticket, account); + + switch (op) + { + case TransactionOperation transaction: + transaction.TicketTransfers = (transaction.TicketTransfers ?? 0) + 1; + break; + case TransferTicketOperation transferTicket: + transferTicket.TicketTransfers = (transferTicket.TicketTransfers ?? 0) + 1; + break; + case SmartRollupExecuteOperation srExecute: + srExecute.TicketTransfers = (srExecute.TicketTransfers ?? 0) + 1; + break; + } + + Db.TryAttach(account); + account.TicketTransfersCount++; + account.LastLevel = op.Level; + + Db.TryAttach(balance); + balance.Balance += amount; + balance.TransfersCount++; + balance.LastLevel = op.Level; + + Db.TryAttach(ticket); + ticket.TransfersCount++; + ticket.LastLevel = op.Level; + ticket.TotalSupply += amount; + if (amount > BigInteger.Zero) + { + ticket.TotalMinted += amount; + if (balance.Balance == amount) + { + account.ActiveTicketsCount++; + ticket.HoldersCount++; + } + } + else if (amount < BigInteger.Zero) + { + ticket.TotalBurned += -amount; + if (balance.Balance == BigInteger.Zero) + { + account.ActiveTicketsCount--; + ticket.HoldersCount--; + } + } + + var state = Cache.AppState.Get(); + state.TicketTransfersCount++; + + Db.TicketTransfers.Add(new TicketTransfer + { + Id = op switch + { + TransactionOperation transaction => Cache.AppState.NextSubId(transaction), + TransferTicketOperation transferTicket => Cache.AppState.NextSubId(transferTicket), + SmartRollupExecuteOperation srExecute => Cache.AppState.NextSubId(srExecute), + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }, + Amount = amount > BigInteger.Zero ? amount : -amount, + FromId = amount < BigInteger.Zero ? account.Id : null, + ToId = amount > BigInteger.Zero ? account.Id : null, + Level = op.Level, + TicketId = ticket.Id, + TicketerId = ticket.TicketerId, + TransactionId = (op as TransactionOperation)?.Id, + TransferTicketId = (op as TransferTicketOperation)?.Id, + SmartRollupExecuteId = (op as SmartRollupExecuteOperation)?.Id + }); + } + + public virtual async Task Revert(Block block) + { + if (!block.Events.HasFlag(BlockEvents.Tickets)) + return; + + var state = Cache.AppState.Get(); + + var transfers = await Db.TicketTransfers + .AsNoTracking() + .Where(x => x.Level == block.Level) + .OrderByDescending(x => x.Id) + .ToListAsync(); + + #region precache + var accountsSet = new HashSet(); + var ticketsSet = new HashSet(); + var balancesSet = new HashSet<(int, long)>(); + + foreach (var tr in transfers) + ticketsSet.Add(tr.TicketId); + + await Cache.Tickets.Preload(ticketsSet); + + foreach (var tr in transfers) + { + if (tr.FromId is int fromId) + { + accountsSet.Add(fromId); + balancesSet.Add((fromId, tr.TicketId)); + } + + if (tr.ToId is int toId) + { + accountsSet.Add(toId); + balancesSet.Add((toId, tr.TicketId)); + } + } + + foreach (var id in ticketsSet) + { + var ticket = Cache.Tickets.GetCached(id); + accountsSet.Add(ticket.TicketerId); + } + + await Cache.Accounts.Preload(accountsSet); + await Cache.TicketBalances.Preload(balancesSet); + #endregion + + var ticketsToRemove = new HashSet(); + var ticketBalancesToRemove = new HashSet(); + + foreach (var transfer in transfers) + { + var ticket = Cache.Tickets.GetCached(transfer.TicketId); + Db.TryAttach(ticket); + ticket.TransfersCount--; + ticket.LastLevel = block.Level; + if (ticket.TransfersCount == 0) + ticketsToRemove.Add(ticket); + + state.TicketTransfersCount--; + + if (transfer.FromId is int fromId && transfer.ToId is int toId) + { + #region revert transfer + var from = Cache.Accounts.GetCached(fromId); + var fromBalance = Cache.TicketBalances.Get(from.Id, ticket.Id); + var to = Cache.Accounts.GetCached(toId); + var toBalance = Cache.TicketBalances.Get(to.Id, ticket.Id); + + Db.TryAttach(from); + from.TicketTransfersCount--; + from.LastLevel = block.Level; + + Db.TryAttach(to); + if (to != from) + { + to.TicketTransfersCount--; + to.LastLevel = block.Level; + } + + Db.TryAttach(fromBalance); + fromBalance.Balance += transfer.Amount; + fromBalance.TransfersCount--; + fromBalance.LastLevel = block.Level; + if (fromBalance.TransfersCount == 0) + ticketBalancesToRemove.Add(fromBalance); + + Db.TryAttach(toBalance); + toBalance.Balance -= transfer.Amount; + if (toBalance != fromBalance) toBalance.TransfersCount--; + toBalance.LastLevel = block.Level; + if (toBalance.TransfersCount == 0) + ticketBalancesToRemove.Add(toBalance); + + if (transfer.Amount != BigInteger.Zero && fromBalance != toBalance) + { + if (fromBalance.Balance == transfer.Amount) + { + from.ActiveTicketsCount++; + ticket.HoldersCount++; + } + if (toBalance.Balance == BigInteger.Zero) + { + to.ActiveTicketsCount--; + ticket.HoldersCount--; + } + } + #endregion + } + else if (transfer.ToId != null) + { + #region revert mint + var to = Cache.Accounts.GetCached((int)transfer.ToId); + var toBalance = Cache.TicketBalances.Get(to.Id, ticket.Id); + + Db.TryAttach(to); + to.TicketTransfersCount--; + to.LastLevel = block.Level; + + Db.TryAttach(toBalance); + toBalance.Balance -= transfer.Amount; + toBalance.TransfersCount--; + toBalance.LastLevel = block.Level; + if (toBalance.TransfersCount == 0) + ticketBalancesToRemove.Add(toBalance); + + if (transfer.Amount != BigInteger.Zero) + { + ticket.TotalSupply -= transfer.Amount; + ticket.TotalMinted -= transfer.Amount; + if (toBalance.Balance == BigInteger.Zero) + { + to.ActiveTicketsCount--; + ticket.HoldersCount--; + } + } + #endregion + } + else + { + #region revert burn + var from = Cache.Accounts.GetCached((int)transfer.FromId); + var fromBalance = Cache.TicketBalances.Get(from.Id, ticket.Id); + + Db.TryAttach(from); + from.TicketTransfersCount--; + from.LastLevel = block.Level; + + Db.TryAttach(fromBalance); + fromBalance.Balance += transfer.Amount; + fromBalance.TransfersCount--; + fromBalance.LastLevel = block.Level; + if (fromBalance.TransfersCount == 0) + ticketBalancesToRemove.Add(fromBalance); + + if (transfer.Amount != BigInteger.Zero) + { + ticket.TotalSupply += transfer.Amount; + ticket.TotalBurned -= transfer.Amount; + if (fromBalance.Balance == transfer.Amount) + { + from.ActiveTicketsCount++; + ticket.HoldersCount++; + } + } + #endregion + } + } + + foreach (var ticketBalance in ticketBalancesToRemove) + { + Db.TicketBalances.Remove(ticketBalance); + Cache.TicketBalances.Remove(ticketBalance); + + var t = Cache.Tickets.GetCached(ticketBalance.TicketId); + Db.TryAttach(t); + t.BalancesCount--; + + var a = Cache.Accounts.GetCached(ticketBalance.AccountId); + Db.TryAttach(a); + a.TicketBalancesCount--; + + state.TicketBalancesCount--; + } + + foreach (var ticket in ticketsToRemove) + { + Db.Tickets.Remove(ticket); + Cache.Tickets.Remove(ticket); + + var contract = (Contract)Cache.Accounts.GetCached(ticket.TicketerId); + Db.TryAttach(contract); + contract.TicketsCount--; + + state.TicketsCount--; + } + + await Db.Database.ExecuteSqlRawAsync($""" + DELETE FROM "TicketTransfers" + WHERE "Level" = {block.Level} + """); + } + } +} \ No newline at end of file diff --git a/Tzkt.Sync/Protocols/Handlers/Proto16/Diagnostics/Diagnostics.cs b/Tzkt.Sync/Protocols/Handlers/Proto16/Diagnostics/Diagnostics.cs index f3d3f5f28..0bf01d82e 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto16/Diagnostics/Diagnostics.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto16/Diagnostics/Diagnostics.cs @@ -1,7 +1,36 @@ -namespace Tzkt.Sync.Protocols.Proto16 +using System.Numerics; +using System.Text.Json; +using Netezos.Encoding; +using Tzkt.Data.Models; + +namespace Tzkt.Sync.Protocols.Proto16 { class Diagnostics : Proto14.Diagnostics { public Diagnostics(ProtocolHandler handler) : base(handler) { } + + protected override async Task TestTicketBalance(int level, TicketBalance balance) + { + var ticketer = await Cache.Accounts.GetAsync(balance.TicketerId); + var ticket = Cache.Tickets.GetCached(balance.TicketId); + var account = await Cache.Accounts.GetAsync(balance.AccountId); + + var ticketIdentity = JsonSerializer.Serialize(new + { + ticketer = ticketer.Address, + content_type = Micheline.FromBytes(ticket.RawType), + content = Micheline.FromBytes(ticket.RawContent) + }); + + if (BigInteger.TryParse((await Rpc.GetTicketBalance(level, account.Address, ticketIdentity)).RequiredString(), out var remoteBalance)) + { + if (remoteBalance != balance.Balance) + throw new Exception($"Diagnostics failed: wrong ticket balance for {account.Address}"); + } + else + { + throw new Exception("Failed to get ticket balance"); + } + } } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto16/Proto16Handler.cs b/Tzkt.Sync/Protocols/Handlers/Proto16/Proto16Handler.cs index 6c7b2fce9..8ab151828 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto16/Proto16Handler.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto16/Proto16Handler.cs @@ -131,6 +131,7 @@ public override async Task Commit(JsonElement block) #endregion var bigMapCommit = new BigMapCommit(this); + var ticketsCommit = new TicketsCommit(this); #region operations 3 foreach (var operation in operations[3].EnumerateArray()) @@ -169,6 +170,8 @@ public override async Task Commit(JsonElement block) await parent.Apply(blockCommit.Block, operation, content); if (parent.BigMapDiffs != null) bigMapCommit.Append(parent.Transaction, parent.Transaction.Target as Contract, parent.BigMapDiffs); + if (parent.TicketUpdates != null) + ticketsCommit.Append(parent.Transaction, parent.Transaction, parent.TicketUpdates); if (content.Required("metadata").TryGetProperty("internal_operation_results", out var internalResult)) { @@ -190,6 +193,8 @@ public override async Task Commit(JsonElement block) await internalTx.ApplyInternal(blockCommit.Block, parent.Transaction, internalContent); if (internalTx.BigMapDiffs != null) bigMapCommit.Append(internalTx.Transaction, internalTx.Transaction.Target as Contract, internalTx.BigMapDiffs); + if (internalTx.TicketUpdates != null) + ticketsCommit.Append(parent.Transaction, internalTx.Transaction, internalTx.TicketUpdates); break; case "event": await new ContractEventCommit(this).Apply(blockCommit.Block, internalContent); @@ -227,6 +232,8 @@ public override async Task Commit(JsonElement block) case "transfer_ticket": var parent1 = new TransferTicketCommit(this); await parent1.Apply(blockCommit.Block, operation, content); + if (parent1.TicketUpdates != null) + ticketsCommit.Append(parent1.Operation, parent1.Operation, parent1.TicketUpdates); if (content.Required("metadata").TryGetProperty("internal_operation_results", out var internalResult1)) { foreach (var internalContent in internalResult1.EnumerateArray()) @@ -238,6 +245,8 @@ public override async Task Commit(JsonElement block) await internalTx.ApplyInternal(blockCommit.Block, parent1.Operation, internalContent); if (internalTx.BigMapDiffs != null) bigMapCommit.Append(internalTx.Transaction, internalTx.Transaction.Target as Contract, internalTx.BigMapDiffs); + if (internalTx.TicketUpdates != null) + ticketsCommit.Append(parent1.Operation, internalTx.Transaction, internalTx.TicketUpdates); break; default: throw new NotImplementedException($"internal '{content.RequiredString("kind")}' inside 'transfer_ticket' is not expected"); @@ -254,6 +263,8 @@ public override async Task Commit(JsonElement block) case "smart_rollup_execute_outbox_message": var parent2 = new SmartRollupExecuteCommit(this); await parent2.Apply(blockCommit.Block, operation, content); + if (parent2.TicketUpdates != null) + ticketsCommit.Append(parent2.Operation, parent2.Operation, parent2.TicketUpdates); if (content.Required("metadata").TryGetProperty("internal_operation_results", out var internalResult2)) { foreach (var internalContent in internalResult2.EnumerateArray()) @@ -274,6 +285,8 @@ public override async Task Commit(JsonElement block) await internalTx.ApplyInternal(blockCommit.Block, parent2.Operation, internalContent); if (internalTx.BigMapDiffs != null) bigMapCommit.Append(internalTx.Transaction, internalTx.Transaction.Target as Contract, internalTx.BigMapDiffs); + if (internalTx.TicketUpdates != null) + ticketsCommit.Append(parent2.Operation, internalTx.Transaction, internalTx.TicketUpdates); break; case "event": await new ContractEventCommit(this).Apply(blockCommit.Block, internalContent); @@ -309,7 +322,8 @@ public override async Task Commit(JsonElement block) new InboxCommit(this).Apply(blockCommit.Block); - await bigMapCommit.Apply(); + await bigMapCommit.Apply(); + await ticketsCommit.Apply(); await new TokensCommit(this).Apply(blockCommit.Block, bigMapCommit.Updates); var brCommit = new BakingRightsCommit(this); @@ -486,6 +500,7 @@ public override async Task Revert() await new DelegatorCycleCommit(this).Revert(currBlock); await new BakingRightsCommit(this).Revert(currBlock); await new TokensCommit(this).Revert(currBlock); + await new TicketsCommit(this).Revert(currBlock); await new BigMapCommit(this).Revert(currBlock); await new ContractEventCommit(this).Revert(currBlock); await new InboxCommit(this).Revert(currBlock); diff --git a/Tzkt.Sync/Protocols/Handlers/Proto16/Rpc/Rpc.cs b/Tzkt.Sync/Protocols/Handlers/Proto16/Rpc/Rpc.cs index bc64238b0..23b9bd61e 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto16/Rpc/Rpc.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto16/Rpc/Rpc.cs @@ -1,9 +1,17 @@ -using Tzkt.Sync.Services; +using System.Text.Json; +using Tzkt.Sync.Services; namespace Tzkt.Sync.Protocols.Proto16 { class Rpc : Proto12.Rpc { public Rpc(TezosNode node) : base(node) { } + + public override Task GetTicketBalance(int level, string address, string ticket) + { + return address.StartsWith("sr1") + ? Node.PostAsync($"chains/main/blocks/{level}/context/smart_rollups/smart_rollup/{address}/ticket_balance", ticket) + : Node.PostAsync($"chains/main/blocks/{level}/context/contracts/{address}/ticket_balance", ticket); + } } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto17/Commits/TicketsCommit.cs b/Tzkt.Sync/Protocols/Handlers/Proto17/Commits/TicketsCommit.cs new file mode 100644 index 000000000..4e010482c --- /dev/null +++ b/Tzkt.Sync/Protocols/Handlers/Proto17/Commits/TicketsCommit.cs @@ -0,0 +1,7 @@ +namespace Tzkt.Sync.Protocols.Proto17 +{ + class TicketsCommit : Proto16.TicketsCommit + { + public TicketsCommit(ProtocolHandler protocol) : base(protocol) { } + } +} \ No newline at end of file diff --git a/Tzkt.Sync/Protocols/Handlers/Proto17/Diagnostics/Diagnostics.cs b/Tzkt.Sync/Protocols/Handlers/Proto17/Diagnostics/Diagnostics.cs index e225a41de..355aea78b 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto17/Diagnostics/Diagnostics.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto17/Diagnostics/Diagnostics.cs @@ -1,7 +1,8 @@ namespace Tzkt.Sync.Protocols.Proto17 { - class Diagnostics : Proto14.Diagnostics + class Diagnostics : Proto16.Diagnostics { public Diagnostics(ProtocolHandler handler) : base(handler) { } + } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto17/Proto17Handler.cs b/Tzkt.Sync/Protocols/Handlers/Proto17/Proto17Handler.cs index 837b8ef5c..f5c23e376 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto17/Proto17Handler.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto17/Proto17Handler.cs @@ -131,6 +131,7 @@ public override async Task Commit(JsonElement block) #endregion var bigMapCommit = new BigMapCommit(this); + var ticketsCommit = new TicketsCommit(this); #region operations 3 foreach (var operation in operations[3].EnumerateArray()) @@ -169,6 +170,8 @@ public override async Task Commit(JsonElement block) await parent.Apply(blockCommit.Block, operation, content); if (parent.BigMapDiffs != null) bigMapCommit.Append(parent.Transaction, parent.Transaction.Target as Contract, parent.BigMapDiffs); + if (parent.TicketUpdates != null) + ticketsCommit.Append(parent.Transaction, parent.Transaction, parent.TicketUpdates); if (content.Required("metadata").TryGetProperty("internal_operation_results", out var internalResult)) { @@ -190,6 +193,8 @@ public override async Task Commit(JsonElement block) await internalTx.ApplyInternal(blockCommit.Block, parent.Transaction, internalContent); if (internalTx.BigMapDiffs != null) bigMapCommit.Append(internalTx.Transaction, internalTx.Transaction.Target as Contract, internalTx.BigMapDiffs); + if (internalTx.TicketUpdates != null) + ticketsCommit.Append(parent.Transaction, internalTx.Transaction, internalTx.TicketUpdates); break; case "event": await new ContractEventCommit(this).Apply(blockCommit.Block, internalContent); @@ -227,6 +232,8 @@ public override async Task Commit(JsonElement block) case "transfer_ticket": var parent1 = new TransferTicketCommit(this); await parent1.Apply(blockCommit.Block, operation, content); + if (parent1.TicketUpdates != null) + ticketsCommit.Append(parent1.Operation, parent1.Operation, parent1.TicketUpdates); if (content.Required("metadata").TryGetProperty("internal_operation_results", out var internalResult1)) { foreach (var internalContent in internalResult1.EnumerateArray()) @@ -238,6 +245,8 @@ public override async Task Commit(JsonElement block) await internalTx.ApplyInternal(blockCommit.Block, parent1.Operation, internalContent); if (internalTx.BigMapDiffs != null) bigMapCommit.Append(internalTx.Transaction, internalTx.Transaction.Target as Contract, internalTx.BigMapDiffs); + if (internalTx.TicketUpdates != null) + ticketsCommit.Append(parent1.Operation, internalTx.Transaction, internalTx.TicketUpdates); break; default: throw new NotImplementedException($"internal '{content.RequiredString("kind")}' inside 'transfer_ticket' is not expected"); @@ -254,6 +263,8 @@ public override async Task Commit(JsonElement block) case "smart_rollup_execute_outbox_message": var parent2 = new SmartRollupExecuteCommit(this); await parent2.Apply(blockCommit.Block, operation, content); + if (parent2.TicketUpdates != null) + ticketsCommit.Append(parent2.Operation, parent2.Operation, parent2.TicketUpdates); if (content.Required("metadata").TryGetProperty("internal_operation_results", out var internalResult2)) { foreach (var internalContent in internalResult2.EnumerateArray()) @@ -274,6 +285,8 @@ public override async Task Commit(JsonElement block) await internalTx.ApplyInternal(blockCommit.Block, parent2.Operation, internalContent); if (internalTx.BigMapDiffs != null) bigMapCommit.Append(internalTx.Transaction, internalTx.Transaction.Target as Contract, internalTx.BigMapDiffs); + if (internalTx.TicketUpdates != null) + ticketsCommit.Append(parent2.Operation, internalTx.Transaction, internalTx.TicketUpdates); break; case "event": await new ContractEventCommit(this).Apply(blockCommit.Block, internalContent); @@ -310,6 +323,7 @@ public override async Task Commit(JsonElement block) new InboxCommit(this).Apply(blockCommit.Block); await bigMapCommit.Apply(); + await ticketsCommit.Apply(); await new TokensCommit(this).Apply(blockCommit.Block, bigMapCommit.Updates); var brCommit = new BakingRightsCommit(this); @@ -486,6 +500,7 @@ public override async Task Revert() await new DelegatorCycleCommit(this).Revert(currBlock); await new BakingRightsCommit(this).Revert(currBlock); await new TokensCommit(this).Revert(currBlock); + await new TicketsCommit(this).Revert(currBlock); await new BigMapCommit(this).Revert(currBlock); await new ContractEventCommit(this).Revert(currBlock); await new InboxCommit(this).Revert(currBlock); diff --git a/Tzkt.Sync/Protocols/Handlers/Proto17/Rpc/Rpc.cs b/Tzkt.Sync/Protocols/Handlers/Proto17/Rpc/Rpc.cs index 634cfed84..1f9b57fca 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto17/Rpc/Rpc.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto17/Rpc/Rpc.cs @@ -2,7 +2,7 @@ namespace Tzkt.Sync.Protocols.Proto17 { - class Rpc : Proto12.Rpc + class Rpc : Proto16.Rpc { public Rpc(TezosNode node) : base(node) { } } diff --git a/Tzkt.Sync/Protocols/Handlers/Proto2/Activation/ProtoActivator.cs b/Tzkt.Sync/Protocols/Handlers/Proto2/Activation/ProtoActivator.cs index 7f75d3a60..c999ec274 100644 --- a/Tzkt.Sync/Protocols/Handlers/Proto2/Activation/ProtoActivator.cs +++ b/Tzkt.Sync/Protocols/Handlers/Proto2/Activation/ProtoActivator.cs @@ -200,6 +200,34 @@ protected override async Task RevertContext(AppState state) ActiveTokensCount = user.ActiveTokensCount, TokenBalancesCount = user.TokenBalancesCount, TokenTransfersCount = user.TokenTransfersCount, + ActiveTicketsCount = user.ActiveTicketsCount, + TicketBalancesCount = user.TicketBalancesCount, + TicketTransfersCount = user.TicketTransfersCount, + TransferTicketCount = user.TransferTicketCount, + TxRollupCommitCount = user.TxRollupCommitCount, + TxRollupDispatchTicketsCount = user.TxRollupDispatchTicketsCount, + TxRollupFinalizeCommitmentCount = user.TxRollupFinalizeCommitmentCount, + TxRollupOriginationCount = user.TxRollupOriginationCount, + TxRollupRejectionCount = user.TxRollupRejectionCount, + TxRollupRemoveCommitmentCount = user.TxRollupRemoveCommitmentCount, + TxRollupReturnBondCount = user.TxRollupReturnBondCount, + TxRollupSubmitBatchCount = user.TxRollupSubmitBatchCount, + IncreasePaidStorageCount = user.IncreasePaidStorageCount, + UpdateConsensusKeyCount = user.UpdateConsensusKeyCount, + DrainDelegateCount = user.DrainDelegateCount, + RollupBonds = user.RollupBonds, + RollupsCount = user.RollupsCount, + SmartRollupBonds = user.SmartRollupBonds, + SmartRollupsCount = user.SmartRollupsCount, + SmartRollupAddMessagesCount = user.SmartRollupAddMessagesCount, + SmartRollupCementCount = user.SmartRollupCementCount, + SmartRollupExecuteCount = user.SmartRollupExecuteCount, + SmartRollupOriginateCount = user.SmartRollupOriginateCount, + SmartRollupPublishCount = user.SmartRollupPublishCount, + SmartRollupRecoverBondCount = user.SmartRollupRecoverBondCount, + SmartRollupRefuteCount = user.SmartRollupRefuteCount, + RefutationGamesCount = user.RefutationGamesCount, + ActiveRefutationGamesCount = user.ActiveRefutationGamesCount }; Db.Entry(user).State = EntityState.Detached; @@ -238,6 +266,34 @@ User DowngradeDelegate(Data.Models.Delegate delegat) ActiveTokensCount = delegat.ActiveTokensCount, TokenBalancesCount = delegat.TokenBalancesCount, TokenTransfersCount = delegat.TokenTransfersCount, + ActiveTicketsCount = delegat.ActiveTicketsCount, + TicketBalancesCount = delegat.TicketBalancesCount, + TicketTransfersCount = delegat.TicketTransfersCount, + TransferTicketCount = delegat.TransferTicketCount, + TxRollupCommitCount = delegat.TxRollupCommitCount, + TxRollupDispatchTicketsCount = delegat.TxRollupDispatchTicketsCount, + TxRollupFinalizeCommitmentCount = delegat.TxRollupFinalizeCommitmentCount, + TxRollupOriginationCount = delegat.TxRollupOriginationCount, + TxRollupRejectionCount = delegat.TxRollupRejectionCount, + TxRollupRemoveCommitmentCount = delegat.TxRollupRemoveCommitmentCount, + TxRollupReturnBondCount = delegat.TxRollupReturnBondCount, + TxRollupSubmitBatchCount = delegat.TxRollupSubmitBatchCount, + IncreasePaidStorageCount = delegat.IncreasePaidStorageCount, + UpdateConsensusKeyCount = delegat.UpdateConsensusKeyCount, + DrainDelegateCount = delegat.DrainDelegateCount, + RollupBonds = delegat.RollupBonds, + RollupsCount = delegat.RollupsCount, + SmartRollupBonds = delegat.SmartRollupBonds, + SmartRollupsCount = delegat.SmartRollupsCount, + SmartRollupAddMessagesCount = delegat.SmartRollupAddMessagesCount, + SmartRollupCementCount = delegat.SmartRollupCementCount, + SmartRollupExecuteCount = delegat.SmartRollupExecuteCount, + SmartRollupOriginateCount = delegat.SmartRollupOriginateCount, + SmartRollupPublishCount = delegat.SmartRollupPublishCount, + SmartRollupRecoverBondCount = delegat.SmartRollupRecoverBondCount, + SmartRollupRefuteCount = delegat.SmartRollupRefuteCount, + RefutationGamesCount = delegat.RefutationGamesCount, + ActiveRefutationGamesCount = delegat.ActiveRefutationGamesCount }; Db.Entry(delegat).State = EntityState.Detached; diff --git a/Tzkt.Sync/Protocols/Helpers/TicketUpdate.cs b/Tzkt.Sync/Protocols/Helpers/TicketUpdate.cs new file mode 100644 index 000000000..f22c5899d --- /dev/null +++ b/Tzkt.Sync/Protocols/Helpers/TicketUpdate.cs @@ -0,0 +1,40 @@ +using System.Numerics; + +namespace Tzkt.Sync.Protocols +{ + public class TicketUpdates + { + public TicketIdentity Ticket { get; set; } + public IEnumerable Updates { get; set; } + } + + public class TicketIdentity + { + public string Ticketer { get; set; } + public byte[] RawType { get; set; } + public byte[] RawContent { get; set; } + public string JsonContent { get; set; } + + public int ContentHash { get; set; } + public int TypeHash { get; set; } + + public override bool Equals(object obj) + { + return obj is TicketIdentity ticket && + ticket.Ticketer == Ticketer && + ticket.RawType.IsEqual(RawType) && + ticket.RawContent.IsEqual(RawContent); + } + + public override int GetHashCode() + { + return HashCode.Combine(Ticketer.GetHashCode(), ContentHash, TypeHash); + } + } + + public class TicketUpdate + { + public string Account { get; set; } + public BigInteger Amount { get; set; } + } +} \ No newline at end of file diff --git a/Tzkt.Sync/Services/Cache/AppStateCache.cs b/Tzkt.Sync/Services/Cache/AppStateCache.cs index 6b064d1ab..a941ba961 100644 --- a/Tzkt.Sync/Services/Cache/AppStateCache.cs +++ b/Tzkt.Sync/Services/Cache/AppStateCache.cs @@ -177,6 +177,20 @@ public long NextSubId(ContractOperation op) return op.Id + (int)op.SubIds; } + public long NextSubId(TransferTicketOperation op) + { + op.SubIds = (op.SubIds ?? 0) + 1; + if (op.SubIds >= 1 << AppState.SubIdBits) throw new Exception("SubId overflow"); + return op.Id + (int)op.SubIds; + } + + public long NextSubId(SmartRollupExecuteOperation op) + { + op.SubIds = (op.SubIds ?? 0) + 1; + if (op.SubIds >= 1 << AppState.SubIdBits) throw new Exception("SubId overflow"); + return op.Id + (int)op.SubIds; + } + public long NextSubId(MigrationOperation op) { op.SubIds = (op.SubIds ?? 0) + 1; diff --git a/Tzkt.Sync/Services/Cache/CacheService.cs b/Tzkt.Sync/Services/Cache/CacheService.cs index 58cd0c7e6..d6371a23d 100644 --- a/Tzkt.Sync/Services/Cache/CacheService.cs +++ b/Tzkt.Sync/Services/Cache/CacheService.cs @@ -21,6 +21,8 @@ public class CacheService public BigMapKeysCache BigMapKeys { get; private set; } public TokensCache Tokens { get; private set; } public TokenBalancesCache TokenBalances { get; private set; } + public TicketsCache Tickets { get; private set; } + public TicketBalancesCache TicketBalances { get; private set; } public SmartRollupCommitmentCache SmartRollupCommitments { get; private set; } public SmartRollupStakesCache SmartRollupStakes { get; private set; } public RefutationGameCache RefutationGames { get; private set; } @@ -43,6 +45,8 @@ public CacheService(TzktContext db) BigMapKeys = new(db); Tokens = new(db); TokenBalances = new(db); + Tickets = new(db); + TicketBalances = new(db); SmartRollupCommitments = new(db); SmartRollupStakes = new(db); RefutationGames = new(db); @@ -64,6 +68,8 @@ public async Task ResetAsync() BigMapKeys.Reset(); Tokens.Reset(); TokenBalances.Reset(); + Tickets.Reset(); + TicketBalances.Reset(); SmartRollupCommitments.Reset(); SmartRollupStakes.Reset(); RefutationGames.Reset(); @@ -76,6 +82,8 @@ public void Trim() { Tokens.Trim(); TokenBalances.Trim(); + Tickets.Trim(); + TicketBalances.Trim(); SmartRollupCommitments.Trim(); SmartRollupStakes.Trim(); RefutationGames.Trim(); diff --git a/Tzkt.Sync/Services/Cache/TicketBalancesCache.cs b/Tzkt.Sync/Services/Cache/TicketBalancesCache.cs new file mode 100644 index 000000000..8ccf8425a --- /dev/null +++ b/Tzkt.Sync/Services/Cache/TicketBalancesCache.cs @@ -0,0 +1,89 @@ +using Microsoft.EntityFrameworkCore; +using Tzkt.Data; +using Tzkt.Data.Models; + +namespace Tzkt.Sync.Services.Cache +{ + public class TicketBalancesCache + { + public const int MaxItems = 4 * 4096; //TODO: set limits in app settings + + static readonly Dictionary<(int, long), TicketBalance> Cached = new(MaxItems); + + readonly TzktContext Db; + + public TicketBalancesCache(TzktContext db) + { + Db = db; + } + + public void Reset() + { + Cached.Clear(); + } + + public void Trim() + { + if (Cached.Count > MaxItems * 0.9) + { + var toRemove = Cached.Values + .OrderBy(x => x.LastLevel) + .Take(MaxItems / 2) + .ToList(); + + foreach (var item in toRemove) + Remove(item); + } + } + + public void Add(TicketBalance ticketBalance) + { + Cached[(ticketBalance.AccountId, ticketBalance.TicketId)] = ticketBalance; + } + + public void Remove(TicketBalance ticketBalance) + { + Cached.Remove((ticketBalance.AccountId, ticketBalance.TicketId)); + } + + public TicketBalance GetOrAdd(TicketBalance ticketBalance) + { + if (Cached.TryGetValue((ticketBalance.AccountId, ticketBalance.TicketId), out var res)) + return res; + Add(ticketBalance); + return ticketBalance; + } + + public TicketBalance Get(int accountId, long ticketId) + { + if (!Cached.TryGetValue((accountId, ticketId), out var ticketBalance)) + throw new Exception($"TicketBalance ({accountId}, {ticketId}) doesn't exist"); + return ticketBalance; + } + + public bool TryGet(int accountId, long ticketId, out TicketBalance ticketBalance) + { + return Cached.TryGetValue((accountId, ticketId), out ticketBalance); + } + + public async Task Preload(IEnumerable<(int, long)> ids) + { + var missed = ids.Where(x => !Cached.ContainsKey(x)).ToHashSet(); + if (missed.Count > 0) + { + for (int i = 0, n = 2048; i < missed.Count; i += n) + { + var corteges = string.Join(',', missed.Skip(i).Take(n).Select(x => $"({x.Item1}, {x.Item2})")); + var items = await Db.TicketBalances + .FromSqlRaw($@" + SELECT * FROM ""{nameof(TzktContext.TicketBalances)}"" + WHERE (""{nameof(TicketBalance.AccountId)}"", ""{nameof(TicketBalance.TicketId)}"") IN ({corteges})") + .ToListAsync(); + + foreach (var item in items) + Add(item); + } + } + } + } +} diff --git a/Tzkt.Sync/Services/Cache/TicketsCache.cs b/Tzkt.Sync/Services/Cache/TicketsCache.cs new file mode 100644 index 000000000..fafc7691e --- /dev/null +++ b/Tzkt.Sync/Services/Cache/TicketsCache.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; +using Tzkt.Data; +using Tzkt.Data.Models; + +namespace Tzkt.Sync.Services.Cache +{ + public class TicketsCache + { + public const int MaxItems = 4 * 4096; //TODO: set limits in app settings + + static readonly Dictionary CachedById = new(MaxItems); + static readonly Dictionary<(int TicketerId, HashableBytes RawType, HashableBytes RawContent), Ticket> CachedByKey = new(MaxItems); + + readonly TzktContext Db; + + public TicketsCache(TzktContext db) + { + Db = db; + } + + public void Reset() + { + CachedById.Clear(); + CachedByKey.Clear(); + } + + public void Trim() + { + if (CachedById.Count > MaxItems * 0.9) + { + var toRemove = CachedById.Values + .OrderBy(x => x.LastLevel) + .Take(MaxItems / 2) + .ToList(); + + foreach (var item in toRemove) + Remove(item); + } + } + + public void Add(Ticket ticket) + { + CachedById[ticket.Id] = ticket; + CachedByKey[(ticket.TicketerId, ticket.RawType, ticket.RawContent)] = ticket; + } + + public void Remove(Ticket ticket) + { + CachedById.Remove(ticket.Id); + CachedByKey.Remove((ticket.TicketerId, ticket.RawType, ticket.RawContent)); + } + + public Ticket GetCached(long id) + { + if (!CachedById.TryGetValue(id, out var token)) + throw new Exception($"Ticket #{id} doesn't exist in the cache"); + return token; + } + + public bool TryGetCached(int ticketerId, byte[] rawType, byte[] rawContent, out Ticket token) + { + return CachedByKey.TryGetValue((ticketerId, rawType, rawContent), out token); + } + + public async Task Preload(IEnumerable ids) + { + var missed = ids.Where(x => !CachedById.ContainsKey(x)).ToHashSet(); + if (missed.Count > 0) + { + var items = await Db.Tickets + .Where(x => missed.Contains(x.Id)) + .ToListAsync(); + + foreach (var item in items) + Add(item); + } + } + + public async Task Preload(IEnumerable<(int, byte[], int, byte[], int)> keys) + { + var missed = keys.Where(x => !CachedByKey.ContainsKey((x.Item1, x.Item2, x.Item4))).ToHashSet(); + if (missed.Count > 0) + { + for (int i = 0, n = 2048; i < missed.Count; i += n) + { + var corteges = string.Join(',', missed.Skip(i).Take(n).Select(x => $"({x.Item1}, '{x.Item3}', '{x.Item5}')")); + var items = await Db.Tickets + .FromSqlRaw($@" + SELECT * FROM ""{nameof(TzktContext.Tickets)}"" + WHERE (""{nameof(Ticket.TicketerId)}"", ""{nameof(Ticket.TypeHash)}"", ""{nameof(Ticket.ContentHash)}"") IN ({corteges})") + .ToListAsync(); + + foreach (var item in items) + Add(item); + } + } + } + } +} diff --git a/Tzkt.Sync/Services/TezosNode/TezosNode.cs b/Tzkt.Sync/Services/TezosNode/TezosNode.cs index b0c10ec71..562097745 100644 --- a/Tzkt.Sync/Services/TezosNode/TezosNode.cs +++ b/Tzkt.Sync/Services/TezosNode/TezosNode.cs @@ -1,10 +1,5 @@ -using System; -using System.Threading.Tasks; +using System.Text.Json; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System.Text.Json; using Tzkt.Data; using Tzkt.Data.Models; @@ -39,6 +34,11 @@ public async Task GetAsync(string url) return doc.RootElement.Clone(); } + public async Task PostAsync(string url, string content) + { + return await Rpc.PostAsync(url, content); + } + public async Task
GetHeaderAsync() { if (DateTime.UtcNow >= NextBlock) diff --git a/Tzkt.Sync/Utils/HashableBytes.cs b/Tzkt.Sync/Utils/HashableBytes.cs new file mode 100644 index 000000000..9aae4096c --- /dev/null +++ b/Tzkt.Sync/Utils/HashableBytes.cs @@ -0,0 +1,11 @@ +namespace Tzkt.Sync +{ + class HashableBytes + { + readonly byte[] Bytes; + public HashableBytes(byte[] bytes) => Bytes = bytes; + public static implicit operator HashableBytes(byte[] array) => new(array); + public override bool Equals(object obj) => obj is HashableBytes hb && hb.Bytes.IsEqual(Bytes); + public override int GetHashCode() => Bytes.GetHashCodeExt(); + } +} diff --git a/Tzkt.Sync/Utils/JsonContent.cs b/Tzkt.Sync/Utils/JsonContent.cs new file mode 100644 index 000000000..4bfb19546 --- /dev/null +++ b/Tzkt.Sync/Utils/JsonContent.cs @@ -0,0 +1,12 @@ +using System.Net.Http.Headers; + +namespace Tzkt.Sync +{ + class JsonContent : StringContent + { + public JsonContent(string content) : base(content) + { + Headers.ContentType = new MediaTypeHeaderValue("application/json"); + } + } +} \ No newline at end of file diff --git a/Tzkt.Sync/Utils/TzktClient.cs b/Tzkt.Sync/Utils/TzktClient.cs index 007572e4f..23d1a6194 100644 --- a/Tzkt.Sync/Utils/TzktClient.cs +++ b/Tzkt.Sync/Utils/TzktClient.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; +using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -63,6 +64,14 @@ public async Task GetObjectAsync(string path) using var stream = await HttpClient.GetStreamAsync(path); return await JsonSerializer.DeserializeAsync(stream, SerializerOptions.Default); } + + public async Task PostAsync(string path, string content) + { + var response = await HttpClient.PostAsync(path, new JsonContent(content)); + + using var stream = await response.Content.ReadAsStreamAsync(); + return await JsonSerializer.DeserializeAsync(stream, SerializerOptions.Default); + } public void Dispose() => _HttpClient?.Dispose(); } diff --git a/docker-compose.ghost.yml b/docker-compose.ghost.yml index 4f506cac1..814cdcd42 100644 --- a/docker-compose.ghost.yml +++ b/docker-compose.ghost.yml @@ -34,6 +34,7 @@ services: ConnectionStrings__DefaultConnection: host=ghost-db;port=5432;database=${POSTGRES_DB:-tzkt_db};username=${POSTGRES_USER:-tzkt};password=${POSTGRES_PASSWORD:-qwerty};command timeout=${COMMAND_TIMEOUT:-600}; Kestrel__Endpoints__Http__Url: http://0.0.0.0:5001 TezosNode__Endpoint: https://rpc.tzkt.io/ghostnet/ + Protocols__Diagnostics: false depends_on: - ghost-db ports: diff --git a/docker-compose.mumbai.yml b/docker-compose.mumbai.yml index 7d4f3e46f..23e5435a1 100644 --- a/docker-compose.mumbai.yml +++ b/docker-compose.mumbai.yml @@ -10,7 +10,7 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-qwerty} POSTGRES_DB: ${POSTGRES_DB:-tzkt_db} volumes: - - postgres:/var/lib/postgresql/data + - mumbai-postgres:/var/lib/postgresql/data ports: - 127.0.0.1:5435:5432 @@ -40,4 +40,4 @@ services: - 0.0.0.0:5031:5001 volumes: - postgres: + mumbai-postgres: diff --git a/docker-compose.nairobi.yml b/docker-compose.nairobi.yml index 843981f1f..2b4d693c7 100644 --- a/docker-compose.nairobi.yml +++ b/docker-compose.nairobi.yml @@ -10,7 +10,7 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-qwerty} POSTGRES_DB: ${POSTGRES_DB:-tzkt_db} volumes: - - postgres:/var/lib/postgresql/data + - nairobi-postgres:/var/lib/postgresql/data ports: - 127.0.0.1:5434:5432 @@ -34,10 +34,11 @@ services: ConnectionStrings__DefaultConnection: host=nairobi-db;port=5432;database=${POSTGRES_DB:-tzkt_db};username=${POSTGRES_USER:-tzkt};password=${POSTGRES_PASSWORD:-qwerty};command timeout=${COMMAND_TIMEOUT:-600}; Kestrel__Endpoints__Http__Url: http://0.0.0.0:5001 TezosNode__Endpoint: https://rpc.tzkt.io/nairobinet/ + Protocols__Diagnostics: false depends_on: - nairobi-db ports: - 0.0.0.0:5021:5001 volumes: - postgres: + nairobi-postgres: