Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial full sync impl #117

Merged
merged 3 commits into from
Mar 4, 2019
Merged

Initial full sync impl #117

merged 3 commits into from
Mar 4, 2019

Conversation

yglukhov
Copy link
Contributor

Some notes:

  • The idea of "separate requestor/receiver" appeared to have another "flaw", namely suboptimal bandwidth control (we want to request the next batch of data as soon as one of the previous requests is done). At this point I felt there are too many nuances in the approach that slowly drive its complexity towards or even above the "classical" one for no significant benefit. cc @arnetheduck
  • The simulation is pretty broken right now, so it's hard to debug the sync.

# TODO: drop this peer
assert(false)

let db = peer.network.protocolState(BeaconSync).db
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be peer.networkState.db


let blks = await p.getBlocks(node.headBlockRoot, numBlocksToDownload.int)
if blks.isSome:
node.applyBlocks(blks.get.blocks)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice leave some placeholders for the required error handling logic here.

var slot = int64(blk.slot)
let targetSlot = slot + step * (num - 1)
while slot != targetSlot:
if slot < 0 or not db.getBlock(uint64(slot), blk):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this operation looks odd - how can the database know the block of a particular slot? that requires following the fork choice, and it might change over time..

@arnetheduck
Copy link
Member

we can start without it, but what will soon happen is that we'll have a tightly coupled logic that will be hard to debug and improve, because the effect of pulling on one piece of code will be hard to analyze - ie if all the responsibilities are intertwined, with no API between them to regulate the flow of information, it's just a matter of time before the code becomes about as reliable as the nim compiler..

the idea with the design is not to remove all information flow between the layers - it's to start with the assumption that "no information should flow" and gradually weaken that to arrive at a minimal design where all information flows for a well defined reason and where the complexity price is motivated by actual benefits, rather than starting with "everything is tangled" and trying to untangle from there

@yglukhov
Copy link
Contributor Author

yglukhov commented Mar 1, 2019

The sync seems to work now, but it relies on the RLP serialization fix, which breaks nimble test in a mysterious way.

@yglukhov yglukhov force-pushed the sync2 branch 2 times, most recently from f82cab3 to bed6510 Compare March 1, 2019 18:01
node*: BeaconNode
db*: BeaconChainDB

func toHeader(b: BeaconBlock): BeaconBlockHeader =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason for not using construction syntax? ie toHeader(b): .. = BeaconBlockHeader(slot: b.slot, ...)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason really. Somehow I keep forgetting about this option :)

@yglukhov yglukhov changed the title WIP: Initial full sync impl Initial full sync impl Mar 1, 2019
@yglukhov
Copy link
Contributor Author

yglukhov commented Mar 1, 2019

Should be good to go

array[N + 1, byte] =
result[0] = byte ord(kind)
result[1 .. ^1] = key
func subkey[T](kind: DbKeyKind, key: T): auto =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this function a bit dangerous? I can pass a string by accident and it will do the wrong thing. Taking openarray[byte] seems a bit safer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Added an overload instead.


proc append*(writer: var RlpWriter, value: ValidatorSig) =
writer.append value.getBytes()

proc read*(rlp: var Rlp, T: type ValidatorSig): T {.inline.} =
ValidatorSig.init rlp.toBytes.toOpenArray
let r = rlp.read(seq[byte])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version using toBytes doesn't perform an allocation. Was it wrong somehow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it wasn't advancing the read cursor. Fixed it differently.

bestRoot: Eth2Digest # TODO
bestSlot: uint64 = node.state.data.slot

await peer.status(protocolVersion, networkId, latestFinalizedRoot, latestFinalizedEpoch,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the new handshake proc that gets a number of tricky details right, but I'll fix it myself in a later commit.

@yglukhov yglukhov merged commit 21e4deb into master Mar 4, 2019
@yglukhov yglukhov deleted the sync2 branch March 4, 2019 19:05
proc putBlock*(db: BeaconChainDB, key: Eth2Digest, value: BeaconBlock) =
let slotKey = subkey(kSlotToBlockRoots, value.slot)
var blockRootsBytes = db.backend.get(slotKey)
var blockRoots = blockRootsBytes.toSeq(Eth2Digest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically could just use ssz here no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Just... why?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for simplicity so we just have one serialization format in db (and reuse the code for flattening a seq)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I don't have a strong opinion, but a few reasons I like my approach more:

  • The serialization format is localized to the beacon_db, so not a lot to keep in mind
  • My format is pretty much memcpy which can easily be optimized to the least possible amount of allocations, thus more efficient.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was more curious than anything.. I wouldn't have bothered with coming up with a special serialization for it (and the additional unsafe code that comes with). I get the feeling the real optimization here will be to drop rocksdb at some point.

)

proc fromHeader(b: var BeaconBlock, h: BeaconBlockHeader) =
b.slot = h.slot
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

humm, this looks a bit dangerous in that it lets BeaconBlock.data keep its old value.. b = BeaconBlock(...)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or ideally take both a body and a header, so it's impossible to forget to set data..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Will do.

await peer.status(protocolVersion, networkId, latestFinalizedRoot, latestFinalizedEpoch,
bestRoot, bestSlot)

let m = await peer.nextMsg(BeaconSync.status)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens here if peer sends something different, or doesn't send anything at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different dispatchers will be called (e.g. getBeaconBlockHeaders, getBeaconBlockBodies, etc), while this coroutine will just hang forever, until the peer is disconnected, and nextMsg eventually raises.


requestResponse:
proc getBeaconBlockHeaders(peer: Peer, blockRoot: Eth2Digest, slot: uint64, maxHeaders: int, skipSlots: int) =
# TODO: validate maxHeaders
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO and implement skipSlots

yglukhov added a commit that referenced this pull request Mar 5, 2019
arnetheduck pushed a commit that referenced this pull request Mar 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants