Last active
March 31, 2024 02:41
-
-
Save resilience-me/0f3831992b027787fe26b0244b51fb8e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package clique | |
import ( | |
"bytes" | |
"encoding/binary" | |
"errors" | |
"fmt" | |
"math/big" | |
"sync" | |
"time" | |
"github.com/ethereum/go-ethereum/accounts" | |
"github.com/ethereum/go-ethereum/common" | |
"github.com/ethereum/go-ethereum/consensus" | |
"github.com/ethereum/go-ethereum/core/state" | |
"github.com/ethereum/go-ethereum/core/types" | |
"github.com/ethereum/go-ethereum/crypto" | |
"github.com/ethereum/go-ethereum/ethdb" | |
"github.com/ethereum/go-ethereum/log" | |
"github.com/ethereum/go-ethereum/params/types/ctypes" | |
"github.com/ethereum/go-ethereum/params/vars" | |
"github.com/ethereum/go-ethereum/rlp" | |
"github.com/ethereum/go-ethereum/rpc" | |
"github.com/ethereum/go-ethereum/trie" | |
"golang.org/x/crypto/sha3" | |
) | |
var ( | |
errInvalidTimestamp = errors.New("invalid timestamp") | |
errMissingSignature = errors.New("extra-data does not contain the signature") | |
errNoWithdrawalsAllowed = errors.New("panarchy does not support withdrawals") | |
errFailedStatePassToSeal = errors.New("Failed to pass state object to Seal. Go-Ethereum consensus engine interface is not perfect fit for Panarchy engine, so we provide the state object to Seal in an unconventional way. We add this error check if it were to fail for some reason.") | |
errHeaderOlderThanCheckpoint = errors.New("Header is older than checkpoint and will therefore be rejected") | |
errValidatorNotElected = errors.New("Validator is not elected to sign the block") | |
) | |
const ( | |
genesis uint64 = 1709960400 | |
period uint64 = 4*7*24*60*60 | |
) | |
var ( | |
slotZero = make([]byte, 32) | |
slotOne = []byte{31: 1} | |
bitpeopleContract = common.Address{19: 13} | |
electionContract = common.Address{19: 14} | |
coinbaseContract = common.Address{19: 15} | |
) | |
type cachedState struct { | |
state *state.StateDB | |
identifier *types.Block | |
} | |
type Panarchy struct { | |
config *ctypes.CliqueConfig | |
checkpoint uint64 | |
lock sync.RWMutex | |
signer common.Address | |
signFn SignerFn | |
cachedState cachedState | |
} | |
type Clique struct { | |
Panarchy | |
} | |
type SignerFn func(signer accounts.Account, mimeType string, message []byte) ([]byte, error) | |
func New(config *ctypes.CliqueConfig, db ethdb.Database) *Clique { | |
p := Panarchy{ | |
config: config, | |
} | |
return &Clique{p} | |
} | |
func (p *Panarchy) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error { | |
return p.verifyHeader(chain, header, nil) | |
} | |
func (p *Panarchy) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { | |
abort := make(chan struct{}) | |
results := make(chan error, len(headers)) | |
go func() { | |
for i, header := range headers { | |
err := p.verifyHeader(chain, header, headers[:i]) | |
select { | |
case <-abort: | |
return | |
case results <- err: | |
} | |
} | |
}() | |
return abort, results | |
} | |
func (p *Panarchy) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { | |
number := header.Number.Uint64() | |
if number == 0 { | |
return nil | |
} | |
var parent *types.Header | |
if len(parents) > 0 { | |
parent = parents[len(parents)-1] | |
} else { | |
parent = chain.GetHeader(header.ParentHash, number-1) | |
} | |
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { | |
return consensus.ErrUnknownAncestor | |
} | |
if err := p.processCheckpoint(header.Time); err != nil { | |
return err | |
} | |
if parent.Time+p.config.Period != header.Time { | |
return errInvalidTimestamp | |
} | |
skipped := header.Nonce.Uint64() - parent.Nonce.Uint64() | |
if header.Time + skipped*p.config.Period > uint64(time.Now().Unix()) { | |
return consensus.ErrFutureBlock | |
} | |
if header.GasLimit > vars.MaxGasLimit { | |
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, vars.MaxGasLimit) | |
} | |
if header.GasUsed > header.GasLimit { | |
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) | |
} | |
return nil | |
} | |
func (p *Panarchy) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { | |
return nil | |
} | |
func schedule(timestamp uint64) uint64 { | |
return (timestamp - genesis) / period | |
} | |
func (p *Panarchy) updateCheckpoint (timestamp uint64) { | |
currentSchedule := schedule(timestamp) | |
if currentSchedule == 0 { | |
return | |
} | |
checkpoint := currentSchedule-1 | |
if p.checkpoint < checkpoint { | |
p.checkpoint = checkpoint | |
} | |
} | |
func (p *Panarchy) processCheckpoint(timestamp uint64) error { | |
p.updateCheckpoint(timestamp) | |
if schedule(timestamp) < p.checkpoint { | |
return errHeaderOlderThanCheckpoint | |
} | |
return nil | |
} | |
func (p *Panarchy) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { | |
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) | |
if parent == nil { | |
return consensus.ErrUnknownAncestor | |
} | |
header.Time = parent.Time + p.config.Period | |
if header.Number.Uint64() > 1 { | |
grandParent := chain.GetHeader(parent.ParentHash, parent.Number.Uint64()-1) | |
skipped := parent.Nonce.Uint64() - grandParent.Nonce.Uint64() | |
header.Time += skipped*p.config.Period | |
} | |
p.updateCheckpoint(header.Time) | |
header.Difficulty = p.CalcDifficulty(chain, header.Time, parent) | |
return nil | |
} | |
func (p *Panarchy) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { | |
if err := p.blockValidator(chain, header, state); err != nil { | |
header.GasUsed=0 | |
log.Error("Error in Finalize. Will now force ValidateState to fail by altering block.Header.GasUsed") | |
} | |
} | |
func (p *Panarchy) blockValidator(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB) error { | |
signer, err := p.Author(header) | |
if err != nil { | |
return err | |
} | |
skipped := header.Nonce.Uint64() | |
if signer != p.getValidator(header, new(big.Int).SetUint64(skipped), state) { | |
return errValidatorNotElected | |
} | |
voterRewardCoinbase := p.rewardVoters(signer, header.Time, state) | |
blockReward := ctypes.EthashBlockReward(chain.Config(), header.Number) | |
state.AddBalance(voterRewardCoinbase, new(big.Int).Set(blockReward)) | |
return nil | |
} | |
func (p *Panarchy) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) { | |
if len(withdrawals) > 0 { | |
return nil, errNoWithdrawalsAllowed | |
} | |
voterRewardCoinbase := p.rewardVoters(p.signer, header.Time, state) | |
blockReward := ctypes.EthashBlockReward(chain.Config(), header.Number) | |
state.AddBalance(voterRewardCoinbase, new(big.Int).Set(blockReward)) | |
header.Root = state.IntermediateRoot(true) | |
block := types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) | |
p.cachedState = cachedState { | |
state: state, | |
identifier: block, | |
} | |
return block, nil | |
} | |
func (p *Panarchy) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { | |
cachedState := p.cachedState | |
if cachedState.identifier != block { | |
return errFailedStatePassToSeal | |
} | |
go func() { | |
p.lock.RLock() | |
signer, signFn := p.signer, p.signFn | |
p.lock.RUnlock() | |
header := block.Header() | |
delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) | |
parentHeader := chain.GetHeaderByHash(header.ParentHash) | |
var i uint64 | |
nonce := parentHeader.Nonce.Uint64() | |
loop: | |
for { | |
select { | |
case <-stop: | |
return | |
case <-time.After(delay): | |
validator := p.getValidator(parentHeader, new(big.Int).SetUint64(nonce+i), cachedState.state); | |
if validator == signer { | |
break loop | |
} | |
i++ | |
delay = time.Duration(p.config.Period) * time.Second | |
} | |
} | |
nonce +=i | |
header.Nonce = types.EncodeNonce(nonce) | |
headerRlp := new(bytes.Buffer) | |
if err := rlp.Encode(headerRlp, header); err != nil { | |
log.Error("failed to RLP encode header during Seal method: %v", err) | |
return | |
} | |
sig, err := signFn(accounts.Account{Address: signer}, "", headerRlp.Bytes()) | |
if err != nil { | |
log.Error("failed to sign the header for account %s: %v", signer.Hex(), err) | |
return | |
} | |
header.Extra = sig | |
select { | |
case results <- block.WithSeal(header): | |
default: | |
log.Warn("Sealing result is not read by miner") | |
} | |
}() | |
return nil | |
} | |
func (p *Panarchy) SealHash(header *types.Header) (hash common.Hash) { | |
sealHeader := *header | |
sealHeader.Nonce = types.BlockNonce{} | |
sealHeader.Extra = nil | |
return SealHash(&sealHeader) | |
} | |
func SealHash(sealHeader *types.Header) (hash common.Hash) { | |
hasher := sha3.NewLegacyKeccak256() | |
if err := rlp.Encode(hasher, sealHeader); err != nil { | |
panic("can't encode: " + err.Error()) | |
} | |
hasher.(crypto.KeccakState).Read(hash[:]) | |
return | |
} | |
func (p *Panarchy) Author(header *types.Header) (common.Address, error) { | |
if len(header.Extra) != 64 { | |
return common.Address{}, errMissingSignature | |
} | |
signature := header.Extra | |
sealHeader := *header | |
sealHeader.Extra = nil | |
pubkey, err := crypto.Ecrecover(SealHash(&sealHeader).Bytes(), signature) | |
if err != nil { | |
return common.Address{}, err | |
} | |
var signer common.Address | |
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) | |
return signer, nil | |
} | |
func (p *Panarchy) getValidator(header *types.Header, skipped *big.Int, state *state.StateDB) common.Address { | |
scheduleToByte := schedule(header.Time) | |
if scheduleToByte != 0 { scheduleToByte-- } | |
index := make([]byte, 32) | |
binary.BigEndian.PutUint64(index, scheduleToByte) | |
seedKey := crypto.Keccak256Hash(append(index, slotZero...)) | |
seed := state.GetState(bitpeopleContract, seedKey) | |
electionSlot := crypto.Keccak256(append(index, slotOne...)) | |
electionLengthValue := state.GetState(electionContract, common.BytesToHash(electionSlot)) | |
electionLength := new(big.Int).SetBytes(electionLengthValue.Bytes()) | |
validatorHeight := new(big.Int).Add(header.Number, skipped).Bytes() | |
randomVoter := new(big.Int).SetBytes(crypto.Keccak256(seed.Bytes(), common.LeftPadBytes(validatorHeight, 32))) | |
randomVoter.Mod(randomVoter, electionLength) | |
electionArray := new(big.Int).SetBytes(crypto.Keccak256(electionSlot)) | |
electionArray.Add(electionArray, randomVoter) | |
validator := state.GetState(electionContract, common.BytesToHash(electionArray.Bytes())) | |
return common.BytesToAddress(validator.Bytes()) | |
} | |
func (p *Panarchy) rewardVoters(validator common.Address, timestamp uint64, state *state.StateDB) common.Address { | |
validatorPadded := common.LeftPadBytes(validator.Bytes(), 32) | |
voterRewardCoinbase := crypto.Keccak256(append(validatorPadded, slotZero...)) | |
validSinceField := new(big.Int).SetBytes(voterRewardCoinbase) | |
validSinceField.Add(validSinceField, common.Big1) | |
validSinceFieldKey := common.BytesToHash(validSinceField.Bytes()) | |
validSinceValue := state.GetState(coinbaseContract, validSinceFieldKey) | |
validSince := binary.BigEndian.Uint64(validSinceValue[common.HashLength-8:]) | |
if validSince == 0 { | |
return validator | |
} | |
if schedule(timestamp) < validSince { | |
getPrevious := crypto.Keccak256Hash(append(validatorPadded, slotOne...)) | |
previous := state.GetState(coinbaseContract, getPrevious) | |
if (previous == common.Hash{}) { | |
return validator | |
} | |
return common.BytesToAddress(previous.Bytes()[0:20]) | |
} | |
voterRewardCoinbaseValue := state.GetState(coinbaseContract, common.BytesToHash(voterRewardCoinbase)) | |
return common.BytesToAddress(voterRewardCoinbaseValue.Bytes()[0:20]) | |
} | |
func (p *Panarchy) Authorize(signer common.Address, signFn SignerFn) { | |
p.lock.Lock() | |
defer p.lock.Unlock() | |
p.signer = signer | |
p.signFn = signFn | |
} | |
func (p *Panarchy) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { | |
return new(big.Int).Set(common.Big1) | |
} | |
func (p *Panarchy) APIs(chain consensus.ChainHeaderReader) []rpc.API { | |
return []rpc.API{} | |
} | |
func (p *Panarchy) Close() error { | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment