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

Implement a time-based block creation algorithm #1100

Merged
merged 5 commits into from
Sep 14, 2020

Conversation

AndrejMitrovic
Copy link
Contributor

@AndrejMitrovic AndrejMitrovic commented Aug 5, 2020

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 a TestNode 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).

@codecov
Copy link

codecov bot commented Aug 5, 2020

Codecov Report

Merging #1100 into v0.x.x will increase coverage by 0.18%.
The diff coverage is 95.39%.

Impacted file tree graph

@@            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     
Flag Coverage Δ
#integration 41.15% <75.47%> (+0.11%) ⬆️
#unittests 77.33% <93.42%> (+0.22%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
source/agora/common/Config.d 91.19% <66.66%> (-0.48%) ⬇️
source/agora/test/TimeDrift.d 92.00% <92.00%> (ø)
source/agora/consensus/protocol/Nominator.d 90.90% <94.11%> (+0.28%) ⬆️
source/agora/test/Base.d 86.51% <96.15%> (+0.89%) ⬆️
source/agora/test/TimeBlockInterval.d 96.15% <96.15%> (ø)
source/agora/consensus/data/ConsensusParams.d 100.00% <100.00%> (ø)
source/agora/network/Clock.d 100.00% <100.00%> (ø)
source/agora/network/NetworkManager.d 77.31% <100.00%> (-0.24%) ⬇️
source/agora/node/FullNode.d 84.00% <100.00%> (+0.21%) ⬆️
source/agora/node/Validator.d 95.38% <100.00%> (+0.14%) ⬆️
... and 12 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update edcd16f...c7ac8cc. Read the comment docs.

@AndrejMitrovic
Copy link
Contributor Author

Of all the targets to fail, it had to be Windows. 🤦‍♂️😄

@AndrejMitrovic
Copy link
Contributor Author

source\agora\test\Base.d(553,16): Error: cannot implicitly convert expression cast(ulong)this.genesis_start_time + height.value * cast(ulong)this.test_conf.block_interval_sec of type ulong to int

Huh, why is time_t 32-bit on Windows? That is not at all what the docs say it should be: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/time-time32-time64?view=vs-2019

time is a wrapper for _time64 and time_t is, by default, equivalent to __time64_t. If you need to force the compiler to interpret time_t as the old 32-bit time_t, you can define _USE_32BIT_TIME_T

But apparently in druntime it's different:

C:\dev> rdmd --eval="import core.stdc.time; writeln(time_t.sizeof);"
4

@AndrejMitrovic
Copy link
Contributor Author

Edit: also with -m64. I think DMD defaults to -m32 on Windows if I'm not mistaken. I'll ask in the D forums.

@AndrejMitrovic AndrejMitrovic force-pushed the timer-blocks branch 4 times, most recently from 2514abe to 26438e4 Compare August 6, 2020 04:06
@AndrejMitrovic AndrejMitrovic changed the title [WIP] Implement timer-based block creation [WIP] Implement time-based block creation Aug 6, 2020
@AndrejMitrovic AndrejMitrovic force-pushed the timer-blocks branch 2 times, most recently from 9e19b2a to 2ec626b Compare August 6, 2020 07:14
@AndrejMitrovic
Copy link
Contributor Author

Update: Added a clock-drift test-case and it works exactly as expected.

@AndrejMitrovic AndrejMitrovic changed the title [WIP] Implement time-based block creation Implement time-based block creation Aug 7, 2020
@AndrejMitrovic AndrejMitrovic marked this pull request as ready for review August 7, 2020 05:48
Copy link
Collaborator

@Geod24 Geod24 left a 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.

@@ -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;
Copy link
Collaborator

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 ?

Copy link
Contributor Author

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.
Copy link
Collaborator

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

@@ -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;
Copy link
Collaborator

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.

Copy link
Contributor Author

@AndrejMitrovic AndrejMitrovic Aug 10, 2020

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
Copy link
Collaborator

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

Comment on lines +285 to +313
if (cur_time < this.params.GenesisStartTime)
{
log.fatal("Clock is out of sync. " ~
"Current time: {}. Genesis time: {}", cur_time,
this.params.GenesisStartTime);
return;
}

Copy link
Collaborator

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.

Copy link
Collaborator

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

Copy link
Contributor Author

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..

this.network.gossipTransaction(tx);
this.onAcceptedTransaction();
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's a sin

Comment on lines +70 to +78
/// Workaround: onRegenerateQuorums() is called from within the ctor,
/// but LocalScheduler is not instantiated yet.
private bool started;
Copy link
Collaborator

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) ?

Copy link
Contributor Author

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.

Comment on lines +411 to +471

/// 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);
}
Copy link
Collaborator

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 ?

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

Copy link
Contributor Author

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.

@AndrejMitrovic
Copy link
Contributor Author

This needs a rebase.

Ok.

@Geod24
Copy link
Collaborator

Geod24 commented Aug 23, 2020

This has looooooads of conflicts. Feel free to submit things piece-meal if that's easier.

@AndrejMitrovic AndrejMitrovic force-pushed the timer-blocks branch 5 times, most recently from ba5e317 to c59dc60 Compare August 24, 2020 07:45
@AndrejMitrovic AndrejMitrovic force-pushed the timer-blocks branch 3 times, most recently from baf140d to 6bf49a9 Compare August 25, 2020 05:25
@AndrejMitrovic AndrejMitrovic changed the title Implement time-based block creation Implement a time-based block creation algorithm Aug 25, 2020
@AndrejMitrovic
Copy link
Contributor Author

This can be reviewed.

Note that synchronization of clocks across nodes will be done in a separate PR.

@AndrejMitrovic AndrejMitrovic force-pushed the timer-blocks branch 3 times, most recently from 066c050 to 34aa299 Compare September 1, 2020 05:13

This routine should be called after every externalize event and
after any valid block is received from the network (getBlocksFrom()).

Copy link
Contributor Author

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,
Copy link
Contributor Author

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.

Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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?


***************************************************************************/

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,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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.

Copy link
Contributor Author

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.
@AndrejMitrovic AndrejMitrovic force-pushed the timer-blocks branch 2 times, most recently from 9c12b49 to c7ac8cc Compare September 11, 2020 07:48
AndrejMitrovic added a commit to AndrejMitrovic/agora that referenced this pull request Sep 11, 2020
Comment on lines 97 to 113
public this (Clock clock,
NetworkManager network, KeyPair key_pair, Ledger ledger,
Copy link
Collaborator

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

Copy link
Contributor Author

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

?

Copy link
Contributor Author

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.

@Geod24 Geod24 merged commit 0330491 into bosagora:v0.x.x Sep 14, 2020
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