Skip to content

Commit

Permalink
todo
Browse files Browse the repository at this point in the history
  • Loading branch information
janmazak committed Dec 20, 2023
1 parent dfeccfa commit ddd9057
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 162 deletions.
1 change: 0 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"multiassets",
"reserialized",
"unfixable",
"UNREG",
"vacuumlabs"
]
}
14 changes: 5 additions & 9 deletions src/errors/validationError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ export enum ValidationErrorReason {
INTEGER_NOT_INT64 = 'Hardware wallets support integers up to int64, integers from -2^63 to 2^63-1',
UNSIGNED_INTEGER_NOT_UINT64 = 'Hardware wallets support unsigned integers up to uint64, unsigned integers from 0 to 2^64-1',
NUMBER_OF_ELEMENTS_EXCEEDS_UINT16 = 'The number of transaction elements individually must not exceed UINT16_MAX, i.e. 65535',
CERTIFICATES_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL = 'All certificates included in a transaction must have the same type of stake credential',
WITHDRAWALS_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL = 'All withdrawals included in a transaction must have the same type of stake credential',
CERTIFICATES_AND_WITHDRAWALS_STAKE_CREDENTIAL_TYPES_MUST_BE_CONSISTENT = 'The stake credential type of certificates must be consistent with the type used for withdrawals',
POOL_REGISTRATION_CERTIFICATE_WITH_OTHER_CERTIFICATES = 'If a transaction contains a pool registration certificate, then it must not contain any other certificate',
POOL_REGISTRATION_CERTIFICATE_WITH_WITHDRAWALS = 'If a transaction contains a pool registration certificate, then it must not contain any withdrawal',
POOL_REGISTRATION_CERTIFICATE_WITH_MINT_ENTRY = 'If a transaction contains a pool registration certificate, then it must not contain mint entry',
TOO_MANY_VOTING_PROCEDURES = 'Only a single voter and a single vote is allowed in voting procedures',

// Fixable validation errors
CBOR_IS_NOT_CANONICAL = 'CBOR is not canonical',
Expand All @@ -32,6 +30,7 @@ const FIXABLE = true
const UNFIXABLE = false

const validationErrorFixability: Record<ValidationErrorReason, boolean> = {
// unfixable
[ValidationErrorReason.UNSUPPORTED_TX_UPDATE]: UNFIXABLE,
[ValidationErrorReason.UNSUPPORTED_TX_PROPOSAL_PROCEDURES]: UNFIXABLE,
[ValidationErrorReason.UNSUPPORTED_CERTIFICATE_GENESIS_KEY_DELEGATION]:
Expand All @@ -46,18 +45,15 @@ const validationErrorFixability: Record<ValidationErrorReason, boolean> = {
[ValidationErrorReason.INTEGER_NOT_INT64]: UNFIXABLE,
[ValidationErrorReason.UNSIGNED_INTEGER_NOT_UINT64]: UNFIXABLE,
[ValidationErrorReason.NUMBER_OF_ELEMENTS_EXCEEDS_UINT16]: UNFIXABLE,
[ValidationErrorReason.CERTIFICATES_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL]:
UNFIXABLE,
[ValidationErrorReason.WITHDRAWALS_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL]:
UNFIXABLE,
[ValidationErrorReason.CERTIFICATES_AND_WITHDRAWALS_STAKE_CREDENTIAL_TYPES_MUST_BE_CONSISTENT]:
UNFIXABLE,
[ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_OTHER_CERTIFICATES]:
UNFIXABLE,
[ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_WITHDRAWALS]:
UNFIXABLE,
[ValidationErrorReason.POOL_REGISTRATION_CERTIFICATE_WITH_MINT_ENTRY]:
UNFIXABLE,
[ValidationErrorReason.TOO_MANY_VOTING_PROCEDURES]: UNFIXABLE,

// fixable
[ValidationErrorReason.CBOR_IS_NOT_CANONICAL]: FIXABLE,
[ValidationErrorReason.OPTIONAL_EMPTY_LISTS_AND_MAPS_MUST_NOT_BE_INCLUDED]:
FIXABLE,
Expand Down
25 changes: 14 additions & 11 deletions src/txParsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ import {
DRepType,
KeyHashDRep,
ScriptHashDRep,
AlwaysAbstainDRep,
AlwaysNoConfidenceDRep,
AbstainDRep,
NoConfidenceDRep,
StakeAndVoteDelegationCertificate,
StakeRegistrationAndDelegationCertificate,
StakeRegistrationWithVoteDelegationCertificate,
Expand Down Expand Up @@ -152,7 +152,7 @@ const parseCredentialType = (
const parseKeyCredential = (data: unknown[]): WithoutType<KeyCredential> => {
validate(data.length === 1, ParseErrorReason.INVALID_CREDENTIAL)
return {
hash: parseBufferOfLength(
keyHash: parseBufferOfLength(
data[0],
KEY_HASH_LENGTH,
ParseErrorReason.INVALID_CREDENTIAL_KEY_HASH,
Expand All @@ -165,7 +165,7 @@ const parseScriptCredential = (
): WithoutType<ScriptCredential> => {
validate(data.length === 1, ParseErrorReason.INVALID_CREDENTIAL)
return {
hash: parseBufferOfLength(
scriptHash: parseBufferOfLength(
data[0],
SCRIPT_HASH_LENGTH,
ParseErrorReason.INVALID_CREDENTIAL_SCRIPT_HASH,
Expand Down Expand Up @@ -581,17 +581,17 @@ const parseScriptHashDRep = (data: unknown[]): WithoutType<ScriptHashDRep> => {
}
}

const parseAlwaysAbstainDRep = (
const parseAbstainDRep = (
data: unknown[],
): WithoutType<AlwaysAbstainDRep> => {
): WithoutType<AbstainDRep> => {
// nothing to parse
validate(data.length === 0, ParseErrorReason.INVALID_DREP)
return {}
}

const parseAlwaysNoConfidenceDRep = (
const parseNoConfidenceDRep = (
data: unknown[],
): WithoutType<AlwaysNoConfidenceDRep> => {
): WithoutType<NoConfidenceDRep> => {
// nothing to parse
validate(data.length === 0, ParseErrorReason.INVALID_DREP)
return {}
Expand Down Expand Up @@ -621,8 +621,8 @@ export const parseDRep = createParser(
parseDRepType,
parseKeyHashDRep,
parseScriptHashDRep,
parseAlwaysAbstainDRep,
parseAlwaysNoConfidenceDRep,
parseAbstainDRep,
parseNoConfidenceDRep,
)

const parseCertificateType = (
Expand Down Expand Up @@ -685,13 +685,16 @@ const parsePoolRetirementCertificate = (
}
}

// removed in Conway, but we keep it here because it might appear
// in erroneous / previously-built transactions
const parseGenesisKeyDelegation = (
data: unknown[],
): WithoutType<GenesisKeyDelegation> => ({
restOfData: data,
})

// TODO these are removed in Conway
// removed in Conway, but we keep it here because it might appear
// in erroneous / previously-built transactions
const parseMoveInstantaneousRewardsCertificate = (
data: unknown[],
): WithoutType<MoveInstantaneousRewardsCertificate> => ({
Expand Down
27 changes: 15 additions & 12 deletions src/txSerializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
Uint,
Int,
CddlSetBase,
CredentialType,
} from './types'
import {
BabbageTransactionOutputKeys,
Expand All @@ -49,7 +50,7 @@ export const identity = <T>(x: T): T => x

export type Serializer<T> = (data: T) => unknown

const serializeCddlSetBase = <T>(
export const serializeCddlSetBase = <T>(
set: CddlSetBase<T>,
serializeEntry: Serializer<T>,
) => {
Expand All @@ -61,7 +62,7 @@ const serializeCddlSetBase = <T>(
}
}

const serializeCddlSetBaseOrUndefined = <T>(
export const serializeCddlSetBaseOrUndefined = <T>(
set: CddlSetBase<T> | undefined,
serializeEntry: Serializer<T>,
) => {
Expand All @@ -71,7 +72,6 @@ const serializeCddlSetBaseOrUndefined = <T>(
return serializeCddlSetBase(set, serializeEntry)
}

// export needed because of uniqueness check during parsing
export const serializeTxInput = (input: TransactionInput) => [
input.transactionId,
input.index,
Expand Down Expand Up @@ -178,19 +178,25 @@ const serializePoolParams = (poolParams: PoolParams) => [
poolParams.poolMetadata && serializePoolMetadata(poolParams.poolMetadata),
]

const serializeCredential = (credential: Credential) => [
credential.type,
credential.hash,
]
const serializeCredential = (credential: Credential) => {
switch (credential.type) {
case CredentialType.KEY_HASH:
return [credential.type, credential.keyHash]
case CredentialType.SCRIPT_HASH:
return [credential.type, credential.scriptHash]
default:
unreachable(credential)
}
}

const serializeDRep = (dRep: DRep) => {
switch (dRep.type) {
case DRepType.KEY_HASH:
return [dRep.type, dRep.keyHash]
case DRepType.SCRIPT_HASH:
return [dRep.type, dRep.scriptHash]
case DRepType.ALWAYS_ABSTAIN:
case DRepType.ALWAYS_NO_CONFIDENCE:
case DRepType.ABSTAIN:
case DRepType.NO_CONFIDENCE:
return [dRep.type]
default:
unreachable(dRep)
Expand All @@ -204,7 +210,6 @@ const serializeAnchor = (anchor: Anchor | null) => {
return [anchor.url, anchor.dataHash]
}

// export needed because of uniqueness check during parsing
export const serializeCertificate = (certificate: Certificate) => {
switch (certificate.type) {
case CertificateType.STAKE_REGISTRATION:
Expand Down Expand Up @@ -304,7 +309,6 @@ export const serializeCertificate = (certificate: Certificate) => {
}
}

// export needed because of uniqueness check during parsing
export const serializeCollateralInput = (collateralInput: TransactionInput) => [
collateralInput.transactionId,
collateralInput.index,
Expand Down Expand Up @@ -335,7 +339,6 @@ const serializeVotingProcedures = (ballots: VoterVotes[]) =>
]),
)

// export needed because of uniqueness check during parsing
export const serializeProposalProcedure = (procedure: ProposalProcedure) => [
procedure.deposit,
procedure.rewardAccount,
Expand Down
123 changes: 39 additions & 84 deletions src/txValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import type {
Mint,
Multiasset,
RequiredSigner,
CredentialType,
StakeDelegationCertificate,
StakeDeregistrationCertificate,
StakeRegistrationCertificate,
TransactionBody,
TransactionInput,
TransactionOutput,
Expand All @@ -19,9 +15,10 @@ import type {
CddlSet,
CddlNonEmptySet,
CddlNonEmptyOrderedSet,
VoterVotes,
} from './types'
import {AmountType, CertificateType, DatumType, TxOutputFormat} from './types'
import {bind, getRewardAccountStakeCredentialType, unreachable} from './utils'
import {bind, unreachable} from './utils'

const UINT16_MAX = 65535
const MAX_UINT_64_STR = '18446744073709551615'
Expand Down Expand Up @@ -311,79 +308,6 @@ function* validateWithdrawals(withdrawals: Withdrawal[]): ValidatorReturnType {
}
}

// TODO does this make sense for Plutus? there can be mixed credentials? probably remove the whole function
function* validateStakeCredentials(
certificates: Certificate[] | undefined,
withdrawals: Withdrawal[] | undefined,
): ValidatorReturnType {
const certificateStakeCredentialTypes: Set<CredentialType> = new Set()
const withdrawalStakeCredentialTypes: Set<CredentialType> = new Set()

if (certificates) {
// TODO add Conway?
// We must first filter out the certificates that contain stake credentials
const certificatesWithStakeCredentials = certificates.filter(
({type}) =>
type === CertificateType.STAKE_REGISTRATION ||
type === CertificateType.STAKE_DEREGISTRATION ||
type === CertificateType.STAKE_DELEGATION,
) as (
| StakeRegistrationCertificate
| StakeDeregistrationCertificate
| StakeDelegationCertificate
)[]
certificatesWithStakeCredentials.forEach(({stakeCredential}) =>
certificateStakeCredentialTypes.add(stakeCredential.type),
)
// We check the set of stake credential types to be less or equal to one,
// because if there are 0 types it means there were no certificates with
// stake credentials which is possible and it shouldn't trigger this error
yield* validate(
certificateStakeCredentialTypes.size <= 1,
err(
ValidationErrorReason.WITHDRAWALS_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL,
'transaction_body.certificates',
),
)
}

if (withdrawals) {
withdrawals.forEach(({rewardAccount}) =>
withdrawalStakeCredentialTypes.add(
getRewardAccountStakeCredentialType(rewardAccount),
),
)
// Here we also check the number of stake credential types to be less or
// equal to one, because we could have been dealing with an empty array
// and that's a ValidationError that is caught elsewhere and shouldn't
// be caught by this check
yield* validate(
withdrawalStakeCredentialTypes.size <= 1,
err(
ValidationErrorReason.WITHDRAWALS_MUST_HAVE_THE_SAME_TYPE_OF_STAKE_CREDENTIAL,
'transaction_body.withdrawals',
),
)
}

if (
certificateStakeCredentialTypes.size === 1 &&
withdrawalStakeCredentialTypes.size === 1
) {
// We only trigger this check if both certificates and withdrawals have
// consistent stake credential types otherwise it is useless to check
// whether they are consistent in respect to each other.
yield* validate(
[...certificateStakeCredentialTypes][0] ===
[...withdrawalStakeCredentialTypes][0],
err(
ValidationErrorReason.CERTIFICATES_AND_WITHDRAWALS_STAKE_CREDENTIAL_TYPES_MUST_BE_CONSISTENT,
'transaction_body',
),
)
}
}

const validateMint = (mint: Mint) =>
validateMultiasset(mint, validateInt64, 'transaction_body.mint')

Expand Down Expand Up @@ -458,8 +382,43 @@ function* validateTxCollateralReturnOutput(
}
}

function* validateVotingProcedures(
voterVotesArray: VoterVotes[],
): ValidatorReturnType {
yield* validateListConstraints(
voterVotesArray,
'transaction_body.voting_procedures',
true,
)

yield* validate(
voterVotesArray.length <= 1,
err(
ValidationErrorReason.TOO_MANY_VOTING_PROCEDURES,
`transaction_body.voting_procedures`,
),
)

for (const [i, voterVotes] of voterVotesArray.entries()) {
yield* validate(
voterVotes.votes.length <= 1,
err(
ValidationErrorReason.TOO_MANY_VOTING_PROCEDURES,
`transaction_body.voting_procedures[${i}].votes`,
),
)

for (const [j, voterVote] of voterVotes.votes.entries()) {
yield* validateUint64(
voterVote.govActionId.index,
`transaction_body.voting_procedures[${i}].votes[${j}].govActionId.index`,
)
}
}
}

/**
* Checks if a transaction contains pool registration certificate, if it does
* Checks if a transaction contains pool registration certificate; if it does,
* runs a series of validators for pool registration transactions.
*/
function* validatePoolRegistrationTransaction(
Expand Down Expand Up @@ -523,10 +482,6 @@ function* validateTxBody(txBody: TransactionBody): ValidatorReturnType {
)
yield* validateOptional(txBody.certificates, validateCertificates)
yield* validateOptional(txBody.withdrawals, validateWithdrawals)
yield* validateStakeCredentials(
txBody.certificates?.items,
txBody.withdrawals,
)
yield* validate(
txBody.update === undefined,
err(ValidationErrorReason.UNSUPPORTED_TX_UPDATE, 'transaction_body.update'),
Expand Down Expand Up @@ -555,7 +510,7 @@ function* validateTxBody(txBody: TransactionBody): ValidatorReturnType {
bind(validateUint64, 'transaction_body.total_collateral'),
)
yield* validateOptional(txBody.referenceInputs, validateReferenceInputs)
// TODO validate voting procedures
yield* validateOptional(txBody.votingProcedures, validateVotingProcedures)
yield* validate(
txBody.proposalProcedures === undefined,
err(
Expand Down
Loading

0 comments on commit ddd9057

Please sign in to comment.