diff --git a/common/types.go b/common/types.go index 0b94fb2c25..a8f5ca6a68 100644 --- a/common/types.go +++ b/common/types.go @@ -35,8 +35,9 @@ const ( ) var ( - hashT = reflect.TypeOf(Hash{}) - addressT = reflect.TypeOf(Address{}) + 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) { diff --git a/core/error.go b/core/error.go index 410eca1e1e..e0bc7e5204 100644 --- a/core/error.go +++ b/core/error.go @@ -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") ) diff --git a/core/state_processor.go b/core/state_processor.go index 4dc58b9dea..c424ca2501 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -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. diff --git a/core/tx_pool.go b/core/tx_pool.go index 1c9516b1b7..cea47348c4 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -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,19 +593,28 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if err != nil { return ErrInvalidSender } - // 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 { - return ErrUnderpriced - } - // Ensure the transaction adheres to nonce ordering - if pool.currentState.GetNonce(from) > tx.Nonce() { - return ErrNonceTooLow - } - // Transactor should have enough funds to cover the costs - // cost == V + GP * GL - if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { - return ErrInsufficientFunds + + // 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 { + return ErrUnderpriced + } + // Ensure the transaction adheres to nonce ordering + if pool.currentState.GetNonce(from) > tx.Nonce() { + return ErrNonceTooLow + } + // Transactor should have enough funds to cover the costs + // cost == V + GP * GL + if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { + return ErrInsufficientFunds + } } intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead) if err != nil { @@ -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 +} diff --git a/core/types/transaction.go b/core/types/transaction.go index c1cb7a043a..35ce765627 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -17,6 +17,7 @@ package types import ( + "bytes" "container/heap" "errors" "io" @@ -32,7 +33,8 @@ import ( //go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go var ( - ErrInvalidSig = errors.New("invalid transaction v, r, s values") + 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) { diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index dfc84fdac7..73a68747d3 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -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 diff --git a/miner/worker.go b/miner/worker.go index 640e9032e7..333e7f9550 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -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) - txs[acc] = append(txs[acc], 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 diff --git a/params/config.go b/params/config.go index dc02c7ca37..298eefda7e 100644 --- a/params/config.go +++ b/params/config.go @@ -41,7 +41,20 @@ var ( EIP158Block: big.NewInt(2675000), ByzantiumBlock: big.NewInt(4370000), ConstantinopleBlock: nil, - Ethash: new(EthashConfig), + 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), } // TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network. @@ -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)) ) @@ -115,14 +130,31 @@ type ChainConfig struct { EIP155Block *big.Int `json:"eip155Block,omitempty"` // EIP155 HF block EIP158Block *big.Int `json:"eip158Block,omitempty"` // EIP158 HF block - 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) + 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.