-
Notifications
You must be signed in to change notification settings - Fork 78
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
Solidity p14 v1 #594
Solidity p14 v1 #594
Conversation
removed RequestID and logic asociated with it since simultanious group creation is unlikely removed SubmitDkgResult and submission methods in preparation for phase14 logic
added reveiveSubmission, receiveVote and submitGroupPublicKey allong with some helper methods. This allows users to submit a DKG result, vote on the currect one, and retreive the final result with the highest votes.
Included a newer web3 version as the older version does not mimic solidity's compact hashing made some phase 14 logic better by removing clutter (mainly in eligibleSubmitter()) Removed TestPublishDkgResult.js tests as the old submission method will no longer be used Added a helper method to get the block during which a user at a given index becomes eligible to submit a DKG result
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.
@NicholasDotSol Just to remind you truffle.js
should be ignored based on #515
Also looks like package-lock.json
files shouldn't be in this commit since you're not modifying any packages
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.
Unverified add p14 logic commit, pls fix
@@ -21,11 +21,11 @@ contract KeepGroupImplV1 is Ownable { | |||
bytes inactive; | |||
} | |||
|
|||
event DkgResultPublishedEvent(uint256 requestId, bytes groupPubKey); | |||
event DkgResultPublishedEvent(address publisher, bytes groupPubKey); |
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.
👍
contracts/solidity/package.json
Outdated
@@ -9,6 +9,8 @@ | |||
"scripts": { | |||
"truffle": "truffle", | |||
"test": "truffle test", | |||
"testm": "truffle test ./test/TestPublishDkgResult.js", |
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 don't think we need to add those separately? truffle test
should pick them up anyway
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.
Nope definitely don't need that here, just used them locally. Same with Truffle.js. Thought it got .gitIgnored
contracts/solidity/package.json
Outdated
@@ -20,7 +20,8 @@ | |||
}, | |||
"homepage": "https://github.com/keep-network/contracts", | |||
"dependencies": { | |||
"openzeppelin-solidity": "^2.0.0" | |||
"openzeppelin-solidity": "^2.0.0", | |||
"web3": "^1.0.0-beta.37" |
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.
Probably best would be to make a separate PR to upgrade truffle
and web3
? what do you think?
In the meantime you can use soliditySHA3
from already included ethereumjs-abi
i.e. abi.soliditySHA3(...
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.
Couldn't get that to work after a couple twists and turns. It doesn't have support for encoding byes, and the hash was different when passed as Uints or Buffers. Can remove it if abi.soliditySHA3()
works and i'm missing something of course
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.
and the hash was different when passed as Uints or Buffers
Right I see, ok how do you feel opening a PR with truffle 5 upgrade? and we'll see if thats a straightforward migration?
It's bundled with web3 1.0 , should help us keep our code cleaner overall, also we'll get rid of ethereumjs-abi
. Bundled BN
library will be useful for BLS relay entry tests (I had to include it separately in #559)
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'm running v5.0.1 locally already, That's why I need to specify solidity compiler in truffle.js
…p-core into solidity-p14-v1 Fix unverified commit.
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.
Left some comment. I really like where it's heading!
|
||
// Legacy code moved from Random Beacon contract | ||
// TODO: refactor according to the Phase 14 | ||
event SubmitGroupPublicKeyEvent(bytes groupPublicKey, uint256 requestID, uint256 activationBlockHeight); | ||
event SubmitGroupPublicKeyEvent(bytes groupPublicKey, uint256 activationBlockHeight);//add RequestId replacement |
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.
What is the potential request ID replacement here?
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.
@pdyraga previous beacon value should work well as unique identifier
mapping (bytes => uint256) internal _submissionVotes; | ||
mapping (address => mapping (bytes => bool)) internal _hasVoted; | ||
mapping (bytes32 => bool) internal _votedDkg; | ||
mapping (bytes32 => bool) internal _resultPublished; |
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.
Do we need _resultPublished
? Submitting a result entails voting on this result so maybe we can go with just _votedDkg
?
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.
nope, _voteDkg
is all we need as there is no instance where _resultPublished[result]
!= _voteDkg[result]
nice (:
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.
Never mind, Voting for a result does not entail Submitting a result. We need to keep both
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.
But publishing result entails voting on it. That's why I suggested to remove _resultPublished
but keep _votedDkg
.
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 need to separate the two in the method. When 2 participants call the method in the same block one will publish and the other will vote.
if not _resultPublished
-> publish
else
-> vote
They also check different conditions. We can't use _voteDkg
to check the condition because we won't know if the user needs to just vote for a result or publish it. besides when entering the method we require()
that the user hasn't voted.
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.
Hmmm... If you submitted a result, you are not allowed to vote later. If you voted, you are not allowed to submit a result later. There is a possibility I miss some important detail...
mapping (address => mapping (bytes => bool)) internal _hasVoted; | ||
mapping (bytes32 => bool) internal _votedDkg; | ||
mapping (bytes32 => bool) internal _resultPublished; | ||
mapping (address => DkgResult) internal _publisherToDkgResult; |
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.
Do we need to store the information who submitted the given result?
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.
Not right now, but we had conversations about using the publisher as an identifier to DKG result. isInactive and isDisqualified methods used it as an input to retrieve the IA and DQ bytes. Should remove it for now as it is just an extra instruction and not worth hoarding :)
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, let's remove it if it's not currently used 💯
//(2* (T_step)) -> time for first submitter to submit DKG Result. | ||
//No way to calculate DKG time on-chain, so DKG result submission opens on ticket-challenge close. | ||
//first submitter usable time period is really (2 * T_step - DKG time). | ||
else if(block.number >= ((T_init + (2 * (T_step))) + ((index-2) * T_step))){ |
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.
Worth noting, there is an ongoing discussion if this check makes sense at all.
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.
Yup, following that (:
* @param index the claimed index of the user. | ||
* @return true if the ticket at the given index is issued by msg.sender. False otherwise. | ||
*/ | ||
function validateIndex(uint index)public returns(bool){ |
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.
👌 Attention to details level 💯
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.
:p
leadingResult = _DkgResultHashes[i]; | ||
} | ||
totalVotes += highestVoteNtemp; | ||
} |
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.
Hmm... can't we achieve the same without highestVoteNtemp
? 🤔
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.
yup, Had a feeling this would save gas as it would avoid the lookup during reassignment but I may have been delusional 😓
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.
As a result of some Gas testing, holding highestVoteNtemp
does save some gas (about 800 here)
uint highestVoteN; | ||
uint highestVoteNtemp; | ||
uint totalVotes; | ||
uint f_max = _groupSize/2 + 1; |
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 should be evaluated from _groupThreshold
in group contract.
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.
...and probably called maximumDishonest
or something similar 😉
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.
okay, this is the perfect comment! thank you @pdyraga.
I thought that in a case where groupSize
!= groupThreshold
what matters is how large the group currently is, (aka #of stakers potentially eligible to vote) and honest majority is assessed based on that number. That doesn't make sense though 😀
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.
Group size and threshold are fixed constants for the random beacon.
In theory, key generation protocol allows to parametrize threshold (above some secure minimum for the given group size) but we are not allowing to parametrize it.
For us, threshold < group size because we want to support scenario when one or more nodes either does not respond or respond incorrectly. If we set threshold = group size, then all nodes need to respond and all nodes need to behave according to the protocol. This is how it should work in a perfect world but since we don't live in a perfect world, we don't want a single node to stop the group from generating key and slowing down network in a long term.
highestVoteNtemp = _submissionVotes[_DkgResultHashes[i]]; | ||
if(highestVoteNtemp > highestVoteN){ | ||
highestVoteN = highestVoteNtemp; | ||
leadingResult = _DkgResultHashes[i]; |
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'll need to add a check for strictly leading. For example, for the following distribution of votes: 2 1 2 1 1
, we have no strictly leading result.
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.
As an extension to the previous comment about _groupSize
, This works if more than half of the voters agree on a result and fails in all other cases
assuming all group-members vote:
[4,4,1] - > this fails
[2,2,3,2] -> this fails
[1,1,1,1,1,2,1,1] -> Should this succeed?
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 are two pieces here:
- strictly leading result
- honest majority
In the example you mentioned: [1,1,1,1,1,2,1,1]
, the result with 2
votes is strictly leading but the entire operation should end with failure since other results received 7
votes in total and the strictly leading result has only 2
votes, hence, we don't have the honest majority.
This is explained here:
[3] If more than fmax participants vote for a non-leading result, our honest majority assumption has failed and we cannot determine the correct outcome.
http://docs.keep.network/cryptography/beacon_dkg.html#3
Though, there is a typo there and f_max
should be M_max
.
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.
okay awesome, That's the way I thought it was initially but mixed up groupSize
and threshold
But we probably don't need strictly leading check here, if honest majority vote for a single result it will surely be the leading result as well.
honest_majority = _groupThreshold/2 + 1
checking if leadingResult
has at least honest_majority
votes should be sufficient. This is the way to method is currently implemented.
but should change
if(totalVotes - highestVoteN >= f_max)
to
if(totalVotes - highestVoteN > f_max)
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.
The key here is that not all members need to vote - you need to look at off-chain phase 13 and 14:
http://docs.keep.network/random-beacon/dkg.html#phase-13
Say we have a group consisting of 5 players: P_1, P_2, P_3, P_4, P_5
Scenario 1
- In phase 13
P_1
publishes DKG result - Since all other members agree
P_1
is the correct result, they don't submit their own results and they don't vote. From phase 14 off-chain pseudocode:elif (correctResult == leadResult and strictlyLeading) or alreadySubmitted: wait()
- We have just one result with just one vote (from
P_1
). It's strictly leading result but it has no honest majority of votes looking at the counter of votes. This is fine, though. The condition is inverted here: if other results have more thanM_max
votes then we have a problem. In this particular case there is only one result, so no other results have votes so we are fine.
Scenario 2
- In phase 13,
P_1
publishes DKG result - In phase 14,
P_3
does not agree with the result published byP_1
and publishes its own result P_2, P_4, P_5
need to vote. Ideally, only one of them should vote because this will make eitherP_1
result orP_3
result strictly leading but we can't guarantee this behavior. Let's say they all agreeP_1
is the correct result. We may end up with the following distribution of votes:
2 1
,3 1
,4 1
. In all those casesP_1
is stricly leading but in not all of them all number of votes on strictly leading result minus number of votes on other result is greater thanM_max
. But this is fine, not all of them need to vote. That's again, why this condition is inverted:If more than M participants vote for a non-leading result, our honest majority assumption has failed and we cannot determine the correct outcome.
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 okay, assuming a vote was mandatory is incorrect then.
Thank you Piotr, Should be easy to change.
* @param index the claimed index of the submitter. | ||
* @return the block number when a user is eligible. | ||
*/ | ||
function eligibleTime(uint index) public view returns(uint){ |
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 should be used in eligibleSubmitter
, right? (Assuming that we need this check at all, per the ongoing discussion)
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, no this is just a helper for testing. I just plucked the logic from eligible submitter. I did not remove it because I thought it could potentially be useful. Will see if we still need it after the discussion is resolved
gas: 4712388 | ||
} | ||
}, | ||
ompilers: { |
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.
ompilers
? 😜
We probably shouldn't alter this file here - guess it was committed by accident.
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.
Yup, didn't see it coming. it's also odd because I checked .gitignore and it should ignore truffle.js in contracts/solidity.
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.
hmmm also it's spelled correctly on my end. else I can't compile.
Merge with Master
We don't currently hold information about who published a DKG result
…selectedParticipants() This way DKG won't occur if there are not enough participants, and the check will be avoided for every DKG result submission
} | ||
await keepGroupImplViaProxy.receiveSubmission(i,success,groupPubKey,disqualified,inactive, {from: sender}); | ||
} | ||
assert.equal(await keepGroupImplViaProxy.submitGroupPublicKey.call(), newWeb3.utils.soliditySha3(success,groupPubKey, disqualified, inactive) , "get correct final result"); |
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.
@NicholasDotSol If you rebase with master now you'll get updated web3
and can remove newWeb3
Closing it as we work on a new version of P13/14. |
Discussion:
cleanup()
not refunding gas. @ngrinkevichTo Do:
TestDkgConflictResolution.js
cleanup()
research