Skip to content

Commit

Permalink
Merge pull request baking-bad#17 from trilitech/palmer@functori@DAL-c…
Browse files Browse the repository at this point in the history
…ommitment-indexing

[DAL] Index DAL commitments
  • Loading branch information
spalmer25 authored Aug 9, 2024
2 parents e32576b + 3b7082f commit 8b3fe34
Show file tree
Hide file tree
Showing 16 changed files with 7,704 additions and 2 deletions.
126 changes: 126 additions & 0 deletions Tzkt.Api/Controllers/DalController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Tzkt.Api.Models;
using Tzkt.Api.Repositories;

namespace Tzkt.Api.Controllers
{
[ApiController]
[Route("v1/dal")]
public class DalController : ControllerBase
{
private readonly DalRepository Dal;

public DalController(DalRepository dal)
{
Dal = dal;
}

#region commiments
/// <summary>
/// Get DAL commitments count
/// </summary>
/// <remarks>
/// Returns total number of DAL commitments published.
/// </remarks>
/// <param name="hash">Filters by DAL commitment hash</param>
/// <param name="level">Filters by level</param>
/// <param name="slotIndex">Filters by slot-index</param>
/// <param name="publisher">Filters by DAL commitment publisher</param>
/// <returns></returns>
[HttpGet("commitments/count")]
public async Task<int> GetDalCommitmentsCount(
DalCommitmentHashParameter hash,
Int32Parameter level,
Int32Parameter slotIndex,
AccountParameter publisher)
{
return await Dal.GetCommitmentsCount(hash, level, slotIndex, publisher);
}

/// <summary>
/// Get DAL commitments information
/// </summary>
/// <remarks>
/// Returns information of the DAL commitments published.
/// </remarks>
/// <param name="hash">Filters by DAL commitment hash</param>
/// <param name="level">Filters by level</param>
/// <param name="slotIndex">Filters by slot-index</param>
/// <param name="publisher">Filters by DAL commitment publisher</param>
/// <param name="select">Specify comma-separated list of fields to include into response or leave it undefined to return full object. If you select single field, response will be an array of values in both `.fields` and `.values` modes.</param>
/// <param name="sort">Sorts DAL commitments by specified field. Supported fields: `level` (default), `slotIndex`.</param>
/// <param name="offset">Specifies which or how many items should be skipped</param>
/// <param name="limit">Maximum number of items to return</param>
/// <returns></returns>
[HttpGet("commitments")]
public async Task<ActionResult<IEnumerable<DalCommitment>>> GetDalCommitments(
DalCommitmentHashParameter hash,
Int32Parameter level,
Int32Parameter slotIndex,
AccountParameter publisher,
SelectParameter select,
SortParameter sort,
OffsetParameter offset,
[Range(0, 10000)] int limit = 100)
{
#region validate
if (sort != null && !sort.Validate("level", "slotIndex"))
return new BadRequest($"{nameof(sort)}", "Sorting by the specified field is not allowed.");
#endregion

if (select == null)
return Ok(await Dal.GetCommitments(hash, level, slotIndex, publisher, sort, offset, limit));

if (select.Values != null)
{
if (select.Values.Length == 1)
return Ok(await Dal.GetCommitments(hash, level, slotIndex, publisher, sort, offset, limit, select.Values[0]));
else
return Ok(await Dal.GetCommitments(hash, level, slotIndex, publisher, sort, offset, limit, select.Values));
}
else
{
if (select.Fields.Length == 1)
return Ok(await Dal.GetCommitments(hash, level, slotIndex, publisher, sort, offset, limit, select.Fields[0]));
else
{
return Ok(new SelectionResponse
{
Cols = select.Fields,
Rows = await Dal.GetCommitments(hash, level, slotIndex, publisher, sort, offset, limit, select.Fields)
});
}
}
}

/// <summary>
/// Get DAL commitments information by hash
/// </summary>
/// <remarks>
/// Returns information of the DAL commitments published with the specified hash.
/// </remarks>
/// <param name="hash">DAL commitment hash</param>
/// <param name="level">Filters by level</param>
/// <param name="slotIndex">Filters by slot-index</param>
/// <param name="publisher">Filters by DAL commitment publisher</param>
/// <param name="select">Specify comma-separated list of fields to include into response or leave it undefined to return full object. If you select single field, response will be an array of values in both `.fields` and `.values` modes.</param>
/// <param name="sort">Sorts DAL commitments by specified field. Supported fields: `level` (default), `slotIndex`.</param>
/// <param name="offset">Specifies which or how many items should be skipped</param>
/// <param name="limit">Maximum number of items to return</param>
/// <returns></returns>
[HttpGet("commitments/{hash}")]
public async Task<ActionResult<IEnumerable<DalCommitment>>> GetDalCommitmentsByHash(
[Required][DalCommitmentHash] string hash,
Int32Parameter level,
Int32Parameter slotIndex,
AccountParameter publisher,
SelectParameter select,
SortParameter sort,
OffsetParameter offset,
[Range(0, 10000)] int limit = 100)
=>
await GetDalCommitments(new DalCommitmentHashParameter { Eq = hash }, level, slotIndex, publisher, select, sort, offset, limit);
#endregion
}
}
61 changes: 61 additions & 0 deletions Tzkt.Api/Extensions/ModelBindingContextExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,67 @@ public static bool TryGetSrc1HashList(this ModelBindingContext bindingContext, s
return true;
}

public static bool TryGetDalCommitmentHash(this ModelBindingContext bindingContext, string name, ref bool hasValue, out string result)
{
result = null;
var valueObject = bindingContext.ValueProvider.GetValue(name);

if (valueObject != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(name, valueObject);
if (!string.IsNullOrEmpty(valueObject.FirstValue))
{
if (!Regex.IsMatch(valueObject.FirstValue, "^sh[0-9A-Za-z]{72}$"))
{
bindingContext.ModelState.TryAddModelError(name, "Invalid DAL commitment hash.");
return false;
}

hasValue = true;
result = valueObject.FirstValue;
}
}

return true;
}

public static bool TryGetDalCommitmentHashList(this ModelBindingContext bindingContext, string name, ref bool hasValue, out List<string> result)
{
result = null;
var valueObject = bindingContext.ValueProvider.GetValue(name);

if (valueObject != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(name, valueObject);
if (!string.IsNullOrEmpty(valueObject.FirstValue))
{
var rawValues = valueObject.FirstValue.Split(',', StringSplitOptions.RemoveEmptyEntries);

if (rawValues.Length == 0)
{
bindingContext.ModelState.TryAddModelError(name, "List should contain at least one item.");
return false;
}

hasValue = true;
result = new List<string>(rawValues.Length);

foreach (var rawValue in rawValues)
{
if (!Regex.IsMatch(rawValue, "^sh[0-9A-Za-z]{72}$"))
{
bindingContext.ModelState.TryAddModelError(name, "List contains invalid DAL commitment hash.");
return false;
}

result.Add(rawValue);
}
}
}

return true;
}

public static bool TryGetContractKind(this ModelBindingContext bindingContext, string name, ref bool hasValue, out int? result)
{
result = null;
Expand Down
25 changes: 25 additions & 0 deletions Tzkt.Api/Models/Dal/DalCommitment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Tzkt.Api.Models
{
public class DalCommitment
{
/// <summary>
/// Level at which the commitment has been published.
/// </summary>
public int Level { get; set; }

/// <summary>
/// Slot index associated with the commitment.
/// </summary>
public int SlotIndex { get; set; }

/// <summary>
/// Hash of the commitment.
/// </summary>
public string Hash { get; set; }

/// <summary>
/// Information about the account who has published the commitment.
/// </summary>
public Alias Publisher { get; set; }
}
}
44 changes: 44 additions & 0 deletions Tzkt.Api/Parameters/Binders/DalCommitmentHashBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Tzkt.Api
{
public class DalCommitmentHashBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var model = bindingContext.ModelName;
var hasValue = false;

if (!bindingContext.TryGetDalCommitmentHash($"{model}", ref hasValue, out var value))
return Task.CompletedTask;

if (!bindingContext.TryGetDalCommitmentHash($"{model}.eq", ref hasValue, out var eq))
return Task.CompletedTask;

if (!bindingContext.TryGetDalCommitmentHash($"{model}.ne", ref hasValue, out var ne))
return Task.CompletedTask;

if (!bindingContext.TryGetDalCommitmentHashList($"{model}.in", ref hasValue, out var @in))
return Task.CompletedTask;

if (!bindingContext.TryGetDalCommitmentHashList($"{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 DalCommitmentHashParameter
{
Eq = value ?? eq,
Ne = ne,
In = @in,
Ni = ni
});

return Task.CompletedTask;
}
}
}
74 changes: 74 additions & 0 deletions Tzkt.Api/Parameters/DalCommitmentHashParameter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Text;
using Microsoft.AspNetCore.Mvc;
using NJsonSchema.Annotations;

namespace Tzkt.Api
{
[ModelBinder(BinderType = typeof(DalCommitmentHashBinder))]
[JsonSchemaExtensionData("x-tzkt-extension", "query-parameter")]
public class DalCommitmentHashParameter : INormalizable
{
/// <summary>
/// **Equal** filter mode (`.eq` suffix can be omitted, i.e. `?param=...` is the same as `?param.eq=...`). \
/// Specify an DAL commitment hash to get items where the specified field is equal to the specified value.
///
/// Example: `?hash=sh...`.
/// </summary>
public string Eq { get; set; }

/// <summary>
/// **Not equal** filter mode. \
/// Specify an DAL commitment hash to get items where the specified field is not equal to the specified value.
///
/// Example: `?hash.ne=sh...`.
/// </summary>
public string Ne { get; set; }

/// <summary>
/// **In list** (any of) filter mode. \
/// Specify a comma-separated list of DAL commitment hashes to get items where the specified field is equal to one of the specified values.
///
/// Example: `?hash.in=hash1,hash2,hash3`.
/// </summary>
public List<string> In { get; set; }

/// <summary>
/// **Not in list** (none of) filter mode. \
/// Specify a comma-separated list of DAL commitment hashes to get items where the specified field is not equal to all the specified values.
///
/// Example: `?hash.ni=hash1,hash2,hash3`.
/// </summary>
public List<string> Ni { get; set; }

#region operators
public static implicit operator DalCommitmentHashParameter(string value) => new() { Eq = value };
#endregion

public string Normalize(string name)
{
var sb = new StringBuilder();

if (Eq != null)
{
sb.Append($"{name}.eq={Eq}&");
}

if (Ne != null)
{
sb.Append($"{name}.ne={Ne}&");
}

if (In?.Count > 0)
{
sb.Append($"{name}.in={string.Join(",", In.OrderBy(x => x))}&");
}

if (Ni?.Count > 0)
{
sb.Append($"{name}.ni={string.Join(",", Ni.OrderBy(x => x))}&");
}

return sb.ToString();
}
}
}
1 change: 1 addition & 0 deletions Tzkt.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
builder.Services.AddTransient<DomainsRepository>();
builder.Services.AddTransient<SmartRollupsRepository>();
builder.Services.AddTransient<StakingRepository>();
builder.Services.AddTransient<DalRepository>();

builder.Services.AddAuthService(builder.Configuration);
builder.Services.AddSingleton<RpcHelpers>();
Expand Down
Loading

0 comments on commit 8b3fe34

Please sign in to comment.