common, core, miner, params: process Casper votes

This commit is contained in:
kimmylin 2018-05-25 13:25:14 +08:00
parent d6ed2f67a8
commit 76d6c27515
8 changed files with 259 additions and 40 deletions

View file

@ -37,6 +37,7 @@ const (
var (
hashT = reflect.TypeOf(Hash{})
addressT = reflect.TypeOf(Address{})
NullSenderAddr = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff")
)
// Hash represents the 32 byte Keccak256 hash of arbitrary data.
@ -195,6 +196,11 @@ func (a Address) Format(s fmt.State, c rune) {
fmt.Fprintf(s, "%"+string(c), a[:])
}
// IsNullSender returns true if this address is NullSenderAddr
func (a Address) IsNullSender() bool {
return a == NullSenderAddr
}
// Sets the address to the value of b. If b is larger than len(a) it will panic
func (a *Address) SetBytes(b []byte) {
if len(b) > len(a) {

View file

@ -32,4 +32,7 @@ var (
// ErrNonceTooHigh is returned if the nonce of a transaction is higher than the
// next one expected based on the local chain.
ErrNonceTooHigh = errors.New("nonce too high")
// ErrFailedVote is returned if a vote failed in the Casper contract.
ErrFailedVote = errors.New("casper vote failed")
)

View file

@ -100,6 +100,14 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common
if err != nil {
return nil, 0, err
}
isVoteTx := msg.From().IsNullSender()
if !isVoteTx {
// Gas used for votes does not count towards accumulated gas used
*usedGas += gas
} else if failed {
return nil, 0, ErrFailedVote
}
// Update the state with pending changes
var root []byte
if config.IsByzantium(header.Number) {
@ -107,7 +115,6 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common
} else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
}
*usedGas += gas
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
// based on the eip phase, we're passing wether the root touch-delete accounts.

View file

@ -76,6 +76,12 @@ var (
// than some meaningful limit a user might use. This is not a consensus error
// making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data")
// ErrInvalidVote is returned if the vote is invalid per Casper contract.
ErrInvalidVote = errors.New("invalid vote")
// ErrTooManyVote is returned if the vote is invalid per Casper contract.
ErrTooManyVote = errors.New("too many vote")
)
var (
@ -99,6 +105,8 @@ var (
// General tx metrics
invalidTxCounter = metrics.NewRegisteredCounter("txpool/invalid", nil)
underpricedTxCounter = metrics.NewRegisteredCounter("txpool/underpriced", nil)
invalidVoteCounter = metrics.NewRegisteredCounter("txpool/invalidvote", nil)
overflowVoteCounter = metrics.NewRegisteredCounter("txpool/overflowvote", nil)
)
// TxStatus is the current status of a transaction as seen by the pool.
@ -205,6 +213,7 @@ type TxPool struct {
beats map[common.Address]time.Time // Last heartbeat from each known account
all *txLookup // All transactions to allow lookups
priced *txPricedList // All transactions sorted by price
votes *txLookup // Votes transactions to allow lookups
wg sync.WaitGroup // for shutdown sync
@ -222,17 +231,18 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
config: config,
chainconfig: chainconfig,
chain: chain,
signer: types.NewEIP155Signer(chainconfig.ChainId),
signer: types.NewEIP1011Signer(chainconfig),
pending: make(map[common.Address]*txList),
queue: make(map[common.Address]*txList),
beats: make(map[common.Address]time.Time),
all: newTxLookup(),
votes: newTxLookup(),
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
}
pool.locals = newAccountSet(pool.signer)
pool.priced = newTxPricedList(pool.all)
pool.reset(nil, chain.CurrentBlock().Header())
pool.reset(nil, chain.CurrentBlock().Header(), nil)
// If local transactions and journaling is enabled, load from disk
if !config.NoLocals && config.Journal != "" {
@ -286,7 +296,7 @@ func (pool *TxPool) loop() {
if pool.chainconfig.IsHomestead(ev.Block.Number()) {
pool.homestead = true
}
pool.reset(head.Header(), ev.Block.Header())
pool.reset(head.Header(), ev.Block.Header(), ev.Block.Transactions())
head = ev.Block
pool.mu.Unlock()
@ -343,14 +353,14 @@ func (pool *TxPool) lockedReset(oldHead, newHead *types.Header) {
pool.mu.Lock()
defer pool.mu.Unlock()
pool.reset(oldHead, newHead)
pool.reset(oldHead, newHead, nil)
}
// reset retrieves the current state of the blockchain and ensures the content
// of the transaction pool is valid with regard to the chain state.
func (pool *TxPool) reset(oldHead, newHead *types.Header) {
func (pool *TxPool) reset(oldHead, newHead *types.Header, txs types.Transactions) {
// If we're reorging an old state, reinject all dropped transactions
var reinject types.Transactions
var reinject, included types.Transactions
if oldHead != nil && oldHead.Hash() != newHead.ParentHash {
// If the reorg is too deep, avoid doing it (will happen during fast sync)
@ -361,7 +371,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
log.Debug("Skipping deep transaction reorg", "depth", depth)
} else {
// Reorg seems shallow enough to pull in all transactions into memory
var discarded, included types.Transactions
var discarded types.Transactions
var (
rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
@ -427,6 +437,13 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
// Check the queue and move transactions over to the pending if possible
// or remove those that have become invalid
pool.promoteExecutables(nil)
// De-dup vote transactions
if included != nil {
pool.dedupCasperVotes(included)
} else {
pool.dedupCasperVotes(txs)
}
}
// Stop terminates the transaction pool.
@ -533,6 +550,12 @@ func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
return pending, nil
}
// Votes returns all currently pooled vote transactions.
// The returned votes is a copy and can be freely modified by calling code.
func (pool *TxPool) Votes() types.Transactions {
return pool.votes.Flatten()
}
// local retrieves all currently known local transactions, groupped by origin
// account and sorted by nonce. The returned transaction set is a copy and can be
// freely modified by calling code.
@ -570,6 +593,14 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if err != nil {
return ErrInvalidSender
}
// Skip checking for gas price, nonce, and acct balance for null senders (Casper votes)
if from.IsNullSender() {
if err := pool.validateVote(tx); err != nil {
invalidVoteCounter.Inc(1)
return ErrInvalidVote
}
} else {
// Drop non-local transactions under our own minimal accepted gas price
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
@ -584,6 +615,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
}
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
if err != nil {
return err
@ -594,6 +626,13 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
return nil
}
// validateVote checks whether a vote transaction is valid according the
// Casper contract.
func (pool *TxPool) validateVote(tx *types.Transaction) error {
// Call Casper Contract validate_vote_signature and votable
return nil
}
// add validates a transaction and inserts it into the non-executable queue for
// later pending promotion and execution. If the transaction is a replacement for
// an already pending or queued one, it overwrites the previous and returns this
@ -615,8 +654,20 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
invalidTxCounter.Inc(1)
return false, err
}
from, _ := types.Sender(pool.signer, tx) // already validated
txCap := pool.config.GlobalSlots + pool.config.GlobalQueue
if from.IsNullSender() {
if uint64(pool.votes.Count()) >= txCap {
overflowVoteCounter.Inc(1)
return false, ErrTooManyVote
}
pool.enqueueCasperVote(hash, tx, local)
return false, nil
}
// If the transaction pool is full, discard underpriced transactions
if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
if uint64(pool.all.Count()) >= txCap {
// If the new transaction is underpriced, don't accept it
if !local && pool.priced.Underpriced(tx, pool.locals) {
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
@ -632,7 +683,6 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
}
}
// If the transaction is replacing an already pending one, do directly
from, _ := types.Sender(pool.signer, tx) // already validated
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
// Nonce already pending, check if required price bump is met
inserted, old := list.Add(tx, pool.config.PriceBump)
@ -672,6 +722,26 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
return replace, nil
}
// dedupCasperVotes removes txs from the vote collection.
func (pool *TxPool) dedupCasperVotes(txs types.Transactions) {
for _, tx := range txs {
pool.votes.Remove(tx.Hash())
}
}
// enqueueCasperVote inserts a new Casper vote into the vote collection.
//
// Note, this method assumes the pool lock is held!
func (pool *TxPool) enqueueCasperVote(hash common.Hash, tx *types.Transaction, local bool) {
pool.votes.Add(tx)
// Mark local addresses and journal local transactions
if local {
pool.locals.add(common.NullSenderAddr)
}
pool.journalTx(common.NullSenderAddr, tx)
log.Trace("Pooled new casper vote", "hash", hash, "local", local)
}
// enqueueTx inserts a new transaction into the non-executable transaction queue.
//
// Note, this method assumes the pool lock is held!
@ -1234,3 +1304,15 @@ func (t *txLookup) Remove(hash common.Hash) {
delete(t.all, hash)
}
// Flatten returns a flattened list of all transactions in the lookup.
func (t *txLookup) Flatten() types.Transactions {
t.lock.RLock()
defer t.lock.RUnlock()
var txs types.Transactions
for _, value := range t.all {
txs = append(txs, value)
}
return txs
}

View file

@ -17,6 +17,7 @@
package types
import (
"bytes"
"container/heap"
"errors"
"io"
@ -33,6 +34,7 @@ import (
var (
ErrInvalidSig = errors.New("invalid transaction v, r, s values")
CasperVoteFunctionByte = common.Hex2Bytes("e9dc0614")
)
// deriveSigner makes a *best* guess about which signer to use.
@ -237,6 +239,22 @@ func (tx *Transaction) AsMessage(s Signer) (Message, error) {
return msg, err
}
// IsCasperVote returns true if all the following conditions are true:
// - it's sending to Casper's contract address
// - it's calling contract method "vote"
// - its signature has R == S == 0, V = ChainId
// - value == nonce == gasprice == 0
func (tx *Transaction) IsCasperVote(chainId *big.Int, casperAddr common.Address) bool {
return *tx.To() == casperAddr &&
bytes.Equal(tx.Data()[0:4], CasperVoteFunctionByte) &&
tx.data.R.Cmp(common.Big0) == 0 &&
tx.data.S.Cmp(common.Big0) == 0 &&
tx.data.V.Cmp(chainId) == 0 &&
tx.Nonce() == 0 &&
tx.GasPrice().Cmp(common.Big0) == 0 &&
tx.Value().Cmp(common.Big0) == 0
}
// WithSignature returns a new transaction with the given signature.
// This signature needs to be formatted as described in the yellow paper (v+27).
func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) {

View file

@ -42,6 +42,8 @@ type sigCache struct {
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
var signer Signer
switch {
case config.IsCasper(blockNumber):
signer = NewEIP1011Signer(config)
case config.IsEIP155(blockNumber):
signer = NewEIP155Signer(config.ChainId)
case config.IsHomestead(blockNumber):
@ -102,6 +104,26 @@ type Signer interface {
Equal(Signer) bool
}
// EIP1011Signer is a hybrid signer that understands both CasperFFG and EIP155Signer
type EIP1011Signer struct {
EIP155Signer
ChainConfig *params.ChainConfig
}
func NewEIP1011Signer(config *params.ChainConfig) Signer {
return &EIP1011Signer{
EIP155Signer: NewEIP155Signer(config.ChainId),
ChainConfig: config,
}
}
func (s EIP1011Signer) Sender(tx *Transaction) (common.Address, error) {
if s.ChainConfig.Casper != nil && tx.IsCasperVote(s.ChainConfig.ChainId, s.ChainConfig.Casper.ContractAddr) {
return common.NullSenderAddr, nil
}
return s.EIP155Signer.Sender(tx)
}
// EIP155Transaction implements Signer using the EIP155 rules.
type EIP155Signer struct {
chainId, chainIdMul *big.Int

View file

@ -72,6 +72,7 @@ type Work struct {
uncles *set.Set // uncle set
tcount int // tx count in cycle
gasPool *core.GasPool // available gas used to pack transactions
vGasPool *core.GasPool // available gas used to pack Casper votes
Block *types.Block // the new block
@ -269,12 +270,17 @@ func (self *worker) update() {
if atomic.LoadInt32(&self.mining) == 0 {
self.currentMu.Lock()
txs := make(map[common.Address]types.Transactions)
var votes types.Transactions
for _, tx := range ev.Txs {
acc, _ := types.Sender(self.current.signer, tx)
if acc.IsNullSender() {
votes = append(votes, tx)
} else {
txs[acc] = append(txs[acc], tx)
}
}
txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs)
self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
self.current.commitTransactions(self.mux, txset, votes, self.chain, self.coinbase)
self.updateSnapshot()
self.currentMu.Unlock()
} else {
@ -370,7 +376,7 @@ func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error
}
work := &Work{
config: self.config,
signer: types.NewEIP155Signer(self.config.ChainId),
signer: types.NewEIP1011Signer(self.config),
state: state,
ancestors: set.New(),
family: set.New(),
@ -462,7 +468,8 @@ func (self *worker) commitNewWork() {
return
}
txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending)
work.commitTransactions(self.mux, txs, self.chain, self.coinbase)
votes := self.eth.TxPool().Votes()
work.commitTransactions(self.mux, txs, votes, self.chain, self.coinbase)
// compute uncles for the new block.
var (
@ -528,13 +535,11 @@ func (self *worker) updateSnapshot() {
self.snapshotState = self.current.state.Copy()
}
func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) {
func (env *Work) commitTxs(txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) []*types.Log {
if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
}
var coalescedLogs []*types.Log
for {
// If we don't have enough gas for any further transactions then we're done
if env.gasPool.Gas() < params.TxGas {
@ -592,6 +597,45 @@ func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsB
txs.Shift()
}
}
return coalescedLogs
}
func (env *Work) commitVotes(votes types.Transactions, bc *core.BlockChain, coinbase common.Address) []*types.Log {
if env.vGasPool == nil {
env.vGasPool = new(core.GasPool).AddGas(env.header.GasLimit)
}
var coalescedLogs []*types.Log
for _, vote := range votes {
hash := vote.Hash()
// If we don't have enough gas for any further transactions then we're done
if env.vGasPool.Gas() < params.TxGas {
log.Trace("Not enough gas for further votes", "gp", env.vGasPool)
break
}
// Start executing the transaction
env.state.Prepare(hash, common.Hash{}, env.tcount)
err, logs := env.commitTransaction(vote, bc, coinbase, env.vGasPool)
switch err {
case core.ErrGasLimitReached:
log.Trace("Vote gas limit exceeded for current block", "vote", hash)
case nil:
// Everything ok, collect the logs
coalescedLogs = append(coalescedLogs, logs...)
env.tcount++
default:
// Strange error, discard the transaction.
log.Debug("Transaction failed, vote skipped", "hash", hash, "err", err)
}
}
return coalescedLogs
}
func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, votes types.Transactions, bc *core.BlockChain, coinbase common.Address) {
coalescedLogs := env.commitTxs(txs, bc, coinbase)
if len(votes) > 0 {
coalescedLogs = append(coalescedLogs, env.commitVotes(votes, bc, coinbase)...)
}
if len(coalescedLogs) > 0 || env.tcount > 0 {
// make a copy, the state caches the logs and these logs get "upgraded" from pending to mined

View file

@ -41,6 +41,19 @@ var (
EIP158Block: big.NewInt(2675000),
ByzantiumBlock: big.NewInt(4370000),
ConstantinopleBlock: nil,
Casper: &CasperConfig{
ForkBlock: big.NewInt(6666666),
ContractAddr: common.HexToAddress("0x"),
ContractCode: common.Hex2Bytes("0x"),
Balance: new(big.Int).Mul(big.NewInt(1250000), big.NewInt(Ether)),
MsgHasherAddr: common.HexToAddress("0x"),
MsgHasherCode: common.Hex2Bytes("0x"),
PurityCheckerAddr: common.HexToAddress("0x"),
PurityCheckerCode: common.Hex2Bytes("0x"),
BlockReward: new(big.Int).Mul(big.NewInt(600), big.NewInt(Finney)),
RewardStpDwnBlkCnt: big.NewInt(550000),
NonRevertMinDeposit: new(big.Int).Mul(big.NewInt(100), big.NewInt(Ether)),
},
Ethash: new(EthashConfig),
}
@ -56,6 +69,7 @@ var (
EIP158Block: big.NewInt(10),
ByzantiumBlock: big.NewInt(1700000),
ConstantinopleBlock: nil,
Casper: nil,
Ethash: new(EthashConfig),
}
@ -71,6 +85,7 @@ var (
EIP158Block: big.NewInt(3),
ByzantiumBlock: big.NewInt(1035301),
ConstantinopleBlock: nil,
Casper: nil,
Clique: &CliqueConfig{
Period: 15,
Epoch: 30000,
@ -82,16 +97,16 @@ var (
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus.
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
TestRules = TestChainConfig.Rules(new(big.Int))
)
@ -117,12 +132,29 @@ type ChainConfig struct {
ByzantiumBlock *big.Int `json:"byzantiumBlock,omitempty"` // Byzantium switch block (nil = no fork, 0 = already on byzantium)
ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty"` // Constantinople switch block (nil = no fork, 0 = already activated)
Casper *CasperConfig `json:"casper,omitempty"`
// Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"`
Clique *CliqueConfig `json:"clique,omitempty"`
}
// CasperConfig is the configuration for applying Casper FFG rules on top of
// the consensus engine that produces blocks.
type CasperConfig struct {
ForkBlock *big.Int `json:"forkBlock,omitempty"`
ContractAddr common.Address `json:"contractAddr,omitempty"`
ContractCode []byte `json:"contractCode,omitempty"`
Balance *big.Int `json:"balance,omitempty"`
MsgHasherAddr common.Address `json:"msgHasherAddr,omitempty"`
MsgHasherCode []byte `json:"msgHasherCode,omitempty"`
PurityCheckerAddr common.Address `json:"purityCheckerAddr,omitempty"`
PurityCheckerCode []byte `json:"purityCheckerCode,omitempty"`
BlockReward *big.Int `json:"blockReward,omitempty"`
RewardStpDwnBlkCnt *big.Int `json:"rewardStpDwnBlkCnt,omitempty"`
NonRevertMinDeposit *big.Int `json:"nonRevertMinDeposit,omitempty"`
}
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
type EthashConfig struct{}
@ -153,7 +185,7 @@ func (c *ChainConfig) String() string {
default:
engine = "unknown"
}
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Engine: %v}",
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Casper: %v Engine: %v}",
c.ChainId,
c.HomesteadBlock,
c.DAOForkBlock,
@ -163,6 +195,7 @@ func (c *ChainConfig) String() string {
c.EIP158Block,
c.ByzantiumBlock,
c.ConstantinopleBlock,
c.Casper,
engine,
)
}
@ -197,6 +230,10 @@ func (c *ChainConfig) IsConstantinople(num *big.Int) bool {
return isForked(c.ConstantinopleBlock, num)
}
func (c *ChainConfig) IsCasper(num *big.Int) bool {
return c.Casper != nil && isForked(c.Casper.ForkBlock, num)
}
// GasTable returns the gas table corresponding to the current phase (homestead or homestead reprice).
//
// The returned GasTable's fields shouldn't, under any circumstances, be changed.