livepeer / protocol

Livepeer protocol
MIT License
154 stars 45 forks source link

Transition from V1 -> Streamflow transcoder pool #355

Closed yondonfu closed 4 years ago

yondonfu commented 4 years ago

Background

The BondingManager.isActiveTranscoder() is used within the BondingManager to determine if an address is an active transcoder during a round.

In V1, the implementation of the function was:

function isActiveTranscoder(address _transcoder, uint256 _round) public view returns (bool) {
    activeTranscoderSet[_round].isActive[_transcoder];
}

In Streamflow, the function signature is modified to drop the _round argument and the implementation is updated to:

function isActiveTranscoder(address _transcoder) public view returns (bool) {
    Transcoder storage t = transcoders[_transcoder];
    uint256 currentRound = roundsManager().currentRound();
    return t.activationRound <= currentRound && currentRound < t.deactivationRound;
}

The Streamflow implementation of isActiveTranscoder() relies on the fact that in round N, if a transcoder is added to transcoderPool, then the transcoder's activationRound will be set to currentRound + 1 and the transcoder's deactivationRound will be set to MAX_FUTURE_ROUND. This is a problem during the transition from V1 to Streamflow after the BondingManager is upgraded for a number of reasons:

Proposals

Proposal 1

We can define an additional function activate() (along with its counterpart that accepts a hint for the list insertion position activateWithHint(). Anyone can call this function and specify an address to try to activate. The address must be a registered transcoder.

Below is a rough sketch of the function implementation for activateWithHint():

function activateWithHint(address _transcoder, address _newPosPrev, address _newPosNext) external {
    require(isRegisteredTranscoder(_transcoder));

    Transcoder storage t = transcoders[_transcoder];
    uint256 totalStake = transcoderTotalStake(_transcoder);
    uint256 activationRound = roundsManager().currentRound().add(1);

    if (!transcoderPool.contains(_transcoder) {
        // If _transcoder is not in the pool then we try to add it to the pool the normal way
        // i.e. Check if pool is full, try to evict last transcoder and then add the new transcoder
        tryToJoinActiveSet(_transcoder, totalStake, activationRound, _newPosPrev, _newPosNext);
    } else if (t.activationRound == 0 && t.deactivationRound == 0) {
        // If _transcoder is in the pool AND it has zero values for activationRound and deactivationRound then
        // it must have been in the pool before the upgrade. So, instead of trying to add it to the
        // the pool let's just update its activation related fields

        // Set activationRound, deactivationRound, earnings pool stake, next round total active stake, etc.
        activateTranscoder(_transcoder, totalStake, activationRound);
    } else {
        revert("transcoder cannot be activated");
    }
}

We allow anyone to call this function so that the transcoder itself or anyone else (including other accounts owned by the transcoder) can call it to activate the transcoder for the next round either in the post BondingManager upgrade scenario mentioned above or in the scenario where someone exits the pool and its current stake (without any additional bonding) is sufficient to allow it to join the pool. So, after the BondingManager upgrade, a single call to this function by anyone will activate transcoder and correct the state update problems mentioned at the beginning of this post.

We'll also need to update the increaseTotalStake() such that if a transcoder is in transcoderPool, but it has zero values for activationRound and deactivationRound instead of doing:

if (transcoderPool.contains(_delegate) {
    transcoderPool.updateKey();
    nextRoundTotalActiveStake = nextRoundTotalActiveStake.add(_amount);
    // Other operations
}

we can do

uint256 currStake = transcoderTotalStake(_delegate);

if (transcoderPool.contains(_delegate) {
    uint256 stakeToBeActivated = _amount;
    if (t.activationRound == 0 && t.deactivationRound == 0 {
        // Existing stake never added to next round's total active stake so let's do that now
        stakeToBeActivated = stakeToBeActivated.add(currStake);
    }

    transcoderPool.updateKey();
    nextRoundTotalActiveStake = nextRoundTotalActiveStake.add(stakeToBeActivated);
}

We would do something similar for decreaseTotalStake().

Proposal 2

We can deprecate the transcoderPool state variable and declare an additional transcoderPoolStreamflow state variable.

All staking operations would point to the new transcoderPoolStreamflow state variable. A consequence of this is that after the upgrade, the transcoder pool would be empty and all transcoders would need to add themselves back to the pool via bond() or transcoder().

I'm currently leaning toward proposal 2 as that is the simpler approach.

yondonfu commented 4 years ago

Closed by #356