Skip to content

Commit

Permalink
Merge pull request bnb-chain#6 from Loverush/fast_finality
Browse files Browse the repository at this point in the history
[WIP]Fast Finality: reward distribution and slash parts
  • Loading branch information
pythonberg1997 authored Mar 15, 2022
2 parents d9bef1e + 1aaaeff commit 2aeab51
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 51 deletions.
101 changes: 89 additions & 12 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ func (p *Parlia) snapshot(chain consensus.ChainHeaderReader, number uint64, hash
return nil, err
}

// new snap shot
// new snapshot
snap = newSnapshot(p.config, p.signatures, number, hash, validators, voteAddrs, p.ethAPI)
if err := snap.store(p.db); err != nil {
return nil, err
Expand Down Expand Up @@ -682,7 +682,7 @@ func (p *Parlia) PrepareVoteAttestation(chain consensus.ChainHeaderReader, heade
return nil
}

var attestation types.VoteAttestation
var attestation *types.VoteEnvelope
// TODO: Add a simple vote aggregation from votePool for test, I will modify this to match the new finality rules.

buf := new(bytes.Buffer)
Expand Down Expand Up @@ -776,6 +776,73 @@ func (p *Parlia) verifyValidators(header *types.Header) error {
return nil
}

func (p *Parlia) distributeFinalityReward(chain consensus.ChainHeaderReader, state *state.StateDB, header *types.Header,
cx core.ChainContext, txs *[]*types.Transaction, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction,
usedGas *uint64, mining bool) error {
currentHeight := header.Number.Uint64()
epoch := p.config.Epoch
chainConfig := chain.Config()
if currentHeight%epoch != 0 || !chainConfig.IsBoneh(header.Number) {
return nil
}

accumulatedWeights := make(map[common.Address]uint64)
for height := currentHeight - epoch; height <= currentHeight; height++ {
head := chain.GetHeaderByNumber(height)
if head == nil {
continue
}
voteAttestation, err := getVoteAttestationFromHeader(head, chainConfig, p.config)
if err != nil {
return err
}
justifiedBlock := chain.GetHeaderByHash(voteAttestation.Data.BlockHash)
if justifiedBlock == nil {
continue
}
rewardCoef := uint64(1)
switch height - justifiedBlock.Number.Uint64() {
case 1:
rewardCoef = 8
case 2:
rewardCoef = 4
}
snap, err := p.snapshot(chain, height, head.Hash(), nil)
if err != nil {
return err
}
validators := snap.validators()
for j := 0; j < 64; j++ {
if ((uint64(voteAttestation.VoteAddressSet) >> j) & 1) == 1 {
accumulatedWeights[validators[j]] += rewardCoef
}
}
}
validators := make([]common.Address, 0, len(accumulatedWeights))
weights := make([]uint64, 0, len(accumulatedWeights))
for val, weight := range accumulatedWeights {
validators = append(validators, val)
weights = append(weights, weight)
}

// method
method := "distributeFinalityReward"

// get packed data
data, err := p.validatorSetABI.Pack(method,
validators,
weights,
)
if err != nil {
log.Error("Unable to pack tx for distributeFinalityReward", "error", err)
return err
}
// get system message
msg := p.getSystemMessage(header.Coinbase, common.HexToAddress(systemcontracts.ValidatorContract), data, common.Big0)
// apply message
return p.applyTransaction(msg, state, header, cx, txs, receipts, systemTxs, usedGas, mining)
}

// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given.
func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction,
Expand All @@ -790,14 +857,20 @@ func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
if !snap.isMajorityFork(hex.EncodeToString(nextForkHash[:])) {
log.Debug("there is a possible fork, and your client is not the majority. Please check...", "nextForkHash", hex.EncodeToString(nextForkHash[:]))
}
// If the block is a epoch end block, verify the validator list
// If the block is an epoch end block, verify the validator list
// The verification can only be done when the state is ready, it can't be done in VerifyHeader.
if err := p.verifyValidators(header); err != nil {
return err
}

// No block rewards in PoA, so the state remains as is and uncles are dropped
// If the block is an epoch end block, distribute the finality reward
// The distribution can only be done when the state is ready, it can't be done in VerifyHeader.
cx := chainContext{Chain: chain, parlia: p}
if err := p.distributeFinalityReward(chain, state, header, cx, txs, receipts, systemTxs, usedGas, false); err != nil {
return err
}

// No block rewards in PoA, so the state remains as is and uncles are dropped
if header.Number.Cmp(common.Big1) == 0 {
err := p.initContract(state, header, cx, txs, receipts, systemTxs, usedGas, false)
if err != nil {
Expand Down Expand Up @@ -1120,14 +1193,14 @@ func (p *Parlia) getCurrentValidators(blockHash common.Hash) ([]common.Address,
blockNr := rpc.BlockNumberOrHashWithHash(blockHash, false)

// method
method := "getValidators"
method := "getMiningValidators"

ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers

data, err := p.validatorSetABI.Pack(method)
if err != nil {
log.Error("Unable to pack tx for getValidators", "error", err)
log.Error("Unable to pack tx for getMiningValidators", "error", err)
return nil, nil, err
}
// call
Expand All @@ -1145,20 +1218,24 @@ func (p *Parlia) getCurrentValidators(blockHash common.Hash) ([]common.Address,

var (
ret0 = new([]common.Address)
ret1 = new([]types.BLSPublicKey)
)
out := ret0
out := struct {
consensusAddrs []common.Address
voteAddrs []types.BLSPublicKey
}{*ret0, *ret1}

if err := p.validatorSetABI.UnpackIntoInterface(out, method, result); err != nil {
return nil, nil, err
}

valz := make([]common.Address, len(*ret0))
for i, a := range *ret0 {
valz[i] = a
voteAddrmap := make(map[common.Address]types.BLSPublicKey)
for i := 0; i < len(*ret0); i++ {
valz[i] = (*ret0)[i]
voteAddrmap[(*ret0)[i]] = (*ret1)[i]
}
return valz, nil, nil

// TODO: return vote address after boneh fork.
return valz, voteAddrmap, nil
}

// slash spoiled validators
Expand Down
23 changes: 11 additions & 12 deletions core/types/vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ type BLSPublicKey [BLSPublicKeyLength]byte
type BLSSignature [BLSSignatureLength]byte
type ValidatorsBitSet uint64

// Bytes gets the string representation of the underlying BLS public key.
func (p BLSPublicKey) Bytes() []byte { return p[:] }

type VoteData struct {
BlockNumber uint64
BlockHash common.Hash
Expand All @@ -26,13 +23,18 @@ type VoteData struct {
type VoteEnvelope struct {
VoteAddress BLSPublicKey
Signature BLSSignature
Data VoteData
Data *VoteData

// caches
hash atomic.Value
}

type VoteEnvelopes []*VoteEnvelope
type VoteAttestation struct {
VoteAddressSet ValidatorsBitSet
AggSignature BLSSignature
Data *VoteData
Extra []byte
}

// Hash returns the vote hash.
func (v *VoteEnvelope) Hash() common.Hash {
Expand All @@ -49,14 +51,11 @@ func (v *VoteEnvelope) calcVoteHash() common.Hash {
voteData := struct {
VoteAddress BLSPublicKey
Signature BLSSignature
Data VoteData
Data *VoteData
}{v.VoteAddress, v.Signature, v.Data}
return rlpHash(voteData)
}

type VoteAttestation struct {
VoteAddressSet ValidatorsBitSet
AggSignature BLSSignature
Data VoteData
Extra []byte
}
func (b BLSPublicKey) Bytes() []byte { return b[:] }

func (b BLSSignature) Bytes() []byte { return b[:] }
73 changes: 70 additions & 3 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ import (
"encoding/binary"
"errors"
"math/big"
"sync"

//lint:ignore SA1019 Needed for precompile
"github.com/prysmaticlabs/prysm/crypto/bls"
"golang.org/x/crypto/ripemd160"
"golang.org/x/crypto/sha3"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/blake2b"
"github.com/ethereum/go-ethereum/crypto/bls12381"
"github.com/ethereum/go-ethereum/crypto/bn256"
"github.com/ethereum/go-ethereum/params"

//lint:ignore SA1019 Needed for precompile
"golang.org/x/crypto/ripemd160"
"github.com/ethereum/go-ethereum/rlp"
)

// PrecompiledContract is the basic interface for native Go contracts. The implementation
Expand Down Expand Up @@ -93,6 +98,8 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},

common.BytesToAddress([]byte{100}): &finalitySignatureVerify{},
}

// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
Expand Down Expand Up @@ -1046,3 +1053,63 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
// Encode the G2 point to 256 bytes
return g.EncodePoint(r), nil
}

var errFinalitySignatureVerify = errors.New("invalid signatures")

// finalitySignatureVerify implements BEP-126 finality signature verification precompile.
type finalitySignatureVerify struct{}

// RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *finalitySignatureVerify) RequiredGas(input []byte) uint64 {
return params.SignatureVerifyGas
}

func (c *finalitySignatureVerify) Run(input []byte) ([]byte, error) {
var (
numA = new(big.Int).SetBytes(getData(input, 0, 32)).Uint64()
headerA = getData(input, 32, 32)
sigA = getData(input, 64, 96)
numB = new(big.Int).SetBytes(getData(input, 160, 32)).Uint64()
headerB = getData(input, 192, 32)
sigB = getData(input, 224, 96)
BLSKey = getData(input, 320, 48)
)

sigs := make([][]byte, 2)
msgs := make([][32]byte, 2)
pubKeys := make([]bls.PublicKey, 2)

pubKey, err := bls.PublicKeyFromBytes(BLSKey)
if err != nil {
return nil, err
}
pubKeys[0] = pubKey
pubKeys[1] = pubKey

msgs[0] = rlpHash(types.VoteData{BlockNumber: numA, BlockHash: common.BytesToHash(headerA)})
msgs[1] = rlpHash(types.VoteData{BlockNumber: numB, BlockHash: common.BytesToHash(headerB)})
sigs[0] = sigA
sigs[1] = sigB

success, err := bls.VerifyMultipleSignatures(sigs, msgs, pubKeys)
if err != nil {
return nil, err
}
if !success {
return nil, errFinalitySignatureVerify
}
return big1.Bytes(), nil
}

// rlpHash encodes x and hashes the encoded bytes.
func rlpHash(x interface{}) (h [32]byte) {
var hasherPool = sync.Pool{
New: func() interface{} { return sha3.NewLegacyKeccak256() },
}
sha := hasherPool.Get().(crypto.KeccakState)
defer hasherPool.Put(sha)
sha.Reset()
rlp.Encode(sha, x)
sha.Read(h[:])
return h
}
8 changes: 4 additions & 4 deletions eth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ type txPool interface {
// votePool defines the methods needed from a votes pool implementation to
// support all the operations needed by the Ethereum chain protocols.
type votePool interface {
PutVote(vote *types.VoteEnvelope) error
Get(hash common.Hash) *types.VoteEnvelope
GetVotes() types.VoteEnvelopes
PutVote(vote *types.VoteEnvelope)
FetchAvailableVotes(blockHash common.Hash) []*types.VoteEnvelope
GetVotes() []*types.VoteEnvelope

// SubscribeNewVotesEvent should return an event subscription of
// NewVotesEvent and send events to the given channel.
Expand Down Expand Up @@ -611,7 +611,7 @@ func (h *handler) ReannounceTransactions(txs types.Transactions) {

// BroadcastVotes will propagate a batch of votes to all peers
// which are not known to already have the given vote.
func (h *handler) BroadcastVotes(votes types.VoteEnvelopes) {
func (h *handler) BroadcastVotes(votes []*types.VoteEnvelope) {
var (
directCount int // Count of announcements made
directPeers int
Expand Down
4 changes: 1 addition & 3 deletions eth/handler_eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,7 @@ func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td
func (h *ethHandler) handleVotesBroadcast(peer *eth.Peer, votes []*types.VoteEnvelope) error {
// Try to put votes into votepool
for _, vote := range votes {
if err := h.votepool.PutVote(vote); err != nil {
return err
}
h.votepool.PutVote(vote)
}
return nil
}
5 changes: 1 addition & 4 deletions eth/protocols/eth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,7 @@ type TxPool interface {
Get(hash common.Hash) *types.Transaction
}

type VotePool interface {
// Get retrieves the vote from the local votepool with the given hash.
Get(hash common.Hash) *types.VoteEnvelope
}
type VotePool interface{}

// MakeProtocols constructs the P2P protocol definitions for `eth`.
func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol {
Expand Down
12 changes: 6 additions & 6 deletions eth/protocols/eth/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ type Peer struct {
txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests

votepool VotePool // Votes pool used by the broadcasters
knownVotes mapset.Set // Set of vote hashes known to be known by this peer
voteBroadcast chan types.VoteEnvelopes // Channel used to queue votes propagation requests
votepool VotePool // Votes pool used by the broadcasters
knownVotes mapset.Set // Set of vote hashes known to be known by this peer
voteBroadcast chan []*types.VoteEnvelope // Channel used to queue votes propagation requests

term chan struct{} // Termination channel to stop the broadcasters
txTerm chan struct{} // Termination channel to stop the tx broadcasters
Expand All @@ -116,7 +116,7 @@ func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool, vot
txBroadcast: make(chan []common.Hash),
txAnnounce: make(chan []common.Hash),
txpool: txpool,
voteBroadcast: make(chan types.VoteEnvelopes),
voteBroadcast: make(chan []*types.VoteEnvelope),
votepool: votepool,
term: make(chan struct{}),
txTerm: make(chan struct{}),
Expand Down Expand Up @@ -401,7 +401,7 @@ func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
}

// SendVotes propagates a batch of votes to the remote peer.
func (p *Peer) SendVotes(votes types.VoteEnvelopes) error {
func (p *Peer) SendVotes(votes []*types.VoteEnvelope) error {
// Mark all the transactions as known, but ensure we don't overflow our limits
for p.knownVotes.Cardinality() > max(0, maxKnownTxs-len(votes)) {
p.knownVotes.Pop()
Expand All @@ -414,7 +414,7 @@ func (p *Peer) SendVotes(votes types.VoteEnvelopes) error {

// AsyncSendVotes queues a batch of vote hashes for propagation to a remote peer. If
// the peer's broadcast queue is full, the event is silently dropped.
func (p *Peer) AsyncSendVotes(votes types.VoteEnvelopes) {
func (p *Peer) AsyncSendVotes(votes []*types.VoteEnvelope) {
select {
case p.voteBroadcast <- votes:
// Mark all the transactions as known, but ensure we don't overflow our limits
Expand Down
Loading

0 comments on commit 2aeab51

Please sign in to comment.