Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
Merge branch 'release/6.0.0' into 9044-fix-aggregate-commit-height
Browse files Browse the repository at this point in the history
  • Loading branch information
shuse2 authored Sep 29, 2023
2 parents b047ec3 + ef9e6be commit 0e77565
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 327 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
MODULE_NAME_INTEROPERABILITY,
EMPTY_HASH,
MAX_NUM_VALIDATORS,
MAX_UINT64,
} from './constants';
import { ccmSchema } from './schemas';
import { CCMsg, CrossChainUpdateTransactionParams, ChainAccount, ChainValidators } from './types';
Expand Down Expand Up @@ -369,9 +370,28 @@ export abstract class BaseInteroperabilityInternalMethod extends BaseInternalMet
);
}

let totalWeight = BigInt(0);
for (const currentValidator of newActiveValidators) {
if (currentValidator.bftWeight === BigInt(0)) {
throw new Error('Validator bft weight must be positive integer.');
}
totalWeight += currentValidator.bftWeight;
if (totalWeight > MAX_UINT64) {
throw new Error('Total BFT weight exceeds maximum value.');
}
}
const certificate = codec.decode<Certificate>(certificateSchema, ccu.certificate);
validator.validate(certificateSchema, certificate);

const { certificateThreshold } = ccu;

if (certificateThreshold < totalWeight / BigInt(3) + BigInt(1)) {
throw new Error('Certificate threshold is too small.');
}
if (certificateThreshold > totalWeight) {
throw new Error('Certificate threshold is too large.');
}

const newValidatorsHash = computeValidatorsHash(newActiveValidators, ccu.certificateThreshold);
if (!certificate.validatorsHash.equals(newValidatorsHash)) {
throw new Error('ValidatorsHash in certificate and the computed values do not match.');
Expand Down
2 changes: 1 addition & 1 deletion framework/src/modules/interoperability/certificates.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* TODO: Now that we have `certificates.ts`, these methods could be moved there
* (checkCertificateTimestamp, checkCertificateValidity, checkValidatorsHashWithCertificate,
* (checkCertificateTimestamp,
* isCertificateEmpty, verifyCertificateSignature)
*/
import { LastCertificate, LastCertificateJSON } from './types';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class RegisterMainchainCommand extends BaseInteroperabilityCommand<Sidech
};
}

if (currentValidator.bftWeight <= 0) {
if (currentValidator.bftWeight === BigInt(0)) {
return {
status: VerifyStatus.FAIL,
error: new Error('Validator bft weight must be positive integer.'),
Expand Down
90 changes: 1 addition & 89 deletions framework/src/modules/interoperability/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
CCMsg,
ChainAccount,
CrossChainUpdateTransactionParams,
ChainValidators,
InboxUpdate,
OutboxRootWitness,
ActiveValidatorsUpdate,
Expand Down Expand Up @@ -119,7 +118,7 @@ export const isInboxUpdateEmpty = (inboxUpdate: InboxUpdate) =>
export const isOutboxRootWitnessEmpty = (outboxRootWitness: OutboxRootWitness) =>
outboxRootWitness.siblingHashes.length === 0 || outboxRootWitness.bitmap.length === 0;

export const checkLivenessRequirementFirstCCU = (
export const checkLivenessRequirement = (
partnerChainAccount: ChainAccount,
txParams: CrossChainUpdateTransactionParams,
): VerificationResult => {
Expand All @@ -142,39 +141,6 @@ export const checkLivenessRequirementFirstCCU = (
};
};

export const checkCertificateValidity = (
partnerChainAccount: ChainAccount,
encodedCertificate: Buffer,
): VerificationResult => {
if (encodedCertificate.equals(EMPTY_BYTES)) {
return {
status: VerifyStatus.OK,
};
}

const certificate = codec.decode<Certificate>(certificateSchema, encodedCertificate);
try {
validator.validate(certificateSchema, certificate);
} catch (err) {
return {
status: VerifyStatus.FAIL,
error: new Error('Certificate is missing required values.'),
};
}

// Last certificate height should be less than new certificate height
if (partnerChainAccount.lastCertificate.height >= certificate.height) {
return {
status: VerifyStatus.FAIL,
error: new Error('Certificate height should be greater than last certificate height.'),
};
}

return {
status: VerifyStatus.OK,
};
};

export const checkCertificateTimestamp = (
txParams: CrossChainUpdateTransactionParams,
certificate: Certificate,
Expand All @@ -190,60 +156,6 @@ export const checkCertificateTimestamp = (
}
};

export const checkValidatorsHashWithCertificate = (
txParams: CrossChainUpdateTransactionParams,
partnerValidators: ChainValidators,
): VerificationResult => {
if (
!emptyActiveValidatorsUpdate(txParams.activeValidatorsUpdate) ||
txParams.certificateThreshold > BigInt(0)
) {
if (txParams.certificate.equals(EMPTY_BYTES)) {
return {
status: VerifyStatus.FAIL,
error: new Error(
'Certificate cannot be empty when activeValidatorsUpdate or certificateThreshold has a non-empty value.',
),
};
}
let certificate: Certificate;
try {
certificate = codec.decode<Certificate>(certificateSchema, txParams.certificate);
validator.validate(certificateSchema, certificate);
} catch (error) {
return {
status: VerifyStatus.FAIL,
error: new Error(
'Certificate should have all required values when activeValidatorsUpdate or certificateThreshold has a non-empty value.',
),
};
}

const newActiveValidators = calculateNewActiveValidators(
partnerValidators.activeValidators,
txParams.activeValidatorsUpdate.blsKeysUpdate,
txParams.activeValidatorsUpdate.bftWeightsUpdate,
txParams.activeValidatorsUpdate.bftWeightsUpdateBitmap,
);

const validatorsHash = computeValidatorsHash(
newActiveValidators,
txParams.certificateThreshold || partnerValidators.certificateThreshold,
);

if (!certificate.validatorsHash.equals(validatorsHash)) {
return {
status: VerifyStatus.FAIL,
error: new Error('Validators hash given in the certificate is incorrect.'),
};
}
}

return {
status: VerifyStatus.OK,
};
};

export const chainAccountToJSON = (chainAccount: ChainAccount) => {
const { lastCertificate, name, status } = chainAccount;

Expand Down
131 changes: 130 additions & 1 deletion framework/test/unit/modules/interoperability/internal_method.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
EMPTY_BYTES,
EMPTY_HASH,
HASH_LENGTH,
MAX_UINT64,
MESSAGE_TAG_CERTIFICATE,
MIN_RETURN_FEE_PER_BYTE_BEDDOWS,
} from '../../../../src/modules/interoperability/constants';
Expand Down Expand Up @@ -149,7 +150,7 @@ describe('Base interoperability internal method', () => {
siblingHashes: [],
},
},
certificateThreshold: BigInt(99),
certificateThreshold: BigInt(9),
sendingChainID: cryptoUtils.getRandomBytes(4),
};
let mainchainInteroperabilityInternalMethod: MainchainInteroperabilityInternalMethod;
Expand Down Expand Up @@ -926,6 +927,134 @@ describe('Base interoperability internal method', () => {
).rejects.toThrow('New validators must have a positive BFT weight.');
});

it('should reject if new active validator bft weight equals 0', async () => {
const ccu = {
...ccuParams,
certificate: codec.encode(certificateSchema, defaultCertificate),
activeValidatorsUpdate: {
blsKeysUpdate: [
Buffer.from([0, 0, 0, 0]),
Buffer.from([0, 0, 0, 1]),
Buffer.from([0, 0, 3, 0]),
],
bftWeightsUpdate: [BigInt(1), BigInt(3), BigInt(4)],
// 7 corresponds to 0111
bftWeightsUpdateBitmap: Buffer.from([7]),
},
};
const existingKey = Buffer.from([0, 2, 3, 0]);
await chainValidatorsSubstore.set(methodContext, ccu.sendingChainID, {
activeValidators: [{ blsKey: existingKey, bftWeight: BigInt(2) }],
certificateThreshold: BigInt(1),
});
const newValidators = [
{ blsKey: Buffer.from([0, 0, 0, 0]), bftWeight: BigInt(1) },
{ blsKey: Buffer.from([0, 0, 0, 1]), bftWeight: BigInt(3) },
{ blsKey: Buffer.from([0, 0, 2, 0]), bftWeight: BigInt(0) },
];
jest.spyOn(utils, 'calculateNewActiveValidators').mockReturnValue(newValidators);

await expect(
mainchainInteroperabilityInternalMethod.verifyValidatorsUpdate(methodContext, ccu),
).rejects.toThrow('Validator bft weight must be positive integer.');
});

it(`should reject if total bft weight > ${MAX_UINT64}`, async () => {
const ccu = {
...ccuParams,
certificate: codec.encode(certificateSchema, defaultCertificate),
activeValidatorsUpdate: {
blsKeysUpdate: [
Buffer.from([0, 0, 0, 0]),
Buffer.from([0, 0, 0, 1]),
Buffer.from([0, 0, 3, 0]),
],
bftWeightsUpdate: [BigInt(1), BigInt(3), BigInt(4)],
// 7 corresponds to 0111
bftWeightsUpdateBitmap: Buffer.from([7]),
},
};
const existingKey = Buffer.from([0, 2, 3, 0]);
await chainValidatorsSubstore.set(methodContext, ccu.sendingChainID, {
activeValidators: [{ blsKey: existingKey, bftWeight: BigInt(2) }],
certificateThreshold: BigInt(1),
});
const newValidators = [
{ blsKey: Buffer.from([0, 0, 0, 0]), bftWeight: BigInt(1) },
{ blsKey: Buffer.from([0, 0, 0, 1]), bftWeight: BigInt(3) },
{ blsKey: Buffer.from([0, 0, 2, 0]), bftWeight: MAX_UINT64 },
];
jest.spyOn(utils, 'calculateNewActiveValidators').mockReturnValue(newValidators);

await expect(
mainchainInteroperabilityInternalMethod.verifyValidatorsUpdate(methodContext, ccu),
).rejects.toThrow('Total BFT weight exceeds maximum value.');
});

it('should reject if certificate threshold is too small', async () => {
const ccu = {
...ccuParams,
certificate: codec.encode(certificateSchema, defaultCertificate),
activeValidatorsUpdate: {
blsKeysUpdate: [
Buffer.from([0, 0, 0, 0]),
Buffer.from([0, 0, 0, 1]),
Buffer.from([0, 0, 3, 0]),
],
bftWeightsUpdate: [BigInt(1), BigInt(3), BigInt(4)],
// 7 corresponds to 0111
bftWeightsUpdateBitmap: Buffer.from([7]),
},
};
const existingKey = Buffer.from([0, 2, 3, 0]);
await chainValidatorsSubstore.set(methodContext, ccu.sendingChainID, {
activeValidators: [{ blsKey: existingKey, bftWeight: BigInt(2) }],
certificateThreshold: BigInt(1),
});
const newValidators = [
{ blsKey: Buffer.from([0, 0, 0, 0]), bftWeight: BigInt(1000000000000) },
{ blsKey: Buffer.from([0, 0, 0, 1]), bftWeight: BigInt(1000000000000) },
{ blsKey: Buffer.from([0, 0, 2, 0]), bftWeight: BigInt(1000000000000) },
];
jest.spyOn(utils, 'calculateNewActiveValidators').mockReturnValue(newValidators);

await expect(
mainchainInteroperabilityInternalMethod.verifyValidatorsUpdate(methodContext, ccu),
).rejects.toThrow('Certificate threshold is too small.');
});

it('should reject if certificate threshold is too large', async () => {
const ccu = {
...ccuParams,
certificate: codec.encode(certificateSchema, defaultCertificate),
activeValidatorsUpdate: {
blsKeysUpdate: [
Buffer.from([0, 0, 0, 0]),
Buffer.from([0, 0, 0, 1]),
Buffer.from([0, 0, 3, 0]),
],
bftWeightsUpdate: [BigInt(1), BigInt(3), BigInt(4)],
// 7 corresponds to 0111
bftWeightsUpdateBitmap: Buffer.from([7]),
},
};
const existingKey = Buffer.from([0, 2, 3, 0]);
await chainValidatorsSubstore.set(methodContext, ccu.sendingChainID, {
activeValidators: [{ blsKey: existingKey, bftWeight: BigInt(2) }],
certificateThreshold: BigInt(1),
});
const newValidators = [
{ blsKey: Buffer.from([0, 0, 0, 0]), bftWeight: BigInt(1) },
{ blsKey: Buffer.from([0, 0, 0, 1]), bftWeight: BigInt(1) },
{ blsKey: Buffer.from([0, 0, 2, 0]), bftWeight: BigInt(1) },
];
jest.spyOn(utils, 'calculateNewActiveValidators').mockReturnValue(newValidators);

await expect(
mainchainInteroperabilityInternalMethod.verifyValidatorsUpdate(methodContext, ccu),
).rejects.toThrow('Certificate threshold is too large.');
});

it('should reject if new validatorsHash does not match with certificate', async () => {
const ccu = {
...ccuParams,
Expand Down
Loading

0 comments on commit 0e77565

Please sign in to comment.