diff --git a/src/ProjectOrigin.Chronicler.Server/BlockReader/BlockReaderJob.cs b/src/ProjectOrigin.Chronicler.Server/BlockReader/BlockReaderJob.cs
index a301b86..f255d0d 100644
--- a/src/ProjectOrigin.Chronicler.Server/BlockReader/BlockReaderJob.cs
+++ b/src/ProjectOrigin.Chronicler.Server/BlockReader/BlockReaderJob.cs
@@ -89,6 +89,10 @@ private async Task ProcessBlock(IUnitOfWork unitOfWork, string registryName, Reg
{
await ProcessClaimedEvent(repository, transaction);
}
+ else if (transaction.Header.PayloadType == Electricity.V1.WithdrawnEvent.Descriptor.FullName)
+ {
+ await ProcessWithdrawnEvent(repository, transaction);
+ }
}
await repository.UpsertReadBlock(new LastReadBlock
@@ -167,6 +171,7 @@ await repository.InsertClaimRecord(new ClaimRecord
CertificateId = fid.StreamId,
Quantity = claimIntent.Quantity,
RandomR = claimIntent.RandomR,
+ State = ClaimRecordState.Claimed
});
await repository.DeleteClaimIntent(claimIntent.Id);
await repository.DeleteClaimAllocation(allocation.Id);
@@ -176,4 +181,11 @@ await repository.InsertClaimRecord(new ClaimRecord
_logger.LogTrace("Claim for certificate {registry}-{certificateId} not relevant", fid.RegistryName, fid.StreamId);
}
}
+
+ private async Task ProcessWithdrawnEvent(IChroniclerRepository repository, Registry.V1.Transaction transaction)
+ {
+ var fid = transaction.Header.FederatedStreamId.ToModel();
+
+ await repository.WithdrawClaimRecord(fid);
+ }
}
diff --git a/src/ProjectOrigin.Chronicler.Server/DatabaseScripts/2.sql b/src/ProjectOrigin.Chronicler.Server/DatabaseScripts/2.sql
new file mode 100644
index 0000000..d403f8e
--- /dev/null
+++ b/src/ProjectOrigin.Chronicler.Server/DatabaseScripts/2.sql
@@ -0,0 +1 @@
+ALTER TABLE claim_records ADD COLUMN state int NOT NULL DEFAULT 0;
diff --git a/src/ProjectOrigin.Chronicler.Server/Models/ClaimRecord.cs b/src/ProjectOrigin.Chronicler.Server/Models/ClaimRecord.cs
index 643495b..fbb1bdc 100644
--- a/src/ProjectOrigin.Chronicler.Server/Models/ClaimRecord.cs
+++ b/src/ProjectOrigin.Chronicler.Server/Models/ClaimRecord.cs
@@ -9,4 +9,11 @@ public record ClaimRecord
public required Guid CertificateId { get; init; }
public required long Quantity { get; init; }
public required byte[] RandomR { get; init; }
+ public required ClaimRecordState State { get; init; }
+}
+
+public enum ClaimRecordState
+{
+ Claimed = 0,
+ Withdrawn = 3
}
diff --git a/src/ProjectOrigin.Chronicler.Server/ProjectOrigin.Chronicler.Server.csproj b/src/ProjectOrigin.Chronicler.Server/ProjectOrigin.Chronicler.Server.csproj
index 25f9c0d..9c6cbdf 100644
--- a/src/ProjectOrigin.Chronicler.Server/ProjectOrigin.Chronicler.Server.csproj
+++ b/src/ProjectOrigin.Chronicler.Server/ProjectOrigin.Chronicler.Server.csproj
@@ -29,13 +29,13 @@
- https://raw.githubusercontent.com/project-origin/registry/v1.3.0/src/Protos/registry.proto
+ https://raw.githubusercontent.com/project-origin/registry/v2.0.0/protos/registry.proto
- https://raw.githubusercontent.com/project-origin/registry/v1.1.0/src/Protos/electricity.proto
+ https://raw.githubusercontent.com/project-origin/verifier_electricity/v1.2.0/protos/electricity.proto
- https://raw.githubusercontent.com/project-origin/registry/v1.3.0/src/Protos/common.proto
+ https://raw.githubusercontent.com/project-origin/registry/v2.0.0/protos/common.proto
diff --git a/src/ProjectOrigin.Chronicler.Server/Properties/launchSettings.json b/src/ProjectOrigin.Chronicler.Server/Properties/launchSettings.json
new file mode 100644
index 0000000..6bba300
--- /dev/null
+++ b/src/ProjectOrigin.Chronicler.Server/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "ProjectOrigin.Chronicler.Server": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:50753;http://localhost:50754"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProjectOrigin.Chronicler.Server/Repositories/ChroniclerRepository.cs b/src/ProjectOrigin.Chronicler.Server/Repositories/ChroniclerRepository.cs
index cc8cc5c..d188b6b 100644
--- a/src/ProjectOrigin.Chronicler.Server/Repositories/ChroniclerRepository.cs
+++ b/src/ProjectOrigin.Chronicler.Server/Repositories/ChroniclerRepository.cs
@@ -145,4 +145,22 @@ public Task InsertClaimRecord(ClaimRecord record)
VALUES (@id, @registryName, @certificateId, @quantity, @randomR)",
record);
}
+
+ public async Task WithdrawClaimRecord(FederatedCertificateId fid)
+ {
+ var rowsChanged = await Connection.ExecuteAsync(
+ @"UPDATE claim_records
+ SET state = @state
+ WHERE registry_name = @registryName
+ AND certificate_id = @certificateId",
+ new
+ {
+ registryName = fid.RegistryName,
+ certificateId = fid.StreamId,
+ state = ClaimRecordState.Withdrawn
+ });
+
+ if (rowsChanged != 1)
+ throw new InvalidOperationException($"ClaimRecord with registry {fid.RegistryName} and certificateId {fid.StreamId} not found");
+ }
}
diff --git a/src/ProjectOrigin.Chronicler.Server/Repositories/IChroniclerRepository.cs b/src/ProjectOrigin.Chronicler.Server/Repositories/IChroniclerRepository.cs
index e8f3ee1..922f5e6 100644
--- a/src/ProjectOrigin.Chronicler.Server/Repositories/IChroniclerRepository.cs
+++ b/src/ProjectOrigin.Chronicler.Server/Repositories/IChroniclerRepository.cs
@@ -23,5 +23,6 @@ public interface IChroniclerRepository
Task DeleteClaimAllocation(Guid id);
Task InsertClaimRecord(ClaimRecord record);
+ Task WithdrawClaimRecord(FederatedCertificateId fid);
}
diff --git a/src/ProjectOrigin.Chronicler.Test/BlockReaderJobTests.cs b/src/ProjectOrigin.Chronicler.Test/BlockReaderJobTests.cs
index 09c131d..a29c06a 100644
--- a/src/ProjectOrigin.Chronicler.Test/BlockReaderJobTests.cs
+++ b/src/ProjectOrigin.Chronicler.Test/BlockReaderJobTests.cs
@@ -268,6 +268,27 @@ public async Task Verify_Claimed_NotFound_NotInserted()
_repository.VerifyNoOtherCalls();
}
+ [Fact]
+ public async Task ProcessWithdrawnEvent_WithdrawsClaim()
+ {
+ // Arrange
+ var fixture = new Fixture();
+ var certificateId = new FederatedCertificateId { RegistryName = RegistryName, StreamId = fixture.Create() };
+ var block = new Registry.V1.Block
+ {
+ Height = 1,
+ };
+ block.AddWithdrawn(certificateId);
+ _registryService.Setup(x => x.GetNextBlock(RegistryName, 0)).ReturnsAsync(block);
+
+ // Act
+ await _job.ProcessRegistryBlocks(RegistryName, 0, default);
+
+ // Assert
+ _repository.Verify(x => x.UpsertReadBlock(It.Is(x => x.BlockHeight == 1)), Times.Once);
+ _repository.Verify(x => x.WithdrawClaimRecord(certificateId), Times.Once);
+ _repository.VerifyNoOtherCalls();
+ }
[Fact]
public async Task Verify_Claimed_Found_Inserted()
@@ -454,5 +475,18 @@ public static void AddClaim(this Registry.V1.Block block, Guid allocationId, Fed
}.ToByteString()
});
}
+
+ public static void AddWithdrawn(this Registry.V1.Block block, FederatedCertificateId id)
+ {
+ block.Transactions.Add(new Registry.V1.Transaction
+ {
+ Header = new Registry.V1.TransactionHeader
+ {
+ PayloadType = Electricity.V1.WithdrawnEvent.Descriptor.FullName,
+ FederatedStreamId = id.ToProto(),
+ },
+ Payload = new Electricity.V1.WithdrawnEvent().ToByteString()
+ });
+ }
}
diff --git a/src/ProjectOrigin.Chronicler.Test/ChroniclerRepositoryTests.cs b/src/ProjectOrigin.Chronicler.Test/ChroniclerRepositoryTests.cs
index caeaec1..1cedd45 100644
--- a/src/ProjectOrigin.Chronicler.Test/ChroniclerRepositoryTests.cs
+++ b/src/ProjectOrigin.Chronicler.Test/ChroniclerRepositoryTests.cs
@@ -376,4 +376,46 @@ public async Task InsertClaimRecord_InsertsClaimRecord()
// Assert
_con.QuerySingle("SELECT * FROM claim_records").Should().BeEquivalentTo(record);
}
+
+ [Fact]
+ public async Task WithdrawClaimRecord_WithdrawsClaimRecord()
+ {
+ var record1 = new ClaimRecord()
+ {
+ RegistryName = _fixture.Create(),
+ State = ClaimRecordState.Claimed,
+ CertificateId = Guid.NewGuid(),
+ Id = Guid.NewGuid(),
+ Quantity = 123,
+ RandomR = _fixture.Create()
+ };
+ var record2 = new ClaimRecord()
+ {
+ RegistryName = _fixture.Create(),
+ State = ClaimRecordState.Claimed,
+ CertificateId = Guid.NewGuid(),
+ Id = Guid.NewGuid(),
+ Quantity = 124,
+ RandomR = _fixture.Create()
+ };
+ await _repository.InsertClaimRecord(record1);
+ await _repository.InsertClaimRecord(record2);
+
+ await _repository.WithdrawClaimRecord(new FederatedCertificateId
+ {
+ RegistryName = record1.RegistryName,
+ StreamId = record1.CertificateId
+ });
+
+ var withdrawnRecord = await _con.QuerySingleAsync(@"SELECT * FROM claim_records
+ WHERE registry_name = @registryName
+ AND certificate_id = @certificateId",
+ new
+ {
+ registryName = record1.RegistryName,
+ certificateId = record1.CertificateId
+ });
+
+ withdrawnRecord.State.Should().Be(ClaimRecordState.Withdrawn);
+ }
}
diff --git a/src/Protos/electricity.proto b/src/Protos/electricity.proto
index c276554..e102336 100644
--- a/src/Protos/electricity.proto
+++ b/src/Protos/electricity.proto
@@ -48,6 +48,14 @@ message ClaimedEvent {
project_origin.common.v1.Uuid allocation_id = 2;
}
+message WithdrawnEvent {
+
+}
+
+message UnclaimedEvent {
+ project_origin.common.v1.Uuid allocation_id = 1;
+}
+
enum GranularCertificateType {
INVALID = 0;
CONSUMPTION = 1;