Fix sending of WantBlocks messages and tracking of peerWants (#1019)

* sends wantBlock to peers with block. wantHave to everyone else

* Cleanup cheapestPeer. Fixes test for peers lists

* Fixes issue where peerWants are only stored for type wantBlock.

* Review comments by Dmitriy

* consistent logging of addresses

* prevents duplicate scheduling. Fixes cancellation

* fast

* Marks cancel-presence situation with todo comment.

* fixtest: testsales enable logging

* Review by Dmitriy: Remember peerWants only if we don't have them.

* rework `wantListHandler` handling

---------

Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com>
This commit is contained in:
Ben Bierens 2025-01-09 23:44:02 +01:00 committed by GitHub
parent 74c46b3651
commit caed3c07a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 77 additions and 97 deletions

View File

@ -130,16 +130,15 @@ proc stop*(b: BlockExcEngine) {.async.} =
proc sendWantHave( proc sendWantHave(
b: BlockExcEngine, b: BlockExcEngine,
addresses: seq[BlockAddress], addresses: seq[BlockAddress],
excluded: seq[BlockExcPeerCtx],
peers: seq[BlockExcPeerCtx]): Future[void] {.async.} = peers: seq[BlockExcPeerCtx]): Future[void] {.async.} =
for p in peers: for p in peers:
if p notin excluded: let toAsk = addresses.filterIt(it notin p.peerHave)
let toAsk = addresses.filterIt(it notin p.peerHave) trace "Sending wantHave request", toAsk, peer = p.id
trace "Sending wantHave request", toAsk, peer = p.id await b.network.request.sendWantList(
await b.network.request.sendWantList( p.id,
p.id, toAsk,
toAsk, wantType = WantType.WantHave)
wantType = WantType.WantHave) codex_block_exchange_want_have_lists_sent.inc()
proc sendWantBlock( proc sendWantBlock(
b: BlockExcEngine, b: BlockExcEngine,
@ -150,6 +149,7 @@ proc sendWantBlock(
blockPeer.id, blockPeer.id,
addresses, addresses,
wantType = WantType.WantBlock) # we want this remote to send us a block wantType = WantType.WantBlock) # we want this remote to send us a block
codex_block_exchange_want_block_lists_sent.inc()
proc monitorBlockHandle( proc monitorBlockHandle(
b: BlockExcEngine, b: BlockExcEngine,
@ -175,6 +175,9 @@ proc monitorBlockHandle(
await b.network.switch.disconnect(peerId) await b.network.switch.disconnect(peerId)
b.discovery.queueFindBlocksReq(@[address.cidOrTreeCid]) b.discovery.queueFindBlocksReq(@[address.cidOrTreeCid])
proc pickPseudoRandom(address: BlockAddress, peers: seq[BlockExcPeerCtx]): BlockExcPeerCtx =
return peers[hash(address) mod peers.len]
proc requestBlock*( proc requestBlock*(
b: BlockExcEngine, b: BlockExcEngine,
address: BlockAddress, address: BlockAddress,
@ -182,26 +185,17 @@ proc requestBlock*(
let blockFuture = b.pendingBlocks.getWantHandle(address, b.blockFetchTimeout) let blockFuture = b.pendingBlocks.getWantHandle(address, b.blockFetchTimeout)
if not b.pendingBlocks.isInFlight(address): if not b.pendingBlocks.isInFlight(address):
let peers = b.peers.selectCheapest(address) let peers = b.peers.getPeersForBlock(address)
if peers.len == 0:
if peers.with.len == 0:
b.discovery.queueFindBlocksReq(@[address.cidOrTreeCid]) b.discovery.queueFindBlocksReq(@[address.cidOrTreeCid])
else:
let maybePeer = let selected = pickPseudoRandom(address, peers.with)
if peers.len > 0: asyncSpawn b.monitorBlockHandle(blockFuture, address, selected.id)
peers[hash(address) mod peers.len].some
elif b.peers.len > 0:
toSeq(b.peers)[hash(address) mod b.peers.len].some
else:
BlockExcPeerCtx.none
if peer =? maybePeer:
asyncSpawn b.monitorBlockHandle(blockFuture, address, peer.id)
b.pendingBlocks.setInFlight(address) b.pendingBlocks.setInFlight(address)
# TODO: Send more block addresses if at all sensible. await b.sendWantBlock(@[address], selected)
await b.sendWantBlock(@[address], peer)
codex_block_exchange_want_block_lists_sent.inc() await b.sendWantHave(@[address], peers.without)
await b.sendWantHave(@[address], @[peer], toSeq(b.peers))
codex_block_exchange_want_have_lists_sent.inc()
# Don't let timeouts bubble up. We can't be too broad here or we break # Don't let timeouts bubble up. We can't be too broad here or we break
# cancellations. # cancellations.
@ -246,7 +240,7 @@ proc blockPresenceHandler*(
) )
if wantCids.len > 0: if wantCids.len > 0:
trace "Peer has blocks in our wantList", peer, wantCount = wantCids.len trace "Peer has blocks in our wantList", peer, wants = wantCids
await b.sendWantBlock(wantCids, peerCtx) await b.sendWantBlock(wantCids, peerCtx)
# if none of the connected peers report our wants in their have list, # if none of the connected peers report our wants in their have list,
@ -276,7 +270,7 @@ proc scheduleTasks(b: BlockExcEngine, blocksDelivery: seq[BlockDelivery]) {.asyn
proc cancelBlocks(b: BlockExcEngine, addrs: seq[BlockAddress]) {.async.} = proc cancelBlocks(b: BlockExcEngine, addrs: seq[BlockAddress]) {.async.} =
## Tells neighboring peers that we're no longer interested in a block. ## Tells neighboring peers that we're no longer interested in a block.
trace "Sending block request cancellations to peers", addrs = addrs.len trace "Sending block request cancellations to peers", addrs, peers = b.peers.mapIt($it.id)
let failed = (await allFinished( let failed = (await allFinished(
b.peers.mapIt( b.peers.mapIt(
@ -342,13 +336,13 @@ proc blocksDeliveryHandler*(
b: BlockExcEngine, b: BlockExcEngine,
peer: PeerId, peer: PeerId,
blocksDelivery: seq[BlockDelivery]) {.async.} = blocksDelivery: seq[BlockDelivery]) {.async.} =
trace "Received blocks from peer", peer, blocks = (blocksDelivery.mapIt($it.address)).join(",") trace "Received blocks from peer", peer, blocks = (blocksDelivery.mapIt(it.address))
var validatedBlocksDelivery: seq[BlockDelivery] var validatedBlocksDelivery: seq[BlockDelivery]
for bd in blocksDelivery: for bd in blocksDelivery:
logScope: logScope:
peer = peer peer = peer
address = bd.address address = bd.address
if err =? b.validateBlockDelivery(bd).errorOption: if err =? b.validateBlockDelivery(bd).errorOption:
warn "Block validation failed", msg = err.msg warn "Block validation failed", msg = err.msg
@ -390,11 +384,13 @@ proc wantListHandler*(
wantList: WantList) {.async.} = wantList: WantList) {.async.} =
let let
peerCtx = b.peers.get(peer) peerCtx = b.peers.get(peer)
if isNil(peerCtx):
if peerCtx.isNil:
return return
var var
presence: seq[BlockPresence] presence: seq[BlockPresence]
schedulePeer = false
for e in wantList.entries: for e in wantList.entries:
let let
@ -405,7 +401,7 @@ proc wantListHandler*(
address = e.address address = e.address
wantType = $e.wantType wantType = $e.wantType
if idx < 0: # updating entry if idx < 0: # Adding new entry to peer wants
let let
have = await e.address in b.localStore have = await e.address in b.localStore
price = @( price = @(
@ -413,24 +409,27 @@ proc wantListHandler*(
.price.toBytesBE) .price.toBytesBE)
if e.wantType == WantType.WantHave: if e.wantType == WantType.WantHave:
codex_block_exchange_want_have_lists_received.inc() if have:
presence.add(
BlockPresence(
address: e.address,
`type`: BlockPresenceType.Have,
price: price))
else:
if e.sendDontHave:
presence.add(
BlockPresence(
address: e.address,
`type`: BlockPresenceType.DontHave,
price: price))
peerCtx.peerWants.add(e)
if not have and e.sendDontHave: codex_block_exchange_want_have_lists_received.inc()
presence.add(
BlockPresence(
address: e.address,
`type`: BlockPresenceType.DontHave,
price: price))
elif have and e.wantType == WantType.WantHave:
presence.add(
BlockPresence(
address: e.address,
`type`: BlockPresenceType.Have,
price: price))
elif e.wantType == WantType.WantBlock: elif e.wantType == WantType.WantBlock:
peerCtx.peerWants.add(e) peerCtx.peerWants.add(e)
schedulePeer = true
codex_block_exchange_want_block_lists_received.inc() codex_block_exchange_want_block_lists_received.inc()
else: else: # Updating existing entry in peer wants
# peer doesn't want this block anymore # peer doesn't want this block anymore
if e.cancel: if e.cancel:
peerCtx.peerWants.del(idx) peerCtx.peerWants.del(idx)
@ -443,8 +442,9 @@ proc wantListHandler*(
trace "Sending presence to remote", items = presence.mapIt($it).join(",") trace "Sending presence to remote", items = presence.mapIt($it).join(",")
await b.network.request.sendPresence(peer, presence) await b.network.request.sendPresence(peer, presence)
if not b.scheduleTask(peerCtx): if schedulePeer:
warn "Unable to schedule task for peer", peer if not b.scheduleTask(peerCtx):
warn "Unable to schedule task for peer", peer
proc accountHandler*( proc accountHandler*(
engine: BlockExcEngine, engine: BlockExcEngine,
@ -555,7 +555,7 @@ proc taskHandler*(b: BlockExcEngine, task: BlockExcPeerCtx) {.gcsafe, async.} =
updateInFlight(failedAddresses, false) updateInFlight(failedAddresses, false)
if blocksDelivery.len > 0: if blocksDelivery.len > 0:
trace "Sending blocks to peer", peer = task.id, blocks = (blocksDelivery.mapIt($it.address)).join(",") trace "Sending blocks to peer", peer = task.id, blocks = (blocksDelivery.mapIt(it.address))
await b.network.request.sendBlocksDelivery( await b.network.request.sendBlocksDelivery(
task.id, task.id,
blocksDelivery blocksDelivery

View File

@ -32,6 +32,9 @@ logScope:
type type
PeerCtxStore* = ref object of RootObj PeerCtxStore* = ref object of RootObj
peers*: OrderedTable[PeerId, BlockExcPeerCtx] peers*: OrderedTable[PeerId, BlockExcPeerCtx]
PeersForBlock* = object of RootObj
with*: seq[BlockExcPeerCtx]
without*: seq[BlockExcPeerCtx]
iterator items*(self: PeerCtxStore): BlockExcPeerCtx = iterator items*(self: PeerCtxStore): BlockExcPeerCtx =
for p in self.peers.values: for p in self.peers.values:
@ -70,32 +73,14 @@ func peersWant*(self: PeerCtxStore, address: BlockAddress): seq[BlockExcPeerCtx]
func peersWant*(self: PeerCtxStore, cid: Cid): seq[BlockExcPeerCtx] = func peersWant*(self: PeerCtxStore, cid: Cid): seq[BlockExcPeerCtx] =
toSeq(self.peers.values).filterIt( it.peerWants.anyIt( it.address.cidOrTreeCid == cid ) ) toSeq(self.peers.values).filterIt( it.peerWants.anyIt( it.address.cidOrTreeCid == cid ) )
func selectCheapest*(self: PeerCtxStore, address: BlockAddress): seq[BlockExcPeerCtx] = proc getPeersForBlock*(self: PeerCtxStore, address: BlockAddress): PeersForBlock =
# assume that the price for all leaves in a tree is the same var res = PeersForBlock()
let rootAddress = BlockAddress(leaf: false, cid: address.cidOrTreeCid) for peer in self:
var peers = self.peersHave(rootAddress) if peer.peerHave.anyIt( it == address ):
res.with.add(peer)
func cmp(a, b: BlockExcPeerCtx): int =
var
priceA = 0.u256
priceB = 0.u256
a.blocks.withValue(rootAddress, precense):
priceA = precense[].price
b.blocks.withValue(rootAddress, precense):
priceB = precense[].price
if priceA == priceB:
0
elif priceA > priceB:
1
else: else:
-1 res.without.add(peer)
res
peers.sort(cmp)
trace "Selected cheapest peers", peers = peers.len
return peers
proc new*(T: type PeerCtxStore): PeerCtxStore = proc new*(T: type PeerCtxStore): PeerCtxStore =
## create new instance of a peer context store ## create new instance of a peer context store

View File

@ -69,27 +69,6 @@ checksuite "Peer Context Store Peer Selection":
check peerCtxs[0] in peers check peerCtxs[0] in peers
check peerCtxs[5] in peers check peerCtxs[5] in peers
test "Should select cheapest peers for Cid":
peerCtxs[0].blocks = collect(initTable):
for i, a in addresses:
{ a: Presence(address: a, price: (5 + i).u256) }
peerCtxs[5].blocks = collect(initTable):
for i, a in addresses:
{ a: Presence(address: a, price: (2 + i).u256) }
peerCtxs[9].blocks = collect(initTable):
for i, a in addresses:
{ a: Presence(address: a, price: i.u256) }
let
peers = store.selectCheapest(addresses[0])
check peers.len == 3
check peers[0] == peerCtxs[9]
check peers[1] == peerCtxs[5]
check peers[2] == peerCtxs[0]
test "Should select peers that want Cid": test "Should select peers that want Cid":
let let
entries = addresses.mapIt( entries = addresses.mapIt(
@ -109,3 +88,19 @@ checksuite "Peer Context Store Peer Selection":
check peers.len == 2 check peers.len == 2
check peerCtxs[0] in peers check peerCtxs[0] in peers
check peerCtxs[5] in peers check peerCtxs[5] in peers
test "Should return peers with and without block":
let address = addresses[2]
peerCtxs[1].blocks[address] = Presence(address: address, price: 0.u256)
peerCtxs[2].blocks[address] = Presence(address: address, price: 0.u256)
let peers = store.getPeersForBlock(address)
for i, pc in peerCtxs:
if i == 1 or i == 2:
check pc in peers.with
check pc notin peers.without
else:
check pc notin peers.with
check pc in peers.without

View File

@ -19,7 +19,7 @@ multinodesuite "Sales":
clients: CodexConfigs.init(nodes=1).some, clients: CodexConfigs.init(nodes=1).some,
providers: CodexConfigs.init(nodes=1).some, providers: CodexConfigs.init(nodes=1).some,
) )
var host: CodexClient var host: CodexClient
var client: CodexClient var client: CodexClient