Fluffy: Add validation and local storage of content in remaining state portal rpc methods (#2723)
* Add validation functions to be used in state portal rpc. * Add validation to remaining state portal rpc methods. * Lookup local content in recursiveFindContent rpc methods. * portal_stateFindContent and portal_stateOffer no longer store in db.
This commit is contained in:
parent
69f646f417
commit
4ae87e6d19
|
@ -96,9 +96,11 @@ func encode*(contentKey: ContentKey): ContentKeyByteList {.inline.} =
|
||||||
func decode*(
|
func decode*(
|
||||||
T: type ContentKey, contentKey: ContentKeyByteList
|
T: type ContentKey, contentKey: ContentKeyByteList
|
||||||
): Result[T, string] {.inline.} =
|
): Result[T, string] {.inline.} =
|
||||||
decodeSsz(contentKey.asSeq(), T)
|
let key = ?decodeSsz(contentKey.asSeq(), T)
|
||||||
|
if key.contentType == unused:
|
||||||
|
return err("ContentKey contentType: unused")
|
||||||
|
ok(key)
|
||||||
|
|
||||||
func toContentId*(contentKey: ContentKeyByteList): ContentId {.inline.} =
|
func toContentId*(contentKey: ContentKeyByteList): ContentId {.inline.} =
|
||||||
# TODO: Should we try to parse the content key here for invalid ones?
|
|
||||||
let idHash = sha256.digest(contentKey.asSeq())
|
let idHash = sha256.digest(contentKey.asSeq())
|
||||||
readUintBE[256](idHash.data)
|
readUintBE[256](idHash.data)
|
||||||
|
|
|
@ -93,34 +93,6 @@ proc gossipOffer*(
|
||||||
srcNodeId: Opt[NodeId],
|
srcNodeId: Opt[NodeId],
|
||||||
keyBytes: ContentKeyByteList,
|
keyBytes: ContentKeyByteList,
|
||||||
offerBytes: seq[byte],
|
offerBytes: seq[byte],
|
||||||
key: AccountTrieNodeKey,
|
|
||||||
offer: AccountTrieNodeOffer,
|
|
||||||
) {.async: (raises: [CancelledError]).} =
|
|
||||||
let req1Peers = await p.neighborhoodGossip(
|
|
||||||
srcNodeId, ContentKeysList.init(@[keyBytes]), @[offerBytes]
|
|
||||||
)
|
|
||||||
debug "Offered content gossipped successfully with peers", keyBytes, peers = req1Peers
|
|
||||||
|
|
||||||
proc gossipOffer*(
|
|
||||||
p: PortalProtocol,
|
|
||||||
srcNodeId: Opt[NodeId],
|
|
||||||
keyBytes: ContentKeyByteList,
|
|
||||||
offerBytes: seq[byte],
|
|
||||||
key: ContractTrieNodeKey,
|
|
||||||
offer: ContractTrieNodeOffer,
|
|
||||||
) {.async: (raises: [CancelledError]).} =
|
|
||||||
let req1Peers = await p.neighborhoodGossip(
|
|
||||||
srcNodeId, ContentKeysList.init(@[keyBytes]), @[offerBytes]
|
|
||||||
)
|
|
||||||
debug "Offered content gossipped successfully with peers", keyBytes, peers = req1Peers
|
|
||||||
|
|
||||||
proc gossipOffer*(
|
|
||||||
p: PortalProtocol,
|
|
||||||
srcNodeId: Opt[NodeId],
|
|
||||||
keyBytes: ContentKeyByteList,
|
|
||||||
offerBytes: seq[byte],
|
|
||||||
key: ContractCodeKey,
|
|
||||||
offer: ContractCodeOffer,
|
|
||||||
) {.async: (raises: [CancelledError]).} =
|
) {.async: (raises: [CancelledError]).} =
|
||||||
let peers = await p.neighborhoodGossip(
|
let peers = await p.neighborhoodGossip(
|
||||||
srcNodeId, ContentKeysList.init(@[keyBytes]), @[offerBytes]
|
srcNodeId, ContentKeysList.init(@[keyBytes]), @[offerBytes]
|
||||||
|
@ -136,7 +108,7 @@ proc recursiveGossipOffer*(
|
||||||
key: AccountTrieNodeKey,
|
key: AccountTrieNodeKey,
|
||||||
offer: AccountTrieNodeOffer,
|
offer: AccountTrieNodeOffer,
|
||||||
): Future[ContentKeyByteList] {.async: (raises: [CancelledError]).} =
|
): Future[ContentKeyByteList] {.async: (raises: [CancelledError]).} =
|
||||||
await gossipOffer(p, srcNodeId, keyBytes, offerBytes, key, offer)
|
await gossipOffer(p, srcNodeId, keyBytes, offerBytes)
|
||||||
|
|
||||||
# root node, recursive gossip is finished
|
# root node, recursive gossip is finished
|
||||||
if key.path.unpackNibbles().len() == 0:
|
if key.path.unpackNibbles().len() == 0:
|
||||||
|
@ -161,7 +133,7 @@ proc recursiveGossipOffer*(
|
||||||
key: ContractTrieNodeKey,
|
key: ContractTrieNodeKey,
|
||||||
offer: ContractTrieNodeOffer,
|
offer: ContractTrieNodeOffer,
|
||||||
): Future[ContentKeyByteList] {.async: (raises: [CancelledError]).} =
|
): Future[ContentKeyByteList] {.async: (raises: [CancelledError]).} =
|
||||||
await gossipOffer(p, srcNodeId, keyBytes, offerBytes, key, offer)
|
await gossipOffer(p, srcNodeId, keyBytes, offerBytes)
|
||||||
|
|
||||||
# root node, recursive gossip is finished
|
# root node, recursive gossip is finished
|
||||||
if key.path.unpackNibbles().len() == 0:
|
if key.path.unpackNibbles().len() == 0:
|
||||||
|
|
|
@ -11,6 +11,7 @@ import
|
||||||
results,
|
results,
|
||||||
chronos,
|
chronos,
|
||||||
chronicles,
|
chronicles,
|
||||||
|
eth/common/hashes,
|
||||||
eth/p2p/discoveryv5/[protocol, enr],
|
eth/p2p/discoveryv5/[protocol, enr],
|
||||||
../../database/content_db,
|
../../database/content_db,
|
||||||
../history/history_network,
|
../history/history_network,
|
||||||
|
@ -19,7 +20,7 @@ import
|
||||||
./state_validation,
|
./state_validation,
|
||||||
./state_gossip
|
./state_gossip
|
||||||
|
|
||||||
export results, state_content
|
export results, state_content, hashes
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "portal_state"
|
topics = "portal_state"
|
||||||
|
@ -47,23 +48,22 @@ proc new*(
|
||||||
historyNetwork = Opt.none(HistoryNetwork),
|
historyNetwork = Opt.none(HistoryNetwork),
|
||||||
validateStateIsCanonical = true,
|
validateStateIsCanonical = true,
|
||||||
): T =
|
): T =
|
||||||
let cq = newAsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])](50)
|
let
|
||||||
|
cq = newAsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])](50)
|
||||||
|
s = streamManager.registerNewStream(cq)
|
||||||
|
portalProtocol = PortalProtocol.new(
|
||||||
|
baseProtocol,
|
||||||
|
getProtocolId(portalNetwork, PortalSubnetwork.state),
|
||||||
|
toContentIdHandler,
|
||||||
|
createGetHandler(contentDB),
|
||||||
|
createStoreHandler(contentDB, portalConfig.radiusConfig),
|
||||||
|
createRadiusHandler(contentDB),
|
||||||
|
s,
|
||||||
|
bootstrapRecords,
|
||||||
|
config = portalConfig,
|
||||||
|
)
|
||||||
|
|
||||||
let s = streamManager.registerNewStream(cq)
|
StateNetwork(
|
||||||
|
|
||||||
let portalProtocol = PortalProtocol.new(
|
|
||||||
baseProtocol,
|
|
||||||
getProtocolId(portalNetwork, PortalSubnetwork.state),
|
|
||||||
toContentIdHandler,
|
|
||||||
createGetHandler(contentDB),
|
|
||||||
createStoreHandler(contentDB, portalConfig.radiusConfig),
|
|
||||||
createRadiusHandler(contentDB),
|
|
||||||
s,
|
|
||||||
bootstrapRecords,
|
|
||||||
config = portalConfig,
|
|
||||||
)
|
|
||||||
|
|
||||||
return StateNetwork(
|
|
||||||
portalProtocol: portalProtocol,
|
portalProtocol: portalProtocol,
|
||||||
contentDB: contentDB,
|
contentDB: contentDB,
|
||||||
contentQueue: cq,
|
contentQueue: cq,
|
||||||
|
@ -108,7 +108,7 @@ proc getContent(
|
||||||
|
|
||||||
n.portalProtocol.storeContent(contentKeyBytes, contentId, contentValueBytes)
|
n.portalProtocol.storeContent(contentKeyBytes, contentId, contentValueBytes)
|
||||||
|
|
||||||
return Opt.some(contentValue)
|
Opt.some(contentValue)
|
||||||
|
|
||||||
proc getAccountTrieNode*(
|
proc getAccountTrieNode*(
|
||||||
n: StateNetwork, key: AccountTrieNodeKey
|
n: StateNetwork, key: AccountTrieNodeKey
|
||||||
|
@ -132,11 +132,11 @@ proc getContractCode*(
|
||||||
proc getStateRootByBlockNumOrHash*(
|
proc getStateRootByBlockNumOrHash*(
|
||||||
n: StateNetwork, blockNumOrHash: uint64 | Hash32
|
n: StateNetwork, blockNumOrHash: uint64 | Hash32
|
||||||
): Future[Opt[Hash32]] {.async: (raises: [CancelledError]).} =
|
): Future[Opt[Hash32]] {.async: (raises: [CancelledError]).} =
|
||||||
if n.historyNetwork.isNone():
|
let hn = n.historyNetwork.valueOr:
|
||||||
warn "History network is not available"
|
warn "History network is not available"
|
||||||
return Opt.none(Hash32)
|
return Opt.none(Hash32)
|
||||||
|
|
||||||
let header = (await n.historyNetwork.get().getVerifiedBlockHeader(blockNumOrHash)).valueOr:
|
let header = (await hn.getVerifiedBlockHeader(blockNumOrHash)).valueOr:
|
||||||
warn "Failed to get block header from history", blockNumOrHash
|
warn "Failed to get block header from history", blockNumOrHash
|
||||||
return Opt.none(Hash32)
|
return Opt.none(Hash32)
|
||||||
|
|
||||||
|
@ -150,20 +150,20 @@ proc processOffer*(
|
||||||
contentKey: AccountTrieNodeKey | ContractTrieNodeKey | ContractCodeKey,
|
contentKey: AccountTrieNodeKey | ContractTrieNodeKey | ContractCodeKey,
|
||||||
V: type ContentOfferType,
|
V: type ContentOfferType,
|
||||||
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
|
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
|
||||||
let contentValue = V.decode(contentValueBytes).valueOr:
|
let
|
||||||
return err("Unable to decode offered content value")
|
contentValue = V.decode(contentValueBytes).valueOr:
|
||||||
|
return err("Unable to decode offered content value")
|
||||||
|
validationRes =
|
||||||
|
if n.validateStateIsCanonical:
|
||||||
|
let stateRoot = (await n.getStateRootByBlockNumOrHash(contentValue.blockHash)).valueOr:
|
||||||
|
return err("Failed to get state root by block hash")
|
||||||
|
validateOffer(Opt.some(stateRoot), contentKey, contentValue)
|
||||||
|
else:
|
||||||
|
# Skip state root validation
|
||||||
|
validateOffer(Opt.none(Hash32), contentKey, contentValue)
|
||||||
|
|
||||||
let res =
|
if validationRes.isErr():
|
||||||
if n.validateStateIsCanonical:
|
return err("Offered content failed validation: " & validationRes.error())
|
||||||
let stateRoot = (await n.getStateRootByBlockNumOrHash(contentValue.blockHash)).valueOr:
|
|
||||||
return err("Failed to get state root by block hash")
|
|
||||||
validateOffer(Opt.some(stateRoot), contentKey, contentValue)
|
|
||||||
else:
|
|
||||||
# Skip state root validation
|
|
||||||
validateOffer(Opt.none(Hash32), contentKey, contentValue)
|
|
||||||
|
|
||||||
if res.isErr():
|
|
||||||
return err("Offered content failed validation: " & res.error())
|
|
||||||
|
|
||||||
let contentId = n.portalProtocol.toContentId(contentKeyBytes).valueOr:
|
let contentId = n.portalProtocol.toContentId(contentKeyBytes).valueOr:
|
||||||
return err("Received offered content with invalid content key")
|
return err("Received offered content with invalid content key")
|
||||||
|
@ -174,8 +174,7 @@ proc processOffer*(
|
||||||
debug "Offered content validated successfully", contentKeyBytes
|
debug "Offered content validated successfully", contentKeyBytes
|
||||||
|
|
||||||
await gossipOffer(
|
await gossipOffer(
|
||||||
n.portalProtocol, maybeSrcNodeId, contentKeyBytes, contentValueBytes, contentKey,
|
n.portalProtocol, maybeSrcNodeId, contentKeyBytes, contentValueBytes
|
||||||
contentValue,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
@ -185,33 +184,33 @@ proc processContentLoop(n: StateNetwork) {.async: (raises: []).} =
|
||||||
while true:
|
while true:
|
||||||
let (srcNodeId, contentKeys, contentValues) = await n.contentQueue.popFirst()
|
let (srcNodeId, contentKeys, contentValues) = await n.contentQueue.popFirst()
|
||||||
|
|
||||||
for i, contentValueBytes in contentValues:
|
for i, contentBytes in contentValues:
|
||||||
let
|
let
|
||||||
contentKeyBytes = contentKeys[i]
|
contentKeyBytes = contentKeys[i]
|
||||||
contentKey = ContentKey.decode(contentKeyBytes).valueOr:
|
contentKey = ContentKey.decode(contentKeyBytes).valueOr:
|
||||||
error "Unable to decode offered content key", contentKeyBytes
|
error "Unable to decode offered content key", contentKeyBytes
|
||||||
continue
|
continue
|
||||||
|
|
||||||
let offerRes =
|
offerRes =
|
||||||
case contentKey.contentType
|
case contentKey.contentType
|
||||||
of unused:
|
of unused:
|
||||||
error "Received content with unused content type"
|
error "Received content with unused content type"
|
||||||
continue
|
continue
|
||||||
of accountTrieNode:
|
of accountTrieNode:
|
||||||
await n.processOffer(
|
await n.processOffer(
|
||||||
srcNodeId, contentKeyBytes, contentValueBytes,
|
srcNodeId, contentKeyBytes, contentBytes, contentKey.accountTrieNodeKey,
|
||||||
contentKey.accountTrieNodeKey, AccountTrieNodeOffer,
|
AccountTrieNodeOffer,
|
||||||
)
|
)
|
||||||
of contractTrieNode:
|
of contractTrieNode:
|
||||||
await n.processOffer(
|
await n.processOffer(
|
||||||
srcNodeId, contentKeyBytes, contentValueBytes,
|
srcNodeId, contentKeyBytes, contentBytes,
|
||||||
contentKey.contractTrieNodeKey, ContractTrieNodeOffer,
|
contentKey.contractTrieNodeKey, ContractTrieNodeOffer,
|
||||||
)
|
)
|
||||||
of contractCode:
|
of contractCode:
|
||||||
await n.processOffer(
|
await n.processOffer(
|
||||||
srcNodeId, contentKeyBytes, contentValueBytes, contentKey.contractCodeKey,
|
srcNodeId, contentKeyBytes, contentBytes, contentKey.contractCodeKey,
|
||||||
ContractCodeOffer,
|
ContractCodeOffer,
|
||||||
)
|
)
|
||||||
if offerRes.isOk():
|
if offerRes.isOk():
|
||||||
info "Offered content processed successfully", contentKeyBytes
|
info "Offered content processed successfully", contentKeyBytes
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -16,7 +16,7 @@ import
|
||||||
|
|
||||||
export results, hashes, accounts, addresses, rlp
|
export results, hashes, accounts, addresses, rlp
|
||||||
|
|
||||||
func fromBytes*(T: type Hash32, hash: openArray[byte]): T =
|
template fromBytes*(T: type Hash32, hash: openArray[byte]): T =
|
||||||
doAssert(hash.len() == 32)
|
doAssert(hash.len() == 32)
|
||||||
Hash32(array[32, byte].initCopyFrom(hash))
|
Hash32(array[32, byte].initCopyFrom(hash))
|
||||||
|
|
||||||
|
@ -69,14 +69,12 @@ func rlpDecodeContractTrieNode*(contractTrieNode: TrieNode): Result[UInt256, str
|
||||||
except RlpError as e:
|
except RlpError as e:
|
||||||
err(e.msg)
|
err(e.msg)
|
||||||
|
|
||||||
func toAccount*(accountProof: TrieProof): Result[Account, string] {.inline.} =
|
template toAccount*(accountProof: TrieProof): Result[Account, string] =
|
||||||
doAssert(accountProof.len() > 0)
|
doAssert(accountProof.len() > 0)
|
||||||
|
|
||||||
rlpDecodeAccountTrieNode(accountProof[^1])
|
rlpDecodeAccountTrieNode(accountProof[^1])
|
||||||
|
|
||||||
func toSlot*(storageProof: TrieProof): Result[UInt256, string] {.inline.} =
|
template toSlot*(storageProof: TrieProof): Result[UInt256, string] =
|
||||||
doAssert(storageProof.len() > 0)
|
doAssert(storageProof.len() > 0)
|
||||||
|
|
||||||
rlpDecodeContractTrieNode(storageProof[^1])
|
rlpDecodeContractTrieNode(storageProof[^1])
|
||||||
|
|
||||||
func removeLeafKeyEndNibbles*(
|
func removeLeafKeyEndNibbles*(
|
||||||
|
@ -93,11 +91,11 @@ func removeLeafKeyEndNibbles*(
|
||||||
|
|
||||||
unpackedNibbles.dropN(leafPrefix.len()).packNibbles()
|
unpackedNibbles.dropN(leafPrefix.len()).packNibbles()
|
||||||
|
|
||||||
func toPath*(hash: Hash32): Nibbles {.inline.} =
|
template toPath*(hash: Hash32): Nibbles =
|
||||||
Nibbles.init(hash.data, isEven = true)
|
Nibbles.init(hash.data, isEven = true)
|
||||||
|
|
||||||
func toPath*(address: Address): Nibbles {.inline.} =
|
template toPath*(address: Address): Nibbles =
|
||||||
keccak256(address.data).toPath()
|
keccak256(address.data).toPath()
|
||||||
|
|
||||||
func toPath*(slotKey: UInt256): Nibbles {.inline.} =
|
template toPath*(slotKey: UInt256): Nibbles =
|
||||||
keccak256(toBytesBE(slotKey)).toPath()
|
keccak256(toBytesBE(slotKey)).toPath()
|
||||||
|
|
|
@ -13,10 +13,10 @@ export results, state_content, hashes
|
||||||
|
|
||||||
from eth/common/eth_types_rlp import rlpHash
|
from eth/common/eth_types_rlp import rlpHash
|
||||||
|
|
||||||
proc hashEquals(value: TrieNode | Bytecode, expectedHash: Hash32): bool {.inline.} =
|
template hashEquals(value: TrieNode | Bytecode, expectedHash: Hash32): bool =
|
||||||
keccak256(value.asSeq()) == expectedHash
|
keccak256(value.asSeq()) == expectedHash
|
||||||
|
|
||||||
proc isValidNextNode(
|
func isValidNextNode(
|
||||||
thisNodeRlp: Rlp, rlpIdx: int, nextNode: TrieNode
|
thisNodeRlp: Rlp, rlpIdx: int, nextNode: TrieNode
|
||||||
): bool {.raises: RlpError.} =
|
): bool {.raises: RlpError.} =
|
||||||
let hashOrShortRlp = thisNodeRlp.listElem(rlpIdx)
|
let hashOrShortRlp = thisNodeRlp.listElem(rlpIdx)
|
||||||
|
@ -36,7 +36,7 @@ proc isValidNextNode(
|
||||||
nextNode.hashEquals(nextHash)
|
nextNode.hashEquals(nextHash)
|
||||||
|
|
||||||
# TODO: Refactor this function to improve maintainability
|
# TODO: Refactor this function to improve maintainability
|
||||||
proc validateTrieProof*(
|
func validateTrieProof*(
|
||||||
expectedRootHash: Opt[Hash32],
|
expectedRootHash: Opt[Hash32],
|
||||||
path: Nibbles,
|
path: Nibbles,
|
||||||
proof: TrieProof,
|
proof: TrieProof,
|
||||||
|
@ -117,7 +117,7 @@ proc validateTrieProof*(
|
||||||
else:
|
else:
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
proc validateRetrieval*(
|
func validateRetrieval*(
|
||||||
key: AccountTrieNodeKey, value: AccountTrieNodeRetrieval
|
key: AccountTrieNodeKey, value: AccountTrieNodeRetrieval
|
||||||
): Result[void, string] =
|
): Result[void, string] =
|
||||||
if value.node.hashEquals(key.nodeHash):
|
if value.node.hashEquals(key.nodeHash):
|
||||||
|
@ -125,7 +125,7 @@ proc validateRetrieval*(
|
||||||
else:
|
else:
|
||||||
err("hash of account trie node doesn't match the expected node hash")
|
err("hash of account trie node doesn't match the expected node hash")
|
||||||
|
|
||||||
proc validateRetrieval*(
|
func validateRetrieval*(
|
||||||
key: ContractTrieNodeKey, value: ContractTrieNodeRetrieval
|
key: ContractTrieNodeKey, value: ContractTrieNodeRetrieval
|
||||||
): Result[void, string] =
|
): Result[void, string] =
|
||||||
if value.node.hashEquals(key.nodeHash):
|
if value.node.hashEquals(key.nodeHash):
|
||||||
|
@ -133,7 +133,7 @@ proc validateRetrieval*(
|
||||||
else:
|
else:
|
||||||
err("hash of contract trie node doesn't match the expected node hash")
|
err("hash of contract trie node doesn't match the expected node hash")
|
||||||
|
|
||||||
proc validateRetrieval*(
|
func validateRetrieval*(
|
||||||
key: ContractCodeKey, value: ContractCodeRetrieval
|
key: ContractCodeKey, value: ContractCodeRetrieval
|
||||||
): Result[void, string] =
|
): Result[void, string] =
|
||||||
if value.code.hashEquals(key.codeHash):
|
if value.code.hashEquals(key.codeHash):
|
||||||
|
@ -141,14 +141,14 @@ proc validateRetrieval*(
|
||||||
else:
|
else:
|
||||||
err("hash of bytecode doesn't match the expected code hash")
|
err("hash of bytecode doesn't match the expected code hash")
|
||||||
|
|
||||||
proc validateOffer*(
|
func validateOffer*(
|
||||||
trustedStateRoot: Opt[Hash32], key: AccountTrieNodeKey, offer: AccountTrieNodeOffer
|
trustedStateRoot: Opt[Hash32], key: AccountTrieNodeKey, offer: AccountTrieNodeOffer
|
||||||
): Result[void, string] =
|
): Result[void, string] =
|
||||||
?validateTrieProof(trustedStateRoot, key.path, offer.proof)
|
?validateTrieProof(trustedStateRoot, key.path, offer.proof)
|
||||||
|
|
||||||
validateRetrieval(key, offer.toRetrievalValue())
|
validateRetrieval(key, offer.toRetrievalValue())
|
||||||
|
|
||||||
proc validateOffer*(
|
func validateOffer*(
|
||||||
trustedStateRoot: Opt[Hash32],
|
trustedStateRoot: Opt[Hash32],
|
||||||
key: ContractTrieNodeKey,
|
key: ContractTrieNodeKey,
|
||||||
offer: ContractTrieNodeOffer,
|
offer: ContractTrieNodeOffer,
|
||||||
|
@ -166,7 +166,7 @@ proc validateOffer*(
|
||||||
|
|
||||||
validateRetrieval(key, offer.toRetrievalValue())
|
validateRetrieval(key, offer.toRetrievalValue())
|
||||||
|
|
||||||
proc validateOffer*(
|
func validateOffer*(
|
||||||
trustedStateRoot: Opt[Hash32], key: ContractCodeKey, offer: ContractCodeOffer
|
trustedStateRoot: Opt[Hash32], key: ContractCodeKey, offer: ContractCodeOffer
|
||||||
): Result[void, string] =
|
): Result[void, string] =
|
||||||
?validateTrieProof(
|
?validateTrieProof(
|
||||||
|
@ -181,3 +181,47 @@ proc validateOffer*(
|
||||||
return err("hash of bytecode doesn't match the code hash in the account proof")
|
return err("hash of bytecode doesn't match the code hash in the account proof")
|
||||||
|
|
||||||
validateRetrieval(key, offer.toRetrievalValue())
|
validateRetrieval(key, offer.toRetrievalValue())
|
||||||
|
|
||||||
|
func validateGetContentKey*(
|
||||||
|
keyBytes: ContentKeyByteList
|
||||||
|
): Result[(ContentKey, ContentId), string] =
|
||||||
|
let key = ?ContentKey.decode(keyBytes)
|
||||||
|
ok((key, toContentId(keyBytes)))
|
||||||
|
|
||||||
|
func validateRetrieval*(
|
||||||
|
key: ContentKey, contentBytes: seq[byte]
|
||||||
|
): Result[void, string] =
|
||||||
|
case key.contentType
|
||||||
|
of unused:
|
||||||
|
raiseAssert("ContentKey contentType: unused")
|
||||||
|
of accountTrieNode:
|
||||||
|
let retrieval = ?AccountTrieNodeRetrieval.decode(contentBytes)
|
||||||
|
?validateRetrieval(key.accountTrieNodeKey, retrieval)
|
||||||
|
of contractTrieNode:
|
||||||
|
let retrieval = ?ContractTrieNodeRetrieval.decode(contentBytes)
|
||||||
|
?validateRetrieval(key.contractTrieNodeKey, retrieval)
|
||||||
|
of contractCode:
|
||||||
|
let retrieval = ?ContractCodeRetrieval.decode(contentBytes)
|
||||||
|
?validateRetrieval(key.contractCodeKey, retrieval)
|
||||||
|
|
||||||
|
func validateOfferGetValue*(
|
||||||
|
trustedStateRoot: Opt[Hash32], key: ContentKey, contentBytes: seq[byte]
|
||||||
|
): Result[seq[byte], string] =
|
||||||
|
let value =
|
||||||
|
case key.contentType
|
||||||
|
of unused:
|
||||||
|
raiseAssert("ContentKey contentType: unused")
|
||||||
|
of accountTrieNode:
|
||||||
|
let offer = ?AccountTrieNodeOffer.decode(contentBytes)
|
||||||
|
?validateOffer(trustedStateRoot, key.accountTrieNodeKey, offer)
|
||||||
|
offer.toRetrievalValue.encode()
|
||||||
|
of contractTrieNode:
|
||||||
|
let offer = ?ContractTrieNodeOffer.decode(contentBytes)
|
||||||
|
?validateOffer(trustedStateRoot, key.contractTrieNodeKey, offer)
|
||||||
|
offer.toRetrievalValue.encode()
|
||||||
|
of contractCode:
|
||||||
|
let offer = ?ContractCodeOffer.decode(contentBytes)
|
||||||
|
?validateOffer(trustedStateRoot, key.contractCodeKey, offer)
|
||||||
|
offer.toRetrievalValue.encode()
|
||||||
|
|
||||||
|
ok(value)
|
||||||
|
|
|
@ -36,26 +36,29 @@ proc installPortalStateApiHandlers*(rpcServer: RpcServer, p: PortalProtocol) =
|
||||||
) -> JsonString:
|
) -> JsonString:
|
||||||
let
|
let
|
||||||
node = toNodeWithAddress(enr)
|
node = toNodeWithAddress(enr)
|
||||||
foundContentResult =
|
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
||||||
await p.findContent(node, ContentKeyByteList.init(hexToSeqByte(contentKey)))
|
(key, _) = validateGetContentKey(keyBytes).valueOr:
|
||||||
|
raise invalidKeyErr()
|
||||||
|
foundContent = (await p.findContent(node, keyBytes)).valueOr:
|
||||||
|
raise newException(ValueError, $error)
|
||||||
|
|
||||||
if foundContentResult.isErr():
|
case foundContent.kind
|
||||||
raise newException(ValueError, $foundContentResult.error)
|
of Content:
|
||||||
else:
|
let contentValue = foundContent.content
|
||||||
let foundContent = foundContentResult.get()
|
validateRetrieval(key, contentValue).isOkOr:
|
||||||
case foundContent.kind
|
raise invalidValueErr()
|
||||||
of Content:
|
|
||||||
let res = ContentInfo(
|
let res = ContentInfo(
|
||||||
content: foundContent.content.to0xHex(), utpTransfer: foundContent.utpTransfer
|
content: contentValue.to0xHex(), utpTransfer: foundContent.utpTransfer
|
||||||
)
|
)
|
||||||
return JrpcConv.encode(res).JsonString
|
JrpcConv.encode(res).JsonString
|
||||||
of Nodes:
|
of Nodes:
|
||||||
let enrs = foundContent.nodes.map(
|
let enrs = foundContent.nodes.map(
|
||||||
proc(n: Node): Record =
|
proc(n: Node): Record =
|
||||||
n.record
|
n.record
|
||||||
)
|
)
|
||||||
let jsonEnrs = JrpcConv.encode(enrs)
|
let jsonEnrs = JrpcConv.encode(enrs)
|
||||||
return ("{\"enrs\":" & jsonEnrs & "}").JsonString
|
("{\"enrs\":" & jsonEnrs & "}").JsonString
|
||||||
|
|
||||||
rpcServer.rpc("portal_stateOffer") do(
|
rpcServer.rpc("portal_stateOffer") do(
|
||||||
enr: Record, contentItems: seq[ContentItem]
|
enr: Record, contentItems: seq[ContentItem]
|
||||||
|
@ -65,11 +68,14 @@ proc installPortalStateApiHandlers*(rpcServer: RpcServer, p: PortalProtocol) =
|
||||||
var contentItemsToOffer: seq[ContentKV]
|
var contentItemsToOffer: seq[ContentKV]
|
||||||
for contentItem in contentItems:
|
for contentItem in contentItems:
|
||||||
let
|
let
|
||||||
contentKey = hexToSeqByte(contentItem[0])
|
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentItem[0]))
|
||||||
contentValue = hexToSeqByte(contentItem[1])
|
(key, _) = validateGetContentKey(keyBytes).valueOr:
|
||||||
contentKV = ContentKV(
|
raise invalidKeyErr()
|
||||||
contentKey: ContentKeyByteList.init(contentKey), content: contentValue
|
contentBytes = hexToSeqByte(contentItem[1])
|
||||||
)
|
contentKV = ContentKV(contentKey: keyBytes, content: contentBytes)
|
||||||
|
|
||||||
|
discard validateOfferGetValue(Opt.none(Hash32), key, contentBytes).valueOr:
|
||||||
|
raise invalidValueErr()
|
||||||
contentItemsToOffer.add(contentKV)
|
contentItemsToOffer.add(contentKV)
|
||||||
|
|
||||||
let offerResult = (await p.offer(node, contentItemsToOffer)).valueOr:
|
let offerResult = (await p.offer(node, contentItemsToOffer)).valueOr:
|
||||||
|
@ -81,92 +87,82 @@ proc installPortalStateApiHandlers*(rpcServer: RpcServer, p: PortalProtocol) =
|
||||||
contentKey: string
|
contentKey: string
|
||||||
) -> ContentInfo:
|
) -> ContentInfo:
|
||||||
let
|
let
|
||||||
key = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
||||||
contentId = p.toContentId(key).valueOr:
|
(key, contentId) = validateGetContentKey(keyBytes).valueOr:
|
||||||
raise invalidKeyErr()
|
raise invalidKeyErr()
|
||||||
|
maybeContent = p.dbGet(keyBytes, contentId)
|
||||||
|
if maybeContent.isSome():
|
||||||
|
return ContentInfo(content: maybeContent.get().to0xHex(), utpTransfer: false)
|
||||||
|
|
||||||
contentResult = (await p.contentLookup(key, contentId)).valueOr:
|
let
|
||||||
|
foundContent = (await p.contentLookup(keyBytes, contentId)).valueOr:
|
||||||
raise contentNotFoundErr()
|
raise contentNotFoundErr()
|
||||||
|
contentValue = foundContent.content
|
||||||
|
|
||||||
return ContentInfo(
|
validateRetrieval(key, contentValue).isOkOr:
|
||||||
content: contentResult.content.to0xHex(), utpTransfer: contentResult.utpTransfer
|
raise invalidValueErr()
|
||||||
)
|
p.storeContent(keyBytes, contentId, contentValue)
|
||||||
|
|
||||||
|
ContentInfo(content: contentValue.to0xHex(), utpTransfer: foundContent.utpTransfer)
|
||||||
|
|
||||||
rpcServer.rpc("portal_stateTraceRecursiveFindContent") do(
|
rpcServer.rpc("portal_stateTraceRecursiveFindContent") do(
|
||||||
contentKey: string
|
contentKey: string
|
||||||
) -> TraceContentLookupResult:
|
) -> TraceContentLookupResult:
|
||||||
let
|
let
|
||||||
key = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
||||||
contentId = p.toContentId(key).valueOr:
|
(key, contentId) = validateGetContentKey(keyBytes).valueOr:
|
||||||
raise invalidKeyErr()
|
raise invalidKeyErr()
|
||||||
|
maybeContent = p.dbGet(keyBytes, contentId)
|
||||||
res = await p.traceContentLookup(key, contentId)
|
if maybeContent.isSome():
|
||||||
|
return TraceContentLookupResult(content: maybeContent, utpTransfer: false)
|
||||||
|
|
||||||
# TODO: Might want to restructure the lookup result here. Potentially doing
|
# TODO: Might want to restructure the lookup result here. Potentially doing
|
||||||
# the json conversion in this module.
|
# the json conversion in this module.
|
||||||
if res.content.isSome():
|
let
|
||||||
return res
|
res = await p.traceContentLookup(keyBytes, contentId)
|
||||||
else:
|
contentValue = res.content.valueOr:
|
||||||
let data = Opt.some(JrpcConv.encode(res.trace).JsonString)
|
let data = Opt.some(JrpcConv.encode(res.trace).JsonString)
|
||||||
raise contentNotFoundErrWithTrace(data)
|
raise contentNotFoundErrWithTrace(data)
|
||||||
|
|
||||||
rpcServer.rpc("portal_stateStore") do(
|
validateRetrieval(key, contentValue).isOkOr:
|
||||||
contentKey: string, contentValue: string
|
raise invalidValueErr()
|
||||||
) -> bool:
|
p.storeContent(keyBytes, contentId, contentValue)
|
||||||
|
|
||||||
|
res
|
||||||
|
|
||||||
|
rpcServer.rpc("portal_stateStore") do(contentKey: string, content: string) -> bool:
|
||||||
let
|
let
|
||||||
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
||||||
key = ContentKey.decode(keyBytes).valueOr:
|
(key, contentId) = validateGetContentKey(keyBytes).valueOr:
|
||||||
raise invalidKeyErr()
|
|
||||||
contentId = p.toContentId(keyBytes).valueOr:
|
|
||||||
raise invalidKeyErr()
|
raise invalidKeyErr()
|
||||||
|
contentBytes = hexToSeqByte(content)
|
||||||
|
contentValue = validateOfferGetValue(Opt.none(Hash32), key, contentBytes).valueOr:
|
||||||
|
raise invalidValueErr()
|
||||||
|
|
||||||
contentBytes = hexToSeqByte(contentValue)
|
p.storeContent(keyBytes, contentId, contentValue)
|
||||||
valueToStore =
|
|
||||||
case key.contentType
|
|
||||||
of unused:
|
|
||||||
raise invalidKeyErr()
|
|
||||||
of accountTrieNode:
|
|
||||||
let offer = AccountTrieNodeOffer.decode(contentBytes).valueOr:
|
|
||||||
raise invalidValueErr
|
|
||||||
validateOffer(Opt.none(Hash32), key.accountTrieNodeKey, offer).isOkOr:
|
|
||||||
raise invalidValueErr
|
|
||||||
offer.toRetrievalValue.encode()
|
|
||||||
of contractTrieNode:
|
|
||||||
let offer = ContractTrieNodeOffer.decode(contentBytes).valueOr:
|
|
||||||
raise invalidValueErr
|
|
||||||
validateOffer(Opt.none(Hash32), key.contractTrieNodeKey, offer).isOkOr:
|
|
||||||
raise invalidValueErr
|
|
||||||
offer.toRetrievalValue.encode()
|
|
||||||
of contractCode:
|
|
||||||
let offer = ContractCodeOffer.decode(contentBytes).valueOr:
|
|
||||||
raise invalidValueErr
|
|
||||||
validateOffer(Opt.none(Hash32), key.contractCodeKey, offer).isOkOr:
|
|
||||||
raise invalidValueErr
|
|
||||||
offer.toRetrievalValue.encode()
|
|
||||||
|
|
||||||
p.storeContent(keyBytes, contentId, valueToStore)
|
|
||||||
|
|
||||||
rpcServer.rpc("portal_stateLocalContent") do(contentKey: string) -> string:
|
rpcServer.rpc("portal_stateLocalContent") do(contentKey: string) -> string:
|
||||||
let
|
let
|
||||||
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
||||||
key = ContentKey.decode(keyBytes).valueOr:
|
(_, contentId) = validateGetContentKey(keyBytes).valueOr:
|
||||||
raise invalidKeyErr()
|
|
||||||
contentId = p.toContentId(keyBytes).valueOr:
|
|
||||||
raise invalidKeyErr()
|
raise invalidKeyErr()
|
||||||
|
|
||||||
contentResult = p.dbGet(keyBytes, contentId).valueOr:
|
contentResult = p.dbGet(keyBytes, contentId).valueOr:
|
||||||
raise contentNotFoundErr()
|
raise contentNotFoundErr()
|
||||||
|
|
||||||
return contentResult.to0xHex()
|
contentResult.to0xHex()
|
||||||
|
|
||||||
rpcServer.rpc("portal_stateGossip") do(
|
rpcServer.rpc("portal_stateGossip") do(contentKey: string, content: string) -> int:
|
||||||
contentKey: string, contentValue: string
|
|
||||||
) -> int:
|
|
||||||
let
|
let
|
||||||
key = hexToSeqByte(contentKey)
|
keyBytes = ContentKeyByteList.init(hexToSeqByte(contentKey))
|
||||||
content = hexToSeqByte(contentValue)
|
(key, contentId) = validateGetContentKey(keyBytes).valueOr:
|
||||||
contentKeys = ContentKeysList(@[ContentKeyByteList.init(key)])
|
raise invalidKeyErr()
|
||||||
numberOfPeers =
|
contentBytes = hexToSeqByte(content)
|
||||||
await p.neighborhoodGossip(Opt.none(NodeId), contentKeys, @[content])
|
contentValue = validateOfferGetValue(Opt.none(Hash32), key, contentBytes).valueOr:
|
||||||
|
raise invalidValueErr()
|
||||||
|
|
||||||
return numberOfPeers
|
p.storeContent(keyBytes, contentId, contentValue)
|
||||||
|
|
||||||
|
await p.neighborhoodGossip(
|
||||||
|
Opt.none(NodeId), ContentKeysList(@[keyBytes]), @[contentBytes]
|
||||||
|
)
|
||||||
|
|
|
@ -262,11 +262,7 @@ procSuite "State Endpoints":
|
||||||
stateNode2.mockStateRootLookup(contentValue.blockHash, stateRoot)
|
stateNode2.mockStateRootLookup(contentValue.blockHash, stateRoot)
|
||||||
|
|
||||||
await stateNode1.portalProtocol.gossipOffer(
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
Opt.none(NodeId),
|
Opt.none(NodeId), contentKeyBytes, contentValueBytes
|
||||||
contentKeyBytes,
|
|
||||||
contentValueBytes,
|
|
||||||
contentKey.contractCodeKey,
|
|
||||||
contentValue,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# wait for gossip to complete
|
# wait for gossip to complete
|
||||||
|
|
|
@ -66,11 +66,7 @@ procSuite "State Gossip - Gossip Offer":
|
||||||
check not stateNode2.containsId(contentId)
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
await stateNode1.portalProtocol.gossipOffer(
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
Opt.none(NodeId),
|
Opt.none(NodeId), contentKeyBytes, contentValueBytes
|
||||||
contentKeyBytes,
|
|
||||||
contentValueBytes,
|
|
||||||
contentKey.accountTrieNodeKey,
|
|
||||||
contentValue,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# wait for offer to be processed by state node 2
|
# wait for offer to be processed by state node 2
|
||||||
|
@ -138,11 +134,7 @@ procSuite "State Gossip - Gossip Offer":
|
||||||
check not stateNode2.containsId(contentId)
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
await stateNode1.portalProtocol.gossipOffer(
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
Opt.none(NodeId),
|
Opt.none(NodeId), contentKeyBytes, contentValueBytes
|
||||||
contentKeyBytes,
|
|
||||||
contentValueBytes,
|
|
||||||
contentKey.contractTrieNodeKey,
|
|
||||||
contentValue,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# wait for offer to be processed by state node 2
|
# wait for offer to be processed by state node 2
|
||||||
|
@ -201,11 +193,7 @@ procSuite "State Gossip - Gossip Offer":
|
||||||
check not stateNode2.containsId(contentId)
|
check not stateNode2.containsId(contentId)
|
||||||
|
|
||||||
await stateNode1.portalProtocol.gossipOffer(
|
await stateNode1.portalProtocol.gossipOffer(
|
||||||
Opt.none(NodeId),
|
Opt.none(NodeId), contentKeyBytes, contentValueBytes
|
||||||
contentKeyBytes,
|
|
||||||
contentValueBytes,
|
|
||||||
contentKey.contractCodeKey,
|
|
||||||
contentValue,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# wait for offer to be processed by state node 2
|
# wait for offer to be processed by state node 2
|
||||||
|
|
Loading…
Reference in New Issue