Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use BN for large uints #95

Merged
merged 2 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 86 additions & 64 deletions beaconChain/helpers/stateTransitionHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { keccakAsU8a } from "@polkadot/util-crypto";
import BN from "bn.js";

import {
ENTRY_EXIT_DELAY,
EPOCH_LENGTH, GENESIS_EPOCH, LATEST_BLOCK_ROOTS_LENGTH, LATEST_INDEX_ROOTS_LENGTH,
Expand All @@ -9,10 +11,15 @@ import {
} from "../constants";
import {
AttestationData,
BeaconState, Bytes32,
BeaconState,
bytes32,
EpochNumber,
Fork,
SlotNumber, Validator, ValidatorIndex,
SlotNumber,
uint24,
uint64,
Validator,
ValidatorIndex,
} from "../types";

type int = number;
Expand All @@ -24,7 +31,7 @@ type hash32 = Uint8Array;
* @returns {EpochNumber}
*/
export function slotToEpoch(slot: SlotNumber): EpochNumber {
return Math.floor(slot / EPOCH_LENGTH);
return slot.divn(EPOCH_LENGTH);
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -42,7 +49,7 @@ export function getCurrentEpoch(state: BeaconState): EpochNumber {
* @returns {SlotNumber}
*/
export function getEpochStartSlot(epoch: EpochNumber): SlotNumber {
return epoch * EPOCH_LENGTH;
return epoch.muln(EPOCH_LENGTH);
}

/**
Expand All @@ -52,7 +59,7 @@ export function getEpochStartSlot(epoch: EpochNumber): SlotNumber {
* @returns {boolean}
*/
export function isActiveValidator(validator: Validator, epoch: EpochNumber): boolean {
return validator.activationEpoch <= epoch && epoch < validator.exitEpoch;
return validator.activationEpoch.lte(epoch) && epoch.lt(validator.exitEpoch);
}

/**
Expand All @@ -62,9 +69,9 @@ export function isActiveValidator(validator: Validator, epoch: EpochNumber): boo
* @returns {ValidatorIndex[]}
*/
export function getActiveValidatorIndices(validators: Validator[], epoch: EpochNumber): ValidatorIndex[] {
return validators.reduce((accumulator: int[], validator: Validator, index: int) => {
return validators.reduce((accumulator: ValidatorIndex[], validator: Validator, index: int) => {
return isActiveValidator(validator, epoch)
? [...accumulator, index]
? [...accumulator, new BN(index)]
: accumulator;
}, []);
}
Expand All @@ -86,7 +93,7 @@ export function readUIntBE(array: Uint8Array, offset: number, byteLength: number
* @param {hash32} seed
* @returns {T[]} Returns the shuffled values with seed as entropy.
*/
function shuffle<T>(values: T[], seed: Bytes32): T[] {
function shuffle<T>(values: T[], seed: bytes32): T[] {
const valuesCount: int = values.length;
// Entropy is consumed from the seed in 3-byte (24 bit) chunks.
const randBytes: number = 3;
Expand Down Expand Up @@ -191,11 +198,11 @@ export function getEpochCommitteeCount(activeValidatorCount: int): int {
* @param {int} slot
* @returns {int[][]}
*/
export function getShuffling(seed: hash32, validators: Validator[], slot: int): int[][] {
export function getShuffling(seed: hash32, validators: Validator[], slot: SlotNumber): ValidatorIndex[][] {
// Normalizes slot to start of epoch boundary
slot -= slot % EPOCH_LENGTH;
const slotAtEpoch = slot.sub(slot.umod(new BN(EPOCH_LENGTH)));

const activeValidatorIndices = getActiveValidatorIndices(validators, slot);
const activeValidatorIndices = getActiveValidatorIndices(validators, slotAtEpoch);

const committeesPerSlot = getEpochCommitteeCount(activeValidatorIndices.length);

Expand Down Expand Up @@ -236,7 +243,7 @@ export function getCurrentEpochCommitteeCount(state: BeaconState): int {
* @returns {Number}
*/
export function getNextEpochCommitteeCount(state: BeaconState): int {
const nextActiveValidators = getActiveValidatorIndices(state.validatorRegistry, getCurrentEpoch(state) + 1);
const nextActiveValidators = getActiveValidatorIndices(state.validatorRegistry, getCurrentEpoch(state).addn(1));
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
return getEpochCommitteeCount(nextActiveValidators.length);
}

Expand All @@ -250,17 +257,17 @@ export function getNextEpochCommitteeCount(state: BeaconState): int {
function getCrosslinkCommitteesAtSlot(state: BeaconState, slot: SlotNumber, registryChange: boolean = false): Array<{ShardNumber, ValidatorIndex}> {
const epoch = slotToEpoch(slot);
const currentEpoch = getCurrentEpoch(state);
const previousEpoch = currentEpoch > GENESIS_EPOCH ? currentEpoch - 1 : currentEpoch;
const nextEpoch = currentEpoch + 1;
const previousEpoch = currentEpoch.gtn(GENESIS_EPOCH) ? currentEpoch.subn(1) : currentEpoch;
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
const nextEpoch = currentEpoch.addn(1);
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved

if (previousEpoch <= epoch && epoch <= nextEpoch) { throw new Error("Slot is too early!"); }

// variables
let committeesPerEpoch;
let seed;
let shufflingEpoch;
let shufflingStartShard;
let currentCommitteesPerEpoch;
let committeesPerEpoch: int;
let seed: bytes32;
let shufflingEpoch: EpochNumber;
let shufflingStartShard: uint64;
let currentCommitteesPerEpoch: int;

if (epoch === previousEpoch) {
committeesPerEpoch = getPreviousEpochCommitteeCount(state);
Expand All @@ -277,11 +284,11 @@ function getCrosslinkCommitteesAtSlot(state: BeaconState, slot: SlotNumber, regi
committeesPerEpoch = getNextEpochCommitteeCount(state);
shufflingEpoch = nextEpoch;

const epochsSinceLastRegistryUpdate = currentEpoch - state.validatorRegistryUpdateEpoch;
const epochsSinceLastRegistryUpdate: BN = currentEpoch.sub(state.validatorRegistryUpdateEpoch);
if (registryChange) {
seed = generateSeed(state, nextEpoch);
shufflingStartShard = (state.currentEpochStartShard + currentCommitteesPerEpoch) % SHARD_COUNT;
} else if (epochsSinceLastRegistryUpdate > 1 && isPowerOfTwo(epochsSinceLastRegistryUpdate)) {
shufflingStartShard = (state.currentEpochStartShard.addn(currentCommitteesPerEpoch)).umod(new BN(SHARD_COUNT));
} else if (epochsSinceLastRegistryUpdate.gtn(1) && isPowerOfTwo(epochsSinceLastRegistryUpdate)) {
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
seed = generateSeed(state, nextEpoch);
shufflingStartShard = state.currentEpochStartShard;
} else {
Expand All @@ -290,10 +297,10 @@ function getCrosslinkCommitteesAtSlot(state: BeaconState, slot: SlotNumber, regi
}
}

const shuffling = getShuffling(seed, state.validatorRegistry, shufflingEpoch);
const offset = slot % EPOCH_LENGTH;
const shuffling: ValidatorIndex[][] = getShuffling(seed, state.validatorRegistry, shufflingEpoch);
const offset: int = slot.umod(new BN(EPOCH_LENGTH)).toNumber();
const committeesPerSlot = Math.floor(committeesPerEpoch / EPOCH_LENGTH);
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved
const slotStartShard = (shufflingStartShard + committeesPerSlot * offset) % SHARD_COUNT;
const slotStartShard = shufflingStartShard.addn(committeesPerSlot * offset).umod(new BN(SHARD_COUNT));
GregTheGreek marked this conversation as resolved.
Show resolved Hide resolved

return Array.apply(null, Array(committeesPerSlot)).map((x, i) => {
return {
Expand All @@ -310,43 +317,43 @@ function getCrosslinkCommitteesAtSlot(state: BeaconState, slot: SlotNumber, regi
* @param {int} slot
* @returns {hash32}
*/
export function getBlockRoot(state: BeaconState, slot: int): Bytes32 {
export function getBlockRoot(state: BeaconState, slot: SlotNumber): bytes32 {
// Returns the block root at a recent ``slot``.
if (state.slot <= slot + LATEST_BLOCK_ROOTS_LENGTH) { throw new Error(); }
if (slot < state.slot) { throw new Error(); }
return state.latestBlockRoots[slot % LATEST_BLOCK_ROOTS_LENGTH];
if (state.slot.lte(slot.addn(LATEST_BLOCK_ROOTS_LENGTH))) { throw new Error(); }
if (slot.lt(state.slot)) { throw new Error(); }
return state.latestBlockRoots[slot.umod(new BN(LATEST_BLOCK_ROOTS_LENGTH)).toNumber()];
}

/**
* Return the randao mix at a recent epoch.
* @param {BeaconState} state
* @param {EpochNumber} epoch
* @returns {Bytes32}
* @returns {bytes32}
*/
export function getRandaoMix(state: BeaconState, epoch: EpochNumber): Bytes32 {
if (getCurrentEpoch(state) - LATEST_RANDAO_MIXES_LENGTH < epoch && epoch < getCurrentEpoch(state)) { throw new Error(""); }
return state.latestRandaoMixes[epoch % LATEST_RANDAO_MIXES_LENGTH];
export function getRandaoMix(state: BeaconState, epoch: EpochNumber): bytes32 {
if (getCurrentEpoch(state).subn(LATEST_RANDAO_MIXES_LENGTH).lt(epoch) && epoch.lt(getCurrentEpoch(state))) { throw new Error(""); }
return state.latestRandaoMixes[epoch.umod(new BN(LATEST_RANDAO_MIXES_LENGTH)).toNumber()];
}

/**
* Return the index root at a recent epoch.
* @param {BeaconState} state
* @param {EpochNumber} epoch
* @returns {Bytes32}
* @returns {bytes32}
*/
export function getActiveIndexRoot(state: BeaconState, epoch: EpochNumber): Bytes32 {
if (getCurrentEpoch(state) - LATEST_INDEX_ROOTS_LENGTH + ENTRY_EXIT_DELAY < epoch && epoch < getCurrentEpoch(state) + ENTRY_EXIT_DELAY) { throw new Error(""); }
return state.latestIndexRoots[epoch % LATEST_INDEX_ROOTS_LENGTH];
export function getActiveIndexRoot(state: BeaconState, epoch: EpochNumber): bytes32 {
if (getCurrentEpoch(state).subn(LATEST_INDEX_ROOTS_LENGTH + ENTRY_EXIT_DELAY).lt(epoch) && epoch.lt(getCurrentEpoch(state).addn(ENTRY_EXIT_DELAY))) { throw new Error(""); }
return state.latestIndexRoots[epoch.umod(new BN(LATEST_INDEX_ROOTS_LENGTH)).toNumber()];
}

/**
* Generate a seed for the given epoch.
* @param {BeaconState} state
* @param {EpochNumber} epoch
* @returns {Bytes32}
* @returns {bytes32}
*/
// TODO FINSIH
export function generateSeed(state: BeaconState, epoch: EpochNumber): Bytes32 {
export function generateSeed(state: BeaconState, epoch: EpochNumber): bytes32 {
// return hash(
// getRandaoMix(state, epoch - SEED_LOOKAHEAD) + getActiveIndexRoot(state, epoch))
// )
Expand All @@ -359,18 +366,18 @@ export function generateSeed(state: BeaconState, epoch: EpochNumber): Bytes32 {
* @param {int} slot
* @returns {int}
*/
export function getBeaconProposerIndex(state: BeaconState, slot: int): int {
export function getBeaconProposerIndex(state: BeaconState, slot: SlotNumber): int {
const firstCommittee = getCrosslinkCommitteesAtSlot(state, slot)[0].ValidatorIndex;
return firstCommittee[slot % firstCommittee.length];
return firstCommittee[slot.umod(new BN(firstCommittee.length)).toNumber()];
}

/**
* Merkleize values where the length of values is a power of two and return the Merkle root.
* @param {Bytes32[]} values
* @returns {Bytes32}
* @param {bytes32[]} values
* @returns {bytes32}
*/
// TODO finish
// export function merkleRoot(values: Bytes32[]): Bytes32 {
// export function merkleRoot(values: bytes32[]): bytes32 {
// let o: Uint8Array = new Uint8Array(values.length + 1);
// o.set(values, o.length - 1);
// }
Expand Down Expand Up @@ -401,11 +408,22 @@ export function getBeaconProposerIndex(state: BeaconState, slot: int): int {
* @param {int} value
* @returns {boolean}
*/
export function isPowerOfTwo(value: int): boolean {
if (value < 0) { throw new Error("Value is negative!"); }
if (value >= 2 ** 53) { throw new Error("Values greater or equal to 2 ** 53 are not supported yet!"); }
// TODO: Add support for values that are greater or equal to 2 ** 53
return value === 0 ? false : 2 ** Math.floor(Math.log2(value)) === value;
export function isPowerOfTwo(value: BN): boolean {
if (value.ltn(0)) {
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
throw new Error("Value is negative!");
} else if (value.eqn(0)) {
return false;
} else {
// a power of two has only one bit set
let hasBitSet: boolean = false;
for (let i = 0; i < value.bitLength(); i++) {
if (hasBitSet) {
return false;
}
hasBitSet = value.testn(i);
}
return true;
}
}

/**
Expand All @@ -416,7 +434,11 @@ export function isPowerOfTwo(value: int): boolean {
*/
export function getEffectiveBalance(state: BeaconState, index: int): int {
// Returns the effective balance (also known as "balance at stake") for a ``validator`` with the given ``index``.
return Math.min(state.validatorBalances[index], MAX_DEPOSIT_AMOUNT);
if (state.validatorBalances[index].ltn(MAX_DEPOSIT_AMOUNT)) {
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
return state.validatorBalances[index].toNumber();
} else {
return MAX_DEPOSIT_AMOUNT;
}
}

/**
Expand All @@ -425,8 +447,8 @@ export function getEffectiveBalance(state: BeaconState, index: int): int {
* @param {EpochNumber} epoch
* @returns {Number}
*/
export function getForkVersion(fork: Fork, epoch: EpochNumber): int {
return epoch < fork.epoch ? fork.previousVersion : fork.currentVersion;
export function getForkVersion(fork: Fork, epoch: EpochNumber): uint64 {
return epoch.lt(fork.epoch) ? fork.previousVersion : fork.currentVersion;
}

/**
Expand All @@ -436,8 +458,8 @@ export function getForkVersion(fork: Fork, epoch: EpochNumber): int {
* @param {int} domainType
* @returns {Number}
*/
export function getDomain(fork: Fork, epoch: EpochNumber, domainType: int): int {
return (getForkVersion(fork, epoch) * 2 ** 32) + domainType;
export function getDomain(fork: Fork, epoch: EpochNumber, domainType: int): uint64 {
return getForkVersion(fork, epoch).muln(2 ** 32).addn(domainType);
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
}

// TODO finish
Expand All @@ -461,9 +483,9 @@ export function getDomain(fork: Fork, epoch: EpochNumber, domainType: int): int
* @returns {boolean}
*/
export function isDoubleVote(attestationData1: AttestationData, attestationData2: AttestationData): boolean {
const targetEpoch1: int = slotToEpoch(attestationData1.slot);
const targetEpoch2: int = slotToEpoch(attestationData2.slot);
return targetEpoch1 === targetEpoch2;
const targetEpoch1: EpochNumber = slotToEpoch(attestationData1.slot);
const targetEpoch2: EpochNumber = slotToEpoch(attestationData2.slot);
return targetEpoch1.eq(targetEpoch2);
}

/**
Expand All @@ -473,13 +495,13 @@ export function isDoubleVote(attestationData1: AttestationData, attestationData2
* @returns {boolean}
*/
export function isSurroundVote(attestationData1: AttestationData, attestationData2: AttestationData): boolean {
const sourceEpoch1: int = attestationData1.justifiedEpoch;
const sourceEpoch2: int = attestationData2.justifiedEpoch;
const targetEpoch1: int = slotToEpoch(attestationData1.slot);
const targetEpoch2: int = slotToEpoch(attestationData2.slot);
const sourceEpoch1: EpochNumber = attestationData1.justifiedEpoch;
const sourceEpoch2: EpochNumber = attestationData2.justifiedEpoch;
const targetEpoch1: EpochNumber = slotToEpoch(attestationData1.slot);
const targetEpoch2: EpochNumber = slotToEpoch(attestationData2.slot);
return (
(sourceEpoch1 < sourceEpoch2) &&
(targetEpoch2 < targetEpoch1)
sourceEpoch1.lt(sourceEpoch2) &&
targetEpoch2.lt(targetEpoch1)
);
}

Expand All @@ -505,5 +527,5 @@ export function intSqrt(n: int): int {
* @returns {EpochNumber}
*/
export function getEntryExitEffectEpoch(epoch: EpochNumber): EpochNumber {
return epoch + 1 + ENTRY_EXIT_DELAY;
return epoch.addn(1 + ENTRY_EXIT_DELAY);
wemeetagain marked this conversation as resolved.
Show resolved Hide resolved
}
Loading