Skip to content

Commit

Permalink
feat!: encapsulate set fields in CborSet
Browse files Browse the repository at this point in the history
Conwy era CDDL defines [sets](https://github.com/IntersectMBO/cardano-ledger/blob/master/eras/conway/impl/cddl-files/extra.cddl#L5)
as mathematical finite sets, encoded in a cbor 258 tag.
https://github.com/input-output-hk/cbor-sets-spec/blob/master/CBOR_SETS.md

BREAKING_CHANGE: All CBOR serialization classes now use CborSet when constructing,
setting or retrieveing fields that are defined by the CDDL as `set/nonempty_set/`. See for example
`TransactionBody referenceInputs` which is now a `CborSet<Cardano.TxIn, TransactionInput>`,
and this is the type used when calling `referenceInputs() or setReferenceInputs(...)`.
  • Loading branch information
mirceahasegan committed Mar 21, 2024
1 parent 3fdd115 commit 06269ab
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 362 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import * as Cardano from '../../../Cardano';
import * as Crypto from '@cardano-sdk/crypto';
import { CborReader, CborReaderState, CborWriter } from '../../CBOR';
import { CborSet, Hash } from '../../Common';
import { HexBlob, InvalidArgumentError } from '@cardano-sdk/util';
import { PoolMetadata } from './PoolMetadata';
import { Relay } from './Relay';
import { UnitInterval } from '../../Common/UnitInterval';

type PoolOwners = CborSet<Crypto.Ed25519KeyHashHex, Hash<Crypto.Ed25519KeyHashHex>>;
/**
* Stake pool update certificate parameters.
*
Expand All @@ -20,7 +22,7 @@ export class PoolParams {
#cost: Cardano.Lovelace;
#margin: UnitInterval;
#rewardAccount: Cardano.RewardAddress;
#poolOwners: Array<Crypto.Ed25519KeyHashHex>;
#poolOwners: PoolOwners;
#relays: Array<Relay>;
#poolMetadata?: PoolMetadata;
#originalBytes: HexBlob | undefined = undefined;
Expand All @@ -47,7 +49,7 @@ export class PoolParams {
cost: Cardano.Lovelace,
margin: UnitInterval,
rewardAccount: Cardano.RewardAddress,
poolOwners: Array<Crypto.Ed25519KeyHashHex>,
poolOwners: PoolOwners,
relays: Array<Relay>,
poolMetadata?: PoolMetadata
) {
Expand Down Expand Up @@ -93,7 +95,7 @@ export class PoolParams {
* An example is the PoolRegistration certificate which flattens the pool parameters in the pool_registration, rather
* than insert as a subgroup
*
* @param the parent writer that already created the subgroup
* @param writer the parent writer that already created the subgroup
* @returns The PoolMetadata in CBOR format.
*/
toFlattenedCbor(writer: CborWriter): HexBlob {
Expand All @@ -103,10 +105,7 @@ export class PoolParams {
writer.writeInt(this.#cost);
writer.writeEncodedValue(Buffer.from(this.#margin.toCbor(), 'hex'));
writer.writeByteString(Buffer.from(this.#rewardAccount.toAddress().toBytes(), 'hex'));

writer.writeStartArray(this.#poolOwners.length);
for (const owner of this.#poolOwners) writer.writeByteString(Buffer.from(owner, 'hex'));

writer.writeEncodedValue(Buffer.from(this.#poolOwners.toCbor(), 'hex'));
writer.writeStartArray(this.#relays.length);
for (const relay of this.#relays) writer.writeEncodedValue(Buffer.from(relay.toCbor(), 'hex'));

Expand Down Expand Up @@ -158,17 +157,14 @@ export class PoolParams {
const cost = reader.readInt();
const margin = UnitInterval.fromCbor(HexBlob.fromBytes(reader.readEncodedValue()));
const rewardAccount = Cardano.Address.fromBytes(HexBlob.fromBytes(reader.readByteString())).asReward()!;
const poolOwner = new Array<Crypto.Ed25519KeyHashHex>();
const relays = new Array<Relay>();
let poolMetadata;

// Pool owners.
reader.readStartArray();

while (reader.peekState() !== CborReaderState.EndArray)
poolOwner.push(Crypto.Ed25519KeyHashHex(HexBlob.fromBytes(reader.readByteString())));

reader.readEndArray();
const poolOwner = CborSet.fromCbor<Crypto.Ed25519KeyHashHex, Hash<Crypto.Ed25519KeyHashHex>>(
HexBlob.fromBytes(reader.readEncodedValue()),
Hash.fromCbor
);

// Relays
reader.readStartArray();
Expand Down Expand Up @@ -200,9 +196,9 @@ export class PoolParams {
id: Cardano.PoolId.fromKeyHash(this.#operator),
margin: this.#margin.toCore(),
metadataJson: this.#poolMetadata?.toCore(),
owners: this.#poolOwners.map((keyHash) =>
Cardano.createRewardAccount(keyHash, rewardAccountAddress.getNetworkId())
),
owners: this.#poolOwners
.toCore()
.map((keyHash) => Cardano.createRewardAccount(keyHash, rewardAccountAddress.getNetworkId())),
pledge: this.#pledge,
relays: this.#relays.map((relay) => relay.toCore()),
rewardAccount: this.#rewardAccount.toAddress().toBech32() as Cardano.RewardAccount,
Expand All @@ -223,8 +219,11 @@ export class PoolParams {
params.cost,
UnitInterval.fromCore(params.margin),
Cardano.Address.fromBech32(params.rewardAccount).asReward()!,
params.owners.map((owner) =>
Crypto.Ed25519KeyHashHex(Cardano.Address.fromBech32(owner).asReward()!.getPaymentCredential()!.hash)
CborSet.fromCore(
params.owners.map((owner) =>
Crypto.Ed25519KeyHashHex(Cardano.Address.fromBech32(owner).asReward()!.getPaymentCredential()!.hash)
),
Hash.fromCore
),
params.relays.map((relay) => Relay.fromCore(relay)),
params.metadataJson ? PoolMetadata.fromCore(params.metadataJson) : undefined
Expand Down Expand Up @@ -356,7 +355,7 @@ export class PoolParams {
*
* @returns The pool owners key hashes.
*/
poolOwners(): Array<Crypto.Ed25519KeyHashHex> {
poolOwners(): PoolOwners {
return this.#poolOwners;
}

Expand All @@ -365,8 +364,8 @@ export class PoolParams {
*
* @param poolOwners The pool owners key hashes.
*/
setPoolOwners(poolOwners: Array<Crypto.Ed25519KeyHashHex>): void {
this.#poolOwners = [...poolOwners];
setPoolOwners(poolOwners: PoolOwners): void {
this.#poolOwners = poolOwners;
this.#originalBytes = undefined;
}

Expand Down
50 changes: 50 additions & 0 deletions packages/core/src/Serialization/Common/Credential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Cardano } from '../..';
import { CborReader, CborWriter } from '../CBOR';
import { Hash28ByteBase16 } from '@cardano-sdk/crypto';
import { HexBlob, InvalidArgumentError } from '@cardano-sdk/util';
import { hexToBytes } from '../../util/misc';

const CREDENTIAL_ARRAY_SIZE = 2;

export class Credential {
#value: Cardano.Credential;

private constructor(value: Cardano.Credential) {
this.#value = value;
}

toCbor() {
const writer = new CborWriter();
writer.writeStartArray(CREDENTIAL_ARRAY_SIZE);
writer.writeInt(this.#value.type);
writer.writeByteString(hexToBytes(this.#value.hash as unknown as HexBlob));
return writer.encodeAsHex();
}

static fromCbor(cbor: HexBlob): Credential {
const reader = new CborReader(cbor);
if (reader.readStartArray() !== CREDENTIAL_ARRAY_SIZE)
throw new InvalidArgumentError(
'cbor',
`Expected an array of ${CREDENTIAL_ARRAY_SIZE} elements, but got an array of ${length} elements`
);

const type = Number(reader.readUInt());
const hash = HexBlob.fromBytes(reader.readByteString()) as unknown as Hash28ByteBase16;

reader.readEndArray();
return new Credential({ hash, type });
}

toCore(): Cardano.Credential {
return { ...this.#value };
}

static fromCore(credential: Cardano.Credential): Credential {
return new Credential({ ...credential });
}

value() {
return { ...this.#value };
}
}
33 changes: 33 additions & 0 deletions packages/core/src/Serialization/Common/Hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CborReader, CborWriter } from '../CBOR';
import { HexBlob } from '@cardano-sdk/util';

export class Hash<T extends string> {
#value: T;

constructor(value: T) {
this.#value = value;
}

toCbor() {
const writer = new CborWriter();
writer.writeByteString(Buffer.from(this.#value, 'hex'));
return writer.encodeAsHex();
}

static fromCbor<T extends string>(cbor: HexBlob): Hash<T> {
const reader = new CborReader(cbor);
return new Hash<T>(HexBlob.fromBytes(reader.readByteString()) as unknown as T);
}

toCore() {
return this.#value;
}

static fromCore<T extends string>(hash: T): Hash<T> {
return new Hash<T>(hash);
}

value() {
return this.#value;
}
}
2 changes: 2 additions & 0 deletions packages/core/src/Serialization/Common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export * from './Datum';
export * from './Anchor';
export * from './GovernanceActionId';
export * from './CborSet';
export * from './Hash';
export * from './Credential';
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
/* eslint-disable max-statements */
import * as Cardano from '../../../Cardano';
import { CborReader, CborReaderState, CborWriter } from '../../CBOR';
import { CommitteeMember, Credential, GovernanceActionType } from '../../../Cardano';
import { CborSet, Credential, UnitInterval } from '../../Common';
import { CommitteeMember, GovernanceActionType } from '../../../Cardano';
import { GovernanceActionId } from '../../Common/GovernanceActionId';
import { GovernanceActionKind } from './GovernanceActionKind';
import { Hash28ByteBase16 } from '@cardano-sdk/crypto';
import { HexBlob, InvalidArgumentError } from '@cardano-sdk/util';
import { UnitInterval } from '../../Common';
import { hexToBytes } from '../../../util/misc';

const EMBEDDED_GROUP_SIZE = 5;
const CREDENTIAL_ARRAY_SIZE = 2;
const CREDENTIAL_INDEX = 0;
const EPOCH_INDEX = 1;

type CredentialSet = CborSet<ReturnType<Credential['toCore']>, Credential>;

/** Modifies the composition of the constitutional committee, its signature threshold, or its terms of operation. */
export class UpdateCommittee {
#govActionId: GovernanceActionId | undefined;
#membersToBeRemoved: Cardano.Credential[];
#membersToBeRemoved: CredentialSet;
#membersToBeAdded: [Cardano.Credential, number][];
#newQuorum: UnitInterval;
#originalBytes: HexBlob | undefined = undefined;
Expand All @@ -31,7 +33,7 @@ export class UpdateCommittee {
* @param govActionId Previous governance action id of `UpdateCommittee`.
*/
constructor(
membersToBeRemoved: Cardano.Credential[],
membersToBeRemoved: CredentialSet,
membersToBeAdded: [Cardano.Credential, number][],
newQuorum: UnitInterval,
govActionId?: GovernanceActionId
Expand All @@ -58,12 +60,7 @@ export class UpdateCommittee {
writer.writeInt(GovernanceActionKind.UpdateCommittee);
this.#govActionId ? writer.writeEncodedValue(hexToBytes(this.#govActionId.toCbor())) : writer.writeNull();

writer.writeStartArray(this.#membersToBeRemoved.length);
for (const entry of this.#membersToBeRemoved) {
writer.writeStartArray(CREDENTIAL_ARRAY_SIZE);
writer.writeInt(entry.type);
writer.writeByteString(hexToBytes(entry.hash as unknown as HexBlob));
}
writer.writeEncodedValue(hexToBytes(this.#membersToBeRemoved.toCbor()));

writer.writeStartMap(this.#membersToBeAdded.length);
for (const entry of this.#membersToBeAdded) {
Expand Down Expand Up @@ -111,24 +108,10 @@ export class UpdateCommittee {
govActionId = GovernanceActionId.fromCbor(HexBlob.fromBytes(reader.readEncodedValue()));
}

reader.readStartArray();

const membersToRemove: Cardano.Credential[] = [];
while (reader.peekState() !== CborReaderState.EndArray) {
if (reader.readStartArray() !== CREDENTIAL_ARRAY_SIZE)
throw new InvalidArgumentError(
'cbor',
`Expected an array of ${CREDENTIAL_ARRAY_SIZE} elements, but got an array of ${length} elements`
);

const type = Number(reader.readUInt());
const hash = HexBlob.fromBytes(reader.readByteString()) as unknown as Hash28ByteBase16;

reader.readEndArray();
membersToRemove.push({ hash, type });
}

reader.readEndArray();
const membersToRemove: CredentialSet = CborSet.fromCbor(
HexBlob.fromBytes(reader.readEncodedValue()),
Credential.fromCbor
);

reader.readStartMap();

Expand Down Expand Up @@ -173,7 +156,7 @@ export class UpdateCommittee {
epoch: Cardano.EpochNo(entry[EPOCH_INDEX])
}))
),
membersToBeRemoved: new Set<Credential>(this.#membersToBeRemoved),
membersToBeRemoved: new Set<Cardano.Credential>(this.#membersToBeRemoved.toCore()),
newQuorumThreshold: this.#newQuorum.toCore()
};
}
Expand All @@ -185,7 +168,7 @@ export class UpdateCommittee {
*/
static fromCore(updateCommittee: Cardano.UpdateCommittee) {
return new UpdateCommittee(
[...updateCommittee.membersToBeRemoved],
CborSet.fromCore([...updateCommittee.membersToBeRemoved], Credential.fromCore),
[...updateCommittee.membersToBeAdded].map((entry) => [entry.coldCredential, entry.epoch]),
UnitInterval.fromCore(updateCommittee.newQuorumThreshold),
updateCommittee.governanceActionId !== null
Expand All @@ -209,7 +192,7 @@ export class UpdateCommittee {
* @returns the committee members to be removed.
*/
membersToBeRemoved(): Cardano.Credential[] {
return this.#membersToBeRemoved;
return this.#membersToBeRemoved.toCore();
}

/**
Expand Down
Loading

0 comments on commit 06269ab

Please sign in to comment.