parent
ff354a4840
commit
83f260542b
@ -0,0 +1,187 @@ |
||||
package chain |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/harmony-one/bls/ffi/go/bls" |
||||
"github.com/pkg/errors" |
||||
"golang.org/x/crypto/sha3" |
||||
|
||||
"github.com/harmony-one/harmony/consensus/engine" |
||||
"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" |
||||
) |
||||
|
||||
type engineImpl struct{} |
||||
|
||||
// Engine is an algorithm-agnostic consensus engine.
|
||||
var Engine = &engineImpl{} |
||||
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
func (e *engineImpl) SealHash(header *types.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 *types.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 *types.Header) error { |
||||
// TODO: implement prepare method
|
||||
return nil |
||||
} |
||||
|
||||
// VerifyHeader checks whether a header conforms to the consensus rules of the bft engine.
|
||||
func (e *engineImpl) VerifyHeader(chain engine.ChainReader, header *types.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 []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { |
||||
abort, results := make(chan struct{}), make(chan error, len(headers)) |
||||
for i := 0; i < len(headers); i++ { |
||||
results <- nil |
||||
} |
||||
return abort, results |
||||
} |
||||
|
||||
// retrievePublicKeysFromLastBlock finds the public keys of last block's committee
|
||||
func retrievePublicKeysFromLastBlock(bc engine.ChainReader, header *types.Header) ([]*bls.PublicKey, error) { |
||||
parentHeader := bc.GetHeaderByHash(header.ParentHash) |
||||
if parentHeader == nil { |
||||
return nil, ctxerror.New("cannot find parent block header in DB", |
||||
"parentHash", header.ParentHash) |
||||
} |
||||
parentShardState, err := bc.ReadShardState(parentHeader.Epoch) |
||||
if err != nil { |
||||
return nil, ctxerror.New("cannot read shard state", |
||||
"epoch", parentHeader.Epoch, |
||||
).WithCause(err) |
||||
} |
||||
parentCommittee := parentShardState.FindCommitteeByID(parentHeader.ShardID) |
||||
if parentCommittee == nil { |
||||
return nil, ctxerror.New("cannot find shard in the shard state", |
||||
"parentBlockNumber", parentHeader.Number, |
||||
"shardID", parentHeader.ShardID, |
||||
) |
||||
} |
||||
var committerKeys []*bls.PublicKey |
||||
for _, member := range parentCommittee.NodeList { |
||||
committerKey := new(bls.PublicKey) |
||||
err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey) |
||||
if err != nil { |
||||
return nil, ctxerror.New("cannot convert BLS public key", |
||||
"blsPublicKey", member.BlsPublicKey).WithCause(err) |
||||
} |
||||
committerKeys = append(committerKeys, committerKey) |
||||
} |
||||
return committerKeys, nil |
||||
} |
||||
|
||||
// VerifySeal implements Engine, checking whether the given block satisfies
|
||||
// the PoS difficulty requirements, i.e. >= 2f+1 valid signatures from the committee
|
||||
func (e *engineImpl) VerifySeal(chain engine.ChainReader, header *types.Header) error { |
||||
if chain.CurrentHeader().Number.Uint64() <= uint64(1) { |
||||
return nil |
||||
} |
||||
publicKeys, err := retrievePublicKeysFromLastBlock(chain, header) |
||||
if err != nil { |
||||
return ctxerror.New("[VerifySeal] Cannot retrieve publickeys from last block").WithCause(err) |
||||
} |
||||
payload := append(header.LastCommitSignature[:], 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) |
||||
} |
||||
parentHeader := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) |
||||
parentQuorum, err := QuorumForBlock(chain, parentHeader) |
||||
if err != nil { |
||||
return errors.Wrapf(err, |
||||
"cannot calculate quorum for block %s", header.Number) |
||||
} |
||||
if count := utils.CountOneBits(mask.Bitmap); count < parentQuorum { |
||||
return ctxerror.New("[VerifySeal] Not enough signature in LastCommitSignature from Block Header", |
||||
"need", parentQuorum, "got", count) |
||||
} |
||||
|
||||
blockNumHash := make([]byte, 8) |
||||
binary.LittleEndian.PutUint64(blockNumHash, header.Number.Uint64()-1) |
||||
lastCommitPayload := append(blockNumHash, header.ParentHash[:]...) |
||||
|
||||
if !aggSig.VerifyHash(mask.AggregatePublic, lastCommitPayload) { |
||||
return ctxerror.New("[VerifySeal] Unable to verify aggregated signature from last block", "lastBlockNum", header.Number.Uint64()-1, "lastBlockHash", header.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 *types.Header, state *state.DB, txs []*types.Transaction, receipts []*types.Receipt, outcxs []*types.CXReceipt, incxs []*types.CXReceiptsProof) (*types.Block, error) { |
||||
// Accumulate any block and uncle rewards and commit the final state root
|
||||
// Header seems complete, assemble into a block and return
|
||||
if err := AccumulateRewards(chain, state, header); err != nil { |
||||
return nil, ctxerror.New("cannot pay block reward").WithCause(err) |
||||
} |
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) |
||||
return types.NewBlock(header, txs, receipts, outcxs, incxs), nil |
||||
} |
||||
|
||||
// QuorumForBlock returns the quorum for the given block header.
|
||||
func QuorumForBlock( |
||||
chain engine.ChainReader, h *types.Header, |
||||
) (quorum int, err error) { |
||||
ss, err := chain.ReadShardState(h.Epoch) |
||||
if err != nil { |
||||
return 0, errors.Wrapf(err, |
||||
"cannot read shard state for epoch %s", h.Epoch) |
||||
} |
||||
c := ss.FindCommitteeByID(h.ShardID) |
||||
if c == nil { |
||||
return 0, errors.Errorf( |
||||
"cannot find shard %d in shard state", h.ShardID) |
||||
} |
||||
return (len(c.NodeList))*2/3 + 1, nil |
||||
} |
@ -0,0 +1,105 @@ |
||||
package chain |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/harmony-one/bls/ffi/go/bls" |
||||
|
||||
"github.com/harmony-one/harmony/common/denominations" |
||||
"github.com/harmony-one/harmony/consensus/engine" |
||||
"github.com/harmony-one/harmony/core/state" |
||||
"github.com/harmony-one/harmony/core/types" |
||||
bls2 "github.com/harmony-one/harmony/crypto/bls" |
||||
common2 "github.com/harmony-one/harmony/internal/common" |
||||
"github.com/harmony-one/harmony/internal/ctxerror" |
||||
"github.com/harmony-one/harmony/internal/utils" |
||||
) |
||||
|
||||
// BlockReward is the block reward, to be split evenly among block signers.
|
||||
var BlockReward = new(big.Int).Mul(big.NewInt(24), big.NewInt(denominations.One)) |
||||
|
||||
// AccumulateRewards credits the coinbase of the given block with the mining
|
||||
// reward. The total reward consists of the static block reward and rewards for
|
||||
// included uncles. The coinbase of each uncle block is also rewarded.
|
||||
func AccumulateRewards( |
||||
bc engine.ChainReader, state *state.DB, header *types.Header, |
||||
) error { |
||||
blockNum := header.Number.Uint64() |
||||
if blockNum == 0 { |
||||
// Epoch block has no parent to reward.
|
||||
return nil |
||||
} |
||||
// TODO ek – retrieving by parent number (blockNum - 1) doesn't work,
|
||||
// while it is okay with hash. Sounds like DB inconsistency.
|
||||
// Figure out why.
|
||||
parentHeader := bc.GetHeaderByHash(header.ParentHash) |
||||
if parentHeader == nil { |
||||
return ctxerror.New("cannot find parent block header in DB", |
||||
"parentHash", header.ParentHash) |
||||
} |
||||
if parentHeader.Number.Cmp(common.Big0) == 0 { |
||||
// Parent is an epoch block,
|
||||
// which is not signed in the usual manner therefore rewards nothing.
|
||||
return nil |
||||
} |
||||
parentShardState, err := bc.ReadShardState(parentHeader.Epoch) |
||||
if err != nil { |
||||
return ctxerror.New("cannot read shard state", |
||||
"epoch", parentHeader.Epoch, |
||||
).WithCause(err) |
||||
} |
||||
parentCommittee := parentShardState.FindCommitteeByID(parentHeader.ShardID) |
||||
if parentCommittee == nil { |
||||
return ctxerror.New("cannot find shard in the shard state", |
||||
"parentBlockNumber", parentHeader.Number, |
||||
"shardID", parentHeader.ShardID, |
||||
) |
||||
} |
||||
var committerKeys []*bls.PublicKey |
||||
for _, member := range parentCommittee.NodeList { |
||||
committerKey := new(bls.PublicKey) |
||||
err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey) |
||||
if err != nil { |
||||
return ctxerror.New("cannot convert BLS public key", |
||||
"blsPublicKey", member.BlsPublicKey).WithCause(err) |
||||
} |
||||
committerKeys = append(committerKeys, committerKey) |
||||
} |
||||
mask, err := bls2.NewMask(committerKeys, nil) |
||||
if err != nil { |
||||
return ctxerror.New("cannot create group sig mask").WithCause(err) |
||||
} |
||||
if err := mask.SetMask(header.LastCommitBitmap); err != nil { |
||||
return ctxerror.New("cannot set group sig mask bits").WithCause(err) |
||||
} |
||||
totalAmount := big.NewInt(0) |
||||
var accounts []common.Address |
||||
signers := []string{} |
||||
for idx, member := range parentCommittee.NodeList { |
||||
if signed, err := mask.IndexEnabled(idx); err != nil { |
||||
return ctxerror.New("cannot check for committer bit", |
||||
"committerIndex", idx, |
||||
).WithCause(err) |
||||
} else if signed { |
||||
accounts = append(accounts, member.EcdsaAddress) |
||||
} |
||||
} |
||||
numAccounts := big.NewInt(int64(len(accounts))) |
||||
last := new(big.Int) |
||||
for i, account := range accounts { |
||||
cur := new(big.Int) |
||||
cur.Mul(BlockReward, big.NewInt(int64(i+1))).Div(cur, numAccounts) |
||||
diff := new(big.Int).Sub(cur, last) |
||||
signers = append(signers, common2.MustAddressToBech32(account)) |
||||
state.AddBalance(account, diff) |
||||
totalAmount = new(big.Int).Add(totalAmount, diff) |
||||
last = cur |
||||
} |
||||
header.Logger(utils.Logger()).Debug(). |
||||
Str("NumAccounts", numAccounts.String()). |
||||
Str("TotalAmount", totalAmount.String()). |
||||
Strs("Signers", signers). |
||||
Msg("[Block Reward] Successfully paid out block reward") |
||||
return nil |
||||
} |
@ -0,0 +1,41 @@ |
||||
package chain |
||||
|
||||
import ( |
||||
"errors" |
||||
|
||||
"github.com/harmony-one/bls/ffi/go/bls" |
||||
|
||||
bls2 "github.com/harmony-one/harmony/crypto/bls" |
||||
"github.com/harmony-one/harmony/internal/utils" |
||||
) |
||||
|
||||
// ReadSignatureBitmapByPublicKeys read the payload of signature and bitmap based on public keys
|
||||
func ReadSignatureBitmapByPublicKeys(recvPayload []byte, publicKeys []*bls.PublicKey) (*bls.Sign, *bls2.Mask, error) { |
||||
if len(recvPayload) < 96 { |
||||
return nil, nil, errors.New("payload not have enough length") |
||||
} |
||||
payload := append(recvPayload[:0:0], recvPayload...) |
||||
//#### Read payload data
|
||||
// 96 byte of multi-sig
|
||||
offset := 0 |
||||
multiSig := payload[offset : offset+96] |
||||
offset += 96 |
||||
// bitmap
|
||||
bitmap := payload[offset:] |
||||
//#### END Read payload data
|
||||
|
||||
aggSig := bls.Sign{} |
||||
err := aggSig.Deserialize(multiSig) |
||||
if err != nil { |
||||
return nil, nil, errors.New("unable to deserialize multi-signature from payload") |
||||
} |
||||
mask, err := bls2.NewMask(publicKeys, nil) |
||||
if err != nil { |
||||
utils.Logger().Warn().Err(err).Msg("onNewView unable to setup mask for prepared message") |
||||
return nil, nil, errors.New("unable to setup mask from payload") |
||||
} |
||||
if err := mask.SetMask(bitmap); err != nil { |
||||
utils.Logger().Warn().Err(err).Msg("mask.SetMask failed") |
||||
} |
||||
return &aggSig, mask, nil |
||||
} |
Loading…
Reference in new issue