Send blocks
Description
The goal of this new feature is to add the possibility for a given node to send a block to another peer (and not to wait for that other node to ask for it), basically allowing us to actively spread blocks of a file in the network.
Requirements
- A node can send a block to another peer
- Peers can refuse to be sent blocks (to protect against possible attacks like being sent enormous amount of data)
Proposed solution
Create two new commands, send_block
and send_block_to
-
send_block_to
either succeeds or fails and returns the result -
send_block
usessend_block_to
to find someone that will accept the block until it succeeds, or none of the connected peers accepts the block
send_block_to
will send to a given peer the information about the block it wants to send by using a SendRequest
(new structure to identify the Send
operation). This uses the existing PeerBlockInfo
structure, but we will add the size of the block as a field. The size of the block will be used by the receiving end to decide whether or not they accept to receive the block.
To keep track of the blocks we are trying to send, we have a hash map. The values are oneshot sender to send back the result of the command. They key is the hash of (PeerId, BlockHash)
. We use this instead of just using the BlockHash
since we could be trying to send the same block to multiple peers at a time.
To respond to a SendRequest
, a peer uses a SendResponse
. This SendResponse
contains the PeerBlockInfo
previously sent (to allow the first peer to know which request is being answered to), and a variant of an Answer
enum (name could probably be better). Variants of the enum are:
-
Accepted(Stream)
: the receiving peer accepts to take the block and gives a stream on which to write the data -
Rejected
: the peer doesn't accept the block. There can be multiple reasons for that:- Not enough storage
- The peer already has that block
But we don't say why we refuse (it doesn't really matter to the one initiating the send request anyway)
In the case where the peer accepted the block, we write the block on the stream. On the other side, the receiver will read the exact amount of data that the sender announced for the block size. We might want to be a bit lenient here because there might be network corrections which make the actual stream bigger than the announced block_size, so we can decide on a security coefficient maybe ? Like read 1.2 times the size of the announced block ?
Upon receiving the block, we verify that the block is valid and store it on the disk. We then send a "Ok" through the stream to signify to the sender everything went correctly. Otherwise, we send an error (if the block was not valid, if the block was too big...).
Managing available storage space
When launching a node for the first time, we create a file to store the size of the blocks we received via Send
, and the list of the blocks / associated file and size.
We require a node to give the size it allows for storing blocks through Send
request (like 20 GB for example). Each time we successfully write a block received via Send
on the disk, we update the file (we also keep a variable for the size of the Send
blocks on disk, to not have to read the file every time we need to decide if we have enough space left).
For the operation of the node, the file is not very useful. But it becomes useful later on for two reasons:
- When we reboot the node later on, we don't need to go into each directory to know what size we have available left, since we keep track of it as we go. We might not even know which blocks we got because we wanted them, and which we go because someone used a
Send
. The file allows us to know that - If we have saturated the space we allowed for
Send
blocks, we can decide to free some space. Keeping track of the list of blocks allows us to free space that was used only bySend
blocks (meaning we don't delete blocks we actually want). It also allows us to delete the oldest blocks for example, or delete by size without having to check every sub-directory for the biggest block
The file is something that's not 100% needed but it will be useful in the long run.