Skip to content

Commit

Permalink
Update the beacon and state network content key prefixes
Browse files Browse the repository at this point in the history
The Portal specifications were adjusted so that the content key
prefixes become unique selectors over all networks.

Fixes issue #1924

SSZ Union is still used, but it could be that in the future we
need to move to our own custom serialization here.

The unused enum value and the fact that we have to add these to
the case of is quite annoying but unless we stop using a case
object seems not avoidable.
  • Loading branch information
kdeme committed Dec 19, 2023
1 parent ffa8ad2 commit 2e7b132
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 19 deletions.
23 changes: 18 additions & 5 deletions fluffy/network/beacon/beacon_content.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Nimbus - Portal Network
# Fluffy - Portal Network
# Copyright (c) 2022-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
Expand Down Expand Up @@ -30,10 +30,12 @@ const

type
ContentType* = enum
lightClientBootstrap = 0x00
lightClientUpdate = 0x01
lightClientFinalityUpdate = 0x02
lightClientOptimisticUpdate = 0x03
# Note: See same note as for state_content.nim
unused = 0x00
lightClientBootstrap = 0x10
lightClientUpdate = 0x11
lightClientFinalityUpdate = 0x12
lightClientOptimisticUpdate = 0x13

# TODO: Consider how we will gossip bootstraps?
# In consensus light client operation a node trusts only one bootstrap hash,
Expand All @@ -59,6 +61,8 @@ type

ContentKey* = object
case contentType*: ContentType
of unused:
discard
of lightClientBootstrap:
lightClientBootstrapKey*: LightClientBootstrapKey
of lightClientUpdate:
Expand All @@ -84,8 +88,17 @@ func forkDigestAtEpoch*(
forkDigests.atEpoch(epoch, cfg)

func encode*(contentKey: ContentKey): ByteList =
doAssert(contentKey.contentType != unused)
ByteList.init(SSZ.encode(contentKey))

proc readSszBytes*(
data: openArray[byte], val: var ContentKey) {.raises: [SszError].} =
mixin readSszValue
if data.len() > 0 and data[0] == ord(unused):
raise newException(MalformedSszError, "SSZ selector unused value")

readSszValue(data, val)

func decode*(contentKey: ByteList): Opt[ContentKey] =
try:
Opt.some(SSZ.decode(contentKey.asSeq(), ContentKey))
Expand Down
4 changes: 4 additions & 0 deletions fluffy/network/beacon/beacon_db.nim
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ proc createGetHandler*(db: BeaconDb): DbGetHandler =
return Opt.none(seq[byte])

case contentKey.contentType:
of unused:
raiseAssert "Should not be used and fail at decoding"
of lightClientBootstrap:
db.get(contentId)
of lightClientUpdate:
Expand Down Expand Up @@ -309,6 +311,8 @@ proc createStoreHandler*(db: BeaconDb): DbStoreHandler =
return

case contentKey.contentType:
of unused:
raiseAssert "Should not be used and fail at decoding"
of lightClientBootstrap:
db.put(contentId, content)
of lightClientUpdate:
Expand Down
2 changes: 2 additions & 0 deletions fluffy/network/beacon/beacon_network.nim
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ proc validateContent(
return err("Error decoding content key")

case key.contentType:
of unused:
raiseAssert "Should not be used and fail at decoding"
of lightClientBootstrap:
let decodingResult = decodeLightClientBootstrapForked(
n.forkDigests, content)
Expand Down
34 changes: 28 additions & 6 deletions fluffy/network/state/state_content.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Nimbus
# Fluffy
# Copyright (c) 2021-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
Expand All @@ -23,11 +23,19 @@ type
Address* = array[20, byte]

ContentType* = enum
accountTrieNode = 0x00
contractStorageTrieNode = 0x01
accountTrieProof = 0x02
contractStorageTrieProof = 0x03
contractBytecode = 0x04
# Note: Need to add this unused value as a case object with an enum without
# a 0 valueis not allowed: "low(contentType) must be 0 for discriminant".
# For prefix values that are in the enum gap, the deserialization will fail
# at runtime as is wanted.
# In the future it might be possible that this will fail at compile time for
# the SSZ Union type, but currently it is allowed in the implementation, and
# the SSZ spec is not explicit about disallowing this.
unused = 0x00
accountTrieNode = 0x20
contractStorageTrieNode = 0x21
accountTrieProof = 0x22
contractStorageTrieProof = 0x23
contractBytecode = 0x24

AccountTrieNodeKey* = object
path*: ByteList
Expand Down Expand Up @@ -55,6 +63,8 @@ type

ContentKey* = object
case contentType*: ContentType
of unused:
discard
of accountTrieNode:
accountTrieNodeKey*: AccountTrieNodeKey
of contractStorageTrieNode:
Expand All @@ -67,8 +77,18 @@ type
contractBytecodeKey*: ContractBytecodeKey

func encode*(contentKey: ContentKey): ByteList =
doAssert(contentKey.contentType != unused)
ByteList.init(SSZ.encode(contentKey))

proc readSszBytes*(
data: openArray[byte], val: var ContentKey
) {.raises: [SszError].} =
mixin readSszValue
if data.len() > 0 and data[0] == ord(unused):
raise newException(MalformedSszError, "SSZ selector is unused value")

readSszValue(data, val)

func decode*(contentKey: ByteList): Opt[ContentKey] =
try:
Opt.some(SSZ.decode(contentKey.asSeq(), ContentKey))
Expand All @@ -84,6 +104,8 @@ template computeContentId*(digestCtxType: type, body: untyped): ContentId =

func toContentId*(contentKey: ContentKey): ContentId =
case contentKey.contentType:
of unused:
raiseAssert "Should not be used and fail at decoding"
of accountTrieNode: # sha256(path | node_hash)
let key = contentKey.accountTrieNodeKey
computeContentId sha256:
Expand Down
25 changes: 25 additions & 0 deletions fluffy/tests/beacon_network_tests/test_beacon_content.nim
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,28 @@ suite "Beacon Content Encodings":
decodeLightClientBootstrapForked(forkDigests, @[]).isErr()
decodeLightClientBootstrapForked(forkDigests, encodedTooEarlyFork).isErr()
decodeLightClientBootstrapForked(forkDigests, encodedUnknownFork).isErr()

suite "Beacon ContentKey Encodings ":
test "Invalid prefix - 0 value":
let encoded = ByteList.init(@[byte 0x00])
let decoded = decode(encoded)

check decoded.isNone()

test "Invalid prefix - before valid range":
let encoded = ByteList.init(@[byte 0x01])
let decoded = decode(encoded)

check decoded.isNone()

test "Invalid prefix - after valid range":
let encoded = ByteList.init(@[byte 0x14])
let decoded = decode(encoded)

check decoded.isNone()

test "Invalid key - empty input":
let encoded = ByteList.init(@[])
let decoded = decode(encoded)

check decoded.isNone()
38 changes: 31 additions & 7 deletions fluffy/tests/portal_spec_tests/mainnet/test_state_content.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Fluffy
# Copyright (c) 2021-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
Expand Down Expand Up @@ -31,7 +31,7 @@ suite "State ContentKey Encodings":

# Output
contentKeyHex =
"0044000000b8be7903aee73b8f6a59cd44a1f52c62148e1f376c0dfa1f5f773a98666efc2bd1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d01020001"
"2044000000b8be7903aee73b8f6a59cd44a1f52c62148e1f376c0dfa1f5f773a98666efc2bd1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d01020001"
contentId =
"41237096982860596884042712109427867048220765019203857308279863638242761605893"
# or
Expand Down Expand Up @@ -67,7 +67,7 @@ suite "State ContentKey Encodings":

# Output
contentKeyHex =
"01829bd824b016326a401d083b33d092293333a830580000003e190b68719aecbcb28ed2271014dd25f2aa633184988eb414189ce0899cade5d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d01000f0e0c00"
"21829bd824b016326a401d083b33d092293333a830580000003e190b68719aecbcb28ed2271014dd25f2aa633184988eb414189ce0899cade5d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d01000f0e0c00"
contentId =
"43529358882110548041037387588279806363134301284609868141745095118932570363585"
# or
Expand Down Expand Up @@ -100,7 +100,7 @@ suite "State ContentKey Encodings":
# Output
const
contentKeyHex =
"02829bd824b016326a401d083b33d092293333a830d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
"22829bd824b016326a401d083b33d092293333a830d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
contentId =
"45301550050471302973396879294932122279426162994178563319590607565171451545101"
# or
Expand Down Expand Up @@ -135,7 +135,7 @@ suite "State ContentKey Encodings":

# Output
contentKeyHex =
"03829bd824b016326a401d083b33d092293333a830c8a6030000000000000000000000000000000000000000000000000000000000d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
"23829bd824b016326a401d083b33d092293333a830c8a6030000000000000000000000000000000000000000000000000000000000d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
contentId =
"80413803151602881485894828440259195604313253842905231566803078625935967002376"
# or
Expand Down Expand Up @@ -172,7 +172,7 @@ suite "State ContentKey Encodings":
# Output
const
contentKeyHex =
"04829bd824b016326a401d083b33d092293333a830d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
"24829bd824b016326a401d083b33d092293333a830d1c390624d3bd4e409a61a858e5dcc5517729a9170d014a6c96530d64dd8621d"
contentId =
"9243655320250466575533858917172702581481192615849913473767356296630272634800"
# or
Expand All @@ -199,3 +199,27 @@ suite "State ContentKey Encodings":
toContentId(contentKey) == parse(contentId, StUint[256], 10)
# In stint this does BE hex string
toContentId(contentKey).toHex() == contentIdHexBE

test "Invalid prefix - 0 value":
let encoded = ByteList.init(@[byte 0x00])
let decoded = decode(encoded)

check decoded.isNone()

test "Invalid prefix - before valid range":
let encoded = ByteList.init(@[byte 0x01])
let decoded = decode(encoded)

check decoded.isNone()

test "Invalid prefix - after valid range":
let encoded = ByteList.init(@[byte 0x25])
let decoded = decode(encoded)

check decoded.isNone()

test "Invalid key - empty input":
let encoded = ByteList.init(@[])
let decoded = decode(encoded)

check decoded.isNone()

0 comments on commit 2e7b132

Please sign in to comment.