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

Stagger allowed miner responses during a dispute #842

Merged
merged 18 commits into from
Aug 20, 2020

Conversation

area
Copy link
Member

@area area commented May 7, 2020

First off, this is (notionally) based on #812 hence draft status for now.

We obviously hope that multiple people will run reputation mining clients. For submissions, we have a window over which it gets easier and easier to submit new hashes, with the difficulty being randomised per user, and with it being easier to submit if you have more staked. We did this because we wanted rewards to be 'fairly' distributed and for miners to not be wasting gas, making submissions that wouldn't succeed because all the slots filled up before their transaction was mined (which wasn't the case when they broadcast it). This PR adds similar functionality to dispute resolutions.

In this implementation, there is a window during a dispute resolution where only those that have submitted a hash are able to respond to a challenge, and the point in this window where a submitter is able to respond is randomised per-submitter for each stage. This additional window lasts 10 minutes, so in the event of a 'full' mining cycle submission, each submitter can expect to get (waves hands vaguely) a minute after they are eligible to respond to a dispute before someone else becomes eligible, which seems adequate. This increases the potential severity of a DDOS attack, because each dispute round can take twice as long to resolve if bad submissions are defended judiciously, but the well-behaving miners shouldn't step on each others toes during submissions, which is well worth the cost.

There's one subtlety in the implementation worth drawing attention to; in the case of all but one of these windows, the window is based on the last response timestamp of the submission being defended. The one case that's not true is the invalidateHash call where an entry is being invalidated (i.e. not the case where a bye is awarded), where the window is based on the last response timestamp of the submission being invalided.

@area area force-pushed the feat/gate-dispute-responses branch 5 times, most recently from 48e70bc to 9910e0c Compare May 26, 2020 13:32
@area area force-pushed the feat/gate-dispute-responses branch 8 times, most recently from ab699cc to 5548d2c Compare June 4, 2020 15:08
@area
Copy link
Member Author

area commented Jun 4, 2020

Further comments as this hopefully nears being done:

ReputationMiningCycleBinarySearch.sol and ReputationMiningCycleCommon.sol have been introduced as the contract sizes were getting too large.

lastResponseTimestamp is now set on submission to the time the window closes. This is fairer (even-numbered submissions now pay the same gas) and also makes working out when a valid window is easier in the case of the last submission being able to get a bye in the first round (when previously, that timestamp wouldn't be set).

I have added a BinarySearchStep event - every other stage of a mining dispute had a corresponding event.

I have added a helper function makeTxAtTimestamp which does what it says on the tin. It stops mining, sends a transaction, and then mines a block at the specified timestamp before re-enabling mining. This helps massively in making tests reproduceable (and was necessary here) for the main test added (should not allow miners to respond immediately during a dispute, but they should be able to some time before the end of the window). It arguably should be used instead of forwardTime wherever possible, but that is much more of a maintenance PR than something that should be here I think.

@area area force-pushed the feat/gate-dispute-responses branch 4 times, most recently from 56ac63b to c7c4635 Compare June 9, 2020 09:28
@area area force-pushed the feat/gate-dispute-responses branch 2 times, most recently from d4dc043 to 57e78d9 Compare June 12, 2020 10:39
@area area force-pushed the feat/gate-dispute-responses branch from 57e78d9 to ffe55ba Compare June 19, 2020 08:34
@area area marked this pull request as ready for review June 19, 2020 10:38
@area area force-pushed the feat/gate-dispute-responses branch 5 times, most recently from 909e18d to 7c59ab2 Compare June 22, 2020 15:49
@area area force-pushed the feat/gate-dispute-responses branch from 551dc1a to 46a7a3b Compare July 13, 2020 08:49
@area area force-pushed the feat/gate-dispute-responses branch from 46a7a3b to 8c4214e Compare July 29, 2020 09:40

contract ReputationMiningCycleBinarySearch is ReputationMiningCycleCommon {
function respondToBinarySearchForChallenge(
uint256 round,
Copy link
Member

Choose a reason for hiding this comment

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

Are we not using the underscore convention in these contracts? We use them in the interface contract...

Copy link
Member Author

Choose a reason for hiding this comment

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

It's barely used in the interface contract either for what it's worth, but I guess it's best to be consistent across all contracts.

return false;
}

uint256 delta = now - since;
Copy link
Member

Choose a reason for hiding this comment

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

Reading through this and I'm still getting confused around which way the window works. One one level it seems like it should be >=, i.e. more restrictive when the delta is large, but we have the opposite. Can we rename delta to responseTimeRemaining or something? since could also be clearer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Rearranged this a bit, that hopefully makes it clearer to the reader.

@@ -367,30 +377,44 @@ class ReputationMinerClient {

// If we're here, we do have an opponent.
// Has our opponent timed out?
// TODO: Remove these magic numbers
Copy link
Member

Choose a reason for hiding this comment

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

Does the client not have a constants.js file?

Copy link
Member Author

Choose a reason for hiding this comment

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

It does not. We could add one, ideally though we'd want these values being picked up off the contracts, which I think is what I had in mind when I wrote this comment.

await forwardTime(MINING_CYCLE_DURATION, this);

let nSubmissionsForHash = await repCycle.getNSubmissionsForHash("0x12345678", 10, "0x00");
expect(nSubmissionsForHash).to.eq.BN(0);
Copy link
Member

Choose a reason for hiding this comment

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

to.be.zero

const noChallengeCompleted = noEventSeen(repCycleEthers, "ChallengeCompleted");
const noHashInvalidated = noEventSeen(repCycleEthers, "HashInvalidated");

await noChallengeCompleted;
Copy link
Member

Choose a reason for hiding this comment

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

Remind me why we don't just await these promises directly?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because it's testing the auto functionality. If we awaited directly where they're created, the actions to trigger them have not yet taken place on chain so the test would time out. If we moved the creation to where they're awaited, then the tests become spotty, as the thing the promises are waiting for might have happened before they've been set up.


await goodClient.submitRootHash();
let reward = await repCycle.getDisputeRewardSize();
expect(reward).to.eq.BN("0");
Copy link
Member

Choose a reason for hiding this comment

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

to.be.zero

@area area force-pushed the feat/gate-dispute-responses branch from e6539f3 to e5dee55 Compare August 12, 2020 12:31
kronosapiens
kronosapiens previously approved these changes Aug 18, 2020
// require user made a submission
if (reputationHashSubmissions[msg.sender].proposedNewRootHash == bytes32(0x00)) {
return false;
}
uint256 target = delta * Y;
uint256 windowOpenFor = now - _responseWindowOpened;
Copy link
Member

Choose a reason for hiding this comment

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

If you want to make the most of this variable I'd be happy using it up in the conditional as

if (windowOpenFor <= SUBMITTER_ONLY_WINDOW_DURATION) {

Copy link
Member

@kronosapiens kronosapiens left a comment

Choose a reason for hiding this comment

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

Nice! This has been in the works a while.

@kronosapiens kronosapiens merged commit f82c660 into develop Aug 20, 2020
@kronosapiens kronosapiens deleted the feat/gate-dispute-responses branch August 20, 2020 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants