mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-02-04 16:25:10 +00:00
Naive implementation of recursive gossip (#2024)
This commit is contained in:
parent
5642662850
commit
3305c02856
@ -157,7 +157,7 @@ func packNibbles*(nibbles: seq[byte]): Nibbles =
|
|||||||
Nibbles(output)
|
Nibbles(output)
|
||||||
|
|
||||||
func unpackNibbles*(nibbles: Nibbles): seq[byte] =
|
func unpackNibbles*(nibbles: Nibbles): seq[byte] =
|
||||||
doAssert(nibbles.len() <= MAX_PACKED_NIBBLES_LEN, "Can't unpack more than 32 nibbles")
|
doAssert(nibbles.len() <= MAX_PACKED_NIBBLES_LEN, "Packed nibbles length is too long")
|
||||||
|
|
||||||
var output = newSeq[byte]()
|
var output = newSeq[byte]()
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
import
|
import
|
||||||
stew/results, chronos, chronicles,
|
stew/results, chronos, chronicles,
|
||||||
|
eth/common/eth_hash,
|
||||||
|
eth/common,
|
||||||
eth/p2p/discoveryv5/[protocol, enr],
|
eth/p2p/discoveryv5/[protocol, enr],
|
||||||
../../database/content_db,
|
../../database/content_db,
|
||||||
../wire/[portal_protocol, portal_stream, portal_protocol_config],
|
../wire/[portal_protocol, portal_stream, portal_protocol_config],
|
||||||
@ -58,10 +60,28 @@ proc getContent*(n: StateNetwork, key: ContentKey):
|
|||||||
# domain types.
|
# domain types.
|
||||||
return Opt.some(contentResult.content)
|
return Opt.some(contentResult.content)
|
||||||
|
|
||||||
proc validateContent(
|
proc validateAccountTrieNode(key: ContentKey, contentValue: seq[byte]): bool =
|
||||||
|
let value = decodeSsz(contentValue, AccountTrieNodeOffer).valueOr:
|
||||||
|
warn "Received invalid account trie proof", error
|
||||||
|
return false
|
||||||
|
true
|
||||||
|
|
||||||
|
proc validateContractTrieNode(key: ContentKey, contentValue: seq[byte]): bool =
|
||||||
|
let value = decodeSsz(contentValue, ContractTrieNodeOffer).valueOr:
|
||||||
|
warn "Received invalid contract trie proof", error
|
||||||
|
return false
|
||||||
|
true
|
||||||
|
|
||||||
|
proc validateContractCode(key: ContentKey, contentValue: seq[byte]): bool =
|
||||||
|
let value = decodeSsz(contentValue, ContractCodeOffer).valueOr:
|
||||||
|
warn "Received invalid contract code", error
|
||||||
|
return false
|
||||||
|
true
|
||||||
|
|
||||||
|
proc validateContent*(
|
||||||
n: StateNetwork,
|
n: StateNetwork,
|
||||||
contentKey: ByteList,
|
contentKey: ByteList,
|
||||||
contentValue: seq[byte]): Future[bool] {.async.} =
|
contentValue: seq[byte]): bool =
|
||||||
let key = contentKey.decode().valueOr:
|
let key = contentKey.decode().valueOr:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@ -70,19 +90,19 @@ proc validateContent(
|
|||||||
warn "Received content with unused content type"
|
warn "Received content with unused content type"
|
||||||
false
|
false
|
||||||
of accountTrieNode:
|
of accountTrieNode:
|
||||||
true
|
validateAccountTrieNode(key, contentValue)
|
||||||
of contractTrieNode:
|
of contractTrieNode:
|
||||||
true
|
validateContractTrieNode(key, contentValue)
|
||||||
of contractCode:
|
of contractCode:
|
||||||
true
|
validateContractCode(key, contentValue)
|
||||||
|
|
||||||
proc validateContent(
|
proc validateContent(
|
||||||
n: StateNetwork,
|
n: StateNetwork,
|
||||||
contentKeys: ContentKeysList,
|
contentKeys: ContentKeysList,
|
||||||
contentValues: seq[seq[byte]]): Future[bool] {.async.} =
|
contentValues: seq[seq[byte]]): bool =
|
||||||
for i, contentValue in contentValues:
|
for i, contentValue in contentValues:
|
||||||
let contentKey = contentKeys[i]
|
let contentKey = contentKeys[i]
|
||||||
if await n.validateContent(contentKey, contentValue):
|
if n.validateContent(contentKey, contentValue):
|
||||||
let contentId = n.portalProtocol.toContentId(contentKey).valueOr:
|
let contentId = n.portalProtocol.toContentId(contentKey).valueOr:
|
||||||
error "Received offered content with invalid content key", contentKey
|
error "Received offered content with invalid content key", contentKey
|
||||||
return false
|
return false
|
||||||
@ -94,6 +114,64 @@ proc validateContent(
|
|||||||
error "Received offered content failed validation", contentKey
|
error "Received offered content failed validation", contentKey
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
proc recursiveGossipAccountTrieNode(
|
||||||
|
p: PortalProtocol,
|
||||||
|
maybeSrcNodeId: Opt[NodeId],
|
||||||
|
decodedKey: ContentKey,
|
||||||
|
contentValue: seq[byte]
|
||||||
|
): Future[void] {.async.} =
|
||||||
|
var nibbles = decodedKey.accountTrieNodeKey.path.unpackNibbles()
|
||||||
|
let decodedValue = decodeSsz(contentValue, AccountTrieNodeOffer).valueOr:
|
||||||
|
raiseAssert "Received offered content failed validation"
|
||||||
|
var proof = decodedValue.proof
|
||||||
|
discard nibbles.pop()
|
||||||
|
discard (distinctBase proof).pop()
|
||||||
|
let
|
||||||
|
updatedValue = AccountTrieNodeOffer(
|
||||||
|
proof: proof,
|
||||||
|
blockHash: decodedValue.blockHash,
|
||||||
|
)
|
||||||
|
updatedNodeHash = keccakHash(distinctBase proof[^1])
|
||||||
|
encodedValue = SSZ.encode(updatedValue)
|
||||||
|
updatedKey = AccountTrieNodeKey(path: nibbles.packNibbles(), nodeHash: updatedNodeHash)
|
||||||
|
encodedKey = ContentKey(accountTrieNodeKey: updatedKey, contentType: accountTrieNode).encode()
|
||||||
|
|
||||||
|
await neighborhoodGossipDiscardPeers(
|
||||||
|
p, maybeSrcNodeId, ContentKeysList.init(@[encodedKey]), @[encodedValue]
|
||||||
|
)
|
||||||
|
|
||||||
|
proc recursiveGossipContractTrieNode(
|
||||||
|
p: PortalProtocol,
|
||||||
|
maybeSrcNodeId: Opt[NodeId],
|
||||||
|
decodedKey: ContentKey,
|
||||||
|
contentValue: seq[byte]
|
||||||
|
): Future[void] {.async.} =
|
||||||
|
return
|
||||||
|
|
||||||
|
proc gossipContent*(
|
||||||
|
p: PortalProtocol,
|
||||||
|
maybeSrcNodeId: Opt[NodeId],
|
||||||
|
contentKeys: ContentKeysList,
|
||||||
|
contentValues: seq[seq[byte]]
|
||||||
|
): Future[void] {.async.} =
|
||||||
|
for i, contentValue in contentValues:
|
||||||
|
let
|
||||||
|
contentKey = contentKeys[i]
|
||||||
|
decodedKey = contentKey.decode().valueOr:
|
||||||
|
raiseAssert "Received offered content with invalid content key"
|
||||||
|
case decodedKey.contentType:
|
||||||
|
of unused:
|
||||||
|
warn("Gossiping content with unused content type")
|
||||||
|
continue
|
||||||
|
of accountTrieNode:
|
||||||
|
await recursiveGossipAccountTrieNode(p, maybeSrcNodeId, decodedKey, contentValue)
|
||||||
|
of contractTrieNode:
|
||||||
|
await recursiveGossipContractTrieNode(p, maybeSrcNodeId, decodedKey, contentValue)
|
||||||
|
of contractCode:
|
||||||
|
await neighborhoodGossipDiscardPeers(
|
||||||
|
p, maybeSrcNodeId, ContentKeysList.init(@[contentKey]), @[contentValue]
|
||||||
|
)
|
||||||
|
|
||||||
proc new*(
|
proc new*(
|
||||||
T: type StateNetwork,
|
T: type StateNetwork,
|
||||||
baseProtocol: protocol.Protocol,
|
baseProtocol: protocol.Protocol,
|
||||||
@ -122,11 +200,14 @@ proc new*(
|
|||||||
proc processContentLoop(n: StateNetwork) {.async.} =
|
proc processContentLoop(n: StateNetwork) {.async.} =
|
||||||
try:
|
try:
|
||||||
while true:
|
while true:
|
||||||
let (maybeContentId, contentKeys, contentValues) = await n.contentQueue.popFirst()
|
let (maybeSrcNodeId, contentKeys, contentValues) = await n.contentQueue.popFirst()
|
||||||
if await n.validateContent(contentKeys, contentValues):
|
if n.validateContent(contentKeys, contentValues):
|
||||||
asyncSpawn n.portalProtocol.neighborhoodGossipDiscardPeers(
|
await gossipContent(
|
||||||
maybeContentId, contentKeys, contentValues
|
n.portalProtocol,
|
||||||
)
|
maybeSrcNodeId,
|
||||||
|
contentKeys,
|
||||||
|
contentValues
|
||||||
|
)
|
||||||
except CancelledError:
|
except CancelledError:
|
||||||
trace "processContentLoop canceled"
|
trace "processContentLoop canceled"
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import
|
|||||||
./test_portal_wire_protocol,
|
./test_portal_wire_protocol,
|
||||||
./state_network_tests/test_state_content_keys,
|
./state_network_tests/test_state_content_keys,
|
||||||
./state_network_tests/test_state_content_values,
|
./state_network_tests/test_state_content_values,
|
||||||
|
./state_network_tests/test_state_network_gossip,
|
||||||
./test_state_proof_verification,
|
./test_state_proof_verification,
|
||||||
./test_accumulator,
|
./test_accumulator,
|
||||||
./test_history_network,
|
./test_history_network,
|
||||||
|
@ -5,9 +5,6 @@
|
|||||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
|
||||||
../../network/state/state_content
|
|
||||||
|
|
||||||
type JsonBlockInfo* = object
|
type JsonBlockInfo* = object
|
||||||
number*: uint64
|
number*: uint64
|
||||||
block_hash*: string
|
block_hash*: string
|
||||||
@ -46,3 +43,9 @@ type JsonContractBytecode* = object
|
|||||||
content_id*: string
|
content_id*: string
|
||||||
content_value_offer*: string
|
content_value_offer*: string
|
||||||
content_value_retrieval*: string
|
content_value_retrieval*: string
|
||||||
|
|
||||||
|
type JsonGossipKVPair* = object
|
||||||
|
content_key*: string
|
||||||
|
content_value*: string
|
||||||
|
|
||||||
|
type JsonRecursiveGossip* = seq[JsonGossipKVPair]
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
# Fluffy
|
||||||
|
# Copyright (c) 2023-2024 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).
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
import
|
||||||
|
stew/[byteutils, results],
|
||||||
|
testutils/unittests,
|
||||||
|
chronos,
|
||||||
|
eth/p2p/discoveryv5/protocol as discv5_protocol,
|
||||||
|
eth/p2p/discoveryv5/routing_table,
|
||||||
|
./helpers,
|
||||||
|
../../network/wire/[portal_protocol, portal_stream],
|
||||||
|
../../network/state/[state_content, state_network],
|
||||||
|
../../database/content_db,
|
||||||
|
.././test_helpers,
|
||||||
|
../../eth_data/history_data_json_store
|
||||||
|
|
||||||
|
const testVectorDir = "./vendor/portal-spec-tests/tests/mainnet/state/"
|
||||||
|
|
||||||
|
procSuite "State Network Gossip":
|
||||||
|
let rng = newRng()
|
||||||
|
|
||||||
|
asyncTest "Test Gossip of Account Trie Node Offer":
|
||||||
|
let
|
||||||
|
recursiveGossipSteps = readJsonType(testVectorDir & "recursive_gossip.json", JsonRecursiveGossip).valueOr:
|
||||||
|
raiseAssert "Cannot read test vector: " & error
|
||||||
|
numOfClients = recursiveGossipSteps.len() - 1
|
||||||
|
|
||||||
|
var clients: seq[StateNetwork]
|
||||||
|
|
||||||
|
for i in 0..numOfClients:
|
||||||
|
let
|
||||||
|
node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20400 + i))
|
||||||
|
sm = StreamManager.new(node)
|
||||||
|
proto = StateNetwork.new(node, ContentDB.new("", uint32.high, inMemory = true), sm)
|
||||||
|
proto.start()
|
||||||
|
clients.add(proto)
|
||||||
|
|
||||||
|
for i, pair in recursiveGossipSteps[0..^2]:
|
||||||
|
let
|
||||||
|
currentNode = clients[i]
|
||||||
|
nextNode = clients[i+1]
|
||||||
|
key = ByteList.init(pair.content_key.hexToSeqByte())
|
||||||
|
decodedKey = key.decode().valueOr:
|
||||||
|
raiseAssert "Cannot decode key"
|
||||||
|
nextKey = ByteList.init(recursiveGossipSteps[1].content_key.hexToSeqByte())
|
||||||
|
decodedNextKey = nextKey.decode().valueOr:
|
||||||
|
raiseAssert "Cannot decode key"
|
||||||
|
value = pair.content_value.hexToSeqByte()
|
||||||
|
nextValue = recursiveGossipSteps[1].content_value.hexToSeqByte()
|
||||||
|
|
||||||
|
check:
|
||||||
|
currentNode.portalProtocol.addNode(nextNode.portalProtocol.localNode) == Added
|
||||||
|
(await currentNode.portalProtocol.ping(nextNode.portalProtocol.localNode)).isOk()
|
||||||
|
|
||||||
|
await currentNode.portalProtocol.gossipContent(Opt.none(NodeId), ContentKeysList.init(@[key]), @[value])
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
let gossipedValue = await nextNode.getContent(decodedNextKey)
|
||||||
|
|
||||||
|
check:
|
||||||
|
gossipedValue.isSome()
|
||||||
|
gossipedValue.get() == nextValue
|
||||||
|
|
||||||
|
for i in 0..numOfClients:
|
||||||
|
await clients[i].portalProtocol.baseProtocol.closeWait()
|
||||||
|
|
||||||
|
# TODO Add tests for Contract Trie Node Offer & Contract Code Offer
|
Loading…
x
Reference in New Issue
Block a user