Skip to content

Commit

Permalink
fix: correct the remaining owner address query
Browse files Browse the repository at this point in the history
fix the storeHandles tests to not clear the database
  • Loading branch information
mkazlauskas committed May 26, 2023
1 parent ee8890c commit 0ec0461
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 37 deletions.
4 changes: 2 additions & 2 deletions packages/projection-typeorm/src/entity/Handle.entity.ts
@@ -1,7 +1,7 @@
import { AssetEntity } from './Asset.entity';
import { BlockEntity } from './Block.entity';
import { Cardano } from '@cardano-sdk/core';
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryColumn } from 'typeorm';

@Entity()
export class HandleEntity {
Expand All @@ -16,7 +16,7 @@ export class HandleEntity {
policyId?: Cardano.PolicyId;
@Column()
hasDatum?: boolean;
@OneToOne(() => BlockEntity, { onDelete: 'CASCADE' })
@ManyToOne(() => BlockEntity, { onDelete: 'CASCADE' })
@JoinColumn()
resolvedAt?: BlockEntity;
}
7 changes: 4 additions & 3 deletions packages/projection-typeorm/src/operators/storeHandles.ts
Expand Up @@ -52,10 +52,11 @@ const rollBackward = async ({ handles, queryRunner }: HandleEventParams) => {

for (const { assetId, handle } of handles) {
if (existingHandlesSet.has(handle)) {
const ownerAddress = await queryRunner.query(`
SELECT o.address FROM tokens t JOIN output o ON o.id = t.output_id WHERE o.consumed_at_slot IS NULL AND t.asset_id = ${assetId}
const ownerAddresses = await queryRunner.query(`
SELECT o.address FROM tokens t JOIN output o ON o.id = t.output_id WHERE o.consumed_at_slot IS NULL AND t.asset_id = '${assetId}'
`);
await handleRepository.update({ handle }, { address: ownerAddress[0].address });
// TODO: if there are multiple distinct addresses, then it's an invalid handle and should be set to null
await handleRepository.update({ handle }, { cardanoAddress: ownerAddresses[0].address });
} else {
await handleRepository.delete({ handle });
}
Expand Down
41 changes: 29 additions & 12 deletions packages/projection-typeorm/test/operators/storeHandle.test.ts
Expand Up @@ -16,8 +16,8 @@ import {
import { Bootstrap, Mappers, ProjectionEvent, requestNext } from '@cardano-sdk/projection';
import { Cardano, ChainSyncEventType } from '@cardano-sdk/core';
import { ChainSyncDataSet, chainSyncData, logger } from '@cardano-sdk/util-dev';
import { Observable, defer, from } from 'rxjs';
import { QueryRunner } from 'typeorm';
import { DataSource, QueryRunner } from 'typeorm';
import { Observable, of } from 'rxjs';
import { createProjectorTilFirst } from './util';
import { initializeDataSource } from '../util';

Expand All @@ -28,15 +28,13 @@ describe('storeHandle', () => {
const stubEvents = chainSyncData(ChainSyncDataSet.WithHandle);
const policyIds = [Cardano.PolicyId('f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a')];
let queryRunner: QueryRunner;
let dataSource$: Observable<DataSource>;
let buffer: TypeormStabilityWindowBuffer;
const entities = [BlockEntity, BlockDataEntity, AssetEntity, TokensEntity, OutputEntity, HandleEntity];

const storeData = (evt$: Observable<ProjectionEvent<Mappers.WithUtxo & Mappers.WithMint & Mappers.WithHandles>>) =>
evt$.pipe(
withTypeormTransaction({
dataSource$: defer(() => from(initializeDataSource({ entities }))),
logger
}),
withTypeormTransaction({ dataSource$, logger }),
storeBlock(),
storeAssets(),
storeUtxo(),
Expand Down Expand Up @@ -64,6 +62,7 @@ describe('storeHandle', () => {

beforeEach(async () => {
const dataSource = await initializeDataSource({ entities });
dataSource$ = of(dataSource);
queryRunner = dataSource.createQueryRunner();
buffer = new TypeormStabilityWindowBuffer({ allowNonSequentialBlockHeights: true, logger });
await buffer.initialize(queryRunner);
Expand Down Expand Up @@ -92,23 +91,32 @@ describe('storeHandle', () => {
it('deletes handle on rollback', async () => {
const handleRepository = queryRunner.manager.getRepository(HandleEntity);
const initialCount = await handleRepository.count();
expect(initialCount).toEqual(0);
const mintEvent = await projectTilFirst(({ handles }) => handles.length > 0);
const mintEvent = await projectTilFirst(
({ handles, eventType }) => eventType === ChainSyncEventType.RollForward && handles.length > 0
);
expect(await handleRepository.count()).toEqual(initialCount + mintEvent.handles.length);
await projectTilFirst(({ eventType }) => eventType === ChainSyncEventType.RollBackward);
await projectTilFirst(
({
eventType,
block: {
header: { hash }
}
}) => eventType === ChainSyncEventType.RollBackward && hash === mintEvent.block.header.hash
);
expect(await handleRepository.count()).toEqual(initialCount);
});

it('minting an existing handle sets address to null', async () => {
const repository = queryRunner.manager.getRepository(HandleEntity);
const mintEvent = await projectTilFirst(
const mintEvent1 = await projectTilFirst(
({ handles, eventType }) => eventType === ChainSyncEventType.RollForward && handles[0]?.handle === 'bob'
);
expect(mintEvent.handles.length).toBe(1);
expect(mintEvent1.handles.length).toBe(1);

await projectTilFirst(
const mintEvent2 = await projectTilFirst(
({ handles, eventType }) => eventType === ChainSyncEventType.RollForward && handles[0]?.handle === 'bob'
);
expect(mintEvent2.handles.length).toBe(1);

expect(
await repository.findOne({ select: { cardanoAddress: true, handle: true }, where: { handle: 'bob' } })
Expand Down Expand Up @@ -138,12 +146,21 @@ describe('storeHandle', () => {
});
});

// blockNo: 1 2 3 4 5 6 7
// mint at block 1 send to addr1
// mint at block 2 send to addr2 - expect Handle.address to be null, because there are 2 handles
// burn one of the handles at block 3 - expect the remaining handle to be valid.
// The input of the burning tx must match the tx id of the mint transaction,
// and index == the index of the output that sent the handle to the address
it('burning a handle with supply >1 sets address to the 1 remaining owner', async () => {
const mintEvent1 = await projectTilFirst(
({ eventType, mint }) => eventType === ChainSyncEventType.RollForward && hasLessThanZeroQuantity(mint)
);

expect(mintEvent1.handles.length).toBeGreaterThan(0);
});

// quite simialr to the previvous test, except that instead of burning, you roll back the 2nd mint transaction
// so that it never existed and only 1 transaction that minted the handle is valid
it.todo('rolling back a transaction that burned a handle with supply >1 sets address to null');
});
124 changes: 104 additions & 20 deletions packages/util-dev/src/chainSync/data/with-handle.json
Expand Up @@ -755,13 +755,6 @@
"__type": "bigint",
"value": "1"
}
],
[
"f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a626f62",
{
"__type": "bigint",
"value": "-1"
}
]
]
},
Expand Down Expand Up @@ -888,7 +881,106 @@
},
{
"block": {
"body": [],
"body": [
{
"body": {
"certificates": [],
"collaterals": [],
"fee": {
"__type": "bigint",
"value": "192517"
},
"inputs": [
{
"index": 1,
"txId": "04e4c197613f7b752f17cc69429faa5d7ceffb3e9299a7a4891af2b85b7f87c4"
}
],
"mint": {
"__type": "Map",
"value": [
[
"f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a626f62",
{
"__type": "bigint",
"value": "1"
}
]
]
},
"outputs": [
{
"address": "addr_test1qph450j2em0kk62p7278qhs38yh722kgup3nj5eyk95ndcjstf9aru532d0m7a52dek7txeekxxr69vdcs75elqgwu0syrcdqg",
"datum": {
"__type": "undefined"
},
"datumHash": {
"__type": "undefined"
},
"scriptReference": {
"__type": "undefined"
},
"value": {
"assets": {
"__type": "Map",
"value": [
[
"f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a626f62",
{
"__type": "bigint",
"value": "1"
}
]
]
},
"coins": {
"__type": "bigint",
"value": "1444443"
}
}
}
],
"requiredExtraSignatures": [],
"scriptIntegrityHash": {
"__type": "undefined"
},
"validityInterval": {
"invalidBefore": {
"__type": "undefined"
},
"invalidHereafter": 13645686
},
"withdrawals": []
},
"id": "497f52b3e58e5c007f0d6914e1af64b4fc2c3266d444896520d998005d26642b",
"inputSource": "inputs",
"witness": {
"bootstrap": [],
"datums": [],
"redeemers": [],
"scripts": [
{
"__type": "native",
"keyHash": "4da965a049dfd15ed1ee19fba6e2974a0b79fc416dd1796a1f97f5e1",
"kind": 0
}
],
"signatures": {
"__type": "Map",
"value": [
[
"8cb46c3cd83bad9a5d5c368152da69ec630c5c0cdae2bccbc933635f10a95978",
"130e1cdb0506f3e1d6e1e85d22abd525dce5128028d1c306dfb2ceec94db16ef550dc1f9be61918d5d40182ce7b9a5faa7a683425f90218e3820e8f5d476610c"
],
[
"b62a7193927f47ef703ba99a06add1ee2d4d94853a16dcd68ad0f5d9baa18530",
"59842fe9d6ddaeb4fd444f981d8d4c01b8003a1e92472d2cbccbca7b24ec19ab7c36e32ec1d0376ed6a499667c7b04ba13a92805aecd84fbc04812b12ce7c702"
]
]
}
}
}
],
"fees": {
"__type": "bigint",
"value": "0"
Expand Down Expand Up @@ -1370,15 +1462,7 @@
"value": {
"assets": {
"__type": "Map",
"value": [
[
"f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a626f62",
{
"__type": "bigint",
"value": "1"
}
]
]
"value": []
},
"coins": {
"__type": "bigint",
Expand Down Expand Up @@ -1421,7 +1505,7 @@
},
"withdrawals": []
},
"id": "297f52b3e58e5c007f0d6914e1af64b4fc2c3266d444896520d998005d26642b",
"id": "497f52b3e58e5c007f0d6914e1af64b4fc2c3266d444896520d998005d26642b",
"inputSource": "inputs",
"witness": {
"bootstrap": [],
Expand Down Expand Up @@ -1479,12 +1563,12 @@
{
"eventType": 1,
"point": {
"hash": "6625b89401da3c51749f89268b65fda8e416ad5c8af2d81b61f1eac0cf7eb8e5",
"hash": "61ce97ccf16eab2db6acf809c2d36ba153b771c5cc616adb80db8ca14aa74061",
"slot": 13633686
},
"tip": {
"blockNo": 317873,
"hash": "6625b89401da3c51749f89268b65fda8e416ad5c8af2d81b61f1eac0cf7eb8e5",
"hash": "61ce97ccf16eab2db6acf809c2d36ba153b771c5cc616adb80db8ca14aa74061",
"slot": 13633686
}
}
Expand Down

0 comments on commit 0ec0461

Please sign in to comment.