Skip to content

Commit

Permalink
feat!: use new conway certs in stake and delegation scenarios
Browse files Browse the repository at this point in the history
BREAKING CHANGE: typo stakeKeyCertficates renamed to stakeKeyCertificates
  • Loading branch information
iccicci authored and mirceahasegan committed Jan 30, 2024
1 parent 94bdbd0 commit 3a59317
Show file tree
Hide file tree
Showing 15 changed files with 531 additions and 296 deletions.
49 changes: 49 additions & 0 deletions packages/core/src/Cardano/types/Certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,46 @@ export type Certificate =
| UnRegisterDelegateRepresentativeCertificate
| UpdateDelegateRepresentativeCertificate;

export const StakeRegistrationCertificateTypes = [
CertificateType.StakeRegistration,
CertificateType.Registration,
CertificateType.VoteRegistrationDelegation,
CertificateType.StakeRegistrationDelegation,
CertificateType.StakeVoteRegistrationDelegation
] as const;

export type StakeRegistrationCertificateTypes = typeof StakeRegistrationCertificateTypes[number];

export type StakeDelegationCertificateUnion =
| StakeDelegationCertificate
| StakeVoteDelegationCertificate
| StakeRegistrationDelegationCertificate
| StakeVoteRegistrationDelegationCertificate;

export const StakeDelegationCertificateTypes = [
CertificateType.StakeDelegation,
CertificateType.StakeVoteDelegation,
CertificateType.StakeRegistrationDelegation,
CertificateType.StakeVoteRegistrationDelegation
] as const;

export type StakeDelegationCertificateTypes = typeof StakeDelegationCertificateTypes[number];

export type RegAndDeregCertificateUnion =
| StakeAddressCertificate
| NewStakeAddressCertificate
| VoteRegistrationDelegationCertificate
| StakeRegistrationDelegationCertificate
| StakeVoteRegistrationDelegationCertificate;

export const RegAndDeregCertificateTypes = [
...StakeRegistrationCertificateTypes,
CertificateType.Unregistration,
CertificateType.StakeDeregistration
] as const;

export type RegAndDeregCertificateTypes = typeof RegAndDeregCertificateTypes[number];

/**
* Creates a stake key registration certificate from a given reward account.
*
Expand Down Expand Up @@ -215,3 +255,12 @@ export const createDelegationCert = (rewardAccount: RewardAccount, poolId: PoolI
type: CredentialType.KeyHash
}
});

/** Filters certificates, returning only stake key register/deregister certificates */
export const stakeKeyCertificates = (certificates?: Certificate[]) =>
certificates?.filter((certificate): certificate is RegAndDeregCertificateUnion =>
RegAndDeregCertificateTypes.includes(certificate.__typename as RegAndDeregCertificateTypes)
) || [];

export const includesAnyCertificate = (haystack: Certificate[], needle: readonly CertificateType[]) =>
haystack.some(({ __typename }) => needle.includes(__typename)) || false;
2 changes: 1 addition & 1 deletion packages/core/src/util/txInspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export const totalAddressOutputsValueInspector: SendReceiveValueInspector = (own
export const getCertificatesByType = (
tx: Tx,
rewardAccounts: RewardAccount[],
certificateTypes?: CertificateType[]
certificateTypes?: readonly CertificateType[]
) => {
if (!tx.body.certificates || tx.body.certificates.length === 0) return [];
const certificates = certificateTypes
Expand Down
20 changes: 19 additions & 1 deletion packages/core/test/Cardano/types/Certificates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
RewardAccount,
createDelegationCert,
createStakeDeregistrationCert,
createStakeRegistrationCert
createStakeRegistrationCert,
stakeKeyCertificates
} from '../../../src/Cardano';

const rewardAccount = RewardAccount('stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr');
Expand Down Expand Up @@ -47,4 +48,21 @@ describe('Certificate', () => {
});
});
});

it('can identify stake key certificates', () => {
const certificates = stakeKeyCertificates([
{ __typename: Cardano.CertificateType.StakeDelegation } as Cardano.Certificate, // does not register stake key
{ __typename: Cardano.CertificateType.StakeRegistration } as Cardano.Certificate,
{ __typename: Cardano.CertificateType.StakeDeregistration } as Cardano.Certificate,
{ __typename: Cardano.CertificateType.StakeVoteDelegation } as Cardano.Certificate, // does not register stake key
{ __typename: Cardano.CertificateType.StakeRegistrationDelegation } as Cardano.Certificate,
{ __typename: Cardano.CertificateType.StakeVoteDelegation } as Cardano.Certificate, // does not register stake key
{ __typename: Cardano.CertificateType.StakeVoteRegistrationDelegation } as Cardano.Certificate,
{ __typename: Cardano.CertificateType.VoteRegistrationDelegation } as Cardano.Certificate,
{ __typename: Cardano.CertificateType.Registration } as Cardano.Certificate
]);
expect(certificates).toHaveLength(6);
expect(certificates[0].__typename).toBe(Cardano.CertificateType.StakeRegistration);
expect(certificates[1].__typename).toBe(Cardano.CertificateType.StakeDeregistration);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ export const withStakeKeyRegistrations = unifiedProjectorOperator<WithCertificat
...evt,
stakeKeyRegistrations: evt.certificates
.map(({ pointer, certificate }): StakeKeyRegistration | null => {
if (certificate.__typename === Cardano.CertificateType.StakeRegistration) {
if (
Cardano.StakeRegistrationCertificateTypes.includes(
certificate.__typename as Cardano.StakeRegistrationCertificateTypes
)
) {
return {
pointer,
stakeKeyHash: certificate.stakeCredential.hash as unknown as Ed25519KeyHashHex
stakeKeyHash: (certificate as Cardano.RegAndDeregCertificateUnion).stakeCredential
.hash as unknown as Ed25519KeyHashHex
};
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,34 @@ export interface WithStakeKeys {
* The intended use case of this operator is to keep track of the current set of active stake keys,
* ignoring **when** they were registered or unregistered.
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
export const withStakeKeys = unifiedProjectorOperator<WithCertificates, WithStakeKeys>((evt) => {
const register = new Set<Crypto.Hash28ByteBase16>();
const deregister = new Set<Crypto.Hash28ByteBase16>();
for (const { certificate } of evt.certificates) {
switch (certificate.__typename) {
case Cardano.CertificateType.StakeRegistration:
if (deregister.has(certificate.stakeCredential.hash)) {
deregister.delete(certificate.stakeCredential.hash);
} else {
register.add(certificate.stakeCredential.hash);
}
break;
case Cardano.CertificateType.StakeDeregistration:
if (register.has(certificate.stakeCredential.hash)) {
register.delete(certificate.stakeCredential.hash);
} else {
deregister.add(certificate.stakeCredential.hash);
}
break;
if (Cardano.RegAndDeregCertificateTypes.includes(certificate.__typename as Cardano.RegAndDeregCertificateTypes)) {
const {
stakeCredential: { hash: stakeCredentialHash }
} = certificate as Cardano.RegAndDeregCertificateUnion;

switch (certificate.__typename) {
case Cardano.CertificateType.StakeDeregistration:
case Cardano.CertificateType.Unregistration:
if (register.has(certificate.stakeCredential.hash)) {
register.delete(certificate.stakeCredential.hash);
} else {
deregister.add(certificate.stakeCredential.hash);
}
break;
default:
// Stake registration
if (deregister.has(stakeCredentialHash)) {
deregister.delete(stakeCredentialHash);
} else {
register.add(stakeCredentialHash);
}
break;
}
}
}
const [insert, del] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { firstValueFrom, of } from 'rxjs';
type EventData = Mappers.WithCertificates & { eventType: ChainSyncEventType };

describe('withStakeKeyRegistrations', () => {
it('collects all key registration certificates', async () => {
it.each(Cardano.StakeRegistrationCertificateTypes)('collects %s registration certificates', async (regCertType) => {
const pointer: Cardano.Pointer = {
certIndex: Cardano.CertIndex(1),
slot: Cardano.Slot(123),
Expand All @@ -16,12 +16,12 @@ describe('withStakeKeyRegistrations', () => {
certificates: [
{
certificate: {
__typename: Cardano.CertificateType.StakeRegistration,
__typename: regCertType,
stakeCredential: {
hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b'),
type: Cardano.CredentialType.KeyHash
}
},
} as Cardano.Certificate,
pointer
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,42 @@ type EventData = Mappers.WithCertificates & { eventType: ChainSyncEventType };

describe('withStakeKeys', () => {
describe('1 certificate per stake key', () => {
it('collects all key registration and deregistration certificates', async () => {
const data: EventData = {
certificates: [
{
certificate: {
__typename: Cardano.CertificateType.StakeRegistration,
stakeCredential: {
hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b'),
type: Cardano.CredentialType.KeyHash
}
},
pointer: {} as Cardano.Pointer
},
{
certificate: {
__typename: Cardano.CertificateType.StakeDeregistration,
stakeCredential: {
hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857c'),
type: Cardano.CredentialType.KeyHash
}
it.each(Cardano.StakeRegistrationCertificateTypes)(
'collects all key registration [%s] and deregistration certificates',
async (regCertType) => {
const data: EventData = {
certificates: [
{
certificate: {
__typename: regCertType,
stakeCredential: {
hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b'),
type: Cardano.CredentialType.KeyHash
}
} as Cardano.Certificate,
pointer: {} as Cardano.Pointer
},
pointer: {} as Cardano.Pointer
}
],
eventType: ChainSyncEventType.RollForward
};
{
certificate: {
__typename: Cardano.CertificateType.StakeDeregistration,
stakeCredential: {
hash: Crypto.Hash28ByteBase16('3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857c'),
type: Cardano.CredentialType.KeyHash
}
},
pointer: {} as Cardano.Pointer
}
],
eventType: ChainSyncEventType.RollForward
};

const result = await firstValueFrom(
Mappers.withStakeKeys()(of(data as UnifiedExtChainSyncEvent<Mappers.WithCertificates & WithBlock>))
);
expect(result.stakeKeys.insert).toEqual(['3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b']);
expect(result.stakeKeys.del).toEqual(['3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857c']);
});
const result = await firstValueFrom(
Mappers.withStakeKeys()(of(data as UnifiedExtChainSyncEvent<Mappers.WithCertificates & WithBlock>))
);
expect(result.stakeKeys.insert).toEqual(['3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857b']);
expect(result.stakeKeys.del).toEqual(['3b62970858d61cf667701c1f34abef41659516b191d7d374e8b0857c']);
}
);

it('reverses the logic on RollBackward', async () => {
const data: EventData = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,13 @@ export const certificateTransactionsWithEpochs = (
)
);

const hasDelegationCert = (certificates: Array<Cardano.Certificate> | undefined): boolean => {
if (!certificates || certificates.length === 0) return false;

return certificates.some((cert) => {
let hasCert = false;

switch (cert.__typename) {
case Cardano.CertificateType.StakeDelegation:
case Cardano.CertificateType.StakeRegistration:
case Cardano.CertificateType.StakeDeregistration:
hasCert = true;
break;
default:
hasCert = false;
}

return hasCert;
});
};
const hasDelegationCert = (certificates: Array<Cardano.Certificate> | undefined): boolean =>
!!certificates &&
certificates.some((cert) =>
[...Cardano.RegAndDeregCertificateTypes, ...Cardano.StakeDelegationCertificateTypes].includes(
cert.__typename as Cardano.RegAndDeregCertificateTypes | Cardano.StakeDelegationCertificateTypes
)
);

export const createDelegationPortfolioTracker = (transactions: Observable<Cardano.HydratedTx[]>) =>
transactions.pipe(
Expand Down Expand Up @@ -153,11 +141,7 @@ export const createDelegationTracker = ({
transactionsTracker,
rewardAccountAddresses$,
slotEpochCalc$,
[
Cardano.CertificateType.StakeDelegation,
Cardano.CertificateType.StakeRegistration,
Cardano.CertificateType.StakeDeregistration
]
[...Cardano.RegAndDeregCertificateTypes, ...Cardano.StakeDelegationCertificateTypes]
).pipe(tap((transactionsWithEpochs) => logger.debug(`Found ${transactionsWithEpochs.length} staking transactions`)));

const rewardsHistory$ = new TrackerSubject(
Expand Down
Loading

0 comments on commit 3a59317

Please sign in to comment.