Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

V0.8.3 tests part2 - BLS + Shuffling + State transition epoch #372

Merged
merged 13 commits into from
Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions beacon_chain/spec/beaconstate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ func get_total_balance*(state: BeaconState, validators: auto): Gwei =
foldl(validators, a + state.validators[b].effective_balance, 0'u64)
)

# XXX: Move to state_transition_epoch.nim?
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.3/specs/core/0_beacon-chain.md#registry-updates
func process_registry_updates*(state: var BeaconState) =
## Process activation eligibility and ejections
Expand Down
15 changes: 9 additions & 6 deletions beacon_chain/spec/state_transition_epoch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -444,21 +444,24 @@ func process_rewards_and_penalties(
decrease_balance(state, i.ValidatorIndex, penalties1[i] + penalties2[i])

# https://github.com/ethereum/eth2.0-specs/blob/v0.8.3/specs/core/0_beacon-chain.md#slashings
func process_slashings(state: var BeaconState) =
func process_slashings*(state: var BeaconState) =
let
epoch = get_current_epoch(state)
total_balance = get_total_active_balance(state)

for index, validator in state.validators:
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR div 2 ==
validator.withdrawable_epoch:
let penalty =
validator.effective_balance *
min(sum(state.slashings) * 3, total_balance) div total_balance
let increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty
# numerator to avoid uint64 overflow
let penalty_numerator =
validator.effective_balance div increment *
min(sum(state.slashings) * 3, total_balance)
let penalty = penalty_numerator div total_balance * increment
decrease_balance(state, index.ValidatorIndex, penalty)

# https://github.com/ethereum/eth2.0-specs/blob/v0.8.3/specs/core/0_beacon-chain.md#final-updates
proc process_final_updates(state: var BeaconState) =
proc process_final_updates*(state: var BeaconState) =
let
current_epoch = get_current_epoch(state)
next_epoch = current_epoch + 1
Expand All @@ -482,7 +485,7 @@ proc process_final_updates(state: var BeaconState) =
let
index_epoch = next_epoch + ACTIVATION_EXIT_DELAY
index_root_position = index_epoch mod EPOCHS_PER_HISTORICAL_VECTOR
indices_list = get_active_validator_indices(state, index_epoch)
indices_list = sszList(get_active_validator_indices(state, index_epoch), VALIDATOR_REGISTRY_LIMIT)
state.active_index_roots[index_root_position] = hash_tree_root(indices_list)

# Set committees root
Expand Down
154 changes: 154 additions & 0 deletions tests/helpers/debug_state.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# beacon_chain
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import
macros,
../../beacon_chain/spec/[datatypes, crypto, digest]
# digest is necessary for them to be printed as hex

# Define comparison of object variants for BLSValue
# https://github.com/nim-lang/Nim/issues/6676
# ----------------------------------------------------------------

proc processNode(arg, a,b, result: NimNode) =
case arg.kind
of nnkIdentDefs:
let field = arg[0]
result.add quote do:
if `a`.`field` != `b`.`field`:
return false
of nnkRecCase:
let kindField = arg[0][0]
processNode(arg[0], a,b, result)
let caseStmt = nnkCaseStmt.newTree(newDotExpr(a, kindField))
for i in 1 ..< arg.len:
let inputBranch = arg[i]
let outputBranch = newTree(inputBranch.kind)
let body = newStmtList()
if inputBranch.kind == nnkOfBranch:
outputBranch.add inputBranch[0]
processNode(inputBranch[1], a,b, body)
else:
inputBranch.expectKind nnkElse
processNode(inputBranch[0], a,b, body)
outputBranch.add body
caseStmt.add outputBranch
result.add caseStmt
of nnkRecList:
for child in arg:
child.expectKind {nnkIdentDefs, nnkRecCase}
processNode(child, a,b, result)
else:
arg.expectKind {nnkIdentDefs, nnkRecCase, nnkRecList}

macro myCompareImpl(a,b: typed): untyped =
a.expectKind nnkSym
b.expectKind nnkSym
assert sameType(a, b)

let typeImpl = a.getTypeImpl
var checks = newSeq[NimNode]()

# uncomment to debug
# echo typeImpl.treeRepr

result = newStmtList()
processNode(typeImpl[2], a, b, result)

result.add quote do:
return true

# uncomment to debug
# echo result.repr

proc `==`*[T](a,b: BlsValue[T]): bool =
myCompareImpl(a,b)
# ---------------------------------------------------------------------

# This tool inspects and compare 2 instances of a type recursively
# highlighting the differences

const builtinTypes = [
"int", "int8", "int16", "int32", "int64",
"uint", "uint8", "uint16", "uint32", "uint64",
"byte", "float32", "float64",
# "array", "seq", # wrapped in nnkBracketExpr
"char", "string"
]

proc compareStmt(xSubField, ySubField: NimNode, stmts: var NimNode) =
let xStr = $xSubField.toStrLit
let yStr = $ySubField.toStrLit

stmts.add quote do:
doAssert(
`xSubField` == `ySubField`,
"\nDiff: " & `xStr` & " = " & $`xSubField` & "\n" &
"and " & `yStr` & " = " & $`ySubField` & "\n"
)

proc compareContainerStmt(xSubField, ySubField: NimNode, stmts: var NimNode) =
let xStr = $xSubField.toStrLit
let yStr = $ySubField.toStrLit


stmts.add quote do:
doAssert(
`xSubField`.len == `ySubField`.len,
"\nDiff: " & `xStr` & ".len = " & $`xSubField`.len & "\n" &
"and " & `yStr` & ".len = " & $`ySubField`.len & "\n"
)
for idx in `xSubField`.low .. `xSubField`.high:
doAssert(
`xSubField`[idx] == `ySubField`[idx],
"\nDiff: " & `xStr` & "[" & $idx & "] = " & $`xSubField`[idx] & "\n" &
"and " & `yStr` & "[" & $idx & "] = " & $`ySubField`[idx] & "\n"
)

proc inspectType(tImpl, xSubField, ySubField: NimNode, stmts: var NimNode) =
# echo "kind: " & $tImpl.kind
# echo " -- field: " & $xSubField.toStrLit
case tImpl.kind
of nnkObjectTy:
# pass the records
let records = tImpl[2]
records.expectKind(nnkRecList)
for decl in records:
inspectType(
decl[1], # field type
nnkDotExpr.newTree(xSubField, decl[0]), # Accessor
nnkDotExpr.newTree(ySubField, decl[0]),
stmts
)
of {nnkRefTy, nnkDistinctTy}:
inspectType(tImpl[0], xSubField, ySubField, stmts)
of {nnkSym, nnkBracketExpr}:
if tImpl.kind == nnkBracketExpr:
assert tImpl[0].eqIdent"seq" or tImpl[0].eqIdent"array", "Error: unsupported generic type: " & $tImpl[0]
compareContainerStmt(xSubField, ySubField, stmts)
elif $tImpl in builtinTypes:
compareStmt(xSubField, ySubField, stmts)
elif $tImpl in ["ValidatorSig", "ValidatorPubKey"]:
# Workaround BlsValue being a case object
compareStmt(xSubField, ySubField, stmts)
else:
inspectType(tImpl.getTypeImpl(), xSubField, ySubField, stmts)
else:
error "Unsupported kind: " & $tImpl.kind &
" for field \"" & $xSubField.toStrLit &
"\" of type \"" & tImpl.repr

macro reportDiff*(x, y: typed{`var`|`let`|`const`}): untyped =
assert sameType(x, y)
result = newStmtList()

let typeImpl = x.getTypeImpl
inspectType(typeImpl, x, y, result)

# echo result.toStrLit

# -----------------------------------------
2 changes: 1 addition & 1 deletion tests/official/fixtures
50 changes: 50 additions & 0 deletions tests/official/fixtures_utils.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# beacon_chain
# Copyright (c) 2018-Present Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import
# Standard library
os, strutils, typetraits,
# Internals
../../beacon_chain/ssz,
# Status libs
stew/byteutils,
serialization, json_serialization

export # Workaround:
# - https://github.com/status-im/nim-serialization/issues/4
# - https://github.com/status-im/nim-serialization/issues/5
# - https://github.com/nim-lang/Nim/issues/11225
serialization.readValue,
Json, ssz

# Process current EF test format (up to 0.8.2+)
# ---------------------------------------------

# #######################
# JSON deserialization

proc readValue*(r: var JsonReader, a: var seq[byte]) {.inline.} =
## Custom deserializer for seq[byte]
a = hexToSeqByte(r.readValue(string))

# #######################
# Test helpers

const
FixturesDir* = currentSourcePath.rsplit(DirSep, 1)[0] / "fixtures"
JsonTestsDir* = FixturesDir/"json_tests_v0.8.3"
SszTestsDir* = FixturesDir/"eth2.0-spec-tests"/"tests"

proc parseTest*(path: string, Format: typedesc[Json or SSZ], T: typedesc): T =
try:
# debugEcho " [Debug] Loading file: \"", path, '\"'
result = Format.loadFile(path, T)
except SerializationError as err:
writeStackTrace()
stderr.write $Format & " load issue for file \"", path, "\"\n"
stderr.write err.formatMsg(path), "\n"
quit 1
7 changes: 7 additions & 0 deletions tests/official/fixtures_utils_v0_8_1.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# beacon_chain
# Copyright (c) 2018-Present Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import
# Standard library
os, strutils,
Expand Down
50 changes: 14 additions & 36 deletions tests/official/test_fixture_bls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,15 @@

import
# Standard libs
ospaths, unittest, endians,
os, unittest, endians,
# Status libs
blscurve, stew/byteutils,
# Beacon chain internals
../../beacon_chain/spec/crypto,
# Test utilities
./fixtures_utils_v0_8_1
./fixtures_utils

type
# # TODO - but already tested in nim-blscurve
# BLSUncompressedG2 = object
# input*: tuple[
# message: seq[byte],
# domain: array[1, byte]
# ]
# output*: ECP2_BLS381

# # TODO - but already tested in nim-blscurve
# BLSCompressedG2 = object
# input*: tuple[
# message: seq[byte],
# domain: array[1, byte]
# ]
# output*: ECP2_BLS381

Domain = distinct uint64
## Domains have custom hex serialization

Expand Down Expand Up @@ -65,40 +49,34 @@ proc readValue*(r: var JsonReader, a: var Domain) {.inline.} =
let be_uint = hexToPaddedByteArray[8](r.readValue(string))
bigEndian64(a.addr, be_uint.unsafeAddr)

const TestsPath = JsonTestsDir / "bls"

var
blsPrivToPubTests: Tests[BLSPrivToPub]
blsSignMsgTests: Tests[BLSSignMsg]
blsAggSigTests: Tests[BLSAggSig]
blsAggPubKeyTests: Tests[BLSAggPubKey]
const BLSDir = JsonTestsDir/"general"/"phase0"/"bls"

suite "Official - BLS tests":
test "Parsing the official BLS tests":
blsPrivToPubTests = parseTests(TestsPath / "priv_to_pub" / "priv_to_pub.json", BLSPrivToPub)
blsSignMsgTests = parseTests(TestsPath / "sign_msg" / "sign_msg.json", BLSSignMsg)
blsAggSigTests = parseTests(TestsPath / "aggregate_sigs" / "aggregate_sigs.json", BLSAggSig)
blsAggPubKeyTests = parseTests(TestsPath / "aggregate_pubkeys" / "aggregate_pubkeys.json", BLSAggPubKey)

test "Private to public key conversion":
for t in blsPrivToPubTests.test_cases:
for file in walkDirRec(BLSDir/"priv_to_pub"):
let t = parseTest(file, Json, BLSPrivToPub)
let implResult = t.input.pubkey()
check: implResult == t.output

test "Message signing":
for t in blsSignMsgTests.test_cases:
for file in walkDirRec(BLSDir/"sign_msg"):
let t = parseTest(file, Json, BLSSignMsg)
let implResult = t.input.privkey.bls_sign(
t.input.message,
uint64(t.input.domain)
)
)
check: implResult == t.output

test "Aggregating signatures":
for t in blsAggSigTests.test_cases:
for file in walkDirRec(BLSDir/"aggregate_sigs"):
let t = parseTest(file, Json, BLSAggSig)
let implResult = t.input.combine()
check: implResult == t.output

test "Aggregating public keys":
for t in blsAggPubKeyTests.test_cases:
for file in walkDirRec(BLSDir/"aggregate_pubkeys"):
let t = parseTest(file, Json, BLSAggPubKey)
let implResult = t.input.combine()
check: implResult == t.output

# TODO: msg_hash_compressed and uncompressed
23 changes: 8 additions & 15 deletions tests/official/test_fixture_shuffling.nim
Original file line number Diff line number Diff line change
@@ -1,37 +1,30 @@
# beacon_chain
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-Present Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import
# Standard library
ospaths, unittest,
os, unittest,
# Beacon chain internals
../../beacon_chain/spec/[datatypes, validator, digest],
# Test utilities
../testutil,
./fixtures_utils_v0_8_1
./fixtures_utils

type
Shuffling* = object
seed*: Eth2Digest
count*: uint64
shuffled*: seq[ValidatorIndex]
mapping*: seq[ValidatorIndex]

when const_preset == "mainnet":
const TestsPath = JsonTestsDir / "shuffling" / "core" / "shuffling_full.json"
elif const_preset == "minimal":
const TestsPath = JsonTestsDir / "shuffling" / "core" / "shuffling_minimal.json"

var shufflingTests: Tests[Shuffling]
const ShufflingDir = JsonTestsDir/const_preset/"phase0"/"shuffling"/"core"/"shuffle"

suite "Official - Shuffling tests [Preset: " & preset():
test "Parsing the official shuffling tests [Preset: " & preset():
shufflingTests = parseTests(TestsPath, Shuffling)

test "Shuffling a sequence of N validators" & preset():
for t in shufflingTests.test_cases:
for file in walkDirRec(ShufflingDir):
let t = parseTest(file, Json, Shuffling)
let implResult = get_shuffled_seq(t.seed, t.count)
check: implResult == t.shuffled
check: implResult == t.mapping
Loading