diff --git a/.swiftlint.yml b/.swiftlint.yml index c21031b..fbffd5d 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -3,11 +3,14 @@ included: - Tests excluded: - Tests/BeaconChainTests/XCTestManifests.swift + - Tests/LinuxMain.swift file_name: excluded: - Package.swift - - LinuxMain.swift identifier_name: excluded: - id - to + - x + - y + - i diff --git a/Makefile b/Makefile index 03575da..3f3e11c 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ lint: swiftlint documentation: - jazzy --author "yeeth" --author_url https://yeeth.af --github_url https://github.com/yeeth/BeaconChain.swift + jazzy --author "yeeth" --author_url https://yeeth.af --github_url https://github.com/yeeth/BeaconChain.swift rm -rf build/ xcode: diff --git a/README.md b/README.md index cac716c..3d08e4d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.com/yeeth/BeaconChain.swift.svg?branch=master)](https://travis-ci.com/yeeth/BeaconChain.swift) [![License](https://img.shields.io/github/license/yeeth/BeaconChain.swift.svg)](LICENSE) -Ethereum 2.0 beacon chain implementation based on the official [specification](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md). The implemented specification version is [bed88](https://github.com/ethereum/eth2.0-specs/tree/bed888810d5c99cd114adc9907c16268a2a285a9). +Ethereum 2.0 beacon chain implementation based on the official [specification](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md). The implemented specification version is [4ea79](https://github.com/ethereum/eth2.0-specs/tree/4ea79ee13b48c405c5a86cb3766af3ca0d3f6633). ## Contributing diff --git a/Sources/BeaconChain/BLS.swift b/Sources/BeaconChain/BLS.swift index ee4e9ea..c5ffd14 100644 --- a/Sources/BeaconChain/BLS.swift +++ b/Sources/BeaconChain/BLS.swift @@ -1,16 +1,18 @@ import Foundation +// @todo this will be in a seperate library + class BLS { - static func verify(pubkey: Data, message: Data, signature: Data, domain: UInt64) -> Bool { - return true + static func verify(pubkey: Data, hash: Hash, signature: Data, domain: Domain) -> Bool { + return false } - static func verify(pubkeys: [Data], messages: [Data], signature: Data, domain: UInt64) -> Bool { - return true + static func verify(pubkeys: [Data], hashes: [Hash], signature: Data, domain: Domain) -> Bool { + return false } static func aggregate(pubkeys: [Data]) -> Data { - return Data(count: 32) + fatalError("not yet implemented") } } diff --git a/Sources/BeaconChain/BeaconChain.swift b/Sources/BeaconChain/BeaconChain.swift index 305452a..c4a1350 100644 --- a/Sources/BeaconChain/BeaconChain.swift +++ b/Sources/BeaconChain/BeaconChain.swift @@ -1,461 +1,5 @@ import Foundation -// @todo figure out better name -// @todo refactor so this isn't all in one class +struct BeaconChain { -class BeaconChain { - - static func hash(_ data: Any) -> Data { - // @todo - return Data(count: 0) - } - - static func hashTreeRoot(_ data: Any) -> Data { - // @todo - return Data(count: 0) - } - - static func signedRoot(_ data: Any, field: String) -> Data { - // @todo - return Data(count: 0) - } - -} - -extension BeaconChain { - - static func getPreviousEpoch(state: BeaconState) -> Epoch { - return max(getCurrentEpoch(state: state) - 1, GENESIS_EPOCH) - } - - static func getCurrentEpoch(state: BeaconState) -> Epoch { - return state.slot.toEpoch() - } -} - -extension BeaconChain { - - // @todo check this shit - static func getPermutedIndex(index i: Int, listSize: Int, seed: Bytes32) -> Int { - assert(i < listSize) - assert(listSize < 2**40) - - var index = i - for round in 0..) -> Int in - return ptr.pointee - } % listSize - let flip = (pivot - index) % listSize - let position = max(index, flip) - - var positionBytes = position / 256 - let source = hash(seed + roundBytes + Data(bytes: &positionBytes, count: 4)) - - let byte = source[(position % 256) / 8] - let bit = (byte >> (position % 8)) % UInt8(2) - index = bit == 1 ? flip : index - } - - return index - } - - static func getShuffling(seed: Bytes32, validators: [Validator], epoch: Epoch) -> [[ValidatorIndex]] { - let activeValidatorIndices = validators.activeIndices(epoch: epoch) - - let shuffledActiveValidatorIndices = activeValidatorIndices.map { - activeValidatorIndices[getPermutedIndex(index: Int($0), listSize: activeValidatorIndices.count, seed: seed)] - } - - return shuffledActiveValidatorIndices.split(count: getEpochCommitteeCount(activeValidatorCount: activeValidatorIndices.count)) - } -} - -extension BeaconChain { - - static func getEpochCommitteeCount(activeValidatorCount: Int) -> Int { - return Int( - max( - 1, - min( - SHARD_COUNT / SLOTS_PER_EPOCH, - UInt64(activeValidatorCount) / SLOTS_PER_EPOCH / TARGET_COMMITTEE_SIZE - ) - ) * SLOTS_PER_EPOCH - ) - } - - static func getPreviousEpochCommitteeCount(state: BeaconState) -> Int { - let previousActiveValidators = state.validatorRegistry.activeIndices(epoch: state.previousShufflingEpoch) - return getEpochCommitteeCount(activeValidatorCount: previousActiveValidators.count) - } - - static func getCurrentEpochCommitteeCount(state: BeaconState) -> Int { - let currentActiveValidators = state.validatorRegistry.activeIndices(epoch: state.currentShufflingEpoch) - return getEpochCommitteeCount(activeValidatorCount: currentActiveValidators.count) - } - - static func getNextEpochCommitteeCount(state: BeaconState) -> Int { - let nextActiveValidators = state.validatorRegistry.activeIndices(epoch: getCurrentEpoch(state: state) + 1) - return getEpochCommitteeCount(activeValidatorCount: nextActiveValidators.count) - } - - static func crosslinkCommittees( - _ state: BeaconState, - at slot: Slot, - registryChange: Bool = false - ) -> [([ValidatorIndex], Shard)] { - let epoch = slot.toEpoch() - let currentEpoch = getCurrentEpoch(state: state) - let previousEpoch = getPreviousEpoch(state: state) - let nextEpoch = currentEpoch + 1 - - assert(previousEpoch <= epoch && epoch <= nextEpoch) - - var committeesPerEpoch: Int! - var seed: Data! - var shufflingEpoch: UInt64! - var shufflingStartShard: UInt64! - - switch epoch { - case currentEpoch: - committeesPerEpoch = getCurrentEpochCommitteeCount(state: state) - seed = state.currentShufflingSeed - shufflingEpoch = state.currentShufflingEpoch - shufflingStartShard = state.currentShufflingStartShard - case previousEpoch: - committeesPerEpoch = getPreviousEpochCommitteeCount(state: state) - seed = state.previousShufflingSeed - shufflingEpoch = state.previousShufflingEpoch - shufflingStartShard = state.previousShufflingStartShard - case nextEpoch: - let currentCommitteesPerEpoch = getCurrentEpochCommitteeCount(state: state) - committeesPerEpoch = getNextEpochCommitteeCount(state: state) - shufflingEpoch = nextEpoch - let epochsSinceLastRegistryUpdate = currentEpoch - state.validatorRegistryUpdateEpoch - if registryChange { - seed = generateSeed(state: state, epoch: nextEpoch) - shufflingStartShard = (state.currentShufflingStartShard + UInt64(currentCommitteesPerEpoch)) % SHARD_COUNT - } else if epochsSinceLastRegistryUpdate > 1 && Int(epochsSinceLastRegistryUpdate).isPowerOfTwo() { - seed = generateSeed(state: state, epoch: nextEpoch) - shufflingStartShard = state.currentShufflingStartShard - } else { - seed = state.currentShufflingSeed - shufflingStartShard = state.currentShufflingStartShard - } - default: - break - } - - let shuffling = getShuffling(seed: seed, validators: state.validatorRegistry, epoch: shufflingEpoch) - - let offset = slot % SLOTS_PER_EPOCH - let committeesPerSlot = UInt64(committeesPerEpoch) / SLOTS_PER_EPOCH - let slotStartShard = (shufflingStartShard + committeesPerSlot * offset) % SHARD_COUNT - - return (0.. Bytes32 { - assert(state.slot <= slot + LATEST_BLOCK_ROOTS_LENGTH) - assert(slot < state.slot) - return state.latestBlockRoots[Int(slot % LATEST_BLOCK_ROOTS_LENGTH)] - } - - static func getRandaoMix(state: BeaconState, epoch: Epoch) -> Bytes32 { - let currentEpoch = getCurrentEpoch(state: state) - assert(currentEpoch - LATEST_RANDAO_MIXES_LENGTH < epoch && epoch <= currentEpoch) - return state.latestRandaoMixes[Int(epoch % LATEST_RANDAO_MIXES_LENGTH)] - } - - static func getActiveIndexRoot(state: BeaconState, epoch: Epoch) -> Bytes32 { - let currentEpoch = getCurrentEpoch(state: state) - assert(currentEpoch - LATEST_ACTIVE_INDEX_ROOTS_LENGTH + ACTIVATION_EXIT_DELAY < epoch && epoch <= currentEpoch + ACTIVATION_EXIT_DELAY) - return state.latestActiveIndexRoots[Int(epoch % LATEST_ACTIVE_INDEX_ROOTS_LENGTH)] - } -} - -extension BeaconChain { - - static func generateSeed(state: BeaconState, epoch: Epoch) -> Data { - return hash( - getRandaoMix(state: state, epoch: epoch - MIN_SEED_LOOKAHEAD) + - getActiveIndexRoot(state: state, epoch: epoch) + - epoch.bytes32 - ) - } - - static func getBeaconProposerIndex(state: BeaconState, slot: Slot) -> ValidatorIndex { - let firstCommittee = crosslinkCommittees(state, at: slot)[0].0 - return firstCommittee[Int(slot) % firstCommittee.count] - } - - static func merkleRoot(values: [Bytes32]) -> Bytes32 { - var o = [Data](repeating: Data(repeating: 0, count: 1), count: values.count - 1) - o.append(contentsOf: values) - - for i in stride(from: values.count - 1, through: 0, by: -1) { - o[i] = hash(o[i * 2] + o[i * 2 + 1]) - } - - return o[1] - } - - static func getAttestationParticipants( - state: BeaconState, - attestationData: AttestationData, - bitfield: Data - ) -> [ValidatorIndex] { - let crosslinkCommittees = self.crosslinkCommittees(state, at: attestationData.slot) - - assert(crosslinkCommittees.map({ return $0.1 }).contains(attestationData.shard)) - - // @todo clean this ugly up - guard let crosslinkCommittee = crosslinkCommittees.first(where: { - $0.1 == attestationData.shard - })?.0 else { - assert(false) - } - - assert(verifyBitfield(bitfield: bitfield, committeeSize: crosslinkCommittee.count)) - - return crosslinkCommittee.enumerated().compactMap { - if getBitfieldBit(bitfield: bitfield, i: $0.offset) == 0b1 { - return $0.element - } - - return nil - } - } - - static func getEffectiveBalance(state: BeaconState, index: ValidatorIndex) -> Gwei { - return min(state.validatorBalances[Int(index)], MAX_DEPOSIT_AMOUNT) - } - - static func getBitfieldBit(bitfield: Data, i: Int) -> Int { - return Int((bitfield[i / 8] >> (i % 8))) % 2 - } - - static func verifyBitfield(bitfield: Data, committeeSize: Int) -> Bool { - if bitfield.count != (committeeSize + 7) / 8 { - return false - } - - for i in (committeeSize + 1)..<(bitfield.count * 8) { - if getBitfieldBit(bitfield: bitfield, i: i) == 0b1 { - return false - } - } - - return true - } -} - -extension BeaconChain { - - static func verifySlashableAttestation(state: BeaconState, slashableAttestation: SlashableAttestation) -> Bool { - if slashableAttestation.custodyBitfield != Data(repeating: 0, count: slashableAttestation.custodyBitfield.count) { - return false - } - - if slashableAttestation.validatorIndices.count == 0 { - return false - } - - for i in 0..<(slashableAttestation.validatorIndices.count - 1) { - if slashableAttestation.validatorIndices[i] >= slashableAttestation.validatorIndices[i + 1] { - return false - } - } - - if !verifyBitfield(bitfield: slashableAttestation.custodyBitfield, committeeSize: slashableAttestation.validatorIndices.count) { - return false - } - - if slashableAttestation.validatorIndices.count > MAX_INDICES_PER_SLASHABLE_VOTE { - return false - } - - var custodyBit0Indices = [UInt64]() - var custodyBit1Indices = [UInt64]() - - for (i, validatorIndex) in slashableAttestation.validatorIndices.enumerated() { - if getBitfieldBit(bitfield: slashableAttestation.custodyBitfield, i: i) == 0b0 { - custodyBit0Indices.append(validatorIndex) - } else { - custodyBit1Indices.append(validatorIndex) - } - } - - return BLS.verify( - pubkeys: [ - BLS.aggregate( - pubkeys: custodyBit0Indices.map { (i) in - return state.validatorRegistry[Int(i)].pubkey - } - ), - BLS.aggregate( - pubkeys: custodyBit1Indices.map { (i) in - return state.validatorRegistry[Int(i)].pubkey - } - ) - ], - messages: [ - hashTreeRoot(AttestationDataAndCustodyBit(data: slashableAttestation.data, custodyBit: false)), - hashTreeRoot(AttestationDataAndCustodyBit(data: slashableAttestation.data, custodyBit: true)), - ], - signature: slashableAttestation.aggregateSignature, - domain: state.fork.domain(epoch: slashableAttestation.data.slot.toEpoch(), type: .attestation) - ) - } - - static func isDoubleVote(_ left: AttestationData, _ right: AttestationData) -> Bool { - return left.slot.toEpoch() == right.slot.toEpoch() - } - - static func isSurroundVote(_ left: AttestationData, _ right: AttestationData) -> Bool { - return left.justifiedEpoch < right.justifiedEpoch && - right.slot.toEpoch() < left.slot.toEpoch() - } -} - -extension BeaconChain { - - static func getInitialBeaconState( - genesisValidatorDeposits: [Deposit], - genesisTime: UInt64, - latestEth1Data: Eth1Data - ) -> BeaconState { - - var state = genesisState( - genesisTime: genesisTime, - latestEth1Data: latestEth1Data, - depositLength: genesisValidatorDeposits.count - ) - - for deposit in genesisValidatorDeposits { - processDeposit(state: &state, deposit: deposit) - } - - for (i, _) in state.validatorRegistry.enumerated() { - if getEffectiveBalance(state: state, index: ValidatorIndex(i)) >= MAX_DEPOSIT_AMOUNT { - state.validatorRegistry[i].activate(state: state, genesis: true) - } - } - - let genesisActiveIndexRoot = hashTreeRoot(state.validatorRegistry.activeIndices(epoch: GENESIS_EPOCH)) - - for i in 0.. BeaconState { - return BeaconState( - slot: GENESIS_SLOT, - genesisTime: genesisTime, - fork: Fork( - previousVersion: GENESIS_FORK_VERSION, - currentVersion: GENESIS_FORK_VERSION, - epoch: GENESIS_EPOCH - ), - validatorRegistry: [Validator](), - validatorBalances: [UInt64](), - validatorRegistryUpdateEpoch: GENESIS_EPOCH, - latestRandaoMixes: [Data](repeating: EMPTY_SIGNATURE, count: Int(LATEST_RANDAO_MIXES_LENGTH)), - previousShufflingStartShard: GENESIS_START_SHARD, - currentShufflingStartShard: GENESIS_START_SHARD, - previousShufflingEpoch: GENESIS_EPOCH, - currentShufflingEpoch: GENESIS_EPOCH, - previousShufflingSeed: ZERO_HASH, - currentShufflingSeed: ZERO_HASH, - previousJustifiedEpoch: GENESIS_EPOCH, - justifiedEpoch: GENESIS_EPOCH, - justificationBitfield: 0, - finalizedEpoch: GENESIS_EPOCH, - latestCrosslinks: [Crosslink](repeating: Crosslink(epoch: GENESIS_EPOCH, crosslinkDataRoot: ZERO_HASH), count: Int(SHARD_COUNT)), - latestBlockRoots: [Data](repeating: ZERO_HASH, count: Int(LATEST_BLOCK_ROOTS_LENGTH)), - latestActiveIndexRoots: [Data](repeating: ZERO_HASH, count: Int(LATEST_ACTIVE_INDEX_ROOTS_LENGTH)), - latestSlashedBalances: [UInt64](repeating: 0, count: Int(LATEST_SLASHED_EXIT_LENGTH)), - latestAttestations: [PendingAttestation](), - batchedBlockRoots: [Data](), - latestEth1Data: latestEth1Data, - eth1DataVotes: [Eth1DataVote](), - depositIndex: UInt64(depositLength) - ) - } -} - -extension BeaconChain { - - static func processDeposit(state: inout BeaconState, deposit: Deposit) { - let depositInput = deposit.depositData.depositInput - - let proofIsValid = BLS.verify( - pubkey: depositInput.pubkey, - message: BeaconChain.signedRoot(depositInput, field: "proofOfPossession"), - signature: depositInput.proofOfPossession, - domain: state.fork.domain(epoch: BeaconChain.getCurrentEpoch(state: state), type: .deposit) - ) - - if !proofIsValid { - return - } - - let pubkey = depositInput.pubkey - let amount = deposit.depositData.amount - let withdrawalCredentials = depositInput.withdrawalCredentials - - if let index = state.validatorRegistry.firstIndex(where: { $0.pubkey == pubkey }) { - assert(state.validatorRegistry[index].withdrawalCredentials == withdrawalCredentials) - state.validatorBalances[index] += amount - } else { - let validator = Validator( - pubkey: pubkey, - withdrawalCredentials: withdrawalCredentials, - activationEpoch: FAR_FUTURE_EPOCH, - exitEpoch: FAR_FUTURE_EPOCH, - withdrawableEpoch: FAR_FUTURE_EPOCH, - initiatedExit: false, - slashed: false - ) - - state.validatorRegistry.append(validator) - state.validatorBalances.append(amount) - } - } -} - -extension BeaconChain { - - static func slashValidator(state: inout BeaconState, index: ValidatorIndex) { - assert(state.slot < state.validatorRegistry[Int(index)].withdrawableEpoch.startSlot()) - state.validatorRegistry[Int(index)].exit(state: state) - - state.latestSlashedBalances[Int(getCurrentEpoch(state: state) % LATEST_SLASHED_EXIT_LENGTH)] += getEffectiveBalance(state: state, index: index) - - let whistleblowerIndex = getBeaconProposerIndex(state: state, slot: state.slot) - let whistleblowerReward = getEffectiveBalance(state: state, index: index) / WHISTLEBLOWER_REWARD_QUOTIENT - - state.validatorBalances[Int(whistleblowerIndex)] += whistleblowerReward - state.validatorBalances[Int(index)] -= whistleblowerReward - - let currentEpoch = getCurrentEpoch(state: state) - state.validatorRegistry[Int(index)].slashed = true - state.validatorRegistry[Int(index)].withdrawableEpoch = currentEpoch + LATEST_SLASHED_EXIT_LENGTH - } } diff --git a/Sources/BeaconChain/Configuration.swift b/Sources/BeaconChain/Configuration.swift new file mode 100644 index 0000000..8dd48b3 --- /dev/null +++ b/Sources/BeaconChain/Configuration.swift @@ -0,0 +1,55 @@ +import Foundation + +// misc +let SHARD_COUNT = 2**10 +let TARGET_COMMITTEE_SIZE = 2**7 +let MAX_VALIDATORS_PER_COMMITTEE = 2**12 +let MIN_PER_EPOCH_CHURN_LIMIT = 2**2 +let CHURN_LIMIT_QUOTIENT = 2**16 +let SHUFFLE_ROUND_COUNT = 90 +let MIN_GENESIS_ACTIVE_VALIDATOR_COUNT = 2**16 +let MIN_GENESIS_TIME = 1578009600 + +// gwei values +let MIN_DEPOSIT_AMOUNT = Gwei(2**0 * 10**9) +let MAX_EFFECTIVE_BALANCE = Gwei(2**5 * 10**9) +let EJECTION_BALANCE = Gwei(2**4 * 10**9) +let EFFECTIVE_BALANCE_INCREMENT = Gwei(2**0 * 10**9) + +// initial values +let GENESIS_SLOT = Slot(0) +let GENESIS_EPOCH = Epoch(0) +let BLS_WITHDRAWAL_PREFIX = 0b00 + +// time parameters +let MIN_ATTESTATION_INCLUSION_DELAY = 2**0 +let SLOTS_PER_EPOCH = 2**6 +let MIN_SEED_LOOKAHEAD = 2**0 +let ACTIVATION_EXIT_DELAY = 2**2 +let SLOTS_PER_ETH1_VOTING_PERIOD = 2**10 +let SLOTS_PER_HISTORICAL_ROOT = 2**13 +let MIN_VALIDATOR_WITHDRAWABILITY_DELAY = 2**8 +let PERSISTENT_COMMITTEE_PERIOD = 2**11 +let MAX_EPOCHS_PER_CROSSLINK = 2**6 +let MIN_EPOCHS_TO_INACTIVITY_PENALTY = 2**2 + +// state list lengths +let EPOCHS_PER_HISTORICAL_VECTOR = 2**16 +let EPOCHS_PER_SLASHINGS_VECTOR = 2**13 +let HISTORICAL_ROOTS_LIMIT = 2**14 +let VALIDATOR_REGISTRY_LIMIT = 2**40 + +// rewards and penalties +let BASE_REWARD_FACTOR = 2**6 +let WHISTLEBLOWER_REWARD_QUOTIENT = 2**9 +let PROPOSER_REWARD_QUOTIENT = 2**3 +let INACTIVITY_PENALTY_QUOTIENT = 2**25 +let MIN_SLASHING_PENALTY_QUOTIENT = 2**5 + +// max operations per block +let MAX_PROPOSER_SLASHINGS = 2**4 +let MAX_ATTESTER_SLASHINGS = 2**0 +let MAX_ATTESTATIONS = 2**7 +let MAX_DEPOSITS = 2**4 +let MAX_VOLUNTARY_EXITS = 2**4 +let MAX_TRANSFERS = 0 diff --git a/Sources/BeaconChain/Constants.swift b/Sources/BeaconChain/Constants.swift index d5a8a0c..dc1f04d 100644 --- a/Sources/BeaconChain/Constants.swift +++ b/Sources/BeaconChain/Constants.swift @@ -1,60 +1,8 @@ import Foundation -// Misc -let SHARD_COUNT = UInt64(2**10) -let TARGET_COMMITTEE_SIZE = UInt64(2**7) -let MAX_BALANCE_CHURN_QUOTIENT = UInt64(2**5) -let BEACON_CHAIN_SHARD_NUMBER = UInt64.max -let MAX_INDICES_PER_SLASHABLE_VOTE = UInt64(2**12) -let MAX_EXIT_DEQUEUES_PER_EPOCH = UInt64(2**2) -let SHUFFLE_ROUND_COUNT = 90 - -// Deposit Contract -//let DEPOSIT_CONTRACT_ADDRESS = -let DEPOSIT_CONTRACT_TREE_DEPTH = UInt64(2**5) - -// GWEI Values -let MIN_DEPOSIT_AMOUNT = UInt64(2**0 * UInt64(1e9)) -let MAX_DEPOSIT_AMOUNT = UInt64(2**5 * UInt64(1e9)) -let FORK_CHOICE_BALANCE_INCREMENT = UInt64(2**0 * UInt64(1e9)) -let EJECTION_BALANCE = UInt64(2**4 * UInt64(1e9)) - -// Initial Values -let GENESIS_FORK_VERSION = UInt64(0) -let GENESIS_SLOT = UInt64(2**32) -let GENESIS_EPOCH = Slot(GENESIS_SLOT).toEpoch() -let GENESIS_START_SHARD = UInt64(0) -let FAR_FUTURE_EPOCH = UInt64.max -let ZERO_HASH = Data(repeating: 0, count: 32) // @todo create type -let EMPTY_SIGNATURE = Data(repeating: 0, count: 96) -let BLS_WITHDRAWAL_PREFIX_BYTE = Data(repeating: 0, count: 1) - -// Time parameters -let SECONDS_PER_SLOT = UInt64(6) -let MIN_ATTESTATION_INCLUSION_DELAY = UInt64(2**2) -let SLOTS_PER_EPOCH = UInt64(2**6) -let MIN_SEED_LOOKAHEAD = UInt64(2**0) -let ACTIVATION_EXIT_DELAY = UInt64(2**2) -let EPOCHS_PER_ETH1_VOTING_PERIOD = UInt64(2**4) -let MIN_VALIDATOR_WITHDRAWABILITY_DELAY = UInt64(2**8) - -// State list lengths -let LATEST_BLOCK_ROOTS_LENGTH = UInt64(2**13) -let LATEST_RANDAO_MIXES_LENGTH = UInt64(2**13) -let LATEST_ACTIVE_INDEX_ROOTS_LENGTH = UInt64(2**13) -let LATEST_SLASHED_EXIT_LENGTH = UInt64(2**13) - -// Reward and penalty quotients -let BASE_REWARD_QUOTIENT = UInt64(2**5) -let WHISTLEBLOWER_REWARD_QUOTIENT = UInt64(2**9) -let ATTESTATION_INCLUSION_REWARD_QUOTIENT = UInt64(2**3) -let INACTIVITY_PENALTY_QUOTIENT = UInt64(2**24) -let MIN_PENALTY_QUOTIENT = UInt64(2**5) - -// Max transactions per block -let MAX_PROPOSER_SLASHINGS = UInt64(2**4) -let MAX_ATTESTER_SLASHINGS = UInt64(2**0) -let MAX_ATTESTATIONS = UInt64(2**7) -let MAX_DEPOSITS = UInt64(2**4) -let MAX_VOLUNTARY_EXITS = UInt64(2**4) -let MAX_TRANSFERS = UInt64(2**4) +let FAR_FUTURE_EPOCH = Epoch(UInt64.max) +let BASE_REWARDS_PER_EPOCH = 5 +let DEPOSIT_CONTRACT_TREE_DEPTH = 2**5 +let SECONDS_PER_DAY = 86400 +let JUSTIFICATION_BITS_LENGTH = 4 +let ENDIANNESS = "little" diff --git a/Sources/BeaconChain/Crypto.swift b/Sources/BeaconChain/Crypto.swift new file mode 100644 index 0000000..e605d4a --- /dev/null +++ b/Sources/BeaconChain/Crypto.swift @@ -0,0 +1,18 @@ +import Foundation + +// @todo this will be in a seperate library + +class SSZ { + + static func hash(_ data: Data) -> Hash { + fatalError("not yet implemented") + } + + static func hashTreeRoot(_ data: Any) -> Hash { + fatalError("not yet implemented") + } + + static func signingRoot(_ data: Any) -> Hash { + fatalError("not yet implemented") + } +} diff --git a/Sources/BeaconChain/DataStructures/Attestation/AttestationData.swift b/Sources/BeaconChain/DataStructures/Attestation/AttestationData.swift new file mode 100644 index 0000000..55883b2 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Attestation/AttestationData.swift @@ -0,0 +1,26 @@ +import Foundation + +/// AttestationData is the main component that is signed by each validator. +public struct AttestationData: Equatable { + + /// Block root of the beacon block seen as the head of the chain at the assigned slot. + public let beaconBlockRoot: Hash + + /// The most recent justified checkpoint in the BeaconState at the assigned slot. + public let source: Checkpoint + + /// The checkpoint attempting to be justified (the current epoch and epoch boundary block). + public let target: Checkpoint + + /// The crosslink attempting to be formed for the assigned shard. + public let crosslink: Crosslink + + /// Check if ``self`` and ``data`` are slashable according to Casper FFG rules. + /// + /// - Parameters + /// - data: `AttestationData` to compare to. + func isSlashable(_ data: AttestationData) -> Bool { + return (self != data && target.epoch == data.target.epoch) + && (source.epoch < data.source.epoch && data.target.epoch < target.epoch) + } +} diff --git a/Sources/BeaconChain/DataStructures/Attestation/AttestationDataAndCustodyBit.swift b/Sources/BeaconChain/DataStructures/Attestation/AttestationDataAndCustodyBit.swift new file mode 100644 index 0000000..0a7b5de --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Attestation/AttestationDataAndCustodyBit.swift @@ -0,0 +1,7 @@ +import Foundation + +/// The actual message signed by validators. +public struct AttestationDataAndCustodyBit { + public let data: AttestationData + public let custodyBit: Bool // @todo probably not the best? +} diff --git a/Sources/BeaconChain/DataStructures/Attestation/IndexedAttestation.swift b/Sources/BeaconChain/DataStructures/Attestation/IndexedAttestation.swift new file mode 100644 index 0000000..ef39a97 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Attestation/IndexedAttestation.swift @@ -0,0 +1,8 @@ +import Foundation + +public struct IndexedAttestation { + let custodyBit0Indices: [ValidatorIndex] + let custodyBit1Indices: [ValidatorIndex] + let data: AttestationData + let signature: BLSSignature +} diff --git a/Sources/BeaconChain/DataStructures/Attestation/PendingAttestation.swift b/Sources/BeaconChain/DataStructures/Attestation/PendingAttestation.swift new file mode 100644 index 0000000..20b66ef --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Attestation/PendingAttestation.swift @@ -0,0 +1,8 @@ +import Foundation + +struct PendingAttestation { + let aggregationBits: [Bool] + let data: AttestationData + let inclusionDelay: Slot + let proposerIndex: ValidatorIndex +} diff --git a/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlock.swift b/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlock.swift new file mode 100644 index 0000000..cbb853e --- /dev/null +++ b/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlock.swift @@ -0,0 +1,20 @@ +import Foundation + +/// This can be thought of as the main container/header for a beacon block. +public struct BeaconBlock { + + /// The slot for which this block is created. Must be greater than the slot of the block defined by `parentRoot`. + public let slot: Slot + + /// The block root of the parent block, forming a block chain. + public let parentRoot: Hash + + /// The hash root of the post state of running the state transition through this block. + public let stateRoot: Hash + + /// Contains all of the aforementioned beacon operations objects as well as a few supplemental fields. + public let body: BeaconBlockBody + + /// Signature of the block by the block proposer. + public let signature: BLSSignature +} diff --git a/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlockBody.swift b/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlockBody.swift new file mode 100644 index 0000000..5fcc309 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlockBody.swift @@ -0,0 +1,21 @@ +import Foundation + +public struct BeaconBlockBody { + + /// The signature of the current epoch (by the block proposer) and, + /// when mixed in with the other validators’ reveals, consitutes the seed for randomness. + public let randaoReveal: BLSSignature + + /// A vote on recent data the Eth1 chain. + public let eth1Data: Eth1Data + + /// This is a space for validators to decorate as they choose. It has no define in-protocol use. + public let graffiti: Data + + public let proposerSlashings: [ProposerSlashing] + public let attesterSlashings: [AttesterSlashing] + public let attestations: [Attestation] + public let deposits: [Deposit] + public let voluntaryExits: [VoluntaryExit] + public let transfers: [Transfer] +} diff --git a/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlockHeader.swift b/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlockHeader.swift new file mode 100644 index 0000000..36c8935 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/BeaconBlock/BeaconBlockHeader.swift @@ -0,0 +1,20 @@ +import Foundation + +/// The Header of a Beacon Block. +public struct BeaconBlockHeader { + + /// The slot for which this block is created. Must be greater than the slot of the block defined by `parentRoot`. + public let slot: Slot + + /// The block root of the parent block, forming a block chain. + public let parentRoot: Hash + + /// The hash root of the post state of running the state transition through this block. + public let stateRoot: Hash + + /// The hash root of the block body. + let bodyRoot: Hash + + /// Signature of the block by the block proposer. + public let signature: BLSSignature +} diff --git a/Sources/BeaconChain/DataStructures/BeaconState.swift b/Sources/BeaconChain/DataStructures/BeaconState.swift new file mode 100644 index 0000000..3389193 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/BeaconState.swift @@ -0,0 +1,101 @@ +import Foundation + +struct BeaconState { + let genesisTime: UInt64 + let slot: Slot + let fork: Fork + let latestBlockHeader: BeaconBlockHeader + let blockRoots: [Hash] + let stateRoots: [Hash] + let historicalRoots: [Hash] + let eth1Data: Eth1Data + let eth1DataVotes: [Eth1Data] + let eth1DepositIndex: UInt64 + let validators: [Validator] + let balances: [Gwei] + let startShard: Shard + let randaoMixes: [Hash] + let activeIndexRoots: [Hash] + let compactCommitteesRoots: [Hash] + let slashings: [Gwei] + let previousEpochAttestations: [PendingAttestation] + let currentEpochAttestations: [PendingAttestation] + let previousCrosslinks: [Crosslink] + let currentCrosslinks: [Crosslink] + let justificationBits: [Bool] + let previousJustifiedCheckpoint: Checkpoint + let currentJustifiedCheckpoint: Checkpoint + let finalizedCheckpoint: Checkpoint + + /// Check if `indexed_attestation` has valid indices and signature. + /// + /// - Parameters: + /// - indexedAttestation: The attestation to check. + func isValid(indexedAttestation attestation: IndexedAttestation) -> Bool { + let bit0Indices = attestation.custodyBit0Indices + let bit1Indices = attestation.custodyBit1Indices + + if bit1Indices.count == 0 { + return false + } + + if !(bit0Indices.count + bit1Indices.count <= MAX_VALIDATORS_PER_COMMITTEE) { + return false + } + + if Set(bit0Indices).intersection(Set(bit1Indices)).count != 0 { + return false + } + + if bit0Indices != bit0Indices.sorted() || bit1Indices != bit1Indices.sorted() { + return false + } + + return BLS.verify( + pubkeys: [ + BLS.aggregate( + pubkeys: bit0Indices.map { (i) in + return validators[Int(i)].pubkey + } + ), + BLS.aggregate( + pubkeys: bit1Indices.map { (i) in + return validators[Int(i)].pubkey + } + ), + ], + hashes: [ + SSZ.hashTreeRoot(AttestationDataAndCustodyBit(data: attestation.data, custodyBit: false)), + SSZ.hashTreeRoot(AttestationDataAndCustodyBit(data: attestation.data, custodyBit: true)), + ], + signature: attestation.signature, + domain: getDomain(type: DomainType.attestation, epoch: attestation.data.target.epoch) + ) + } + + /// Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. + /// + /// - Parameters + /// - leaf: The leaf to check for. + /// - branch: The merkle branch. + /// - depth: The depth of the tree. + /// - index: The index of the leaf. + /// - root: The merkle root. + static func isValidMerkleBranch(leaf: Hash, branch: [Hash], depth: Int, index: Int, root: Hash) -> Bool { + var value = leaf + + for i in stride(from: 0, through: depth, by: 1) { + if index / (2**i) % 2 == 0 { + value = SSZ.hash(value + branch[i]) + } else { + value = SSZ.hash(branch[i] + value) + } + } + + return value == root + } + + func getDomain(type: DomainType, epoch: Epoch) -> Domain { + + } +} diff --git a/Sources/BeaconChain/DataStructures/Blocks/BeaconBlock.swift b/Sources/BeaconChain/DataStructures/Blocks/BeaconBlock.swift deleted file mode 100644 index 01903cc..0000000 --- a/Sources/BeaconChain/DataStructures/Blocks/BeaconBlock.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public struct BeaconBlock: Equatable { - let slot: UInt64 - let parentRoot: Data - let stateRoot: Data - let randaoReveal: Data - let eth1Data: Eth1Data - let body: BeaconBlockBody - var signature: Data -} diff --git a/Sources/BeaconChain/DataStructures/Blocks/BeaconBlockBody.swift b/Sources/BeaconChain/DataStructures/Blocks/BeaconBlockBody.swift deleted file mode 100644 index 2936799..0000000 --- a/Sources/BeaconChain/DataStructures/Blocks/BeaconBlockBody.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -struct BeaconBlockBody: Equatable { - let proposerSlashings: [ProposerSlashing] - let attesterSlashings: [AttesterSlashing] - let attestations: [Attestation] - let deposits: [Deposit] - let voluntaryExits: [VoluntaryExit] - let transfers: [Transfer] -} diff --git a/Sources/BeaconChain/DataStructures/Blocks/ProposalSignedData.swift b/Sources/BeaconChain/DataStructures/Blocks/ProposalSignedData.swift deleted file mode 100644 index 56f1c31..0000000 --- a/Sources/BeaconChain/DataStructures/Blocks/ProposalSignedData.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -struct Proposal: Equatable { - let slot: UInt64 - let shard: UInt64 - let blockRoot: Data - let signature: Data -} diff --git a/Sources/BeaconChain/DataStructures/Checkpoint.swift b/Sources/BeaconChain/DataStructures/Checkpoint.swift new file mode 100644 index 0000000..2608461 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Checkpoint.swift @@ -0,0 +1,6 @@ +import Foundation + +public struct Checkpoint: Equatable { + let epoch: Epoch + let root: Hash +} diff --git a/Sources/BeaconChain/DataStructures/CompactCommittee.swift b/Sources/BeaconChain/DataStructures/CompactCommittee.swift new file mode 100644 index 0000000..5d7820e --- /dev/null +++ b/Sources/BeaconChain/DataStructures/CompactCommittee.swift @@ -0,0 +1,6 @@ +import Foundation + +struct CompactCommittee { + let pubkeys: [BLSPubKey] + let compactValidators: [UInt64] +} diff --git a/Sources/BeaconChain/DataStructures/Crosslink.swift b/Sources/BeaconChain/DataStructures/Crosslink.swift new file mode 100644 index 0000000..06f6415 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Crosslink.swift @@ -0,0 +1,9 @@ +import Foundation + +public struct Crosslink: Equatable { + let shard: Shard + let parentRoot: Hash + let startEpoch: Epoch + let endEpoch: Epoch + let dataRoot: Hash +} diff --git a/Sources/BeaconChain/DataStructures/DepositData.swift b/Sources/BeaconChain/DataStructures/DepositData.swift new file mode 100644 index 0000000..7f26969 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/DepositData.swift @@ -0,0 +1,18 @@ +import Foundation + +/// The DepositData submit to the deposit contract to be verified using the proof against the deposit root. +public struct DepositData { + + /// BLS12-381 pubkey to be used to sign messages by the validator. + public let pubkey: BLSPubKey + + /// The hash of an offline pubkey to be used to withdraw the staked funds after exiting. + /// This key is not used actively in validation and can be kept in cold storage. + public let withdrawalCredentials: Hash + + /// Amount in Gwei that was deposited. + public let amount: Gwei + + /// This is used as a one-time “proof of custody” required for securely using BLS keys. + public let signature: BLSSignature +} diff --git a/Sources/BeaconChain/DataStructures/Eth1Data.swift b/Sources/BeaconChain/DataStructures/Eth1Data.swift new file mode 100644 index 0000000..7eacaab --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Eth1Data.swift @@ -0,0 +1,16 @@ +import Foundation + +/// A vote on recent data the Eth1 chain. +public struct Eth1Data { + + /// The SSZ List hash_tree_root of the deposits in the deposit contract. + public let depositRoot: Hash + + /// The number of deposits that have occured thusfar. + public let depositCount: UInt64 + + /// The eth1 block hash that contained the deposit_root. This block_hash + /// might be used for finalization of the Eth1 chain in the future (similar to how the FFG contract was going to + /// be used to finalize the eth1 chain). + public let blockHash: Hash +} diff --git a/Sources/BeaconChain/DataStructures/Fork.swift b/Sources/BeaconChain/DataStructures/Fork.swift new file mode 100644 index 0000000..322d619 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Fork.swift @@ -0,0 +1,7 @@ +import Foundation + +struct Fork { + let previousVersion: Version + let currentVersion: Version + let epoch: Epoch +} diff --git a/Sources/BeaconChain/DataStructures/HistoricalBatch.swift b/Sources/BeaconChain/DataStructures/HistoricalBatch.swift new file mode 100644 index 0000000..d2c42a0 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/HistoricalBatch.swift @@ -0,0 +1,6 @@ +import Foundation + +struct HistoricalBatch { + let blockRoots: [Hash] + let stateRoots: [Hash] +} diff --git a/Sources/BeaconChain/DataStructures/Operations/Attestation.swift b/Sources/BeaconChain/DataStructures/Operations/Attestation.swift new file mode 100644 index 0000000..d83bc25 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Operations/Attestation.swift @@ -0,0 +1,18 @@ +import Foundation + +/// Primary message type that validators create for consensus. +public struct Attestation { + + /// Stores a single bit for each member of the committee assigning a value of 1 to each validator that participated + /// in this aggregate signature. + public let aggregationBits: [Bool] + + /// The AttestationData that was signed by the validator or committee of validators. + public let data: AttestationData + + /// Represents each committee member’s “proof of custody” bit (0 if non-participating). + public let custodyBits: [Bool] + + /// The aggregate BLS signature of the data. + public let signature: BLSSignature +} diff --git a/Sources/BeaconChain/DataStructures/Operations/AttesterSlashing.swift b/Sources/BeaconChain/DataStructures/Operations/AttesterSlashing.swift new file mode 100644 index 0000000..cfe53ea --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Operations/AttesterSlashing.swift @@ -0,0 +1,12 @@ +import Foundation + +/// Beacon attesters can be slashed if they sign two conflicting attestations where conflicting is defined by +/// is_slashable_attestation_data which checks for the Casper FFG “double” and “surround” vote conditions. +public struct AttesterSlashing { + + /// The first of the two slashable attestations. Note that this is in “indexed” form. + public let attestation1: IndexedAttestation + + /// The first of the two slashable attestations. Note that this is in “indexed” form. + public let attestation2: IndexedAttestation +} diff --git a/Sources/BeaconChain/DataStructures/Operations/Deposit.swift b/Sources/BeaconChain/DataStructures/Operations/Deposit.swift new file mode 100644 index 0000000..c102b1c --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Operations/Deposit.swift @@ -0,0 +1,11 @@ +import Foundation + +/// Represents incoming validator deposits from the eth1 chain deposit contract. +public struct Deposit { + + /// The merkle proof against the BeaconState current Eth1Data.root + public let proof: [Hash] + + /// The DepositData submit to the deposit contract to be verified using the proof against the deposit root. + public let data: DepositData +} diff --git a/Sources/BeaconChain/DataStructures/Operations/ProposerSlashing.swift b/Sources/BeaconChain/DataStructures/Operations/ProposerSlashing.swift new file mode 100644 index 0000000..bedfe8f --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Operations/ProposerSlashing.swift @@ -0,0 +1,15 @@ +import Foundation + +/// Beacon block proposers can be slashed if they signed two different beacon blocks for the same epoch. +/// ProposerSlashing contains proof that such a slashable offense has occurred. +public struct ProposerSlashing { + + /// `ValidatorIndex` of the validator to be slashed for double proposing. + public let proposerIndex: ValidatorIndex + + /// The header of the first of the two slashable beacon blocks. + public let header1: BeaconBlockHeader + + /// The header of the second of the two slashable beacon blocks. + public let header2: BeaconBlockHeader +} diff --git a/Sources/BeaconChain/DataStructures/Operations/Transfer.swift b/Sources/BeaconChain/DataStructures/Operations/Transfer.swift new file mode 100644 index 0000000..0d6f0dd --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Operations/Transfer.swift @@ -0,0 +1,26 @@ +import Foundation + +/// Allows validators to transfer balances. +public struct Transfer { + + /// Validator index of the sender of funds. + public let sender: ValidatorIndex + + /// Validator index of the recipient of funds. + public let recipient: ValidatorIndex + + /// Amount in Gwei to send. + public let amount: Gwei + + /// Fee in Gwei to be paid to the block proposer for including the transfer. + public let fee: Gwei + + /// The specific slot that this signed `Transfer` can be included on chain. prevents replay attacks. + public let slot: Slot + + /// The withdrawal pubkey of the sender. The hash of this pubkey must match the senders withdrawalCredentials. + public let pubkey: BLSPubKey + + /// The signature of the `Transfer` signed by the `transfer.pubkey`. + public let signature: BLSSignature +} diff --git a/Sources/BeaconChain/DataStructures/Operations/VoluntaryExit.swift b/Sources/BeaconChain/DataStructures/Operations/VoluntaryExit.swift new file mode 100644 index 0000000..1c52057 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Operations/VoluntaryExit.swift @@ -0,0 +1,15 @@ +import Foundation + +/// Message type that allows a validator to voluntarily exit validation duties. +public struct VoluntaryExit { + + /// Minimum epoch at which this exit can be included on chain. + /// Helps prevent accidental/nefarious use in chain reorgs/forks. + public let epoch: Epoch + + /// Index of validator exiting. + public let validatorIndex: ValidatorIndex + + /// Signature of the `VoluntaryExit` by the pubkey associated with the + public let signature: BLSSignature +} diff --git a/Sources/BeaconChain/DataStructures/State/BeaconState.swift b/Sources/BeaconChain/DataStructures/State/BeaconState.swift deleted file mode 100644 index 1a36ae7..0000000 --- a/Sources/BeaconChain/DataStructures/State/BeaconState.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation - -struct BeaconState { - var slot: UInt64 - let genesisTime: UInt64 - let fork: Fork - - var validatorRegistry: [Validator] - var validatorBalances: [UInt64] - var validatorRegistryUpdateEpoch: UInt64 - - var latestRandaoMixes: [Data] - var previousShufflingStartShard: UInt64 - let currentShufflingStartShard: UInt64 - var previousShufflingEpoch: UInt64 - var currentShufflingEpoch: UInt64 - var previousShufflingSeed: Data - var currentShufflingSeed: Data - - var previousJustifiedEpoch: UInt64 - var justifiedEpoch: UInt64 - var justificationBitfield: UInt64 - var finalizedEpoch: UInt64 - - var latestCrosslinks: [Crosslink] - var latestBlockRoots: [Data] - var latestActiveIndexRoots: [Data] - var latestSlashedBalances: [UInt64] - var latestAttestations: [PendingAttestation] - var batchedBlockRoots: [Data] - - var latestEth1Data: Eth1Data - var eth1DataVotes: [Eth1DataVote] - var depositIndex: UInt64 -} diff --git a/Sources/BeaconChain/DataStructures/State/Crosslink.swift b/Sources/BeaconChain/DataStructures/State/Crosslink.swift deleted file mode 100644 index d9f0e81..0000000 --- a/Sources/BeaconChain/DataStructures/State/Crosslink.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -struct Crosslink: Equatable { - let epoch: UInt64 - let crosslinkDataRoot: Data -} diff --git a/Sources/BeaconChain/DataStructures/State/Eth1Data.swift b/Sources/BeaconChain/DataStructures/State/Eth1Data.swift deleted file mode 100644 index 7fc1993..0000000 --- a/Sources/BeaconChain/DataStructures/State/Eth1Data.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -struct Eth1Data: Equatable { - let depositRoot: Data - let blockHash: Data -} diff --git a/Sources/BeaconChain/DataStructures/State/Eth1DataVote.swift b/Sources/BeaconChain/DataStructures/State/Eth1DataVote.swift deleted file mode 100644 index 74d3dd8..0000000 --- a/Sources/BeaconChain/DataStructures/State/Eth1DataVote.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -struct Eth1DataVote { - let eth1Data: Eth1Data - var voteCount: UInt64 -} diff --git a/Sources/BeaconChain/DataStructures/State/Fork.swift b/Sources/BeaconChain/DataStructures/State/Fork.swift deleted file mode 100644 index 1f18384..0000000 --- a/Sources/BeaconChain/DataStructures/State/Fork.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -struct Fork { - let previousVersion: UInt64 - let currentVersion: UInt64 - let epoch: UInt64 - - func version(epoch: Epoch) -> UInt64 { - if epoch < self.epoch { - return previousVersion - } - - return currentVersion - } - - func domain(epoch: Epoch, type: Domain) -> UInt64 { - return version(epoch: epoch) * 2 ** 32 + type.rawValue - } -} diff --git a/Sources/BeaconChain/DataStructures/State/PendingAttestation.swift b/Sources/BeaconChain/DataStructures/State/PendingAttestation.swift deleted file mode 100644 index 2aec5c5..0000000 --- a/Sources/BeaconChain/DataStructures/State/PendingAttestation.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -struct PendingAttestation { - let aggregationBitfield: Data - let data: AttestationData - let custodyBitfield: Data - let inclusionSlot: UInt64 -} diff --git a/Sources/BeaconChain/DataStructures/State/Validator.swift b/Sources/BeaconChain/DataStructures/State/Validator.swift deleted file mode 100644 index ce22f4b..0000000 --- a/Sources/BeaconChain/DataStructures/State/Validator.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -struct Validator { - let pubkey: Data - let withdrawalCredentials: Data - private(set) var activationEpoch: UInt64 - private(set) var exitEpoch: UInt64 - var withdrawableEpoch: UInt64 - var initiatedExit: Bool - var slashed: Bool - - func isActive(epoch: Epoch) -> Bool { - return activationEpoch <= epoch && epoch < exitEpoch - } - - // @todo passing state kinda seems ugly - mutating func activate(state: BeaconState, genesis: Bool) { - activationEpoch = genesis ? GENESIS_EPOCH : BeaconChain.getCurrentEpoch(state: state).delayedActivationExitEpoch() - } - - mutating func exit(state: BeaconState) { - if exitEpoch <= BeaconChain.getCurrentEpoch(state: state).delayedActivationExitEpoch() { - return - } - - exitEpoch = BeaconChain.getCurrentEpoch(state: state).delayedActivationExitEpoch() - } - - mutating func prepareForWithdrawal(state: BeaconState) { - withdrawableEpoch = BeaconChain.getCurrentEpoch(state: state) + MIN_VALIDATOR_WITHDRAWABILITY_DELAY - } -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/Attestations/Attestation.swift b/Sources/BeaconChain/DataStructures/Transactions/Attestations/Attestation.swift deleted file mode 100644 index 3ddf7ac..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/Attestations/Attestation.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -public struct Attestation: Equatable { - let aggregationBitfield: Data - let data: AttestationData - let custodyBitfield: Data - let aggregateSignature: Data -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/Attestations/AttestationData.swift b/Sources/BeaconChain/DataStructures/Transactions/Attestations/AttestationData.swift deleted file mode 100644 index c749f31..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/Attestations/AttestationData.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -struct AttestationData: Equatable { - let slot: Slot - let shard: UInt64 - let beaconBlockRoot: Data - let epochBoundaryRoot: Data - let crosslinkDataRoot: Data - let latestCrosslink: Crosslink - let justifiedEpoch: Epoch - let justifiedBlockRoot: Data -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/Attestations/AttestationDataAndCustodyBit.swift b/Sources/BeaconChain/DataStructures/Transactions/Attestations/AttestationDataAndCustodyBit.swift deleted file mode 100644 index ce72d23..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/Attestations/AttestationDataAndCustodyBit.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -struct AttestationDataAndCustodyBit { - let data: AttestationData - let custodyBit: Bool -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/AttesterSlashings/AttesterSlashing.swift b/Sources/BeaconChain/DataStructures/Transactions/AttesterSlashings/AttesterSlashing.swift deleted file mode 100644 index 0960a6f..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/AttesterSlashings/AttesterSlashing.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -struct AttesterSlashing: Equatable { - let slashableAttestation1: SlashableAttestation - let slashableAttestation2: SlashableAttestation -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/AttesterSlashings/SlashableAttestation.swift b/Sources/BeaconChain/DataStructures/Transactions/AttesterSlashings/SlashableAttestation.swift deleted file mode 100644 index ccca11a..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/AttesterSlashings/SlashableAttestation.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -struct SlashableAttestation: Equatable { - let validatorIndices: [UInt64] - let data: AttestationData - let custodyBitfield: Data - let aggregateSignature: Data -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/Deposits/Deposit.swift b/Sources/BeaconChain/DataStructures/Transactions/Deposits/Deposit.swift deleted file mode 100644 index 140d64c..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/Deposits/Deposit.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -struct Deposit: Equatable { - let branch: [Data] - let index: UInt64 - let depositData: DepositData -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/Deposits/DepositData.swift b/Sources/BeaconChain/DataStructures/Transactions/Deposits/DepositData.swift deleted file mode 100644 index 55b2b73..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/Deposits/DepositData.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -struct DepositData: Equatable { - let amount: UInt64 - let timestamp: UInt64 - let depositInput: DepositInput -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/Deposits/DepositInput.swift b/Sources/BeaconChain/DataStructures/Transactions/Deposits/DepositInput.swift deleted file mode 100644 index 1fbcc05..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/Deposits/DepositInput.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -struct DepositInput: Equatable { - let pubkey: Data - let withdrawalCredentials: Data - let proofOfPossession: Data -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/ProposerSlashing.swift b/Sources/BeaconChain/DataStructures/Transactions/ProposerSlashing.swift deleted file mode 100644 index b3ef745..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/ProposerSlashing.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -struct ProposerSlashing: Equatable { - let proposerIndex: UInt64 - let proposal1: Proposal - let proposal2: Proposal -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/Transfer.swift b/Sources/BeaconChain/DataStructures/Transactions/Transfer.swift deleted file mode 100644 index 5dcede7..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/Transfer.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -struct Transfer: Equatable { - let from: UInt64 - let to: UInt64 - let amount: UInt64 - let fee: UInt64 - let slot: UInt64 - let pubkey: Data - let signature: Data -} diff --git a/Sources/BeaconChain/DataStructures/Transactions/VoluntaryExit.swift b/Sources/BeaconChain/DataStructures/Transactions/VoluntaryExit.swift deleted file mode 100644 index afea8bd..0000000 --- a/Sources/BeaconChain/DataStructures/Transactions/VoluntaryExit.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -struct VoluntaryExit: Equatable { - let epoch: UInt64 - let validatorIndex: UInt64 - let signature: Data -} diff --git a/Sources/BeaconChain/DataStructures/Validator.swift b/Sources/BeaconChain/DataStructures/Validator.swift new file mode 100644 index 0000000..965d7a3 --- /dev/null +++ b/Sources/BeaconChain/DataStructures/Validator.swift @@ -0,0 +1,30 @@ +import Foundation + +/// A validator is an entity that participates in the consensus of the Ethereum 2.0 protocol. +public struct Validator { + + let pubkey: BLSPubKey + let withdrawalCredentials: Hash + let effectiveBalance: Gwei + let slashed: Bool + let activationEligibilityEpoch: Epoch + let activationEpoch: Epoch + let exitEpoch: Epoch + let withdrawableEpoch: Epoch + + /// Checks if a `validator` is active. + /// + /// - Parameters: + /// - epoch: The given epoch to check activation for. + func isActive(epoch: Epoch) -> Bool { + return activationEpoch <= epoch && epoch < exitEpoch + } + + /// Checks if a `validator` is slashable. + /// + /// - Parameters: + /// - epoch: The given epoch to check slashability for. + func isSlashable(epoch: Epoch) -> Bool { + return !slashed && activationEpoch <= epoch && epoch < withdrawableEpoch + } +} diff --git a/Sources/BeaconChain/Enums/Domain.swift b/Sources/BeaconChain/Enums/Domain.swift deleted file mode 100644 index 7a0fa95..0000000 --- a/Sources/BeaconChain/Enums/Domain.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -enum Domain: UInt64 { - case deposit - case attestation - case proposal - case exit - case randao - case transfer -} diff --git a/Sources/BeaconChain/Enums/DomainType.swift b/Sources/BeaconChain/Enums/DomainType.swift new file mode 100644 index 0000000..981d683 --- /dev/null +++ b/Sources/BeaconChain/Enums/DomainType.swift @@ -0,0 +1,6 @@ +import Foundation + +/// A signature domain type. +public enum DomainType: UInt32 { + case beaconProposer, randao, attestation, deposit, voluntaryExit, transfer +} diff --git a/Sources/BeaconChain/Extensions/Array+AttestationTarget.swift b/Sources/BeaconChain/Extensions/Array+AttestationTarget.swift deleted file mode 100644 index 577446d..0000000 --- a/Sources/BeaconChain/Extensions/Array+AttestationTarget.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -extension Array where Element == AttestationTarget { - - func votes(store: Store, state: BeaconState, block: BeaconBlock) -> UInt64 { - return compactMap { - (index, target) in - - guard store.ancestor(block: target, slot: target.slot) == block else { - return nil - } - - return BeaconChain.getEffectiveBalance(state: state, index: index) / FORK_CHOICE_BALANCE_INCREMENT - } - .reduce(0, +) - } -} diff --git a/Sources/BeaconChain/Extensions/Array+Validator.swift b/Sources/BeaconChain/Extensions/Array+Validator.swift deleted file mode 100644 index c27c903..0000000 --- a/Sources/BeaconChain/Extensions/Array+Validator.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -extension Array where Element == Validator { - - func activeIndices(epoch: Epoch) -> [ValidatorIndex] { - return enumerated().compactMap { - (k, v) in - if v.isActive(epoch: epoch) { - return ValidatorIndex(k) - } - - return nil - } - } -} diff --git a/Sources/BeaconChain/Extensions/Array+ValidatorIndex.swift b/Sources/BeaconChain/Extensions/Array+ValidatorIndex.swift deleted file mode 100644 index f196d39..0000000 --- a/Sources/BeaconChain/Extensions/Array+ValidatorIndex.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -extension Array where Element == ValidatorIndex { - - func totalBalance(state: BeaconState) -> Gwei { - return map { BeaconChain.getEffectiveBalance(state: state, index: $0) }.reduce(0, +) - } -} diff --git a/Sources/BeaconChain/Extensions/Array.swift b/Sources/BeaconChain/Extensions/Array.swift deleted file mode 100644 index 4b7af6f..0000000 --- a/Sources/BeaconChain/Extensions/Array.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension Array { - - func split(count: Int) -> [[Element]] { - let length = self.count - - return (0.. Data { - var temp = left - - for i in 0.. Bool { - for i in 0...left.count { - if left[i] > right[i] { - return false - } - } - - return true - } -} diff --git a/Sources/BeaconChain/Extensions/Domain.swift b/Sources/BeaconChain/Extensions/Domain.swift new file mode 100644 index 0000000..e7d9c82 --- /dev/null +++ b/Sources/BeaconChain/Extensions/Domain.swift @@ -0,0 +1,13 @@ +import Foundation + +extension Domain { + + /// Return the domain for the `DomainType` and `Version`. + /// + /// - Parameters + /// - type: The domain type. + /// - fork: The fork version. + init(type: DomainType, fork: Version) { + self.init(UInt64(type.rawValue + fork)) + } +} diff --git a/Sources/BeaconChain/Extensions/Epoch.swift b/Sources/BeaconChain/Extensions/Epoch.swift deleted file mode 100644 index 2d917f0..0000000 --- a/Sources/BeaconChain/Extensions/Epoch.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -extension Epoch { - - func startSlot() -> Slot { - return self * SLOTS_PER_EPOCH - } - - func delayedActivationExitEpoch() -> Epoch { - return self + 1 + ACTIVATION_EXIT_DELAY - } -} diff --git a/Sources/BeaconChain/Extensions/Int.swift b/Sources/BeaconChain/Extensions/Int.swift deleted file mode 100644 index a8f20d1..0000000 --- a/Sources/BeaconChain/Extensions/Int.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -extension Int { - - func isPowerOfTwo() -> Bool { - return (self > 0) && (self & (self - 1) == 0) - } -} diff --git a/Sources/BeaconChain/Extensions/Numeric.swift b/Sources/BeaconChain/Extensions/Numeric.swift deleted file mode 100644 index d5a9adb..0000000 --- a/Sources/BeaconChain/Extensions/Numeric.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -extension Numeric { - - // @todo kinda ugly? - var bytes32: Data { - var source = self - - return Data(bytes: &source, count: 32) - } -} diff --git a/Sources/BeaconChain/Extensions/Slot.swift b/Sources/BeaconChain/Extensions/Slot.swift deleted file mode 100644 index 77179c6..0000000 --- a/Sources/BeaconChain/Extensions/Slot.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -extension Slot { - - func toEpoch() -> Epoch { - return self / SLOTS_PER_EPOCH - } -} diff --git a/Sources/BeaconChain/Extensions/UInt64.swift b/Sources/BeaconChain/Extensions/UInt64.swift index 2d8cfc4..16a097f 100644 --- a/Sources/BeaconChain/Extensions/UInt64.swift +++ b/Sources/BeaconChain/Extensions/UInt64.swift @@ -2,9 +2,8 @@ import Foundation extension UInt64 { + /// Return the largest integer ``x`` such that ``x**2 <= n``. func sqrt() -> UInt64 { - assert(self >= 0) - var x = self var y = (x + 1) / 2 diff --git a/Sources/BeaconChain/ForkChoice/ForkChoice.swift b/Sources/BeaconChain/ForkChoice/ForkChoice.swift deleted file mode 100644 index a7f50c0..0000000 --- a/Sources/BeaconChain/ForkChoice/ForkChoice.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -protocol ForkChoice { - - func execute(store: Store, startState: BeaconState, startBlock: BeaconBlock) -> BeaconBlock - -} diff --git a/Sources/BeaconChain/ForkChoice/LMDGhost.swift b/Sources/BeaconChain/ForkChoice/LMDGhost.swift deleted file mode 100644 index 691fa2c..0000000 --- a/Sources/BeaconChain/ForkChoice/LMDGhost.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -class LMDGhost: ForkChoice { - - func execute(store: Store, startState: BeaconState, startBlock: BeaconBlock) -> BeaconBlock { - let targets = startState.validatorRegistry.activeIndices(epoch: startState.slot.toEpoch()).map { - ($0, store.latestAttestationTarget(validator: $0)) - } - - var head = startBlock - while true { - let children = store.children(head) - if children.count == 0 { - return head - } - - head = children.max { - targets.votes(store: store, state: startState, block: $0) < targets.votes(store: store, state: startState, block: $1) - }! - } - } -} diff --git a/Sources/BeaconChain/Operators/XOR.swift b/Sources/BeaconChain/Operators/XOR.swift new file mode 100644 index 0000000..d38e733 --- /dev/null +++ b/Sources/BeaconChain/Operators/XOR.swift @@ -0,0 +1,15 @@ +import Foundation + +extension Data { + + /// Return the exclusive-or of two byte strings. + static func ^ (left: Data, right: Data) -> Data { + var temp = left + + for i in 0..= 1) - - for i in slashableIndices { - BeaconChain.slashValidator(state: &state, index: i) - } - } - } - - static func attestations(state: inout BeaconState, block: BeaconBlock) { - assert(block.body.attestations.count <= MAX_ATTESTATIONS) - - for attestation in block.body.attestations { - assert(attestation.data.slot >= GENESIS_SLOT) - assert(attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot) - assert(state.slot < attestation.data.slot + SLOTS_PER_EPOCH) - - let e = (attestation.data.slot + 1).toEpoch() >= BeaconChain.getCurrentEpoch(state: state) ? state.justifiedEpoch : state.previousJustifiedEpoch - assert(attestation.data.justifiedEpoch == e) - assert(attestation.data.justifiedBlockRoot == BeaconChain.getBlockRoot(state: state, slot: attestation.data.justifiedEpoch.startSlot())) - - assert( - state.latestCrosslinks[Int(attestation.data.shard)] == attestation.data.latestCrosslink || - state.latestCrosslinks[Int(attestation.data.shard)] == Crosslink( - epoch: attestation.data.slot.toEpoch(), - crosslinkDataRoot: attestation.data.crosslinkDataRoot - ) - ) - - assert(attestation.custodyBitfield == Data(repeating: 0, count: 32)) - assert(attestation.aggregationBitfield != Data(repeating: 0, count: 32)) - - let crosslinkCommittee = BeaconChain.crosslinkCommittees(state, at: attestation.data.slot).filter { - $0.1 == attestation.data.shard - }.first?.0 - - for i in 0.. epoch.delayedActivationExitEpoch()) - assert(epoch >= exit.epoch) - - assert( - BLS.verify( - pubkey: validator.pubkey, - message: BeaconChain.signedRoot(exit, field: "signature"), - signature: exit.signature, - domain: state.fork.domain(epoch: exit.epoch, type: .exit) - ) - ) - - state.validatorRegistry[Int(exit.validatorIndex)].initiatedExit = true - } - } - - static func transfers(state: inout BeaconState, block: BeaconBlock) { - assert(block.body.transfers.count <= MAX_TRANSFERS) - - for transfer in block.body.transfers { - assert(state.validatorBalances[Int(transfer.from)] >= transfer.amount) - assert(state.validatorBalances[Int(transfer.from)] >= transfer.fee) - assert( - state.validatorBalances[Int(transfer.from)] == transfer.amount + transfer.fee - || state.validatorBalances[Int(transfer.from)] >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT - ) - - assert(state.slot == transfer.slot) - assert( - BeaconChain.getCurrentEpoch(state: state) >= state.validatorRegistry[Int(transfer.from)].withdrawableEpoch - || state.validatorRegistry[Int(transfer.from)].activationEpoch == FAR_FUTURE_EPOCH - ) - assert(state.validatorRegistry[Int(transfer.from)].withdrawalCredentials == BLS_WITHDRAWAL_PREFIX_BYTE + BeaconChain.hash(transfer.pubkey).suffix(from: 1)) - - assert( - BLS.verify( - pubkey: transfer.pubkey, - message: BeaconChain.signedRoot(transfer, field: "signature"), - signature: transfer.signature, - domain: state.fork.domain(epoch: transfer.slot.toEpoch(), type: .transfer) - ) - ) - - state.validatorBalances[Int(transfer.from)] -= transfer.amount + transfer.fee - state.validatorBalances[Int(transfer.to)] += transfer.amount - state.validatorBalances[Int(BeaconChain.getBeaconProposerIndex(state: state, slot: state.slot))] += transfer.fee - } - } - - static func verifyMerkleBranch(leaf: Bytes32, branch: [Bytes32], depth: Int, index: Int, root: Bytes32) -> Bool { - var value = leaf - for i in 0.. state.validatorRegistryUpdateEpoch && shards.count == 0 { - updateValidatorRegistry(state: &state) - } else { - let epochsSinceLastRegistryUpdate = currentEpoch - state.validatorRegistryUpdateEpoch - if epochsSinceLastRegistryUpdate > 1 && Int(epochsSinceLastRegistryUpdate).isPowerOfTwo() { - state.currentShufflingEpoch = nextEpoch - state.currentShufflingSeed = BeaconChain.generateSeed(state: state, epoch: state.currentShufflingEpoch) - } - } - - processSlashing(state: &state) - processExitQueue(state: &state) - - state.latestActiveIndexRoots[Int((nextEpoch % ACTIVATION_EXIT_DELAY) % LATEST_ACTIVE_INDEX_ROOTS_LENGTH)] = BeaconChain.hashTreeRoot( - state.validatorRegistry.activeIndices(epoch: nextEpoch + ACTIVATION_EXIT_DELAY) - ) - - state.latestSlashedBalances[Int(nextEpoch % LATEST_SLASHED_EXIT_LENGTH)] = state.latestSlashedBalances[Int(currentEpoch % LATEST_SLASHED_EXIT_LENGTH)] - state.latestRandaoMixes[Int(nextEpoch % LATEST_RANDAO_MIXES_LENGTH)] = BeaconChain.getRandaoMix( - state: state, - epoch: currentEpoch - ) - - // @todo check this - // Remove any attestation in state.latest_attestations such that slot_to_epoch(attestation.data.slot) < current_epoch. - state.latestAttestations.removeAll { - $0.data.slot.toEpoch() >= currentEpoch - } - } - - private static func eth1data(state: inout BeaconState, nextEpoch: Epoch) { - if nextEpoch % EPOCHS_PER_ETH1_VOTING_PERIOD != 0 { - return - } - - for vote in state.eth1DataVotes { - if vote.voteCount * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH { - state.latestEth1Data = vote.eth1Data - } - } - - state.eth1DataVotes = [Eth1DataVote]() - } - - private static func justification( - state: inout BeaconState, - previousEpoch: Epoch, - currentEpoch: Epoch, - previousTotalBalance: Gwei, - previousEpochBoundaryAttestingBalance: Gwei, - currentTotalBalance: Gwei, - currentEpochBoundaryAttestingBalance: Gwei - ) { - var newJustifiedEpoch = state.justifiedEpoch - state.justificationBitfield = state.justificationBitfield << 1 - - if 3 * previousEpochBoundaryAttestingBalance >= 2 * previousTotalBalance { - state.justificationBitfield |= 2 - newJustifiedEpoch = previousEpoch - } - - if 3 * currentEpochBoundaryAttestingBalance >= 2 * currentTotalBalance { - state.justificationBitfield |= 1 - newJustifiedEpoch = currentEpoch - } - - if (state.justificationBitfield >> 1) % 8 == 0b111 && state.previousJustifiedEpoch == previousEpoch - 2 { - state.finalizedEpoch = state.previousJustifiedEpoch - } - - if (state.justificationBitfield >> 1) % 4 == 0b11 && state.previousJustifiedEpoch == previousEpoch - 1 { - state.finalizedEpoch = state.previousJustifiedEpoch - } - - if (state.justificationBitfield >> 0) % 8 == 0b111 && state.justifiedEpoch == previousEpoch - 1 { - state.finalizedEpoch = state.justifiedEpoch - } - - if (state.justificationBitfield >> 0) % 4 == 0b11 && state.justifiedEpoch == previousEpoch { - state.finalizedEpoch = state.justifiedEpoch - } - - state.previousJustifiedEpoch = state.justifiedEpoch - state.justifiedEpoch = newJustifiedEpoch - } - - private static func crosslink( - state: inout BeaconState, - previousEpoch: Epoch, - currentEpoch: Epoch, - nextEpoch: Epoch, - currentEpochAttestations: [PendingAttestation], - previousEpochAttestations: [PendingAttestation] - ) { - - for slot in previousEpoch.startSlot()..= 2 * crosslinkCommittee.totalBalance(state: state) { - state.latestCrosslinks[Int(shard)] = Crosslink( - epoch: slot.toEpoch(), - crosslinkDataRoot: winningRoot( - state: state, - committee: crosslinkCommittee, - shard: shard, - currentEpochAttestations: currentEpochAttestations, - previousEpochAttestations: previousEpochAttestations - ) - ) - } - } - } - } - - private static func rewardsAndPenalties( - state: inout BeaconState, - previousEpoch: Epoch, - currentEpoch: Epoch, - nextEpoch: Epoch, - previousTotalBalance: Gwei, - totalBalance: UInt64, - previousEpochAttestingBalance: UInt64, - previousEpochBoundaryAttesterIndices: [ValidatorIndex], - previousEpochBoundaryAttestingBalance: UInt64, - previousEpochHeadAttesterIndices: [ValidatorIndex], - previousEpochHeadAttestingBalance: UInt64, - previousEpochAttesterIndices: [ValidatorIndex], - currentEpochAttestations: [PendingAttestation], - previousEpochAttestations: [PendingAttestation] - ) { - let baseRewardQuotient = previousTotalBalance.sqrt() / BASE_REWARD_QUOTIENT - - let epochsSinceFinality = nextEpoch - state.finalizedEpoch - - let activeValidators = Set(state.validatorRegistry.activeIndices(epoch: currentEpoch)) - - if epochsSinceFinality <= 4 { - - expectedFFGSource( - state: &state, - previousEpochAttesterIndices: previousEpochAttesterIndices, - activeValidators: activeValidators, - previousEpochAttestingBalance: previousEpochAttestingBalance, - baseRewardQuotient: baseRewardQuotient, - totalBalance: previousTotalBalance - ) - - expectedFFGTarget( - state: &state, - previousEpochBoundaryAttesterIndices: previousEpochBoundaryAttesterIndices, - activeValidators: activeValidators, - previousEpochBoundaryAttestingBalance: previousEpochBoundaryAttestingBalance, - baseRewardQuotient: baseRewardQuotient, - totalBalance: previousTotalBalance - ) - - expectedBeaconChainHead( - state: &state, - previousEpochHeadAttesterIndices: previousEpochHeadAttesterIndices, - activeValidators: activeValidators, - previousEpochHeadAttestingBalance: previousEpochHeadAttestingBalance, - baseRewardQuotient: baseRewardQuotient, - totalBalance: previousTotalBalance - ) - - for index in previousEpochAttesterIndices { - state.validatorBalances[Int(index)] += baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) + MIN_ATTESTATION_INCLUSION_DELAY / inclusionDistance(state: state, index: index) - } - - } else { - deductInactivityBalance( - state: &state, - activeValidators: activeValidators, - excluding: previousEpochAttesterIndices, - epochsSinceFinality: epochsSinceFinality, - baseRewardQuotient: baseRewardQuotient - ) - - deductInactivityBalance( - state: &state, - activeValidators: activeValidators, - excluding: previousEpochBoundaryAttesterIndices, - epochsSinceFinality: epochsSinceFinality, - baseRewardQuotient: baseRewardQuotient - ) - - activeValidators.subtracting(Set(previousEpochHeadAttesterIndices)).forEach({ - (index) in - state.validatorBalances[Int(index)] -= baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) - }) - - activeValidators.forEach({ - index in - if state.validatorRegistry[Int(index)].slashed { - state.validatorBalances[Int(index)] -= 2 * inactivityPenalty(state: state, index: index, epochsSinceFinality: epochsSinceFinality, baseRewardQuotient: baseRewardQuotient) + baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) - } - }) - - for index in previousEpochAttesterIndices { - let base = baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) - state.validatorBalances[Int(index)] -= base - base * MIN_ATTESTATION_INCLUSION_DELAY / inclusionDistance(state: state, index: index) - } - } - - for index in previousEpochAttesterIndices { - let proposer = BeaconChain.getBeaconProposerIndex( - state: state, - slot: inclusionSlot(state: state, index: Int(index)) - ) - state.validatorBalances[Int(proposer)] += baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) / ATTESTATION_INCLUSION_REWARD_QUOTIENT - } - - for slot in previousEpoch.startSlot()..= validator.exitEpoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY - } - } - - eligibleIndices.sort { - state.validatorRegistry[$0].exitEpoch > state.validatorRegistry[$1].exitEpoch - } - - for (dequeues, i) in eligibleIndices.enumerated() { - if dequeues >= MAX_EXIT_DEQUEUES_PER_EPOCH { - break - } - - state.validatorRegistry[i].prepareForWithdrawal(state: state) - } - } - - static func updateValidatorRegistry(state: inout BeaconState) { - let currentEpoch = BeaconChain.getCurrentEpoch(state: state) - let activeValidatorIndices = state.validatorRegistry.activeIndices(epoch: currentEpoch) - - let totalBalance = activeValidatorIndices.totalBalance(state: state) - let maxBalanceChurn = max(MAX_DEPOSIT_AMOUNT, totalBalance / (2 * MAX_BALANCE_CHURN_QUOTIENT)) - - var balanceChurn = UInt64(0) - for (i, v) in state.validatorRegistry.enumerated() { - if v.activationEpoch == FAR_FUTURE_EPOCH && state.validatorBalances[Int(i)] >= MAX_DEPOSIT_AMOUNT { - balanceChurn += BeaconChain.getEffectiveBalance(state: state, index: ValidatorIndex(i)) - if balanceChurn > maxBalanceChurn { - break - } - - state.validatorRegistry[i].activate(state: state, genesis: false) - } - } - - balanceChurn = 0 - for (i, v) in state.validatorRegistry.enumerated() { - if v.activationEpoch == FAR_FUTURE_EPOCH && v.initiatedExit { - balanceChurn += BeaconChain.getEffectiveBalance(state: state, index: ValidatorIndex(i)) - if balanceChurn > maxBalanceChurn { - break - } - - state.validatorRegistry[i].exit(state: state) - } - } - - state.validatorRegistryUpdateEpoch = currentEpoch - } - - static func shufflingSeedData(state: inout BeaconState, nextEpoch: Epoch) { - state.previousShufflingEpoch = state.currentShufflingEpoch - state.previousShufflingSeed = state.currentShufflingSeed - state.previousShufflingStartShard = state.currentShufflingStartShard - } - - static func processEjections(state: inout BeaconState) { - for i in state.validatorRegistry.activeIndices(epoch: BeaconChain.getCurrentEpoch(state: state)) { - if state.validatorBalances[Int(i)] < EJECTION_BALANCE { - state.validatorRegistry[Int(i)].exit(state: state) - } - } - } - - static func deductInactivityBalance( - state: inout BeaconState, - activeValidators: Set, - excluding: [ValidatorIndex], - epochsSinceFinality: UInt64, - baseRewardQuotient: UInt64 - ) { - activeValidators.subtracting(Set(excluding)).forEach { - state.validatorBalances[Int($0)] -= inactivityPenalty(state: state, index: $0, epochsSinceFinality: epochsSinceFinality, baseRewardQuotient: baseRewardQuotient) - } - } - - static func expectedFFGSource( - state: inout BeaconState, - previousEpochAttesterIndices: [ValidatorIndex], - activeValidators: Set, - previousEpochAttestingBalance: UInt64, - baseRewardQuotient: UInt64, - totalBalance: UInt64 - ) { - for index in previousEpochAttesterIndices { - state.validatorBalances[Int(index)] += baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) * previousEpochAttestingBalance / totalBalance - } - - activeValidators.subtracting(Set(previousEpochAttesterIndices)).forEach({ - (index) in - state.validatorBalances[Int(index)] -= baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) - }) - } - - static func expectedFFGTarget( - state: inout BeaconState, - previousEpochBoundaryAttesterIndices: [ValidatorIndex], - activeValidators: Set, - previousEpochBoundaryAttestingBalance: UInt64, - baseRewardQuotient: UInt64, - totalBalance: UInt64 - ) { - for index in previousEpochBoundaryAttesterIndices { - state.validatorBalances[Int(index)] += baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) * previousEpochBoundaryAttestingBalance / totalBalance - } - - activeValidators.subtracting(Set(previousEpochBoundaryAttesterIndices)).forEach({ - (index) in - state.validatorBalances[Int(index)] -= baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) - }) - } - - static func expectedBeaconChainHead( - state: inout BeaconState, - previousEpochHeadAttesterIndices: [ValidatorIndex], - activeValidators: Set, - previousEpochHeadAttestingBalance: UInt64, - baseRewardQuotient: UInt64, - totalBalance: UInt64 - ) { - for index in previousEpochHeadAttesterIndices { - state.validatorBalances[Int(index)] += baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) * previousEpochHeadAttestingBalance / totalBalance - } - - activeValidators.subtracting(Set(previousEpochHeadAttesterIndices)).forEach({ - (index) in - state.validatorBalances[Int(index)] -= baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) - }) - } - - private static func baseReward(state: BeaconState, index: ValidatorIndex, baseRewardQuotient: UInt64) -> UInt64 { - return BeaconChain.getEffectiveBalance(state: state, index: index) / baseRewardQuotient / 5 - } - - private static func inactivityPenalty( - state: BeaconState, - index: ValidatorIndex, - epochsSinceFinality: UInt64, - baseRewardQuotient: UInt64 - ) -> UInt64 { - return baseReward(state: state, index: index, baseRewardQuotient: baseRewardQuotient) + BeaconChain.getEffectiveBalance(state: state, index: index) * epochsSinceFinality / INACTIVITY_PENALTY_QUOTIENT / 2 - } - - private static func attestingValidators( - state: BeaconState, - committee: [ValidatorIndex], - shard: UInt64, - currentEpochAttestations: [PendingAttestation], - previousEpochAttestations: [PendingAttestation] - ) -> [ValidatorIndex] { - let root = winningRoot( - state: state, - committee: committee, - shard: shard, - currentEpochAttestations: currentEpochAttestations, - previousEpochAttestations: previousEpochAttestations - ) - - return attestingValidatorIndices( - state: state, - committee: committee, - shard: shard, - crosslinkDataRoot: root, - currentEpochAttestations: currentEpochAttestations, - previousEpochAttestations: previousEpochAttestations - ) - } - - private static func totalAttestingBalance( - state: BeaconState, - committee: [ValidatorIndex], - shard: UInt64, - currentEpochAttestations: [PendingAttestation], - previousEpochAttestations: [PendingAttestation] - ) -> UInt64 { - return attestingValidators( - state: state, - committee: committee, - shard: shard, - currentEpochAttestations: currentEpochAttestations, - previousEpochAttestations: previousEpochAttestations - ) - .totalBalance(state: state) - } - - private static func attestingValidatorIndices( - state: BeaconState, - committee: [ValidatorIndex], - shard: UInt64, - crosslinkDataRoot: Data, - currentEpochAttestations: [PendingAttestation], - previousEpochAttestations: [PendingAttestation] - ) -> [ValidatorIndex] { - return (currentEpochAttestations + previousEpochAttestations) - .filter { - $0.data.shard == shard && $0.data.crosslinkDataRoot == crosslinkDataRoot - } - .flatMap { - return BeaconChain.getAttestationParticipants( - state: state, - attestationData: $0.data, - bitfield: $0.aggregationBitfield - ) - } - } - - private static func winningRoot( - state: BeaconState, - committee: [ValidatorIndex], - shard: UInt64, - currentEpochAttestations: [PendingAttestation], - previousEpochAttestations: [PendingAttestation] - ) -> Data { - let candidateRoots = (currentEpochAttestations + previousEpochAttestations) - .filter { - $0.data.shard == shard - } - .map { - $0.data.crosslinkDataRoot - } - - var winnerRoot = Data(count: 0) - var winnerBalance = UInt64(0) - for root in candidateRoots { - let indices = attestingValidatorIndices( - state: state, - committee: committee, - shard: shard, - crosslinkDataRoot: root, - currentEpochAttestations: currentEpochAttestations, - previousEpochAttestations: previousEpochAttestations - ) - - let rootBalance = indices.totalBalance(state: state) - - if rootBalance > winnerBalance || (rootBalance == winnerBalance && root < winnerRoot) { - winnerBalance = rootBalance - winnerRoot = root - } - } - - return winnerRoot - } - - private static func inclusionDistance(state: BeaconState, index: ValidatorIndex) -> UInt64 { - for a in state.latestAttestations { - let participated = BeaconChain.getAttestationParticipants(state: state, attestationData: a.data, bitfield: a.aggregationBitfield) - - for i in participated { - if index == i { - return a.inclusionSlot - a.data.slot - } - } - } - - return 0 - } - - private static func inclusionSlot(state: BeaconState, index: Int) -> UInt64 { - for a in state.latestAttestations { - let participated = BeaconChain.getAttestationParticipants(state: state, attestationData: a.data, bitfield: a.aggregationBitfield) - - for i in participated { - if index == i { - return a.inclusionSlot - } - } - } - - return 0 - } -} - -extension StateTransition { - - static func processStateRoot(state: BeaconState) { - // @todo Verify block.state_root == hash_tree_root(state) if there exists a block for the slot being processed. - } - -} diff --git a/Sources/BeaconChain/Store/Store.swift b/Sources/BeaconChain/Store/Store.swift deleted file mode 100644 index baba99f..0000000 --- a/Sources/BeaconChain/Store/Store.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation - -public protocol Store { - - func parent(_ block: BeaconBlock) -> BeaconBlock - func children(_ block: BeaconBlock) -> [BeaconBlock] - func latestAttestation(validator: ValidatorIndex) -> Attestation - func latestAttestationTarget(validator: ValidatorIndex) -> BeaconBlock -} - -extension Store { - - func ancestor(block: BeaconBlock, slot: Slot) -> BeaconBlock? { - if block.slot == slot { - return block - } - - if block.slot < slot { - return nil - } - - return ancestor(block: parent(block), slot: slot) - } -} - diff --git a/Sources/BeaconChain/Types.swift b/Sources/BeaconChain/Types.swift index 425cd80..728512c 100644 --- a/Sources/BeaconChain/Types.swift +++ b/Sources/BeaconChain/Types.swift @@ -1,12 +1,31 @@ import Foundation +/// A slot number. public typealias Slot = UInt64 -typealias Epoch = UInt64 -typealias Shard = UInt64 + +/// An epoch number. +public typealias Epoch = UInt64 + +/// A shard number. +public typealias Shard = UInt64 + +/// A validator registry index. public typealias ValidatorIndex = UInt64 -typealias Gwei = UInt64 -typealias Bytes32 = Data // @todo needs to be 32 fixed length data -typealias BLSPubkey = Data // @todo needs to be 48 fixed length data -typealias BLSSignature = Data // @todo needs to be 96 fixed length data -typealias AttestationTarget = (ValidatorIndex, BeaconBlock) +/// An amount in Gwei. +public typealias Gwei = UInt64 + +/// A hash. +public typealias Hash = Data + +/// A fork version number. +public typealias Version = UInt32 + +/// A signature domain. +public typealias Domain = UInt64 + +/// A BLS12-381 public key. +public typealias BLSPubKey = Data + +/// A BLS12-381 signature. +public typealias BLSSignature = Data diff --git a/Tests/BeaconChainTests/BeaconChainTests.swift b/Tests/BeaconChainTests/BeaconChainTests.swift deleted file mode 100644 index 660ab2f..0000000 --- a/Tests/BeaconChainTests/BeaconChainTests.swift +++ /dev/null @@ -1,59 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class BeaconChainTests: XCTestCase { - - func testIsDoubleVote() { - let dummy = Data(count: 1) - let attestation = AttestationData( - slot: 128, - shard: 0, - beaconBlockRoot: dummy, - epochBoundaryRoot: dummy, - crosslinkDataRoot: dummy, - latestCrosslink: Crosslink(epoch: 0, crosslinkDataRoot: dummy), - justifiedEpoch: 0, - justifiedBlockRoot: dummy - ) - - XCTAssert(BeaconChain.isDoubleVote(attestation, attestation)) - } - - func testIntegerSquareRoot() { - let numbers = [(UInt64(20), UInt64(4)), (200, 14), (1987, 44), (34989843, 5915), (97282, 311)] - - for num in numbers { - XCTAssertEqual(num.1, num.0.sqrt()) - } - } - -// func testIsSurroundVote() { -// let data = AttestationData( -// slot: 192, -// shard: 0, -// beaconBlockRoot: Data(count: 32), -// epochBoundaryRoot: Data(count: 32), -// shardBlockRoot: Data(count: 32), -// latestCrosslinkRoot: Data(count: 32), -// justifiedEpoch: 0, -// justifiedBlockRoot: Data(count: 32) -// ) -// -// XCTAssertFalse(BeaconChain.isSurroundVote(data, data)) -// XCTAssertTrue( -// BeaconChain.isSurroundVote( -// data, -// AttestationData( -// slot: 128, -// shard: 0, -// beaconBlockRoot: Data(count: 32), -// epochBoundaryRoot: Data(count: 32), -// shardBlockRoot: Data(count: 32), -// latestCrosslinkRoot: Data(count: 32), -// justifiedEpoch: 64, -// justifiedBlockRoot: Data(count: 32) -// ) -// ) -// ) -// } -} diff --git a/Tests/BeaconChainTests/DataStructures/State/ForkTests.swift b/Tests/BeaconChainTests/DataStructures/State/ForkTests.swift deleted file mode 100644 index 6abaee3..0000000 --- a/Tests/BeaconChainTests/DataStructures/State/ForkTests.swift +++ /dev/null @@ -1,27 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class ForkTests: XCTestCase { - - func testVersion() { - let fork = Fork(previousVersion: 10, currentVersion: 20, epoch: 1) - XCTAssertEqual(10, fork.version(epoch: 0)) - XCTAssertEqual(20, fork.version(epoch: 2)) - } - - - func testDomain() { - let data = Fork(previousVersion: 2, currentVersion: 3, epoch: 10) - let constant = 2**32 - - XCTAssertEqual( - data.domain(epoch: 9, type: .proposal), - Epoch((2*constant)+2) - ) - - XCTAssertEqual( - data.domain(epoch: 11, type: .exit), - Epoch((3*constant)+3) - ) - } -} diff --git a/Tests/BeaconChainTests/DataStructures/State/ValidatorTests.swift b/Tests/BeaconChainTests/DataStructures/State/ValidatorTests.swift deleted file mode 100644 index c51c2d6..0000000 --- a/Tests/BeaconChainTests/DataStructures/State/ValidatorTests.swift +++ /dev/null @@ -1,68 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class ValidatorTests: XCTestCase { - - func testIsActive() { - let epoch = Epoch(10) - - XCTAssertFalse(createValidator(epoch: epoch).isActive(epoch: epoch + 2)) - XCTAssertTrue(createValidator(epoch: epoch).isActive(epoch: epoch)) - - } - - func testActivate() { - var state = BeaconChain.genesisState( - genesisTime: 0, - latestEth1Data: Eth1Data(depositRoot: ZERO_HASH, blockHash: ZERO_HASH), - depositLength: 0 - ) - - state.slot = 10 - var validator = createValidator(epoch: 1) - - validator.activate(state: state, genesis: false) - XCTAssertEqual(validator.activationEpoch, 5) - } - - func testExitValidator() { - var state = BeaconChain.genesisState( - genesisTime: 0, - latestEth1Data: Eth1Data(depositRoot: ZERO_HASH, blockHash: ZERO_HASH), - depositLength: 0 - ) - - state.slot = 100 - var validator = createValidator(epoch: BeaconChain.getCurrentEpoch(state: state).delayedActivationExitEpoch()) - validator.exit(state: state) - - XCTAssertEqual(validator.exitEpoch, BeaconChain.getCurrentEpoch(state: state).delayedActivationExitEpoch()) - } - - func testPrepareForWithdrawal() { - var state = BeaconChain.genesisState( - genesisTime: 0, - latestEth1Data: Eth1Data(depositRoot: ZERO_HASH, blockHash: ZERO_HASH), - depositLength: 0 - ) - - state.slot = 0 - var validator = createValidator(epoch: 1) - validator.prepareForWithdrawal(state: state) - - XCTAssertEqual(validator.withdrawableEpoch, MIN_VALIDATOR_WITHDRAWABILITY_DELAY) - } - - private func createValidator(epoch: Epoch) -> Validator { - return Validator( - pubkey: ZERO_HASH, - withdrawalCredentials: ZERO_HASH, - activationEpoch: epoch - 1, - exitEpoch: epoch + 1, - withdrawableEpoch: epoch, - initiatedExit: false, - slashed: false - ) - } - -} diff --git a/Tests/BeaconChainTests/DataStructures/ValidatorTests.swift b/Tests/BeaconChainTests/DataStructures/ValidatorTests.swift new file mode 100644 index 0000000..8655aa4 --- /dev/null +++ b/Tests/BeaconChainTests/DataStructures/ValidatorTests.swift @@ -0,0 +1,46 @@ +import XCTest +@testable import BeaconChain + +class ValidatorTests: XCTestCase { + + func testIsActive() { + let validator = Validator( + pubkey: BLSPubKey(repeating: 0, count: 0), + withdrawalCredentials: Hash(repeating: 0, count: 0), + effectiveBalance: 0, + slashed: false, + activationEligibilityEpoch: 0, + activationEpoch: 10, + exitEpoch: 15, + withdrawableEpoch: 10 + ) + + XCTAssertTrue(validator.isActive(epoch: 11)) + } + + func testIsSlashable() { + + let tests: [(slashed: Bool, activationEpoch: Epoch, withdrawalEpoch: Epoch, epoch: Epoch, expected: Bool)] = [ + (false, 10, 10, 11, false), + (true, 10, 10, 11, false), + (false, 10, 12, 10, true) + ] + + for test in tests { + let validator = Validator( + pubkey: BLSPubKey(repeating: 0, count: 0), + withdrawalCredentials: Hash(repeating: 0, count: 0), + effectiveBalance: 0, + slashed: test.slashed, + activationEligibilityEpoch: 0, + activationEpoch: test.activationEpoch, + exitEpoch: 10, + withdrawableEpoch: test.withdrawalEpoch + ) + + XCTAssertEqual(test.expected, validator.isSlashable(epoch: test.epoch)) + } + + } + +} diff --git a/Tests/BeaconChainTests/Extensions/Array+AttestationTargetTests.swift b/Tests/BeaconChainTests/Extensions/Array+AttestationTargetTests.swift deleted file mode 100644 index f0a698e..0000000 --- a/Tests/BeaconChainTests/Extensions/Array+AttestationTargetTests.swift +++ /dev/null @@ -1,47 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class ArrayAttestationTargetTests: XCTestCase { - - func testVotes() { - var state = BeaconChain.genesisState( - genesisTime: 10, - latestEth1Data: Eth1Data(depositRoot: Data(count: 32), blockHash: Data(count: 32)), - depositLength: 0 - ) - - state.validatorBalances.append(32000000000) - state.validatorBalances.append(32000000000) - state.validatorBalances.append(32000000000) - - let block = BeaconBlock( - slot: GENESIS_SLOT, - parentRoot: ZERO_HASH, - stateRoot: ZERO_HASH, - randaoReveal: ZERO_HASH, - eth1Data: state.latestEth1Data, - body: BeaconBlockBody( - proposerSlashings: [ProposerSlashing](), - attesterSlashings: [AttesterSlashing](), - attestations: [Attestation](), - deposits: [Deposit](), - voluntaryExits: [VoluntaryExit](), - transfers: [Transfer]() - ), - signature: ZERO_HASH - ) - - let store = MockStore() - - var badBlock = block - badBlock.signature = EMPTY_SIGNATURE - - var targets = [AttestationTarget]() - targets.append((0, block)) - targets.append((1, badBlock)) - targets.append((2, block)) - - XCTAssertEqual(64, targets.votes(store: store, state: state, block: block)) - } - -} diff --git a/Tests/BeaconChainTests/Extensions/Array+ValidatorIndexTests.swift b/Tests/BeaconChainTests/Extensions/Array+ValidatorIndexTests.swift deleted file mode 100644 index 5babb23..0000000 --- a/Tests/BeaconChainTests/Extensions/Array+ValidatorIndexTests.swift +++ /dev/null @@ -1,20 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class ArrayValidatorIndexTests: XCTestCase { - - func testTotalBalance() { - var state = BeaconChain.genesisState( - genesisTime: 10, - latestEth1Data: Eth1Data(depositRoot: Data(count: 32), blockHash: Data(count: 32)), - depositLength: 0 - ) - - // @todo make dynamic - state.validatorBalances.append(10) - state.validatorBalances.append(10) - - XCTAssertEqual([ValidatorIndex(0), 1].totalBalance(state: state), 20) - } - -} \ No newline at end of file diff --git a/Tests/BeaconChainTests/Extensions/Array+ValidatorTests.swift b/Tests/BeaconChainTests/Extensions/Array+ValidatorTests.swift deleted file mode 100644 index 07f8168..0000000 --- a/Tests/BeaconChainTests/Extensions/Array+ValidatorTests.swift +++ /dev/null @@ -1,30 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class ArrayValidatorTests: XCTestCase { - - func testActiveIndices() { - - let epoch = Epoch(10) - - let validators = [ - createValidator(epoch: epoch), - createValidator(epoch: epoch), - createValidator(epoch: Epoch(12)) - ] - - XCTAssertEqual(validators.activeIndices(epoch: epoch), [ValidatorIndex(0), 1]) - } - - private func createValidator(epoch: Epoch) -> Validator { - return Validator( - pubkey: ZERO_HASH, - withdrawalCredentials: ZERO_HASH, - activationEpoch: epoch - 1, - exitEpoch: epoch + 1, - withdrawableEpoch: 1, - initiatedExit: false, - slashed: false - ) - } -} diff --git a/Tests/BeaconChainTests/Extensions/ArrayTests.swift b/Tests/BeaconChainTests/Extensions/ArrayTests.swift deleted file mode 100644 index f3e9b50..0000000 --- a/Tests/BeaconChainTests/Extensions/ArrayTests.swift +++ /dev/null @@ -1,11 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class ArrayTests: XCTestCase { - - func testSplit() { - let array = [0, 1, 2, 3] - XCTAssertEqual([[0, 1], [2, 3]], array.split(count: 2)) - } - -} \ No newline at end of file diff --git a/Tests/BeaconChainTests/Extensions/EpochTests.swift b/Tests/BeaconChainTests/Extensions/EpochTests.swift deleted file mode 100644 index 8b3fdcd..0000000 --- a/Tests/BeaconChainTests/Extensions/EpochTests.swift +++ /dev/null @@ -1,13 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class EpochTests: XCTestCase { - - func testStartSlot() { - XCTAssertEqual(Epoch(1).startSlot(), 64) - } - - func testEntryExitEpoch() { - XCTAssertEqual(Epoch(1).delayedActivationExitEpoch(), 6) - } -} diff --git a/Tests/BeaconChainTests/Extensions/IntTests.swift b/Tests/BeaconChainTests/Extensions/IntTests.swift deleted file mode 100644 index f521b3a..0000000 --- a/Tests/BeaconChainTests/Extensions/IntTests.swift +++ /dev/null @@ -1,10 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class IntTests: XCTestCase { - - func testIsPowerOfTwo() { - XCTAssertTrue(4.isPowerOfTwo()) - XCTAssertFalse(3.isPowerOfTwo()) - } -} diff --git a/Tests/BeaconChainTests/Extensions/SlotTests.swift b/Tests/BeaconChainTests/Extensions/SlotTests.swift deleted file mode 100644 index 57c35a4..0000000 --- a/Tests/BeaconChainTests/Extensions/SlotTests.swift +++ /dev/null @@ -1,9 +0,0 @@ -import XCTest -@testable import BeaconChain - -final class SlotTests: XCTestCase { - - func testToEpoch() { - XCTAssertEqual(Slot(128).toEpoch(), 2) - } -} diff --git a/Tests/BeaconChainTests/Mocks/MockStore.swift b/Tests/BeaconChainTests/Mocks/MockStore.swift deleted file mode 100644 index bb34686..0000000 --- a/Tests/BeaconChainTests/Mocks/MockStore.swift +++ /dev/null @@ -1,20 +0,0 @@ -import BeaconChain - -class MockStore: Store { - - func parent(_ block: BeaconBlock) -> BeaconBlock { - return block - } - - func children(_ block: BeaconBlock) -> [BeaconBlock] { - return [block] - } - - func latestAttestation(validator: ValidatorIndex) -> Attestation { - fatalError() - } - - func latestAttestationTarget(validator: ValidatorIndex) -> BeaconBlock { - fatalError() - } -} diff --git a/Tests/BeaconChainTests/Operators/PowerTests.swift b/Tests/BeaconChainTests/Operators/PowerTests.swift new file mode 100644 index 0000000..afcb8a9 --- /dev/null +++ b/Tests/BeaconChainTests/Operators/PowerTests.swift @@ -0,0 +1,28 @@ +import XCTest +@testable import BeaconChain + +class PowerTests: XCTestCase { + + // swiftlint:disable large_tuple + let tests: [(base: Int, exponent: Int, expected: Int)] = [ + (2, 0, 1), + (2, 1, 2), + (2, 2, 4), + (2, 3, 8), + (2, 4, 16) + ] + // swiftlint:enable large_tuple + + func testPowerForInt() { + for test in tests { + XCTAssert(test.base ** test.exponent == test.expected) + } + } + + func testPowerForUInt64() { + for test in tests { + XCTAssert(UInt64(test.base) ** UInt64(test.exponent) == UInt64(test.expected)) + } + } + +} diff --git a/Tests/BeaconChainTests/StateTransitionTests.swift b/Tests/BeaconChainTests/StateTransitionTests.swift deleted file mode 100644 index 4b47fa7..0000000 --- a/Tests/BeaconChainTests/StateTransitionTests.swift +++ /dev/null @@ -1,224 +0,0 @@ -import XCTest -@testable import BeaconChain - -//final class StateTransitionTests: XCTestCase { -// -// func testBaseReward() { -// let tests = [ -// (UInt64(0), UInt64(0)), -// (MIN_DEPOSIT_AMOUNT, UInt64(61)), -// (MAX_DEPOSIT_AMOUNT, UInt64(1976)), -// (UInt64(40 * 1e9), UInt64(1976)) -// ] -// -// for test in tests { -// var state = BeaconChain.genesisState( -// genesisTime: 0, -// latestEth1Data: Eth1Data(depositRoot: Data(count: 32), blockHash: Data(count: 32)) -// ) -// -// state.validatorBalances.append(test.0) -// -// XCTAssertEqual(test.1, StateTransition.baseReward(state: state, index: 0, baseRewardQuotient: 3237888)) -// } -// } -// -// func testInactivityPenalty() { -// let tests = [ -// (UInt64(1), UInt64(2929)), -// (UInt64(2), UInt64(3883)), -// (UInt64(5), UInt64(6744)), -// (UInt64(10), UInt64(11512)), -// (UInt64(50), UInt64(49659)) -// ] as [(UInt64, UInt64)] -// -// for test in tests { -// let state = BeaconChain.genesisState( -// genesisTime: TimeInterval(0), -// latestEth1Data: Eth1Data(depositRoot: Data(count: 32), blockHash: Data(count: 32)) -// ) -// -// state.validatorBalances.append(MAX_DEPOSIT_AMOUNT) -// -// XCTAssertEqual( -// test.1, -// StateTransition.inactivityPenalty( -// state: state, -// index: 0, -// epochsSinceFinality: test.0, -// baseRewardQuotient: 3237888 -// ) -// ) -// } -// } -// -// func testExpectedFFGTarget() { -// // @todo check these numbers -// let tests = [ -// ([Int](), [31999427550, 31999427550, 31999427550, 31999427550]), -// ([0,1], [32000286225, 32000286225, 31999427550, 31999427550]), -// ([0,1,2,3], [32000572450, 32000572450, 32000572450, 32000572450]) -// ] as [([Int], [UInt64])] -// -// for test in tests { -// let state = BeaconChain.genesisState( -// genesisTime: TimeInterval(0), -// latestEth1Data: Eth1Data(depositRoot: Data(count: 32), blockHash: Data(count: 32)) -// ) -// -// for _ in 0..<4 { -// state.validatorBalances.append(MAX_DEPOSIT_AMOUNT) -// state.validatorRegistry.append( -// Validator( -// pubkey: Data(count: 32), -// withdrawalCredentials: Data(count: 32), -// randaoCommitment: Data(count: 32), -// randaoLayers: 0, -// activationSlot: 0, -// exitSlot: FAR_FUTURE_SLOT, -// withdrawalSlot: 0, -// penalizedSlot: 0, -// exitCount: 0, -// statusFlags: 0, -// custodyCommitment: Data(count: 32), -// latestCustodyReseedSlot: 0, -// penultimateCustodyReseedSlot: 0 -// ) -// ) -// } -// -// let totalBalance = MAX_DEPOSIT_AMOUNT * UInt64(test.1.count) -// -// let newState = StateTransition.expectedFFGTarget( -// state: state, -// previousEpochBoundaryAttesterIndices: test.0, -// activeValidators: Set([0,1,2,3]), -// previousEpochBoundaryAttestingBalance: MAX_DEPOSIT_AMOUNT * UInt64(test.0.count), -// baseRewardQuotient: StateTransition.baseRewardQuotient(totalBalance: totalBalance), -// totalBalance: totalBalance -// ) -// -// for (i, balance) in test.1.enumerated() { -// XCTAssertEqual(balance, newState.validatorBalances[i]) -// } -// } -// } -// -// func testExpectedFFGSource() { -// // @todo check these numbers -// let tests = [ -// ([Int](), [31999427550, 31999427550, 31999427550, 31999427550]), -// ([0,1], [32000286225, 32000286225, 31999427550, 31999427550]), -// ([0,1,2,3], [32000572450, 32000572450, 32000572450, 32000572450]) -// ] as [([Int], [UInt64])] -// -// for test in tests { -// let state = BeaconChain.genesisState( -// genesisTime: TimeInterval(0), -// latestEth1Data: Eth1Data(depositRoot: Data(count: 32), blockHash: Data(count: 32)) -// ) -// -// for _ in 0..<4 { -// state.validatorBalances.append(MAX_DEPOSIT_AMOUNT) -// state.validatorRegistry.append( -// Validator( -// pubkey: Data(count: 32), -// withdrawalCredentials: Data(count: 32), -// randaoCommitment: Data(count: 32), -// randaoLayers: 0, -// activationSlot: 0, -// exitSlot: FAR_FUTURE_SLOT, -// withdrawalSlot: 0, -// penalizedSlot: 0, -// exitCount: 0, -// statusFlags: 0, -// custodyCommitment: Data(count: 32), -// latestCustodyReseedSlot: 0, -// penultimateCustodyReseedSlot: 0 -// ) -// ) -// } -// -// let totalBalance = MAX_DEPOSIT_AMOUNT * UInt64(test.1.count) -// -// let newState = StateTransition.expectedFFGSource( -// state: state, -// previousEpochJustifiedAttesterIndices: test.0, -// activeValidators: Set([0,1,2,3]), -// previousEpochJustifiedAttestingBalance: MAX_DEPOSIT_AMOUNT * UInt64(test.0.count), -// baseRewardQuotient: StateTransition.baseRewardQuotient(totalBalance: totalBalance), -// totalBalance: totalBalance -// ) -// -// for (i, balance) in test.1.enumerated() { -// XCTAssertEqual(balance, newState.validatorBalances[i]) -// } -// } -// } -// -// func testExpectedBeaconChainHead() { -// // @todo check these numbers -// let tests = [ -// ([Int](), [31999427550, 31999427550, 31999427550, 31999427550]), -// ([0,1], [32000286225, 32000286225, 31999427550, 31999427550]), -// ([0,1,2,3], [32000572450, 32000572450, 32000572450, 32000572450]) -// ] as [([Int], [UInt64])] -// -// for test in tests { -// let state = BeaconChain.genesisState( -// genesisTime: TimeInterval(0), -// latestEth1Data: Eth1Data(depositRoot: Data(count: 32), blockHash: Data(count: 32)) -// ) -// -// for _ in 0..<4 { -// state.validatorBalances.append(MAX_DEPOSIT_AMOUNT) -// state.validatorRegistry.append( -// Validator( -// pubkey: Data(count: 32), -// withdrawalCredentials: Data(count: 32), -// randaoCommitment: Data(count: 32), -// randaoLayers: 0, -// activationSlot: 0, -// exitSlot: FAR_FUTURE_SLOT, -// withdrawalSlot: 0, -// penalizedSlot: 0, -// exitCount: 0, -// statusFlags: 0, -// custodyCommitment: Data(count: 32), -// latestCustodyReseedSlot: 0, -// penultimateCustodyReseedSlot: 0 -// ) -// ) -// } -// -// let totalBalance = MAX_DEPOSIT_AMOUNT * UInt64(test.1.count) -// -// let newState = StateTransition.expectedBeaconChainHead( -// state: state, -// previousEpochHeadAttesterIndices: test.0, -// activeValidators: Set([0,1,2,3]), -// previousEpochHeadAttestingBalance: MAX_DEPOSIT_AMOUNT * UInt64(test.0.count), -// baseRewardQuotient: StateTransition.baseRewardQuotient(totalBalance: totalBalance), -// totalBalance: totalBalance -// ) -// -// for (i, balance) in test.1.enumerated() { -// XCTAssertEqual(balance, newState.validatorBalances[i]) -// } -// } -// } -// -// func testBaseRewardQuotient() { -// var tests = [(UInt64, UInt64)]() -// tests.append((UInt64(0), UInt64(0))) -// tests.append((UInt64(1e6 * 1e9), UInt64(988211))) -// tests.append((UInt64(2e6 * 1e9), UInt64(1397542))) -// tests.append((UInt64(5e6 * 1e9), UInt64(2209708))) -// tests.append((UInt64(10e6 * 1e9), UInt64(3125000))) -// tests.append((UInt64(20e6 * 1e9), UInt64(4419417))) -// -// for test in tests { -// XCTAssertEqual(StateTransition.baseRewardQuotient(totalBalance: test.0), test.1) -// } -// } -//} diff --git a/Tests/BeaconChainTests/XCTestManifests.swift b/Tests/BeaconChainTests/XCTestManifests.swift index de3d05a..cfb3be8 100644 --- a/Tests/BeaconChainTests/XCTestManifests.swift +++ b/Tests/BeaconChainTests/XCTestManifests.swift @@ -1,87 +1,13 @@ #if !canImport(ObjectiveC) import XCTest -extension ArrayAttestationTargetTests { +extension PowerTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` // to regenerate. - static let __allTests__ArrayAttestationTargetTests = [ - ("testVotes", testVotes), - ] -} - -extension ArrayTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ArrayTests = [ - ("testSplit", testSplit), - ] -} - -extension ArrayValidatorIndexTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ArrayValidatorIndexTests = [ - ("testTotalBalance", testTotalBalance), - ] -} - -extension ArrayValidatorTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ArrayValidatorTests = [ - ("testActiveIndices", testActiveIndices), - ] -} - -extension BeaconChainTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__BeaconChainTests = [ - ("testIntegerSquareRoot", testIntegerSquareRoot), - ("testIsDoubleVote", testIsDoubleVote), - ] -} - -extension EpochTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__EpochTests = [ - ("testEntryExitEpoch", testEntryExitEpoch), - ("testStartSlot", testStartSlot), - ] -} - -extension ForkTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__ForkTests = [ - ("testDomain", testDomain), - ("testVersion", testVersion), - ] -} - -extension IntTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__IntTests = [ - ("testIsPowerOfTwo", testIsPowerOfTwo), - ] -} - -extension SlotTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__SlotTests = [ - ("testToEpoch", testToEpoch), + static let __allTests__PowerTests = [ + ("testPowerForInt", testPowerForInt), + ("testPowerForUInt64", testPowerForUInt64), ] } @@ -90,24 +16,14 @@ extension ValidatorTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__ValidatorTests = [ - ("testActivate", testActivate), - ("testExitValidator", testExitValidator), ("testIsActive", testIsActive), - ("testPrepareForWithdrawal", testPrepareForWithdrawal), + ("testIsSlashable", testIsSlashable), ] } public func __allTests() -> [XCTestCaseEntry] { return [ - testCase(ArrayAttestationTargetTests.__allTests__ArrayAttestationTargetTests), - testCase(ArrayTests.__allTests__ArrayTests), - testCase(ArrayValidatorIndexTests.__allTests__ArrayValidatorIndexTests), - testCase(ArrayValidatorTests.__allTests__ArrayValidatorTests), - testCase(BeaconChainTests.__allTests__BeaconChainTests), - testCase(EpochTests.__allTests__EpochTests), - testCase(ForkTests.__allTests__ForkTests), - testCase(IntTests.__allTests__IntTests), - testCase(SlotTests.__allTests__SlotTests), + testCase(PowerTests.__allTests__PowerTests), testCase(ValidatorTests.__allTests__ValidatorTests), ] }