-
Notifications
You must be signed in to change notification settings - Fork 22
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
Implement a time-based block creation algorithm #1100
Conversation
Codecov Report
@@ Coverage Diff @@
## v0.x.x #1100 +/- ##
==========================================
+ Coverage 78.76% 78.95% +0.18%
==========================================
Files 86 89 +3
Lines 8203 8309 +106
==========================================
+ Hits 6461 6560 +99
- Misses 1742 1749 +7
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
Of all the targets to fail, it had to be Windows. 🤦♂️😄 |
Huh, why is
But apparently in druntime it's different:
|
Edit: also with |
2514abe
to
26438e4
Compare
9e19b2a
to
2ec626b
Compare
Update: Added a clock-drift test-case and it works exactly as expected. |
2ec626b
to
5e38b35
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a rebase.
source/agora/common/Config.d
Outdated
@@ -138,6 +138,9 @@ public struct NodeConfig | |||
|
|||
/// Maximum number of nodes to include in an autogenerated quorum set | |||
public uint max_quorum_nodes = 7; | |||
|
|||
/// Threshold to use in the autogenerated quorum | |||
public double quorum_threshold = 0.80; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That feels like going back to QuorumConfig, just with a global value.
Do you envision this as a temporary solution, or do you think it's going to be permanent ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only for the unittests. In this case it makes it significantly easier to write a simple test-case - otherwise I would have to instantiate many more nodes and I prefer to have tests which are simple to understand.
I don't actually want this to be in the node configs. But there was a commit which removed the ConsensusParams
from the FullNode
ctor parameter list and ConsensusParams
was instead instantiated directly in the constructor body using the node config parameters.
@@ -0,0 +1,39 @@ | |||
/******************************************************************************* | |||
|
|||
Contains a Clock implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love this commit <3
source/agora/common/Config.d
Outdated
@@ -102,6 +102,13 @@ public struct NodeConfig | |||
/// in place of the built-in genesis block as defined by CoinNet | |||
public string genesis_block; | |||
|
|||
/// The Unix timestamp of the Genesis block. Blocks are created at a | |||
/// regular interval starting from this time. | |||
public time_t genesis_start_time = 1596179709; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooooohhhh didn't think of this. Right. I would say, if we parse it from the config file, let's make it parse an ISO8601 string. Also, mention the time is UTC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Also I'm not sure whether we need or don't need a timestamp in the block header yet. It might be possible to omit it for now since the estimated block timestamp is derived from its height.
@@ -181,7 +229,8 @@ extern(D): | |||
|
|||
***************************************************************************/ | |||
|
|||
protected bool prepareNominatingSet (out ConsensusData data) @safe | |||
protected bool prepareNominatingSet (out ConsensusData data, | |||
in time_t cur_time) @safe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you did here
if (cur_time < this.params.GenesisStartTime) | ||
{ | ||
log.fatal("Clock is out of sync. " ~ | ||
"Current time: {}. Genesis time: {}", cur_time, | ||
this.params.GenesisStartTime); | ||
return; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should just check it at startup and assert
here. I can see a lot of headache otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check it at startup: Needs to be user-friendly for the user in regular build, and dev friendly in UT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add another check at start-up, however I will leave this here as well. There is another PR coming which implements synchronization of the clock across the network, together with a lot of documentation about clock time drift and so on..
source/agora/node/FullNode.d
Outdated
this.network.gossipTransaction(tx); | ||
this.onAcceptedTransaction(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a sin
/// Workaround: onRegenerateQuorums() is called from within the ctor, | ||
/// but LocalScheduler is not instantiated yet. | ||
private bool started; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can the timer be caller if the scheduler isn't running ? Can I have an ELIMM (Explain Like It's Monday Morning) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot use setTimer
until LocalScheduler
is instantiated, and it is instantiated after the constructor finishes. In the constructor of the node onRegenerateQuorums
is called. But onRegenerateQuorums
is also called when a block is externalized.
|
||
/// the adjustable local clock time for this node. | ||
/// This does not affect request timeouts and is only | ||
/// used in the Nomination protocol. | ||
private shared(time_t)* cur_time; | ||
|
||
/// Get the current clock time | ||
@property time_t time () @trusted @nogc nothrow | ||
{ | ||
return atomicLoad(*this.cur_time); | ||
} | ||
|
||
/// Set the new time | ||
@property void time (time_t new_time) @trusted @nogc nothrow | ||
{ | ||
atomicStore(*this.cur_time, new_time); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not in the TestAPI
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's explained in the PR description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, missed that. The overhead is negligible TBH (we have a resolution of 1 second and the overhead is usually 10ms, 400ms is the highest I've seen it). But I'm fine if we leave it this way for the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to reply here. There is also the issue of filtering - I want to circumvent active filters on the node but still have the ability to change its time.
Ok. |
This has looooooads of conflicts. Feel free to submit things piece-meal if that's easier. |
ba5e317
to
c59dc60
Compare
baf140d
to
6bf49a9
Compare
8620e52
to
556ead7
Compare
3a37c8b
to
caf3f0d
Compare
9131899
to
9dfc863
Compare
This can be reviewed. Note that synchronization of clocks across nodes will be done in a separate PR. |
066c050
to
34aa299
Compare
|
||
This routine should be called after every externalize event and | ||
after any valid block is received from the network (getBlocksFrom()). | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing Params:
@@ -168,7 +244,8 @@ extern(D): | |||
|
|||
***************************************************************************/ | |||
|
|||
protected bool prepareNominatingSet (out ConsensusData data) @safe | |||
protected bool prepareNominatingSet (out ConsensusData data, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs Params
docs. cur_time
is used by derived classes, and will also be used here but by subsequent PRs.
@@ -187,31 +264,61 @@ extern(D): | |||
|
|||
/*************************************************************************** | |||
|
|||
Try to begin a nomination round. | |||
Gets the expected block nomination time for for the provided height. | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing Params
|
||
***************************************************************************/ | ||
|
||
private ulong getExpectedBlockTime (Height height) @safe @nogc nothrow pure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should change all time_t
to ulong
since time_t
doesn't seem to be portable on Windows.
|
||
***************************************************************************/ | ||
|
||
public void tryNominate () @safe | ||
public void checkNominate () @safe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is called periodically by the nominating timer.
Should this be private
then?
34aa299
to
f750e23
Compare
|
||
***************************************************************************/ | ||
|
||
public this (immutable(Block) genesis, uint validator_cycle = 1008, | ||
uint max_quorum_nodes = 7, uint quorum_threshold = 80, | ||
uint quorum_shuffle_interval = 30) | ||
uint quorum_shuffle_interval = 30, | ||
time_t genesis_start_time = 1596179709, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
time_t genesis_start_time = 1596179709, | |
time_t genesis_start_time = 1596153600, |
assert(SysTime(DateTime.fromISOString("20200731T000000"), UTC()).toUnixTime == 1596153600);
According to the time declared in config value, the unix timestamp value is as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes. In any case these are just placeholder values. We need to change them to the actual time of when mainnet will be deployed. But we don't know this yet.
This code will later be supplemented by network-synchronization features, therefore it will live in the networking package.
It will need them to control the nominating behavior.
- genesis_start_time => The Unix timestamp of the Genesis block. Blocks are created at `block_interval` intervals starting from `genesis_start_time` - block_interval => How frequently blocks are created. It is assumed that block #1 was created at timestamp: `genesis_start_time` + `block_interval`
The two config options `genesis_start_time` and `block_interval` dictate when and how often a Validator will nominate blocks. `genesis_start_time` is the expected timestamp of when the UnitNet/TestNet/CointNet was deployed, whereas `block_interval` dictates how often new blocks should be nominated. It is therefore expected that block height #1 should be nominated at the time: genesis_start_time + (block_inteval * 1) Note that this is a preliminary implementation and lacks network clock synchronization primitives.
9c12b49
to
c7ac8cc
Compare
public this (Clock clock, | ||
NetworkManager network, KeyPair key_pair, Ledger ledger, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have weird life choices
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh you mean the order of parameters. I kind of tried to order them by importance, but this is purely subjective. I'll add params at the end from now on.
|
||
mixin AddLogger!(); | ||
|
||
/// The current implementation uses NTP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh well outdated comment. But under the hood the OS uses NTP. Interestingly enough Ethereum (or was it Bitcoin?) tried to roll out their own NTP code and realized it was worse than what the OS already implements, so they rolled back.
This implements one part of #1004.
There is no synchronization of clocks between nodes - but this is something I'm working on and might file as a separate issue.
Note that I'm using
shared(time_t)
rather than aTestNode
API endpoint to get & set a custom clock time because I want to avoid request overheads when setting the time (especially because these requests may be delayed).