The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
woop/internal/chain/engine.go

590 lines
19 KiB

package chain
import (
"bytes"
"encoding/binary"
"math/big"
"sort"
"github.com/harmony-one/harmony/staking/availability"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/consensus/reward"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/multibls"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee"
"github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
)
type engineImpl struct {
beacon engine.ChainReader
}
// Engine is an algorithm-agnostic consensus engine.
var Engine = &engineImpl{nil}
func (e *engineImpl) Beaconchain() engine.ChainReader {
return e.beacon
}
// SetBeaconchain assigns the beaconchain handle used
func (e *engineImpl) SetBeaconchain(beaconchain engine.ChainReader) {
e.beacon = beaconchain
}
// SealHash returns the hash of a block prior to it being sealed.
func (e *engineImpl) SealHash(header *block.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256()
// TODO: update with new fields
if err := rlp.Encode(hasher, []interface{}{
header.ParentHash(),
header.Coinbase(),
header.Root(),
header.TxHash(),
header.ReceiptHash(),
header.Bloom(),
header.Number(),
header.GasLimit(),
header.GasUsed(),
header.Time(),
header.Extra(),
}); err != nil {
utils.Logger().Warn().Err(err).Msg("rlp.Encode failed")
}
hasher.Sum(hash[:0])
return hash
}
// Seal is to seal final block.
func (e *engineImpl) Seal(chain engine.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
// TODO: implement final block sealing
return nil
}
// Author returns the author of the block header.
func (e *engineImpl) Author(header *block.Header) (common.Address, error) {
// TODO: implement this
return common.Address{}, nil
}
// Prepare is to prepare ...
// TODO(RJ): fix it.
func (e *engineImpl) Prepare(chain engine.ChainReader, header *block.Header) error {
// TODO: implement prepare method
return nil
}
// VerifyHeader checks whether a header conforms to the consensus rules of the bft engine.
// Note that each block header contains the bls signature of the parent block
func (e *engineImpl) VerifyHeader(chain engine.ChainReader, header *block.Header, seal bool) error {
parentHeader := chain.GetHeader(header.ParentHash(), header.Number().Uint64()-1)
if parentHeader == nil {
return engine.ErrUnknownAncestor
}
if seal {
if err := e.VerifySeal(chain, header); err != nil {
return err
}
}
return nil
}
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
// concurrently. The method returns a quit channel to abort the operations and
// a results channel to retrieve the async verifications.
func (e *engineImpl) VerifyHeaders(chain engine.ChainReader, headers []*block.Header, seals []bool) (chan<- struct{}, <-chan error) {
abort, results := make(chan struct{}), make(chan error, len(headers))
go func() {
for i, header := range headers {
err := e.VerifyHeader(chain, header, seals[i])
select {
case <-abort:
return
case results <- err:
}
}
}()
return abort, results
}
// ReadPublicKeysFromLastBlock finds the public keys of last block's committee
func ReadPublicKeysFromLastBlock(bc engine.ChainReader, header *block.Header) ([]*bls.PublicKey, error) {
parentHeader := bc.GetHeaderByHash(header.ParentHash())
return GetPublicKeys(bc, parentHeader, false)
}
// VerifyShardState implements Engine, checking the shardstate is valid at epoch transition
func (e *engineImpl) VerifyShardState(bc engine.ChainReader, beacon engine.ChainReader, header *block.Header) error {
if bc.ShardID() != header.ShardID() {
return ctxerror.New("[VerifyShardState] shardID not match", "bc.ShardID", bc.ShardID(), "header.ShardID", header.ShardID())
}
headerShardStateBytes := header.ShardState()
// TODO: figure out leader withhold shardState
if len(headerShardStateBytes) == 0 {
return nil
}
shardState, err := bc.SuperCommitteeForNextEpoch(beacon, header, true)
if err != nil {
return ctxerror.New("[VerifyShardState] SuperCommitteeForNexEpoch calculation had error", "shardState", shardState).WithCause(err)
}
isStaking := false
if shardState.Epoch != nil && bc.Config().IsStaking(shardState.Epoch) {
isStaking = true
}
shardStateBytes, err := shard.EncodeWrapper(*shardState, isStaking)
if err != nil {
return ctxerror.New("[VerifyShardState] ShardState Encoding had error", "shardStateBytes", shardStateBytes).WithCause(err)
}
if !bytes.Equal(shardStateBytes, headerShardStateBytes) {
headerSS, err := header.GetShardState()
if err != nil {
headerSS = shard.State{}
}
utils.Logger().Error().
Str("shard-state", hexutil.Encode(shardStateBytes)).
Str("header-shard-state", hexutil.Encode(headerShardStateBytes)).
Msg("Shard states did not match, use rlpdump to inspect")
return ctxerror.New(
"[VerifyShardState] ShardState is Invalid", "shardStateEpoch", shardState.Epoch, "headerEpoch",
header.Epoch(), "headerShardStateEpoch", headerSS.Epoch, "beaconEpoch",
beacon.CurrentHeader().Epoch(),
)
}
return nil
}
// VerifySeal implements Engine, checking whether the given block's parent block satisfies
// the PoS difficulty requirements, i.e. >= 2f+1 valid signatures from the committee
// Note that each block header contains the bls signature of the parent block
func (e *engineImpl) VerifySeal(chain engine.ChainReader, header *block.Header) error {
if chain.CurrentHeader().Number().Uint64() <= uint64(1) {
return nil
}
publicKeys, err := ReadPublicKeysFromLastBlock(chain, header)
if err != nil {
return ctxerror.New("[VerifySeal] Cannot retrieve publickeys from last block").WithCause(err)
}
sig := header.LastCommitSignature()
payload := append(sig[:], header.LastCommitBitmap()...)
aggSig, mask, err := ReadSignatureBitmapByPublicKeys(payload, publicKeys)
if err != nil {
return ctxerror.New(
"[VerifySeal] Unable to deserialize the LastCommitSignature" +
" and LastCommitBitmap in Block Header",
).WithCause(err)
}
parentHash := header.ParentHash()
parentHeader := chain.GetHeader(parentHash, header.Number().Uint64()-1)
if chain.Config().IsStaking(parentHeader.Epoch()) {
slotList, err := chain.ReadShardState(parentHeader.Epoch())
if err != nil {
return errors.Wrapf(err, "cannot decoded shard state")
}
subComm, err := slotList.FindCommitteeByID(parentHeader.ShardID())
if err != nil {
return err
}
// TODO(audit): reuse a singleton decider and not recreate it for every single block
d := quorum.NewDecider(
quorum.SuperMajorityStake, subComm.ShardID,
)
d.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) {
return nil, nil
})
if _, err := d.SetVoters(subComm, slotList.Epoch); err != nil {
return err
}
if !d.IsQuorumAchievedByMask(mask) {
return ctxerror.New(
"[VerifySeal] Not enough voting power in LastCommitSignature from Block Header",
)
}
} else {
parentQuorum, err := QuorumForBlock(chain, parentHeader, false)
if err != nil {
return errors.Wrapf(err,
"cannot calculate quorum for block %s", header.Number())
}
if count := utils.CountOneBits(mask.Bitmap); count < int64(parentQuorum) {
return ctxerror.New(
"[VerifySeal] Not enough signature in LastCommitSignature from Block Header",
"need", parentQuorum, "got", count,
)
}
}
// TODO(audit): verify signature on hash+blockNum+viewID (add a hard fork)
blockNumHash := make([]byte, 8)
binary.LittleEndian.PutUint64(blockNumHash, header.Number().Uint64()-1)
lastCommitPayload := append(blockNumHash, parentHash[:]...)
if !aggSig.VerifyHash(mask.AggregatePublic, lastCommitPayload) {
const msg = "[VerifySeal] Unable to verify aggregated signature from last block"
return ctxerror.New(
msg, "lastBlockNum", header.Number().Uint64()-1, "lastBlockHash", parentHash,
)
}
return nil
}
// Finalize implements Engine, accumulating the block rewards,
// setting the final state and assembling the block.
func (e *engineImpl) Finalize(
chain engine.ChainReader, header *block.Header,
state *state.DB, txs []*types.Transaction,
receipts []*types.Receipt, outcxs []*types.CXReceipt,
incxs []*types.CXReceiptsProof, stks staking.StakingTransactions,
doubleSigners slash.Records,
) (*types.Block, reward.Reader, error) {
// Accumulate block rewards and commit the final state root
// Header seems complete, assemble into a block and return
payout, err := AccumulateRewards(
chain, state, header, e.Beaconchain(),
)
if err != nil {
return nil, nil, ctxerror.New("cannot pay block reward").WithCause(err)
}
isBeaconChain := header.ShardID() == shard.BeaconChainShardID
isNewEpoch := len(header.ShardState()) > 0
inStakingEra := chain.Config().IsStaking(header.Epoch())
// Process Undelegations, set LastEpochInCommittee and set EPoS status
if isBeaconChain && isNewEpoch && inStakingEra {
if err := payoutUndelegations(chain, header, state); err != nil {
return nil, nil, err
}
if err := setLastEpochInCommittee(header, state); err != nil {
return nil, nil, err
}
curShardState, err := chain.ReadShardState(chain.CurrentBlock().Epoch())
if err != nil {
return nil, nil, err
}
for _, addr := range curShardState.StakedValidators().Addrs {
if err := availability.ComputeAndMutateEPOSStatus(
chain, state, addr,
); err != nil {
return nil, nil, err
}
}
}
// Apply slashes
if isBeaconChain && inStakingEra && len(doubleSigners) > 0 {
if err := applySlashes(chain, header, state, doubleSigners); err != nil {
return nil, nil, err
}
} else if len(doubleSigners) > 0 {
return nil, nil, errors.New("slashes proposed in non-beacon chain or non-staking epoch")
}
// Finalize the state root
header.SetRoot(state.IntermediateRoot(chain.Config().IsS3(header.Epoch())))
return types.NewBlock(header, txs, receipts, outcxs, incxs, stks), payout, nil
}
// Withdraw unlocked tokens to the delegators' accounts
func payoutUndelegations(
chain engine.ChainReader, header *block.Header, state *state.DB,
) error {
currentHeader := chain.CurrentHeader()
nowEpoch, blockNow := currentHeader.Epoch(), currentHeader.Number()
utils.AnalysisStart("payoutUndelegations", nowEpoch, blockNow)
defer utils.AnalysisEnd("payoutUndelegations", nowEpoch, blockNow)
validators, err := chain.ReadValidatorList()
countTrack := map[common.Address]int{}
if err != nil {
const msg = "[Finalize] failed to read all validators"
return ctxerror.New(msg).WithCause(err)
}
// Payout undelegated/unlocked tokens
for _, validator := range validators {
wrapper, err := state.ValidatorWrapper(validator)
if err != nil {
return ctxerror.New(
"[Finalize] failed to get validator from state to finalize",
).WithCause(err)
}
for i := range wrapper.Delegations {
delegation := &wrapper.Delegations[i]
totalWithdraw := delegation.RemoveUnlockedUndelegations(
header.Epoch(), wrapper.LastEpochInCommittee,
)
state.AddBalance(delegation.DelegatorAddress, totalWithdraw)
}
countTrack[validator] = len(wrapper.Delegations)
if err := state.UpdateValidatorWrapper(
validator, wrapper,
); err != nil {
const msg = "[Finalize] failed update validator info"
return ctxerror.New(msg).WithCause(err)
}
}
utils.Logger().Info().
Uint64("epoch", header.Epoch().Uint64()).
Uint64("block-number", header.Number().Uint64()).
Interface("count-track", countTrack).
Msg("paid out delegations")
return nil
}
func setLastEpochInCommittee(header *block.Header, state *state.DB) error {
newShardState, err := header.GetShardState()
if err != nil {
const msg = "[Finalize] failed to read shard state"
return ctxerror.New(msg).WithCause(err)
}
for _, addr := range newShardState.StakedValidators().Addrs {
wrapper, err := state.ValidatorWrapper(addr)
if err != nil {
return ctxerror.New(
"[Finalize] failed to get validator from state to finalize",
).WithCause(err)
}
wrapper.LastEpochInCommittee = newShardState.Epoch
if err := state.UpdateValidatorWrapper(
addr, wrapper,
); err != nil {
const msg = "[Finalize] failed update validator info"
return ctxerror.New(msg).WithCause(err)
}
}
return nil
}
func applySlashes(
chain engine.ChainReader,
header *block.Header,
state *state.DB,
doubleSigners slash.Records,
) error {
type keyStruct struct {
height uint64
viewID uint64
shardID uint32
epoch uint64
}
groupedRecords := map[keyStruct]slash.Records{}
// First group slashes by same signed blocks
for i := range doubleSigners {
thisKey := keyStruct{
height: doubleSigners[i].Evidence.AlreadyCastBallot.Height,
viewID: doubleSigners[i].Evidence.AlreadyCastBallot.ViewID,
shardID: doubleSigners[i].Evidence.Moment.ShardID,
epoch: doubleSigners[i].Evidence.Moment.Epoch.Uint64(),
}
if _, ok := groupedRecords[thisKey]; ok {
groupedRecords[thisKey] = append(groupedRecords[thisKey], doubleSigners[i])
} else {
groupedRecords[thisKey] = slash.Records{doubleSigners[i]}
}
}
sortedKeys := []keyStruct{}
for key := range groupedRecords {
sortedKeys = append(sortedKeys, key)
}
// Sort them so the slashes are always consistent
sort.SliceStable(sortedKeys, func(i, j int) bool {
if sortedKeys[i].shardID < sortedKeys[j].shardID {
return true
} else if sortedKeys[i].height < sortedKeys[j].height {
return true
} else if sortedKeys[i].viewID < sortedKeys[j].viewID {
return true
}
return false
})
// Do the slashing by groups in the sorted order
for _, key := range sortedKeys {
records := groupedRecords[key]
superCommittee, err := chain.ReadShardState(big.NewInt(int64(key.epoch)))
if err != nil {
return errors.New("could not read shard state")
}
subComm, err := superCommittee.FindCommitteeByID(key.shardID)
if err != nil {
return errors.New("could not find shard committee")
}
// Apply the slashes, invariant: assume been verified as legit slash by this point
var slashApplied *slash.Application
votingPower, err := lookupVotingPower(
header.Epoch(), new(big.Int).SetUint64(key.epoch), subComm,
)
if err != nil {
return errors.Wrapf(err, "could not lookup cached voting power in slash application")
}
rate := slash.Rate(votingPower, records)
utils.Logger().Info().
Str("rate", rate.String()).
RawJSON("records", []byte(records.String())).
Msg("now applying slash to state during block finalization")
if slashApplied, err = slash.Apply(
chain,
state,
records,
rate,
); err != nil {
return ctxerror.New("[Finalize] could not apply slash").WithCause(err)
}
utils.Logger().Info().
Str("rate", rate.String()).
RawJSON("records", []byte(records.String())).
RawJSON("applied", []byte(slashApplied.String())).
Msg("slash applied successfully")
}
return nil
}
// QuorumForBlock returns the quorum for the given block header.
func QuorumForBlock(
chain engine.ChainReader, h *block.Header, reCalculate bool,
) (quorum int, err error) {
ss := new(shard.State)
if reCalculate {
ss, _ = committee.WithStakingEnabled.Compute(h.Epoch(), chain)
} else {
ss, err = chain.ReadShardState(h.Epoch())
if err != nil {
return 0, ctxerror.New("failed to read shard state of epoch",
"epoch", h.Epoch().Uint64()).WithCause(err)
}
}
subComm, err := ss.FindCommitteeByID(h.ShardID())
if err != nil {
return 0, errors.Errorf("cannot find shard %d in shard state", h.ShardID())
}
return (len(subComm.Slots))*2/3 + 1, nil
}
// Similiar to VerifyHeader, which is only for verifying the block headers of one's own chain, this verification
// is used for verifying "incoming" block header against commit signature and bitmap sent from the other chain cross-shard via libp2p.
// i.e. this header verification api is more flexible since the caller specifies which commit signature and bitmap to use
// for verifying the block header, which is necessary for cross-shard block header verification. Example of such is cross-shard transaction.
func (e *engineImpl) VerifyHeaderWithSignature(chain engine.ChainReader, header *block.Header, commitSig []byte, commitBitmap []byte, reCalculate bool) error {
if chain.Config().IsStaking(header.Epoch()) {
// Never recalculate after staking is enabled
reCalculate = false
}
publicKeys, err := GetPublicKeys(chain, header, reCalculate)
if err != nil {
return ctxerror.New("[VerifyHeaderWithSignature] Cannot get publickeys for block header").WithCause(err)
}
payload := append(commitSig[:], commitBitmap[:]...)
aggSig, mask, err := ReadSignatureBitmapByPublicKeys(payload, publicKeys)
if err != nil {
return ctxerror.New("[VerifyHeaderWithSignature] Unable to deserialize the commitSignature and commitBitmap in Block Header").WithCause(err)
}
hash := header.Hash()
if e := header.Epoch(); chain.Config().IsStaking(e) {
slotList, err := chain.ReadShardState(e)
if err != nil {
return errors.Wrapf(err, "cannot read shard state")
}
subComm, err := slotList.FindCommitteeByID(header.ShardID())
if err != nil {
return err
}
// TODO(audit): reuse a singleton decider and not recreate it for every single block
d := quorum.NewDecider(quorum.SuperMajorityStake, subComm.ShardID)
d.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) {
return nil, nil
})
if _, err := d.SetVoters(subComm, e); err != nil {
return err
}
if !d.IsQuorumAchievedByMask(mask) {
return ctxerror.New(
"[VerifySeal] Not enough voting power in commitSignature from Block Header",
)
}
} else {
quorumCount, err := QuorumForBlock(chain, header, reCalculate)
if err != nil {
return errors.Wrapf(err,
"cannot calculate quorum for block %s", header.Number())
}
if count := utils.CountOneBits(mask.Bitmap); count < int64(quorumCount) {
return ctxerror.New("[VerifyHeaderWithSignature] Not enough signature in commitSignature from Block Header",
"need", quorumCount, "got", count)
}
}
// TODO(audit): verify signature on hash+blockNum+viewID (add a hard fork)
blockNumHash := make([]byte, 8)
binary.LittleEndian.PutUint64(blockNumHash, header.Number().Uint64())
commitPayload := append(blockNumHash, hash[:]...)
if !aggSig.VerifyHash(mask.AggregatePublic, commitPayload) {
return ctxerror.New("[VerifySeal] Unable to verify aggregated signature for block", "blockNum", header.Number().Uint64()-1, "blockHash", hash)
}
return nil
}
// GetPublicKeys finds the public keys of the committee that signed the block header
func GetPublicKeys(
chain engine.ChainReader, header *block.Header, reCalculate bool,
) ([]*bls.PublicKey, error) {
shardState := new(shard.State)
var err error
if reCalculate {
shardState, _ = committee.WithStakingEnabled.Compute(header.Epoch(), chain)
} else {
shardState, err = chain.ReadShardState(header.Epoch())
if err != nil {
return nil, ctxerror.New("failed to read shard state of epoch",
"epoch", header.Epoch().Uint64()).WithCause(err)
}
}
subCommittee, err := shardState.FindCommitteeByID(header.ShardID())
if err != nil {
return nil, ctxerror.New("cannot find shard in the shard state",
"blockNumber", header.Number(),
"shardID", header.ShardID(),
)
}
return subCommittee.BLSPublicKeys()
}