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

Special rewards for block proposer #2625

Merged
merged 7 commits into from
Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changelog/2625.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Special rewards for block proposer

- a larger portion of the fees
- an additional reward
32 changes: 30 additions & 2 deletions go/consensus/tendermint/abci/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,13 @@ type ApplicationState interface {
// GetBaseEpoch returns the base epoch.
GetBaseEpoch() (epochtime.EpochTime, error)

// GetEpoch returns current epoch at block height.
// GetEpoch returns epoch at block height.
GetEpoch(ctx context.Context, blockHeight int64) (epochtime.EpochTime, error)

// GetCurrentEpoch returns the epoch at the current block height,
// applying all the weird legacy off-by-one rules we have.
GetCurrentEpoch(ctx context.Context) (epochtime.EpochTime, error)

// EpochChanged returns true iff the current epoch has changed since the
// last block. As a matter of convenience, the current epoch is returned.
EpochChanged(ctx *Context) (bool, epochtime.EpochTime)
Expand Down Expand Up @@ -193,11 +197,31 @@ func (s *applicationState) GetBaseEpoch() (epochtime.EpochTime, error) {
return s.timeSource.GetBaseEpoch(s.ctx)
}

// GetEpoch returns current epoch at block height.
// GetEpoch returns epoch at block height.
func (s *applicationState) GetEpoch(ctx context.Context, blockHeight int64) (epochtime.EpochTime, error) {
return s.timeSource.GetEpoch(ctx, blockHeight)
}

// GetCurrentEpoch returns the epoch at the current block height,
// applying all the weird legacy off-by-one rules we have.
func (s *applicationState) GetCurrentEpoch(ctx context.Context) (epochtime.EpochTime, error) {
blockHeight := s.BlockHeight()
if blockHeight == 0 {
return epochtime.EpochInvalid, nil
} else if blockHeight == 1 {
currentEpoch, err := s.timeSource.GetEpoch(ctx, blockHeight)
if err != nil {
return epochtime.EpochInvalid, fmt.Errorf("application state time source get epoch for height %d: %w", blockHeight, err)
}
return currentEpoch, nil
}
currentEpoch, err := s.timeSource.GetEpoch(ctx, blockHeight+1)
if err != nil {
return epochtime.EpochInvalid, fmt.Errorf("application state time source get epoch for height %d: %w", blockHeight+1, err)
}
return currentEpoch, nil
}

// EpochChanged returns true iff the current epoch has changed since the
// last block. As a matter of convenience, the current epoch is returned.
func (s *applicationState) EpochChanged(ctx *Context) (bool, epochtime.EpochTime) {
Expand Down Expand Up @@ -518,6 +542,10 @@ func (ms *mockApplicationState) GetEpoch(ctx context.Context, blockHeight int64)
return ms.cfg.CurrentEpoch, nil
}

func (ms *mockApplicationState) GetCurrentEpoch(ctx context.Context) (epochtime.EpochTime, error) {
return ms.cfg.CurrentEpoch, nil
}

func (ms *mockApplicationState) EpochChanged(ctx *Context) (bool, epochtime.EpochTime) {
return ms.cfg.EpochChanged, ms.cfg.CurrentEpoch
}
Expand Down
16 changes: 12 additions & 4 deletions go/consensus/tendermint/apps/staking/fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import (
stakingState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/staking/state"
)

const (
// The proportion of fees disbursed to entities of the nodes that voted for a block.
weightVote = 1
// The proportion of fees disbursed to the entity of the node that proposed a block.
weightPropose = 1
)

type disbursement struct {
id signature.PublicKey
weight int64
Expand All @@ -17,9 +24,7 @@ type disbursement struct {
// disburseFees disburses fees.
//
// In case of errors the state may be inconsistent.
func (app *stakingApplication) disburseFees(ctx *abci.Context, signingEntities []signature.PublicKey) error {
stakeState := stakingState.NewMutableState(ctx.State())

func (app *stakingApplication) disburseFees(ctx *abci.Context, stakeState *stakingState.MutableState, proposerEntity *signature.PublicKey, signingEntities []signature.PublicKey) error {
totalFees, err := stakeState.LastBlockFees()
if err != nil {
return fmt.Errorf("staking: failed to query last block fees: %w", err)
Expand All @@ -39,7 +44,10 @@ func (app *stakingApplication) disburseFees(ctx *abci.Context, signingEntities [
d := disbursement{
id: entityID,
// For now we just disburse equally.
weight: 1,
weight: weightVote,
}
if proposerEntity != nil && proposerEntity.Equal(entityID) {
d.weight += weightPropose
}
rewardAccounts = append(rewardAccounts, d)
totalWeight += d.weight
Expand Down
48 changes: 48 additions & 0 deletions go/consensus/tendermint/apps/staking/proposing_rewards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package staking

import (
"encoding/hex"
"fmt"

"github.com/tendermint/tendermint/abci/types"

"github.com/oasislabs/oasis-core/go/common/crypto/signature"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/abci"
registryState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/registry/state"
stakingState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/staking/state"
epochtime "github.com/oasislabs/oasis-core/go/epochtime/api"
staking "github.com/oasislabs/oasis-core/go/staking/api"
)

func (app *stakingApplication) resolveEntityIDFromProposer(regState *registryState.MutableState, request types.RequestBeginBlock, ctx *abci.Context) *signature.PublicKey {
var proposingEntity *signature.PublicKey
proposerNode, err := regState.NodeByConsensusAddress(request.Header.ProposerAddress)
if err != nil {
ctx.Logger().Warn("failed to get proposer node",
"err", err,
"address", hex.EncodeToString(request.Header.ProposerAddress),
)
} else {
proposingEntity = &proposerNode.EntityID
}
return proposingEntity
}

func (app *stakingApplication) rewardBlockProposing(ctx *abci.Context, stakeState *stakingState.MutableState, proposingEntity *signature.PublicKey) error {
if proposingEntity == nil {
return nil
}

epoch, err := app.state.GetCurrentEpoch(ctx.Ctx())
if err != nil {
return fmt.Errorf("app state getting current epoch: %w", err)
}
if epoch == epochtime.EpochInvalid {
ctx.Logger().Info("rewardBlockProposing: this block does not belong to an epoch. no block proposing reward")
return nil
}
if err = stakeState.AddRewards(epoch, staking.RewardFactorBlockProposed, []signature.PublicKey{*proposingEntity}); err != nil {
return fmt.Errorf("adding rewards: %w", err)
}
return nil
}
4 changes: 1 addition & 3 deletions go/consensus/tendermint/apps/staking/signing_rewards.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import (
staking "github.com/oasislabs/oasis-core/go/staking/api"
)

func (app *stakingApplication) updateEpochSigning(ctx *abci.Context, signingEntities []signature.PublicKey) error {
stakeState := stakingState.NewMutableState(ctx.State())

func (app *stakingApplication) updateEpochSigning(ctx *abci.Context, stakeState *stakingState.MutableState, signingEntities []signature.PublicKey) error {
epochSigning, err := stakeState.EpochSigning()
if err != nil {
return fmt.Errorf("loading epoch signing info: %w", err)
Expand Down
18 changes: 15 additions & 3 deletions go/consensus/tendermint/apps/staking/staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/oasislabs/oasis-core/go/consensus/api/transaction"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/abci"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/api"
registryState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/registry/state"
stakingState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/staking/state"
epochtime "github.com/oasislabs/oasis-core/go/epochtime/api"
staking "github.com/oasislabs/oasis-core/go/staking/api"
Expand Down Expand Up @@ -52,16 +53,27 @@ func (app *stakingApplication) OnCleanup() {
}

func (app *stakingApplication) BeginBlock(ctx *abci.Context, request types.RequestBeginBlock) error {
regState := registryState.NewMutableState(ctx.State())
stakeState := stakingState.NewMutableState(ctx.State())

// Look up the proposer's entity.
proposingEntity := app.resolveEntityIDFromProposer(regState, request, ctx)

// Go through all signers of the previous block and resolve entities.
signingEntities := app.resolveEntityIDsFromVotes(ctx, request.GetLastCommitInfo())
signingEntities := app.resolveEntityIDsFromVotes(ctx, regState, request.GetLastCommitInfo())

// Disburse fees from previous block.
if err := app.disburseFees(ctx, signingEntities); err != nil {
if err := app.disburseFees(ctx, stakeState, proposingEntity, signingEntities); err != nil {
return fmt.Errorf("staking: failed to disburse fees: %w", err)
}

// Add rewards for proposer.
if err := app.rewardBlockProposing(ctx, stakeState, proposingEntity); err != nil {
return fmt.Errorf("staking: block proposing reward: %w", err)
}

// Track signing for rewards.
if err := app.updateEpochSigning(ctx, signingEntities); err != nil {
if err := app.updateEpochSigning(ctx, stakeState, signingEntities); err != nil {
return fmt.Errorf("staking: failed to update epoch signing info: %w", err)
}

Expand Down
4 changes: 1 addition & 3 deletions go/consensus/tendermint/apps/staking/votes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import (
registryState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/registry/state"
)

func (app *stakingApplication) resolveEntityIDsFromVotes(ctx *abci.Context, lastCommitInfo types.LastCommitInfo) []signature.PublicKey {
regState := registryState.NewMutableState(ctx.State())

func (app *stakingApplication) resolveEntityIDsFromVotes(ctx *abci.Context, regState *registryState.MutableState, lastCommitInfo types.LastCommitInfo) []signature.PublicKey {
var entityIDs []signature.PublicKey
for _, a := range lastCommitInfo.Votes {
if !a.SignedLastBlock {
Expand Down
7 changes: 7 additions & 0 deletions go/staking/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ var (
// RewardFactorEpochSigned is the factor for a reward distributed per epoch to
// entities that have signed at least a threshold fraction of the blocks.
RewardFactorEpochSigned *quantity.Quantity
// RewardFactorBlockProposed is the factor for a reward distributed per block
// to the entity that proposed the block.
RewardFactorBlockProposed *quantity.Quantity

// ErrInvalidArgument is the error returned on malformed arguments.
ErrInvalidArgument = errors.New(ModuleName, 1, "staking: invalid argument")
Expand Down Expand Up @@ -436,4 +439,8 @@ func init() {
if err := RewardFactorEpochSigned.FromInt64(1); err != nil {
panic(err)
}
RewardFactorBlockProposed = quantity.NewQuantity()
if err := RewardFactorBlockProposed.FromInt64(1); err != nil {
panic(err)
}
}