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

Delegator slashing #10

Open
yondonfu opened this issue Jun 4, 2018 · 9 comments
Open

Delegator slashing #10

yondonfu opened this issue Jun 4, 2018 · 9 comments

Comments

@yondonfu
Copy link
Member

yondonfu commented Jun 4, 2018

At the moment, when a transcoder faults, the transcoder is slashed, but its delegators are not. We can create a stronger incentive for delegators to delegate toward quality transcoders that do not fault (whether it be purposefully or accidental due to a client malfunction) by also applying slashing penalties to a transcoder's delegators.

The BondingManager contract can keep track of the blocks at which a transcoder was slashed in a manner similar to the description for this comment and if a transcoder is slashed, the slashing penalty will be immediately applied to the transcoder's total stake, but will not be immediately applied to its own stake. Instead, the slashing penalty will be applied to all delegators of a transcoder (since the transcoder is also a delegator the slashing penalty will also be applied in this case) when they claim earnings through the round in which the transcoder was slashed. If the transcoder called BondingManager#reward() during the round, a delegator will first credit its stake with its reward share for the round. Then, if the transcoder was slashed during the round, the tracked slashing penalty is applied to the delegator's stake. Note: if the transcoder was slashed before it called BondingManager#reward() the order of these steps still works because the delegator would be credited with 0 reward shares for the round and then the slashing penalty would be applied to the delegator's stake. At the end of these steps, if the transcoder was slashed, a delegator's stake will be reduced by the same slashing percentage that the transcoder's total stake was reduced by when it was first slashed. Since we apply the slashing penalty to a delegator's stake for the round during which the slash occurred and since delegators cannot execute bonding related actions i.e. BondingManager#bond(), BondingManager#unbond() unless they have claimed all earnings through the current round the delegator's stake should always reflect any slashing penalties from being delegated to a transcoder that faulted at the time of rebonding or unbonding.

An example:

Let Alice be a transcoder with a total delegated stake of 100 LPT with 20 LPT delegated towards itself. Alice's reward cut is 0% meaning that she does not keep any rewards for herself as a transcoder (she still can claim reward shares as a delegator towards herself). Let Bob be a delegator with 20 LPT delegated toward Alice. Let Eve be a delegator with 60 LPT delegated toward Alice.

  • Alice calls BondingManager#reward() in round 100, minting 100 LPT which is placed in the reward pool for round 100 and increases her total delegated stake to 200
  • Alice calls BondingManager#reward() in round 101, minting 100 LPT which is placed in the reward pool for round 101 and increases her total delegated stake to 300
  • In round 101, Alice is slashed for double claiming a segment. Let the slashing percentage for double claims be 20%

Alice's total delegated stake becomes 300 - (300 * .2) = 240 and she is kicked out of the transcoder pool.

Alice wants her remaining LPT so she automatically claims earnings through round 101 when she unbonds:

  • For round 100, Alice's stake represents 20 / 100 = .2 of the total delegated stake. After claiming earnings through round 100, Alice's stake = 20 + (100 * .2) = 40
  • For round 101, Alice's stake represents 40 / 200 = .2 of the total delegated stake. After claiming earnings through round 101, Alice's stake = 40 + (100 * .2) = 60
  • After applying the 20% slashing penalty, Alice's stake = 60 - (60 * .2) = 48

Eve wants her remaining LPT so she automatically claims earnings through round 101 when she rebonds to another transcoder:

  • For round 100, Eve's stake represents 60 / 100 = .6 of the total delegated stake. After claiming earnings through round 100, Eve's stake = 60 + (100 * .6) = 120
  • For round 101, Eve's stake represents 120 / 200 = .6 of the total delegated stake. After claiming earnings through round 101, Eve's stake = 120 + (100 * .6) = 180
  • After applying the 20% slashing penalty, Eve's stake = 180 - (180 * .2) = 144

Bob wants his remaining LPT so he automatically claims earnings through round 101 when he rebonds to another transcoder:

  • For round 100, Bob's stake represents 20 / 100 = .2 of the total delegated stake. After claiming earnings through round 100, Alice's stake = 20 + (100 * .2) = 40
  • For round 101, Bob's stake represents 40 / 200 = .2 of the total delegated stake. After claiming earnings through round 101, Bob's stake = 40 + (100 * .2) = 60
  • After applying the 20% slashing penalty, Alice's stake = 60 - (60 * .2) = 48

At this point, the sum of the stakes of Alice, Eve and Bob = 48 + 144 + 48 = 240 which is Alice's total delegated stake as a transcoder after she is slashed.

@j0sh
Copy link

j0sh commented Jun 12, 2018

Currently, one way for transcoders to avoid the effects of slashing is to transfer its LPT to another account (say an offline wallet), then delegate the entire amount. Arguably that's better opsec, but still a loophole in the protocol, which this proposal fixes.

I'm a little undecided on whether I like the idea of penalizing delegators for the sins of the transcoder. Perhaps, instead of a slashing, the rewards are reduced by a corresponding amount for a certain number of rounds? That way, delegator stake isn't reduced, and by spreading the penalty out over time, it'd give delegators a chance to unbond and avoid missing out on stake accumulation, since we're talking about significant (20%+) penalties.

For comparison, in other PoS protocols such as Tezos, delegator funds are never at risk. Rather, the protocol enforces that the owner has a minimum amount of slashable funds. Although this works better for Tezos for various reasons, we could adopt a similar scheme.

@yondonfu
Copy link
Member Author

IMO the main reasons for slashing delegators are:

  • Encourage better judgement around deciding who to delegate to because humans are generally more loss averse. I think delegators would be more likely to take action in an effort to avoid loss of their already owned LPT due to the endowment effect, more so than if they instead only stood to receive less rewards (which is LPT that they do not yet own).
  • Related to the above - encourage delegators to diversify their LPT holdings across more transcoders in the active set. If delegators are slashed for the faults of their transcoders, they have a stronger incentive to not risk all their LPT by only delegating to a single transcoder which could potentially reduce stake concentration and increase decentralization.
  • Ensuring that a single entity behind a transcoder node will be penalized based on all LPT that it has delegated toward the transcoder node no matter how many accounts it is delegating from.

A worthwhile concern with this proposal is that delegators could be slashed even when their transcoder is not being malicious - the transcoder might trigger a slashing condition due to a software bug. What could help here is the parameterization of the slashing penalties for each fault scenario such that fault scenarios where a bug is more likely can have lower slashing penalties and fault scenarios where a bug is unlikely can have higher slashing penalties (an example of this would be double claiming a segment).

Rather, the protocol enforces that the owner has a minimum amount of slashable funds.

While I think enforcing a minimum stake for a transcoder might have some benefits, properly choosing the minimum stake introduces some additional questions around whether it be a chosen value (what should the initial value be?) that is then moveable via governance or it be a dynamically adjusted value deciding by some pre-determined algorithm. One approach might be to enforce a minimum ratio between a transcoder's bonded stake and its total stake such that if the amount of LPT increases by a large enough amount it must increase its own bonded stake in order to make sure it meets the required ratio.

@yondonfu
Copy link
Member Author

yondonfu commented Jun 20, 2018

Some additional details following up on the original comment:

struct Transcoder {
    ...
    mapping (uint256 => uint256) slashedPercInRound;
}

struct Delegator {
    ...
    uint256 lastSlashedRound;
}

T.slashedPercInRound[N] is the slashed % incurred by the transcoder T due to a fault in round N. If this value is 0 then the transcoder was not slashed during round N.

D.lastSlashedRound is the last round that the delegator D had a slashing penalty applied to it.

When a transcoder is slashed, its total delegated stake is decreased immediately, but slashing penalties are applied to delegators' bonded amounts retroactively - if a transcoder incurs a slashing penalty, delegators have the penalty applied to their bonded amounts when the delegators claim earnings through the round in which its transcoder was slashed. After claiming earnings for a round, we check if the delegator should have any slashing penalties applied to its bonded amount based on its transcoder. If so, the slashing penalty is applied to the delegator's bonded amount - the updated value is then used to calculate the delegator's earnings for the following round.

The implementation (unoptimized) might look look something like this:

function updateDelegatorWithEarnings(address _delegator, uint256 _endRound) internal {
    ...

    // If the delegator was slashed during `del.lastClaimRound` we know that slashing penalties were properly accounted for
    // during that round so we can start from `del.lastClaimRound + 1`
    // If the delegator was not slashed during `del.lastClaimRound` there is a possibility that slashing penalties were not
    // all properly accounted for during that round, so we start from `del.lastClaimRound` and make sure slashing penalties
    // from that round are accounted for first before moving on to claiming earnings for `del.lastClaimRound + 1`
    uint256 firstClaimRound = del.lastClaimRound == del.lastSlashedRound ? del.lastClaimRound + 1 : del.lastClaimRound
    uint256 currentBondedAmount = del.bondedAmount;
    uint256 currentFees = del.fees;
    uint256 currentLastSlashedRound = del.lastSlashedRound;
    uint256 currentPenalty = 0;

    for (uint256 i = firstClaimRound; i <= endRound; i++) {
        ...

        // If the round is == `del.lastClaimRound` then we are accounting for slashing penalties in that round first
        // but we already claimed earnings for that round so we should not do so again
        if (i > del.lastClaimRound && earningsPool.hasClaimableShares) {
            ...
        }

        uint256 transcoderSlashedPerc = transcoders[del.delegateAddress].slashedPercInRound[i];

        if (transcoderSlashedPerc > 0) {
            uint256 penalty = calcPenalty(currentBondedAmount, transcoderSlashedPerc)

            currentBondedAmount = currentBondedAmount.sub(penalty);
            currentPenalty = currentPenalty.add(penalty);
            currentLastSlashedRound = i;
        }
    }

    del.bondedAmount = currentBondedAmount;
    del.fees = currentFees;
    del.lastSlashedRound = currentLastSlashedRound;

    if (currentPenalty > 0) {
        minter().trustedBurnTokens(currentPenalty);
    }
}

@dob
Copy link
Member

dob commented Jun 21, 2018

Delegators are ˆsomewhatˆ protected from continuously getting slashed because the transcoder becomes inactivated for the round that they are slashed in. So they wouldn't get continuously slashed down to zero. AND, if we get the software and automation right, no one should ever be slashed under the current conditions unless they are doing something actively malicious.

I think the beginning of this proposal looks good and could move towards PR, however I think it's still questionable when we should activate delegator slashing. At the moment we don't have the automation right, so it may be better to put the burden on transcoders to do the work to improve the software to avoid slashing conditions.

There are proposals being debated for more of a masternode style concept (which is like a minimum required amount to become an orchestrator), so that work would no longer be assigned in direct proportion to stake. and instead would encourage orchestrators to add many nodes to the network. We are a ways away from adopting that and figuring out if the economics work, but big changes like that could also affect decisions around slashing.

@yondonfu
Copy link
Member Author

yondonfu commented Jun 21, 2018

I think the beginning of this proposal looks good and could move towards PR, however I think it's still questionable when we should activate delegator slashing.

Yeah I think the concerns about including delegator slashing in an upgrade due to the current state of the software and automation are valid. However, I think we should try to keep the focus of these LIP discussions around the technical merits of the proposal (i.e. does this proposal accomplish the right goal? Are there any flaws in the design?) as opposed to whether the timing is right today (more importantly the question should be: if the timing is not right today, do we at least think the inclusion of this makes sense at some point?) for including the feature in an upgrade. IMO IF we believe that the outlined designs are appropriate, we would merge a draft LIP and evaluate whether it should be included in an upgrade separately.

EDIT: Which is not to say that concerns relating to inclusion timing shouldn't be brought up, just that more thorough discussion about those concerns can continue on separately!

@yondonfu
Copy link
Member Author

One of the flaws in this proposed solution is that delegators are slashed based on the round during which evidence of a fault is submitted. As a result, delegators can be penalized for a transcoder's faults in the past (during a time that the delegator was not even delegating stake toward the transcoder) that are not surfaced until the present or in the future.

Instead, delegators should be slashed based on the round during which a transcoder that they delegated stake towards committed a fault. As a result, delegators would only be penalized for a transcoder's faults during the period of time where the delegator is delegating stake toward that transcoder. Delegators are not held accountable for a transcoder faults for any rounds during which they did not delegate stake toward a transcoder. In order to accomplish this the BondingManager#slashTranscoder() would need to also accept a round number indicating the round during which the fault occurred.

@dob
Copy link
Member

dob commented Jun 27, 2018

Does this get complicated if the delegator has already claimed rewards in the round for which they are later slashed? Or is the slashing independent of the claim accounting?

@yondonfu
Copy link
Member Author

I imagine that a delegator would be slashed for a round in the past during which its transcoder committed a fault based on how much stake it currently has (including accumulated rewards) in the round that the evidence for the fault was submitted. The slashing penalty could also be calculated based on the stake that the delegator had during the round of the fault and then subtracted from the stake that the delegator has during the round of the evidence submission though this would increase the amount of state the contract needs to track by quite a bit

@yondonfu
Copy link
Member Author

yondonfu commented Jul 16, 2018

Linking to a comment made in #8 to make sure we keep track of the fact slashing mechanism updates will also need to address any attack vectors presented by partial unbonding:

However, in the interest of keeping this LIP focused on partial unbonding and to avoid having it also encompass potentially large changes to the slashing mechanisms, I think we can push forward with this draft proposal as is, move the rest of the discussion around covering all the slashing edge cases to the delegator slashing issue and acknowledge the following implications:

There is no enforcement of slashing penalties on the unbonding locks in this proposal. This is fine for delegators because delegator slashing is not enforced yet. However, this means transcoders can reduce their slashable stake by creating unbonding locks that might just be rebonded later on after a slash occurs. While this is an attack vector, at the same the current reality is 1) there is another loop hole that allows transcoders to minimize their slashable stake by delegating to themselves using a separate account since delegator slashing is not enforced yet so any meaningful update to slashing would also need to address this scenario 2) during this current alpha stage of the network, slashing penalties are minimal since it is possible for bugs in early software to lead to faults that are not necessarily a result of malicious behavior (note: transcoders will still be kicked out of the registered pool for the remainder of a round if slashed) 3) there is an immediate need for partial unbonding for stakeholders to create more freedom around how they can use their earned rewards

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

No branches or pull requests

3 participants