aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coreth.go270
-rw-r--r--eth/api.go533
-rw-r--r--eth/api_backend.go244
-rw-r--r--eth/api_tracer.go823
-rw-r--r--eth/backend.go566
-rw-r--r--eth/bloombits.go138
-rw-r--r--eth/config.go157
-rw-r--r--eth/enr_entry.go61
-rw-r--r--eth/gen_config.go221
-rw-r--r--eth/handler.go844
-rw-r--r--eth/metrics.go139
-rw-r--r--eth/peer.go546
-rw-r--r--eth/protocol.go196
-rw-r--r--eth/sync.go216
-rw-r--r--internal/ethapi/addrlock.go53
-rw-r--r--internal/ethapi/api.go1767
-rw-r--r--internal/ethapi/backend.go130
-rw-r--r--miner/miner.go51
-rw-r--r--miner/unconfirmed.go136
-rw-r--r--miner/worker.go1002
-rw-r--r--node/config.go545
-rw-r--r--node/defaults.go113
-rw-r--r--node/errors.go63
-rw-r--r--node/service.go131
24 files changed, 8945 insertions, 0 deletions
diff --git a/coreth.go b/coreth.go
new file mode 100644
index 0000000..c7b5e42
--- /dev/null
+++ b/coreth.go
@@ -0,0 +1,270 @@
+package coreth
+
+import (
+ "math/big"
+ "fmt"
+ "time"
+ "errors"
+ "runtime"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/common"
+ //"github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/Determinant/coreth/miner"
+ "github.com/Determinant/coreth/eth"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/Determinant/coreth/node"
+ "golang.org/x/crypto/sha3"
+)
+
+type Tx = types.Transaction
+type Block = types.Block
+type Hash = common.Hash
+
+type ETHChain struct {
+ mux *event.TypeMux
+ worker *miner.Worker
+ backend *eth.Ethereum
+}
+
+type DummyEngine struct {
+}
+
+var (
+ allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks
+)
+
+var (
+ errZeroBlockTime = errors.New("timestamp equals parent's")
+)
+
+// modified from consensus.go
+func (self *DummyEngine) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
+ // Ensure that the header's extra-data section is of a reasonable size
+ if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
+ return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
+ }
+ // Verify the header's timestamp
+ if !uncle {
+ if header.Time > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) {
+ return consensus.ErrFutureBlock
+ }
+ }
+ if header.Time <= parent.Time {
+ return errZeroBlockTime
+ }
+ // Verify that the gas limit is <= 2^63-1
+ cap := uint64(0x7fffffffffffffff)
+ if header.GasLimit > cap {
+ return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
+ }
+ // Verify that the gasUsed is <= gasLimit
+ if header.GasUsed > header.GasLimit {
+ return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
+ }
+
+ // Verify that the gas limit remains within allowed bounds
+ diff := int64(parent.GasLimit) - int64(header.GasLimit)
+ if diff < 0 {
+ diff *= -1
+ }
+ limit := parent.GasLimit / params.GasLimitBoundDivisor
+
+ if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
+ return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
+ }
+ // Verify that the block number is parent's +1
+ if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
+ return consensus.ErrInvalidNumber
+ }
+ // Verify the engine specific seal securing the block
+ if seal {
+ if err := self.VerifySeal(chain, header); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (self *DummyEngine) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error {
+ var parent *types.Header
+ if index == 0 {
+ parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1)
+ } else if headers[index-1].Hash() == headers[index].ParentHash {
+ parent = headers[index-1]
+ }
+ if parent == nil {
+ return consensus.ErrUnknownAncestor
+ }
+ if chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()) != nil {
+ return nil // known block
+ }
+ return self.verifyHeader(chain, headers[index], parent, false, seals[index])
+}
+
+func (self *DummyEngine) Author(header *types.Header) (common.Address, error) {
+ addr := common.Address{}
+ return addr, nil
+}
+
+func (self *DummyEngine) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
+ // Short circuit if the header is known, or it's parent not
+ number := header.Number.Uint64()
+ if chain.GetHeader(header.Hash(), number) != nil {
+ return nil
+ }
+ parent := chain.GetHeader(header.ParentHash, number-1)
+ if parent == nil {
+ return consensus.ErrUnknownAncestor
+ }
+ // Sanity checks passed, do a proper verification
+ return self.verifyHeader(chain, header, parent, false, seal)
+}
+
+func (self *DummyEngine) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
+ // Spawn as many workers as allowed threads
+ workers := runtime.GOMAXPROCS(0)
+ if len(headers) < workers {
+ workers = len(headers)
+ }
+
+ // Create a task channel and spawn the verifiers
+ var (
+ inputs = make(chan int)
+ done = make(chan int, workers)
+ errors = make([]error, len(headers))
+ abort = make(chan struct{})
+ )
+ for i := 0; i < workers; i++ {
+ go func() {
+ for index := range inputs {
+ errors[index] = self.verifyHeaderWorker(chain, headers, seals, index)
+ done <- index
+ }
+ }()
+ }
+
+ errorsOut := make(chan error, len(headers))
+ go func() {
+ defer close(inputs)
+ var (
+ in, out = 0, 0
+ checked = make([]bool, len(headers))
+ inputs = inputs
+ )
+ for {
+ select {
+ case inputs <- in:
+ if in++; in == len(headers) {
+ // Reached end of headers. Stop sending to workers.
+ inputs = nil
+ }
+ case index := <-done:
+ for checked[index] = true; checked[out]; out++ {
+ errorsOut <- errors[out]
+ if out == len(headers)-1 {
+ return
+ }
+ }
+ case <-abort:
+ return
+ }
+ }
+ }()
+ return abort, errorsOut
+}
+
+func (self *DummyEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
+ return nil
+}
+
+func (self *DummyEngine) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
+ return nil
+}
+
+func (self *DummyEngine) Prepare(chain consensus.ChainReader, header *types.Header) error {
+ header.Difficulty = new(big.Int).SetUint64(0)
+ return nil
+}
+
+func (self *DummyEngine) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
+ uncles []*types.Header) {
+ // commit the final state root
+ header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
+}
+
+func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
+ uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
+ // commit the final state root
+ header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
+
+ // Header seems complete, assemble into a block and return
+ return types.NewBlock(header, txs, uncles, receipts), nil
+}
+
+func (self *DummyEngine) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
+ return nil
+}
+
+func (self *DummyEngine) SealHash(header *types.Header) (hash common.Hash) {
+ hasher := sha3.NewLegacyKeccak256()
+
+ rlp.Encode(hasher, []interface{}{
+ header.ParentHash,
+ header.UncleHash,
+ header.Coinbase,
+ header.Root,
+ header.TxHash,
+ header.ReceiptHash,
+ header.Bloom,
+ header.Difficulty,
+ header.Number,
+ header.GasLimit,
+ header.GasUsed,
+ header.Time,
+ header.Extra,
+ })
+ hasher.Sum(hash[:0])
+ return hash
+}
+
+func (self *DummyEngine) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
+ return new(big.Int).SetUint64(0)
+}
+
+func (self *DummyEngine) APIs(chain consensus.ChainReader) []rpc.API {
+ return nil
+}
+
+func (self *DummyEngine) Close() error {
+ return nil
+}
+
+func isLocalBlock(block *types.Block) bool {
+ return false
+}
+
+func NewETHChain(config *miner.Config, chainConfig *params.ChainConfig) *ETHChain {
+ if config == nil {
+ config = &eth.DefaultConfig.Miner
+ }
+ if chainConfig == nil {
+ chainConfig = params.MainnetChainConfig
+ }
+ mux := new(event.TypeMux)
+ ctx := node.NewServiceContext(mux)
+ backend, _ := eth.New(&ctx, &eth.DefaultConfig)
+ chain := &ETHChain {
+ mux: mux,
+ worker: miner.NewWorker(config, chainConfig, &DummyEngine{}, backend, mux, isLocalBlock),
+ }
+ return chain
+}
+
+func (self *ETHChain) Start() {
+ self.worker.Start()
+}
diff --git a/eth/api.go b/eth/api.go
new file mode 100644
index 0000000..34f41db
--- /dev/null
+++ b/eth/api.go
@@ -0,0 +1,533 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "compress/gzip"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ "os"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/Determinant/coreth/internal/ethapi"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/ethereum/go-ethereum/trie"
+)
+
+// PublicEthereumAPI provides an API to access Ethereum full node-related
+// information.
+type PublicEthereumAPI struct {
+ e *Ethereum
+}
+
+// NewPublicEthereumAPI creates a new Ethereum protocol API for full nodes.
+func NewPublicEthereumAPI(e *Ethereum) *PublicEthereumAPI {
+ return &PublicEthereumAPI{e}
+}
+
+// Etherbase is the address that mining rewards will be send to
+func (api *PublicEthereumAPI) Etherbase() (common.Address, error) {
+ return api.e.Etherbase()
+}
+
+// Coinbase is the address that mining rewards will be send to (alias for Etherbase)
+func (api *PublicEthereumAPI) Coinbase() (common.Address, error) {
+ return api.Etherbase()
+}
+
+// Hashrate returns the POW hashrate
+func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 {
+ return hexutil.Uint64(api.e.Miner().HashRate())
+}
+
+// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
+func (api *PublicEthereumAPI) ChainId() hexutil.Uint64 {
+ chainID := new(big.Int)
+ if config := api.e.blockchain.Config(); config.IsEIP155(api.e.blockchain.CurrentBlock().Number()) {
+ chainID = config.ChainID
+ }
+ return (hexutil.Uint64)(chainID.Uint64())
+}
+
+// PublicMinerAPI provides an API to control the miner.
+// It offers only methods that operate on data that pose no security risk when it is publicly accessible.
+type PublicMinerAPI struct {
+ e *Ethereum
+}
+
+// NewPublicMinerAPI create a new PublicMinerAPI instance.
+func NewPublicMinerAPI(e *Ethereum) *PublicMinerAPI {
+ return &PublicMinerAPI{e}
+}
+
+// Mining returns an indication if this node is currently mining.
+func (api *PublicMinerAPI) Mining() bool {
+ return api.e.IsMining()
+}
+
+// PrivateMinerAPI provides private RPC methods to control the miner.
+// These methods can be abused by external users and must be considered insecure for use by untrusted users.
+type PrivateMinerAPI struct {
+ e *Ethereum
+}
+
+// NewPrivateMinerAPI create a new RPC service which controls the miner of this node.
+func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI {
+ return &PrivateMinerAPI{e: e}
+}
+
+// Start starts the miner with the given number of threads. If threads is nil,
+// the number of workers started is equal to the number of logical CPUs that are
+// usable by this process. If mining is already running, this method adjust the
+// number of threads allowed to use and updates the minimum price required by the
+// transaction pool.
+func (api *PrivateMinerAPI) Start(threads *int) error {
+ if threads == nil {
+ return api.e.StartMining(runtime.NumCPU())
+ }
+ return api.e.StartMining(*threads)
+}
+
+// Stop terminates the miner, both at the consensus engine level as well as at
+// the block creation level.
+func (api *PrivateMinerAPI) Stop() {
+ api.e.StopMining()
+}
+
+// SetExtra sets the extra data string that is included when this miner mines a block.
+func (api *PrivateMinerAPI) SetExtra(extra string) (bool, error) {
+ if err := api.e.Miner().SetExtra([]byte(extra)); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+// SetGasPrice sets the minimum accepted gas price for the miner.
+func (api *PrivateMinerAPI) SetGasPrice(gasPrice hexutil.Big) bool {
+ api.e.lock.Lock()
+ api.e.gasPrice = (*big.Int)(&gasPrice)
+ api.e.lock.Unlock()
+
+ api.e.txPool.SetGasPrice((*big.Int)(&gasPrice))
+ return true
+}
+
+// SetEtherbase sets the etherbase of the miner
+func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
+ api.e.SetEtherbase(etherbase)
+ return true
+}
+
+// SetRecommitInterval updates the interval for miner sealing work recommitting.
+func (api *PrivateMinerAPI) SetRecommitInterval(interval int) {
+ api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond)
+}
+
+// GetHashrate returns the current hashrate of the miner.
+func (api *PrivateMinerAPI) GetHashrate() uint64 {
+ return api.e.miner.HashRate()
+}
+
+// PrivateAdminAPI is the collection of Ethereum full node-related APIs
+// exposed over the private admin endpoint.
+type PrivateAdminAPI struct {
+ eth *Ethereum
+}
+
+// NewPrivateAdminAPI creates a new API definition for the full node private
+// admin methods of the Ethereum service.
+func NewPrivateAdminAPI(eth *Ethereum) *PrivateAdminAPI {
+ return &PrivateAdminAPI{eth: eth}
+}
+
+// ExportChain exports the current blockchain into a local file.
+func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) {
+ // Make sure we can create the file to export into
+ out, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
+ if err != nil {
+ return false, err
+ }
+ defer out.Close()
+
+ var writer io.Writer = out
+ if strings.HasSuffix(file, ".gz") {
+ writer = gzip.NewWriter(writer)
+ defer writer.(*gzip.Writer).Close()
+ }
+
+ // Export the blockchain
+ if err := api.eth.BlockChain().Export(writer); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool {
+ for _, b := range bs {
+ if !chain.HasBlock(b.Hash(), b.NumberU64()) {
+ return false
+ }
+ }
+
+ return true
+}
+
+// ImportChain imports a blockchain from a local file.
+func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) {
+ // Make sure the can access the file to import
+ in, err := os.Open(file)
+ if err != nil {
+ return false, err
+ }
+ defer in.Close()
+
+ var reader io.Reader = in
+ if strings.HasSuffix(file, ".gz") {
+ if reader, err = gzip.NewReader(reader); err != nil {
+ return false, err
+ }
+ }
+
+ // Run actual the import in pre-configured batches
+ stream := rlp.NewStream(reader, 0)
+
+ blocks, index := make([]*types.Block, 0, 2500), 0
+ for batch := 0; ; batch++ {
+ // Load a batch of blocks from the input file
+ for len(blocks) < cap(blocks) {
+ block := new(types.Block)
+ if err := stream.Decode(block); err == io.EOF {
+ break
+ } else if err != nil {
+ return false, fmt.Errorf("block %d: failed to parse: %v", index, err)
+ }
+ blocks = append(blocks, block)
+ index++
+ }
+ if len(blocks) == 0 {
+ break
+ }
+
+ if hasAllBlocks(api.eth.BlockChain(), blocks) {
+ blocks = blocks[:0]
+ continue
+ }
+ // Import the batch and reset the buffer
+ if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil {
+ return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err)
+ }
+ blocks = blocks[:0]
+ }
+ return true, nil
+}
+
+// PublicDebugAPI is the collection of Ethereum full node APIs exposed
+// over the public debugging endpoint.
+type PublicDebugAPI struct {
+ eth *Ethereum
+}
+
+// NewPublicDebugAPI creates a new API definition for the full node-
+// related public debug methods of the Ethereum service.
+func NewPublicDebugAPI(eth *Ethereum) *PublicDebugAPI {
+ return &PublicDebugAPI{eth: eth}
+}
+
+// DumpBlock retrieves the entire state of the database at a given block.
+func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) {
+ if blockNr == rpc.PendingBlockNumber {
+ // If we're dumping the pending state, we need to request
+ // both the pending block as well as the pending state from
+ // the miner and operate on those
+ _, stateDb := api.eth.miner.Pending()
+ return stateDb.RawDump(false, false, true), nil
+ }
+ var block *types.Block
+ if blockNr == rpc.LatestBlockNumber {
+ block = api.eth.blockchain.CurrentBlock()
+ } else {
+ block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
+ }
+ if block == nil {
+ return state.Dump{}, fmt.Errorf("block #%d not found", blockNr)
+ }
+ stateDb, err := api.eth.BlockChain().StateAt(block.Root())
+ if err != nil {
+ return state.Dump{}, err
+ }
+ return stateDb.RawDump(false, false, true), nil
+}
+
+// PrivateDebugAPI is the collection of Ethereum full node APIs exposed over
+// the private debugging endpoint.
+type PrivateDebugAPI struct {
+ eth *Ethereum
+}
+
+// NewPrivateDebugAPI creates a new API definition for the full node-related
+// private debug methods of the Ethereum service.
+func NewPrivateDebugAPI(eth *Ethereum) *PrivateDebugAPI {
+ return &PrivateDebugAPI{eth: eth}
+}
+
+// Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
+func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
+ if preimage := rawdb.ReadPreimage(api.eth.ChainDb(), hash); preimage != nil {
+ return preimage, nil
+ }
+ return nil, errors.New("unknown preimage")
+}
+
+// BadBlockArgs represents the entries in the list returned when bad blocks are queried.
+type BadBlockArgs struct {
+ Hash common.Hash `json:"hash"`
+ Block map[string]interface{} `json:"block"`
+ RLP string `json:"rlp"`
+}
+
+// GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network
+// and returns them as a JSON list of block-hashes
+func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) {
+ blocks := api.eth.BlockChain().BadBlocks()
+ results := make([]*BadBlockArgs, len(blocks))
+
+ var err error
+ for i, block := range blocks {
+ results[i] = &BadBlockArgs{
+ Hash: block.Hash(),
+ }
+ if rlpBytes, err := rlp.EncodeToBytes(block); err != nil {
+ results[i].RLP = err.Error() // Hacky, but hey, it works
+ } else {
+ results[i].RLP = fmt.Sprintf("0x%x", rlpBytes)
+ }
+ if results[i].Block, err = ethapi.RPCMarshalBlock(block, true, true); err != nil {
+ results[i].Block = map[string]interface{}{"error": err.Error()}
+ }
+ }
+ return results, nil
+}
+
+// AccountRangeResult returns a mapping from the hash of an account addresses
+// to its preimage. It will return the JSON null if no preimage is found.
+// Since a query can return a limited amount of results, a "next" field is
+// also present for paging.
+type AccountRangeResult struct {
+ Accounts map[common.Hash]*common.Address `json:"accounts"`
+ Next common.Hash `json:"next"`
+}
+
+func accountRange(st state.Trie, start *common.Hash, maxResults int) (AccountRangeResult, error) {
+ if start == nil {
+ start = &common.Hash{0}
+ }
+ it := trie.NewIterator(st.NodeIterator(start.Bytes()))
+ result := AccountRangeResult{Accounts: make(map[common.Hash]*common.Address), Next: common.Hash{}}
+
+ if maxResults > AccountRangeMaxResults {
+ maxResults = AccountRangeMaxResults
+ }
+
+ for i := 0; i < maxResults && it.Next(); i++ {
+ if preimage := st.GetKey(it.Key); preimage != nil {
+ addr := &common.Address{}
+ addr.SetBytes(preimage)
+ result.Accounts[common.BytesToHash(it.Key)] = addr
+ } else {
+ result.Accounts[common.BytesToHash(it.Key)] = nil
+ }
+ }
+
+ if it.Next() {
+ result.Next = common.BytesToHash(it.Key)
+ }
+
+ return result, nil
+}
+
+// AccountRangeMaxResults is the maximum number of results to be returned per call
+const AccountRangeMaxResults = 256
+
+// AccountRange enumerates all accounts in the latest state
+func (api *PrivateDebugAPI) AccountRange(ctx context.Context, start *common.Hash, maxResults int) (AccountRangeResult, error) {
+ var statedb *state.StateDB
+ var err error
+ block := api.eth.blockchain.CurrentBlock()
+
+ if len(block.Transactions()) == 0 {
+ statedb, err = api.computeStateDB(block, defaultTraceReexec)
+ if err != nil {
+ return AccountRangeResult{}, err
+ }
+ } else {
+ _, _, statedb, err = api.computeTxEnv(block.Hash(), len(block.Transactions())-1, 0)
+ if err != nil {
+ return AccountRangeResult{}, err
+ }
+ }
+
+ trie, err := statedb.Database().OpenTrie(block.Header().Root)
+ if err != nil {
+ return AccountRangeResult{}, err
+ }
+
+ return accountRange(trie, start, maxResults)
+}
+
+// StorageRangeResult is the result of a debug_storageRangeAt API call.
+type StorageRangeResult struct {
+ Storage storageMap `json:"storage"`
+ NextKey *common.Hash `json:"nextKey"` // nil if Storage includes the last key in the trie.
+}
+
+type storageMap map[common.Hash]storageEntry
+
+type storageEntry struct {
+ Key *common.Hash `json:"key"`
+ Value common.Hash `json:"value"`
+}
+
+// StorageRangeAt returns the storage at the given block height and transaction index.
+func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
+ _, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0)
+ if err != nil {
+ return StorageRangeResult{}, err
+ }
+ st := statedb.StorageTrie(contractAddress)
+ if st == nil {
+ return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress)
+ }
+ return storageRangeAt(st, keyStart, maxResult)
+}
+
+func storageRangeAt(st state.Trie, start []byte, maxResult int) (StorageRangeResult, error) {
+ it := trie.NewIterator(st.NodeIterator(start))
+ result := StorageRangeResult{Storage: storageMap{}}
+ for i := 0; i < maxResult && it.Next(); i++ {
+ _, content, _, err := rlp.Split(it.Value)
+ if err != nil {
+ return StorageRangeResult{}, err
+ }
+ e := storageEntry{Value: common.BytesToHash(content)}
+ if preimage := st.GetKey(it.Key); preimage != nil {
+ preimage := common.BytesToHash(preimage)
+ e.Key = &preimage
+ }
+ result.Storage[common.BytesToHash(it.Key)] = e
+ }
+ // Add the 'next key' so clients can continue downloading.
+ if it.Next() {
+ next := common.BytesToHash(it.Key)
+ result.NextKey = &next
+ }
+ return result, nil
+}
+
+// GetModifiedAccountsByNumber returns all accounts that have changed between the
+// two blocks specified. A change is defined as a difference in nonce, balance,
+// code hash, or storage hash.
+//
+// With one parameter, returns the list of accounts modified in the specified block.
+func (api *PrivateDebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64) ([]common.Address, error) {
+ var startBlock, endBlock *types.Block
+
+ startBlock = api.eth.blockchain.GetBlockByNumber(startNum)
+ if startBlock == nil {
+ return nil, fmt.Errorf("start block %x not found", startNum)
+ }
+
+ if endNum == nil {
+ endBlock = startBlock
+ startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash())
+ if startBlock == nil {
+ return nil, fmt.Errorf("block %x has no parent", endBlock.Number())
+ }
+ } else {
+ endBlock = api.eth.blockchain.GetBlockByNumber(*endNum)
+ if endBlock == nil {
+ return nil, fmt.Errorf("end block %d not found", *endNum)
+ }
+ }
+ return api.getModifiedAccounts(startBlock, endBlock)
+}
+
+// GetModifiedAccountsByHash returns all accounts that have changed between the
+// two blocks specified. A change is defined as a difference in nonce, balance,
+// code hash, or storage hash.
+//
+// With one parameter, returns the list of accounts modified in the specified block.
+func (api *PrivateDebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) {
+ var startBlock, endBlock *types.Block
+ startBlock = api.eth.blockchain.GetBlockByHash(startHash)
+ if startBlock == nil {
+ return nil, fmt.Errorf("start block %x not found", startHash)
+ }
+
+ if endHash == nil {
+ endBlock = startBlock
+ startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash())
+ if startBlock == nil {
+ return nil, fmt.Errorf("block %x has no parent", endBlock.Number())
+ }
+ } else {
+ endBlock = api.eth.blockchain.GetBlockByHash(*endHash)
+ if endBlock == nil {
+ return nil, fmt.Errorf("end block %x not found", *endHash)
+ }
+ }
+ return api.getModifiedAccounts(startBlock, endBlock)
+}
+
+func (api *PrivateDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]common.Address, error) {
+ if startBlock.Number().Uint64() >= endBlock.Number().Uint64() {
+ return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64())
+ }
+ triedb := api.eth.BlockChain().StateCache().TrieDB()
+
+ oldTrie, err := trie.NewSecure(startBlock.Root(), triedb)
+ if err != nil {
+ return nil, err
+ }
+ newTrie, err := trie.NewSecure(endBlock.Root(), triedb)
+ if err != nil {
+ return nil, err
+ }
+ diff, _ := trie.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}))
+ iter := trie.NewIterator(diff)
+
+ var dirty []common.Address
+ for iter.Next() {
+ key := newTrie.GetKey(iter.Key)
+ if key == nil {
+ return nil, fmt.Errorf("no preimage found for hash %x", iter.Key)
+ }
+ dirty = append(dirty, common.BytesToAddress(key))
+ }
+ return dirty, nil
+}
diff --git a/eth/api_backend.go b/eth/api_backend.go
new file mode 100644
index 0000000..69904a7
--- /dev/null
+++ b/eth/api_backend.go
@@ -0,0 +1,244 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "context"
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/bloombits"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/gasprice"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+// EthAPIBackend implements ethapi.Backend for full nodes
+type EthAPIBackend struct {
+ extRPCEnabled bool
+ eth *Ethereum
+ gpo *gasprice.Oracle
+}
+
+// ChainConfig returns the active chain configuration.
+func (b *EthAPIBackend) ChainConfig() *params.ChainConfig {
+ return b.eth.blockchain.Config()
+}
+
+func (b *EthAPIBackend) CurrentBlock() *types.Block {
+ return b.eth.blockchain.CurrentBlock()
+}
+
+func (b *EthAPIBackend) SetHead(number uint64) {
+ b.eth.protocolManager.downloader.Cancel()
+ b.eth.blockchain.SetHead(number)
+}
+
+func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
+ // Pending block is only known by the miner
+ if number == rpc.PendingBlockNumber {
+ block := b.eth.miner.PendingBlock()
+ return block.Header(), nil
+ }
+ // Otherwise resolve and return the block
+ if number == rpc.LatestBlockNumber {
+ return b.eth.blockchain.CurrentBlock().Header(), nil
+ }
+ return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
+}
+
+func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
+ return b.eth.blockchain.GetHeaderByHash(hash), nil
+}
+
+func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
+ // Pending block is only known by the miner
+ if number == rpc.PendingBlockNumber {
+ block := b.eth.miner.PendingBlock()
+ return block, nil
+ }
+ // Otherwise resolve and return the block
+ if number == rpc.LatestBlockNumber {
+ return b.eth.blockchain.CurrentBlock(), nil
+ }
+ return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
+}
+
+func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
+ return b.eth.blockchain.GetBlockByHash(hash), nil
+}
+
+func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
+ // Pending state is only known by the miner
+ if number == rpc.PendingBlockNumber {
+ block, state := b.eth.miner.Pending()
+ return state, block.Header(), nil
+ }
+ // Otherwise resolve the block number and return its state
+ header, err := b.HeaderByNumber(ctx, number)
+ if err != nil {
+ return nil, nil, err
+ }
+ if header == nil {
+ return nil, nil, errors.New("header not found")
+ }
+ stateDb, err := b.eth.BlockChain().StateAt(header.Root)
+ return stateDb, header, err
+}
+
+func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
+ return b.eth.blockchain.GetReceiptsByHash(hash), nil
+}
+
+func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
+ receipts := b.eth.blockchain.GetReceiptsByHash(hash)
+ if receipts == nil {
+ return nil, nil
+ }
+ logs := make([][]*types.Log, len(receipts))
+ for i, receipt := range receipts {
+ logs[i] = receipt.Logs
+ }
+ return logs, nil
+}
+
+func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int {
+ return b.eth.blockchain.GetTdByHash(blockHash)
+}
+
+func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) {
+ state.SetBalance(msg.From(), math.MaxBig256)
+ vmError := func() error { return nil }
+
+ context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil)
+ return vm.NewEVM(context, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil
+}
+
+func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
+ return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch)
+}
+
+func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
+ return b.eth.BlockChain().SubscribeChainEvent(ch)
+}
+
+func (b *EthAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
+ return b.eth.BlockChain().SubscribeChainHeadEvent(ch)
+}
+
+func (b *EthAPIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
+ return b.eth.BlockChain().SubscribeChainSideEvent(ch)
+}
+
+func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
+ return b.eth.BlockChain().SubscribeLogsEvent(ch)
+}
+
+func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
+ return b.eth.txPool.AddLocal(signedTx)
+}
+
+func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
+ pending, err := b.eth.txPool.Pending()
+ if err != nil {
+ return nil, err
+ }
+ var txs types.Transactions
+ for _, batch := range pending {
+ txs = append(txs, batch...)
+ }
+ return txs, nil
+}
+
+func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
+ return b.eth.txPool.Get(hash)
+}
+
+func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
+ tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.eth.ChainDb(), txHash)
+ return tx, blockHash, blockNumber, index, nil
+}
+
+func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
+ return b.eth.txPool.Nonce(addr), nil
+}
+
+func (b *EthAPIBackend) Stats() (pending int, queued int) {
+ return b.eth.txPool.Stats()
+}
+
+func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
+ return b.eth.TxPool().Content()
+}
+
+func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
+ return b.eth.TxPool().SubscribeNewTxsEvent(ch)
+}
+
+func (b *EthAPIBackend) Downloader() *downloader.Downloader {
+ return b.eth.Downloader()
+}
+
+func (b *EthAPIBackend) ProtocolVersion() int {
+ return b.eth.EthVersion()
+}
+
+func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
+ return b.gpo.SuggestPrice(ctx)
+}
+
+func (b *EthAPIBackend) ChainDb() ethdb.Database {
+ return b.eth.ChainDb()
+}
+
+func (b *EthAPIBackend) EventMux() *event.TypeMux {
+ return b.eth.EventMux()
+}
+
+func (b *EthAPIBackend) AccountManager() *accounts.Manager {
+ return b.eth.AccountManager()
+}
+
+func (b *EthAPIBackend) ExtRPCEnabled() bool {
+ return b.extRPCEnabled
+}
+
+func (b *EthAPIBackend) RPCGasCap() *big.Int {
+ return b.eth.config.RPCGasCap
+}
+
+func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
+ sections, _, _ := b.eth.bloomIndexer.Sections()
+ return params.BloomBitsBlocks, sections
+}
+
+func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
+ for i := 0; i < bloomFilterThreads; i++ {
+ go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
+ }
+}
diff --git a/eth/api_tracer.go b/eth/api_tracer.go
new file mode 100644
index 0000000..7d983fa
--- /dev/null
+++ b/eth/api_tracer.go
@@ -0,0 +1,823 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "runtime"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+ "github.com/Determinant/coreth/internal/ethapi"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/ethereum/go-ethereum/trie"
+)
+
+const (
+ // defaultTraceTimeout is the amount of time a single transaction can execute
+ // by default before being forcefully aborted.
+ defaultTraceTimeout = 5 * time.Second
+
+ // defaultTraceReexec is the number of blocks the tracer is willing to go back
+ // and reexecute to produce missing historical state necessary to run a specific
+ // trace.
+ defaultTraceReexec = uint64(128)
+)
+
+// TraceConfig holds extra parameters to trace functions.
+type TraceConfig struct {
+ *vm.LogConfig
+ Tracer *string
+ Timeout *string
+ Reexec *uint64
+}
+
+// StdTraceConfig holds extra parameters to standard-json trace functions.
+type StdTraceConfig struct {
+ *vm.LogConfig
+ Reexec *uint64
+ TxHash common.Hash
+}
+
+// txTraceResult is the result of a single transaction trace.
+type txTraceResult struct {
+ Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
+ Error string `json:"error,omitempty"` // Trace failure produced by the tracer
+}
+
+// blockTraceTask represents a single block trace task when an entire chain is
+// being traced.
+type blockTraceTask struct {
+ statedb *state.StateDB // Intermediate state prepped for tracing
+ block *types.Block // Block to trace the transactions from
+ rootref common.Hash // Trie root reference held for this task
+ results []*txTraceResult // Trace results procudes by the task
+}
+
+// blockTraceResult represets the results of tracing a single block when an entire
+// chain is being traced.
+type blockTraceResult struct {
+ Block hexutil.Uint64 `json:"block"` // Block number corresponding to this trace
+ Hash common.Hash `json:"hash"` // Block hash corresponding to this trace
+ Traces []*txTraceResult `json:"traces"` // Trace results produced by the task
+}
+
+// txTraceTask represents a single transaction trace task when an entire block
+// is being traced.
+type txTraceTask struct {
+ statedb *state.StateDB // Intermediate state prepped for tracing
+ index int // Transaction offset in the block
+}
+
+// TraceChain returns the structured logs created during the execution of EVM
+// between two blocks (excluding start) and returns them as a JSON object.
+func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) {
+ // Fetch the block interval that we want to trace
+ var from, to *types.Block
+
+ switch start {
+ case rpc.PendingBlockNumber:
+ from = api.eth.miner.PendingBlock()
+ case rpc.LatestBlockNumber:
+ from = api.eth.blockchain.CurrentBlock()
+ default:
+ from = api.eth.blockchain.GetBlockByNumber(uint64(start))
+ }
+ switch end {
+ case rpc.PendingBlockNumber:
+ to = api.eth.miner.PendingBlock()
+ case rpc.LatestBlockNumber:
+ to = api.eth.blockchain.CurrentBlock()
+ default:
+ to = api.eth.blockchain.GetBlockByNumber(uint64(end))
+ }
+ // Trace the chain if we've found all our blocks
+ if from == nil {
+ return nil, fmt.Errorf("starting block #%d not found", start)
+ }
+ if to == nil {
+ return nil, fmt.Errorf("end block #%d not found", end)
+ }
+ if from.Number().Cmp(to.Number()) >= 0 {
+ return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start)
+ }
+ return api.traceChain(ctx, from, to, config)
+}
+
+// traceChain configures a new tracer according to the provided configuration, and
+// executes all the transactions contained within. The return value will be one item
+// per transaction, dependent on the requested tracer.
+func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) {
+ // Tracing a chain is a **long** operation, only do with subscriptions
+ notifier, supported := rpc.NotifierFromContext(ctx)
+ if !supported {
+ return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
+ }
+ sub := notifier.CreateSubscription()
+
+ // Ensure we have a valid starting state before doing any work
+ origin := start.NumberU64()
+ database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) // Chain tracing will probably start at genesis
+
+ if number := start.NumberU64(); number > 0 {
+ start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
+ if start == nil {
+ return nil, fmt.Errorf("parent block #%d not found", number-1)
+ }
+ }
+ statedb, err := state.New(start.Root(), database)
+ if err != nil {
+ // If the starting state is missing, allow some number of blocks to be reexecuted
+ reexec := defaultTraceReexec
+ if config != nil && config.Reexec != nil {
+ reexec = *config.Reexec
+ }
+ // Find the most recent block that has the state available
+ for i := uint64(0); i < reexec; i++ {
+ start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
+ if start == nil {
+ break
+ }
+ if statedb, err = state.New(start.Root(), database); err == nil {
+ break
+ }
+ }
+ // If we still don't have the state available, bail out
+ if err != nil {
+ switch err.(type) {
+ case *trie.MissingNodeError:
+ return nil, errors.New("required historical state unavailable")
+ default:
+ return nil, err
+ }
+ }
+ }
+ // Execute all the transaction contained within the chain concurrently for each block
+ blocks := int(end.NumberU64() - origin)
+
+ threads := runtime.NumCPU()
+ if threads > blocks {
+ threads = blocks
+ }
+ var (
+ pend = new(sync.WaitGroup)
+ tasks = make(chan *blockTraceTask, threads)
+ results = make(chan *blockTraceTask, threads)
+ )
+ for th := 0; th < threads; th++ {
+ pend.Add(1)
+ go func() {
+ defer pend.Done()
+
+ // Fetch and execute the next block trace tasks
+ for task := range tasks {
+ signer := types.MakeSigner(api.eth.blockchain.Config(), task.block.Number())
+
+ // Trace all the transactions contained within
+ for i, tx := range task.block.Transactions() {
+ msg, _ := tx.AsMessage(signer)
+ vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil)
+
+ res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
+ if err != nil {
+ task.results[i] = &txTraceResult{Error: err.Error()}
+ log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
+ break
+ }
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ task.statedb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number()))
+ task.results[i] = &txTraceResult{Result: res}
+ }
+ // Stream the result back to the user or abort on teardown
+ select {
+ case results <- task:
+ case <-notifier.Closed():
+ return
+ }
+ }
+ }()
+ }
+ // Start a goroutine to feed all the blocks into the tracers
+ begin := time.Now()
+
+ go func() {
+ var (
+ logged time.Time
+ number uint64
+ traced uint64
+ failed error
+ proot common.Hash
+ )
+ // Ensure everything is properly cleaned up on any exit path
+ defer func() {
+ close(tasks)
+ pend.Wait()
+
+ switch {
+ case failed != nil:
+ log.Warn("Chain tracing failed", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin), "err", failed)
+ case number < end.NumberU64():
+ log.Warn("Chain tracing aborted", "start", start.NumberU64(), "end", end.NumberU64(), "abort", number, "transactions", traced, "elapsed", time.Since(begin))
+ default:
+ log.Info("Chain tracing finished", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin))
+ }
+ close(results)
+ }()
+ // Feed all the blocks both into the tracer, as well as fast process concurrently
+ for number = start.NumberU64() + 1; number <= end.NumberU64(); number++ {
+ // Stop tracing if interruption was requested
+ select {
+ case <-notifier.Closed():
+ return
+ default:
+ }
+ // Print progress logs if long enough time elapsed
+ if time.Since(logged) > 8*time.Second {
+ if number > origin {
+ nodes, imgs := database.TrieDB().Size()
+ log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin), "memory", nodes+imgs)
+ } else {
+ log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin))
+ }
+ logged = time.Now()
+ }
+ // Retrieve the next block to trace
+ block := api.eth.blockchain.GetBlockByNumber(number)
+ if block == nil {
+ failed = fmt.Errorf("block #%d not found", number)
+ break
+ }
+ // Send the block over to the concurrent tracers (if not in the fast-forward phase)
+ if number > origin {
+ txs := block.Transactions()
+
+ select {
+ case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))}:
+ case <-notifier.Closed():
+ return
+ }
+ traced += uint64(len(txs))
+ }
+ // Generate the next state snapshot fast without tracing
+ _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
+ if err != nil {
+ failed = err
+ break
+ }
+ // Finalize the state so any modifications are written to the trie
+ root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
+ if err != nil {
+ failed = err
+ break
+ }
+ if err := statedb.Reset(root); err != nil {
+ failed = err
+ break
+ }
+ // Reference the trie twice, once for us, once for the tracer
+ database.TrieDB().Reference(root, common.Hash{})
+ if number >= origin {
+ database.TrieDB().Reference(root, common.Hash{})
+ }
+ // Dereference all past tries we ourselves are done working with
+ if proot != (common.Hash{}) {
+ database.TrieDB().Dereference(proot)
+ }
+ proot = root
+
+ // TODO(karalabe): Do we need the preimages? Won't they accumulate too much?
+ }
+ }()
+
+ // Keep reading the trace results and stream the to the user
+ go func() {
+ var (
+ done = make(map[uint64]*blockTraceResult)
+ next = origin + 1
+ )
+ for res := range results {
+ // Queue up next received result
+ result := &blockTraceResult{
+ Block: hexutil.Uint64(res.block.NumberU64()),
+ Hash: res.block.Hash(),
+ Traces: res.results,
+ }
+ done[uint64(result.Block)] = result
+
+ // Dereference any paret tries held in memory by this task
+ database.TrieDB().Dereference(res.rootref)
+
+ // Stream completed traces to the user, aborting on the first error
+ for result, ok := done[next]; ok; result, ok = done[next] {
+ if len(result.Traces) > 0 || next == end.NumberU64() {
+ notifier.Notify(sub.ID, result)
+ }
+ delete(done, next)
+ next++
+ }
+ }
+ }()
+ return sub, nil
+}
+
+// TraceBlockByNumber returns the structured logs created during the execution of
+// EVM and returns them as a JSON object.
+func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
+ // Fetch the block that we want to trace
+ var block *types.Block
+
+ switch number {
+ case rpc.PendingBlockNumber:
+ block = api.eth.miner.PendingBlock()
+ case rpc.LatestBlockNumber:
+ block = api.eth.blockchain.CurrentBlock()
+ default:
+ block = api.eth.blockchain.GetBlockByNumber(uint64(number))
+ }
+ // Trace the block if it was found
+ if block == nil {
+ return nil, fmt.Errorf("block #%d not found", number)
+ }
+ return api.traceBlock(ctx, block, config)
+}
+
+// TraceBlockByHash returns the structured logs created during the execution of
+// EVM and returns them as a JSON object.
+func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
+ block := api.eth.blockchain.GetBlockByHash(hash)
+ if block == nil {
+ return nil, fmt.Errorf("block %#x not found", hash)
+ }
+ return api.traceBlock(ctx, block, config)
+}
+
+// TraceBlock returns the structured logs created during the execution of EVM
+// and returns them as a JSON object.
+func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) {
+ block := new(types.Block)
+ if err := rlp.Decode(bytes.NewReader(blob), block); err != nil {
+ return nil, fmt.Errorf("could not decode block: %v", err)
+ }
+ return api.traceBlock(ctx, block, config)
+}
+
+// TraceBlockFromFile returns the structured logs created during the execution of
+// EVM and returns them as a JSON object.
+func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) {
+ blob, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("could not read file: %v", err)
+ }
+ return api.TraceBlock(ctx, blob, config)
+}
+
+// TraceBadBlockByHash returns the structured logs created during the execution of
+// EVM against a block pulled from the pool of bad ones and returns them as a JSON
+// object.
+func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
+ blocks := api.eth.blockchain.BadBlocks()
+ for _, block := range blocks {
+ if block.Hash() == hash {
+ return api.traceBlock(ctx, block, config)
+ }
+ }
+ return nil, fmt.Errorf("bad block %#x not found", hash)
+}
+
+// StandardTraceBlockToFile dumps the structured logs created during the
+// execution of EVM to the local file system and returns a list of files
+// to the caller.
+func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
+ block := api.eth.blockchain.GetBlockByHash(hash)
+ if block == nil {
+ return nil, fmt.Errorf("block %#x not found", hash)
+ }
+ return api.standardTraceBlockToFile(ctx, block, config)
+}
+
+// StandardTraceBadBlockToFile dumps the structured logs created during the
+// execution of EVM against a block pulled from the pool of bad ones to the
+// local file system and returns a list of files to the caller.
+func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
+ blocks := api.eth.blockchain.BadBlocks()
+ for _, block := range blocks {
+ if block.Hash() == hash {
+ return api.standardTraceBlockToFile(ctx, block, config)
+ }
+ }
+ return nil, fmt.Errorf("bad block %#x not found", hash)
+}
+
+// traceBlock configures a new tracer according to the provided configuration, and
+// executes all the transactions contained within. The return value will be one item
+// per transaction, dependent on the requestd tracer.
+func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
+ // Create the parent state database
+ if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
+ return nil, err
+ }
+ parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
+ if parent == nil {
+ return nil, fmt.Errorf("parent %#x not found", block.ParentHash())
+ }
+ reexec := defaultTraceReexec
+ if config != nil && config.Reexec != nil {
+ reexec = *config.Reexec
+ }
+ statedb, err := api.computeStateDB(parent, reexec)
+ if err != nil {
+ return nil, err
+ }
+ // Execute all the transaction contained within the block concurrently
+ var (
+ signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number())
+
+ txs = block.Transactions()
+ results = make([]*txTraceResult, len(txs))
+
+ pend = new(sync.WaitGroup)
+ jobs = make(chan *txTraceTask, len(txs))
+ )
+ threads := runtime.NumCPU()
+ if threads > len(txs) {
+ threads = len(txs)
+ }
+ for th := 0; th < threads; th++ {
+ pend.Add(1)
+ go func() {
+ defer pend.Done()
+
+ // Fetch and execute the next transaction trace tasks
+ for task := range jobs {
+ msg, _ := txs[task.index].AsMessage(signer)
+ vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
+
+ res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
+ if err != nil {
+ results[task.index] = &txTraceResult{Error: err.Error()}
+ continue
+ }
+ results[task.index] = &txTraceResult{Result: res}
+ }
+ }()
+ }
+ // Feed the transactions into the tracers and return
+ var failed error
+ for i, tx := range txs {
+ // Send the trace task over for execution
+ jobs <- &txTraceTask{statedb: statedb.Copy(), index: i}
+
+ // Generate the next state snapshot fast without tracing
+ msg, _ := tx.AsMessage(signer)
+ vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
+
+ vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{})
+ if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
+ failed = err
+ break
+ }
+ // Finalize the state so any modifications are written to the trie
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+ }
+ close(jobs)
+ pend.Wait()
+
+ // If execution failed in between, abort
+ if failed != nil {
+ return nil, failed
+ }
+ return results, nil
+}
+
+// standardTraceBlockToFile configures a new tracer which uses standard JSON output,
+// and traces either a full block or an individual transaction. The return value will
+// be one filename per transaction traced.
+func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) {
+ // If we're tracing a single transaction, make sure it's present
+ if config != nil && config.TxHash != (common.Hash{}) {
+ if !containsTx(block, config.TxHash) {
+ return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash)
+ }
+ }
+ // Create the parent state database
+ if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
+ return nil, err
+ }
+ parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
+ if parent == nil {
+ return nil, fmt.Errorf("parent %#x not found", block.ParentHash())
+ }
+ reexec := defaultTraceReexec
+ if config != nil && config.Reexec != nil {
+ reexec = *config.Reexec
+ }
+ statedb, err := api.computeStateDB(parent, reexec)
+ if err != nil {
+ return nil, err
+ }
+ // Retrieve the tracing configurations, or use default values
+ var (
+ logConfig vm.LogConfig
+ txHash common.Hash
+ )
+ if config != nil {
+ if config.LogConfig != nil {
+ logConfig = *config.LogConfig
+ }
+ txHash = config.TxHash
+ }
+ logConfig.Debug = true
+
+ // Execute transaction, either tracing all or just the requested one
+ var (
+ signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number())
+ dumps []string
+ )
+ for i, tx := range block.Transactions() {
+ // Prepare the trasaction for un-traced execution
+ var (
+ msg, _ = tx.AsMessage(signer)
+ vmctx = core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
+
+ vmConf vm.Config
+ dump *os.File
+ writer *bufio.Writer
+ err error
+ )
+ // If the transaction needs tracing, swap out the configs
+ if tx.Hash() == txHash || txHash == (common.Hash{}) {
+ // Generate a unique temporary file to dump it into
+ prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4])
+
+ dump, err = ioutil.TempFile(os.TempDir(), prefix)
+ if err != nil {
+ return nil, err
+ }
+ dumps = append(dumps, dump.Name())
+
+ // Swap out the noop logger to the standard tracer
+ writer = bufio.NewWriter(dump)
+ vmConf = vm.Config{
+ Debug: true,
+ Tracer: vm.NewJSONLogger(&logConfig, writer),
+ EnablePreimageRecording: true,
+ }
+ }
+ // Execute the transaction and flush any traces to disk
+ vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf)
+ _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
+ if writer != nil {
+ writer.Flush()
+ }
+ if dump != nil {
+ dump.Close()
+ log.Info("Wrote standard trace", "file", dump.Name())
+ }
+ if err != nil {
+ return dumps, err
+ }
+ // Finalize the state so any modifications are written to the trie
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+
+ // If we've traced the transaction we were looking for, abort
+ if tx.Hash() == txHash {
+ break
+ }
+ }
+ return dumps, nil
+}
+
+// containsTx reports whether the transaction with a certain hash
+// is contained within the specified block.
+func containsTx(block *types.Block, hash common.Hash) bool {
+ for _, tx := range block.Transactions() {
+ if tx.Hash() == hash {
+ return true
+ }
+ }
+ return false
+}
+
+// computeStateDB retrieves the state database associated with a certain block.
+// If no state is locally available for the given block, a number of blocks are
+// attempted to be reexecuted to generate the desired state.
+func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) {
+ // If we have the state fully available, use that
+ statedb, err := api.eth.blockchain.StateAt(block.Root())
+ if err == nil {
+ return statedb, nil
+ }
+ // Otherwise try to reexec blocks until we find a state or reach our limit
+ origin := block.NumberU64()
+ database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16)
+
+ for i := uint64(0); i < reexec; i++ {
+ block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
+ if block == nil {
+ break
+ }
+ if statedb, err = state.New(block.Root(), database); err == nil {
+ break
+ }
+ }
+ if err != nil {
+ switch err.(type) {
+ case *trie.MissingNodeError:
+ return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
+ default:
+ return nil, err
+ }
+ }
+ // State was available at historical point, regenerate
+ var (
+ start = time.Now()
+ logged time.Time
+ proot common.Hash
+ )
+ for block.NumberU64() < origin {
+ // Print progress logs if long enough time elapsed
+ if time.Since(logged) > 8*time.Second {
+ log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start))
+ logged = time.Now()
+ }
+ // Retrieve the next block to regenerate and process it
+ if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil {
+ return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1)
+ }
+ _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
+ if err != nil {
+ return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err)
+ }
+ // Finalize the state so any modifications are written to the trie
+ root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
+ if err != nil {
+ return nil, err
+ }
+ if err := statedb.Reset(root); err != nil {
+ return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err)
+ }
+ database.TrieDB().Reference(root, common.Hash{})
+ if proot != (common.Hash{}) {
+ database.TrieDB().Dereference(proot)
+ }
+ proot = root
+ }
+ nodes, imgs := database.TrieDB().Size()
+ log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs)
+ return statedb, nil
+}
+
+// TraceTransaction returns the structured logs created during the execution of EVM
+// and returns them as a JSON object.
+func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
+ // Retrieve the transaction and assemble its EVM context
+ tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash)
+ if tx == nil {
+ return nil, fmt.Errorf("transaction %#x not found", hash)
+ }
+ reexec := defaultTraceReexec
+ if config != nil && config.Reexec != nil {
+ reexec = *config.Reexec
+ }
+ msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec)
+ if err != nil {
+ return nil, err
+ }
+ // Trace the transaction and return
+ return api.traceTx(ctx, msg, vmctx, statedb, config)
+}
+
+// traceTx configures a new tracer according to the provided configuration, and
+// executes the given message in the provided environment. The return value will
+// be tracer dependent.
+func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
+ // Assemble the structured logger or the JavaScript tracer
+ var (
+ tracer vm.Tracer
+ err error
+ )
+ switch {
+ case config != nil && config.Tracer != nil:
+ // Define a meaningful timeout of a single transaction trace
+ timeout := defaultTraceTimeout
+ if config.Timeout != nil {
+ if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
+ return nil, err
+ }
+ }
+ // Constuct the JavaScript tracer to execute with
+ if tracer, err = tracers.New(*config.Tracer); err != nil {
+ return nil, err
+ }
+ // Handle timeouts and RPC cancellations
+ deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
+ go func() {
+ <-deadlineCtx.Done()
+ tracer.(*tracers.Tracer).Stop(errors.New("execution timeout"))
+ }()
+ defer cancel()
+
+ case config == nil:
+ tracer = vm.NewStructLogger(nil)
+
+ default:
+ tracer = vm.NewStructLogger(config.LogConfig)
+ }
+ // Run the transaction with tracing enabled.
+ vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer})
+
+ ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
+ if err != nil {
+ return nil, fmt.Errorf("tracing failed: %v", err)
+ }
+ // Depending on the tracer type, format and return the output
+ switch tracer := tracer.(type) {
+ case *vm.StructLogger:
+ return &ethapi.ExecutionResult{
+ Gas: gas,
+ Failed: failed,
+ ReturnValue: fmt.Sprintf("%x", ret),
+ StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
+ }, nil
+
+ case *tracers.Tracer:
+ return tracer.GetResult()
+
+ default:
+ panic(fmt.Sprintf("bad tracer type %T", tracer))
+ }
+}
+
+// computeTxEnv returns the execution environment of a certain transaction.
+func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) {
+ // Create the parent state database
+ block := api.eth.blockchain.GetBlockByHash(blockHash)
+ if block == nil {
+ return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash)
+ }
+ parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
+ if parent == nil {
+ return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash())
+ }
+ statedb, err := api.computeStateDB(parent, reexec)
+ if err != nil {
+ return nil, vm.Context{}, nil, err
+ }
+
+ if txIndex == 0 && len(block.Transactions()) == 0 {
+ return nil, vm.Context{}, statedb, nil
+ }
+
+ // Recompute transactions up to the target index.
+ signer := types.MakeSigner(api.eth.blockchain.Config(), block.Number())
+
+ for idx, tx := range block.Transactions() {
+ // Assemble the transaction call message and return if the requested offset
+ msg, _ := tx.AsMessage(signer)
+ context := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
+ if idx == txIndex {
+ return msg, context, statedb, nil
+ }
+ // Not yet the searched for transaction, execute on top of the current state
+ vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{})
+ if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
+ return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
+ }
+ // Ensure any modifications are committed to the state
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+ }
+ return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash)
+}
diff --git a/eth/backend.go b/eth/backend.go
new file mode 100644
index 0000000..92dfa28
--- /dev/null
+++ b/eth/backend.go
@@ -0,0 +1,566 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Package eth implements the Ethereum protocol.
+package eth
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+ "runtime"
+ "sync"
+ "sync/atomic"
+
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/consensus/clique"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/bloombits"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/filters"
+ "github.com/ethereum/go-ethereum/eth/gasprice"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/Determinant/coreth/internal/ethapi"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/miner"
+ "github.com/Determinant/coreth/node"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/p2p/enr"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+type LesServer interface {
+ Start(srvr *p2p.Server)
+ Stop()
+ APIs() []rpc.API
+ Protocols() []p2p.Protocol
+ SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
+ SetContractBackend(bind.ContractBackend)
+}
+
+// Ethereum implements the Ethereum full node service.
+type Ethereum struct {
+ config *Config
+
+ // Channel for shutting down the service
+ shutdownChan chan bool
+
+ server *p2p.Server
+
+ // Handlers
+ txPool *core.TxPool
+ blockchain *core.BlockChain
+ protocolManager *ProtocolManager
+ lesServer LesServer
+
+ // DB interfaces
+ chainDb ethdb.Database // Block chain database
+
+ eventMux *event.TypeMux
+ engine consensus.Engine
+ accountManager *accounts.Manager
+
+ bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
+ bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
+
+ APIBackend *EthAPIBackend
+
+ miner *miner.Miner
+ gasPrice *big.Int
+ etherbase common.Address
+
+ networkID uint64
+ netRPCService *ethapi.PublicNetAPI
+
+ lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
+}
+
+func (s *Ethereum) AddLesServer(ls LesServer) {
+ s.lesServer = ls
+ ls.SetBloomBitsIndexer(s.bloomIndexer)
+}
+
+// SetClient sets a rpc client which connecting to our local node.
+func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) {
+ // Pass the rpc client to les server if it is enabled.
+ if s.lesServer != nil {
+ s.lesServer.SetContractBackend(backend)
+ }
+}
+
+// New creates a new Ethereum object (including the
+// initialisation of the common Ethereum object)
+func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
+ // Ensure configuration values are compatible and sane
+ if config.SyncMode == downloader.LightSync {
+ return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
+ }
+ if !config.SyncMode.IsValid() {
+ return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
+ }
+ if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 {
+ log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", DefaultConfig.Miner.GasPrice)
+ config.Miner.GasPrice = new(big.Int).Set(DefaultConfig.Miner.GasPrice)
+ }
+ if config.NoPruning && config.TrieDirtyCache > 0 {
+ config.TrieCleanCache += config.TrieDirtyCache
+ config.TrieDirtyCache = 0
+ }
+ log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
+
+ // Assemble the Ethereum object
+ chainDb, err := ctx.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/")
+ if err != nil {
+ return nil, err
+ }
+ chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis)
+ if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
+ return nil, genesisErr
+ }
+ log.Info("Initialised chain configuration", "config", chainConfig)
+
+ eth := &Ethereum{
+ config: config,
+ chainDb: chainDb,
+ eventMux: ctx.EventMux,
+ accountManager: ctx.AccountManager,
+ engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb),
+ shutdownChan: make(chan bool),
+ networkID: config.NetworkId,
+ gasPrice: config.Miner.GasPrice,
+ etherbase: config.Miner.Etherbase,
+ bloomRequests: make(chan chan *bloombits.Retrieval),
+ bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
+ }
+
+ bcVersion := rawdb.ReadDatabaseVersion(chainDb)
+ var dbVer = "<nil>"
+ if bcVersion != nil {
+ dbVer = fmt.Sprintf("%d", *bcVersion)
+ }
+ log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId, "dbversion", dbVer)
+
+ if !config.SkipBcVersionCheck {
+ if bcVersion != nil && *bcVersion > core.BlockChainVersion {
+ return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion)
+ } else if bcVersion == nil || *bcVersion < core.BlockChainVersion {
+ log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion)
+ rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
+ }
+ }
+ var (
+ vmConfig = vm.Config{
+ EnablePreimageRecording: config.EnablePreimageRecording,
+ EWASMInterpreter: config.EWASMInterpreter,
+ EVMInterpreter: config.EVMInterpreter,
+ }
+ cacheConfig = &core.CacheConfig{
+ TrieCleanLimit: config.TrieCleanCache,
+ TrieCleanNoPrefetch: config.NoPrefetch,
+ TrieDirtyLimit: config.TrieDirtyCache,
+ TrieDirtyDisabled: config.NoPruning,
+ TrieTimeLimit: config.TrieTimeout,
+ }
+ )
+ eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve)
+ if err != nil {
+ return nil, err
+ }
+ // Rewind the chain in case of an incompatible config upgrade.
+ if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
+ log.Warn("Rewinding chain to upgrade configuration", "err", compat)
+ eth.blockchain.SetHead(compat.RewindTo)
+ rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
+ }
+ eth.bloomIndexer.Start(eth.blockchain)
+
+ if config.TxPool.Journal != "" {
+ config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
+ }
+ eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain)
+
+ // Permit the downloader to use the trie cache allowance during fast sync
+ cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit
+ checkpoint := config.Checkpoint
+ if checkpoint == nil {
+ checkpoint = params.TrustedCheckpoints[genesisHash]
+ }
+ if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil {
+ return nil, err
+ }
+ eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
+ eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
+
+ eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil}
+ gpoParams := config.GPO
+ if gpoParams.Default == nil {
+ gpoParams.Default = config.Miner.GasPrice
+ }
+ eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
+
+ return eth, nil
+}
+
+func makeExtraData(extra []byte) []byte {
+ if len(extra) == 0 {
+ // create default extradata
+ extra, _ = rlp.EncodeToBytes([]interface{}{
+ uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch),
+ "geth",
+ runtime.Version(),
+ runtime.GOOS,
+ })
+ }
+ if uint64(len(extra)) > params.MaximumExtraDataSize {
+ log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize)
+ extra = nil
+ }
+ return extra
+}
+
+// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service
+func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine {
+ // If proof-of-authority is requested, set it up
+ if chainConfig.Clique != nil {
+ return clique.New(chainConfig.Clique, db)
+ }
+ // Otherwise assume proof-of-work
+ switch config.PowMode {
+ case ethash.ModeFake:
+ log.Warn("Ethash used in fake mode")
+ return ethash.NewFaker()
+ case ethash.ModeTest:
+ log.Warn("Ethash used in test mode")
+ return ethash.NewTester(nil, noverify)
+ case ethash.ModeShared:
+ log.Warn("Ethash used in shared mode")
+ return ethash.NewShared()
+ default:
+ engine := ethash.New(ethash.Config{
+ CacheDir: ctx.ResolvePath(config.CacheDir),
+ CachesInMem: config.CachesInMem,
+ CachesOnDisk: config.CachesOnDisk,
+ DatasetDir: config.DatasetDir,
+ DatasetsInMem: config.DatasetsInMem,
+ DatasetsOnDisk: config.DatasetsOnDisk,
+ }, notify, noverify)
+ engine.SetThreads(-1) // Disable CPU mining
+ return engine
+ }
+}
+
+// APIs return the collection of RPC services the ethereum package offers.
+// NOTE, some of these services probably need to be moved to somewhere else.
+func (s *Ethereum) APIs() []rpc.API {
+ apis := ethapi.GetAPIs(s.APIBackend)
+
+ // Append any APIs exposed explicitly by the les server
+ if s.lesServer != nil {
+ apis = append(apis, s.lesServer.APIs()...)
+ }
+ // Append any APIs exposed explicitly by the consensus engine
+ apis = append(apis, s.engine.APIs(s.BlockChain())...)
+
+ // Append any APIs exposed explicitly by the les server
+ if s.lesServer != nil {
+ apis = append(apis, s.lesServer.APIs()...)
+ }
+
+ // Append all the local APIs and return
+ return append(apis, []rpc.API{
+ {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: NewPublicEthereumAPI(s),
+ Public: true,
+ }, {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: NewPublicMinerAPI(s),
+ Public: true,
+ }, {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux),
+ Public: true,
+ }, {
+ Namespace: "miner",
+ Version: "1.0",
+ Service: NewPrivateMinerAPI(s),
+ Public: false,
+ }, {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: filters.NewPublicFilterAPI(s.APIBackend, false),
+ Public: true,
+ }, {
+ Namespace: "admin",
+ Version: "1.0",
+ Service: NewPrivateAdminAPI(s),
+ }, {
+ Namespace: "debug",
+ Version: "1.0",
+ Service: NewPublicDebugAPI(s),
+ Public: true,
+ }, {
+ Namespace: "debug",
+ Version: "1.0",
+ Service: NewPrivateDebugAPI(s),
+ }, {
+ Namespace: "net",
+ Version: "1.0",
+ Service: s.netRPCService,
+ Public: true,
+ },
+ }...)
+}
+
+func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
+ s.blockchain.ResetWithGenesisBlock(gb)
+}
+
+func (s *Ethereum) Etherbase() (eb common.Address, err error) {
+ s.lock.RLock()
+ etherbase := s.etherbase
+ s.lock.RUnlock()
+
+ if etherbase != (common.Address{}) {
+ return etherbase, nil
+ }
+ if wallets := s.AccountManager().Wallets(); len(wallets) > 0 {
+ if accounts := wallets[0].Accounts(); len(accounts) > 0 {
+ etherbase := accounts[0].Address
+
+ s.lock.Lock()
+ s.etherbase = etherbase
+ s.lock.Unlock()
+
+ log.Info("Etherbase automatically configured", "address", etherbase)
+ return etherbase, nil
+ }
+ }
+ return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
+}
+
+// isLocalBlock checks whether the specified block is mined
+// by local miner accounts.
+//
+// We regard two types of accounts as local miner account: etherbase
+// and accounts specified via `txpool.locals` flag.
+func (s *Ethereum) isLocalBlock(block *types.Block) bool {
+ author, err := s.engine.Author(block.Header())
+ if err != nil {
+ log.Warn("Failed to retrieve block author", "number", block.NumberU64(), "hash", block.Hash(), "err", err)
+ return false
+ }
+ // Check whether the given address is etherbase.
+ s.lock.RLock()
+ etherbase := s.etherbase
+ s.lock.RUnlock()
+ if author == etherbase {
+ return true
+ }
+ // Check whether the given address is specified by `txpool.local`
+ // CLI flag.
+ for _, account := range s.config.TxPool.Locals {
+ if account == author {
+ return true
+ }
+ }
+ return false
+}
+
+// shouldPreserve checks whether we should preserve the given block
+// during the chain reorg depending on whether the author of block
+// is a local account.
+func (s *Ethereum) shouldPreserve(block *types.Block) bool {
+ // The reason we need to disable the self-reorg preserving for clique
+ // is it can be probable to introduce a deadlock.
+ //
+ // e.g. If there are 7 available signers
+ //
+ // r1 A
+ // r2 B
+ // r3 C
+ // r4 D
+ // r5 A [X] F G
+ // r6 [X]
+ //
+ // In the round5, the inturn signer E is offline, so the worst case
+ // is A, F and G sign the block of round5 and reject the block of opponents
+ // and in the round6, the last available signer B is offline, the whole
+ // network is stuck.
+ if _, ok := s.engine.(*clique.Clique); ok {
+ return false
+ }
+ return s.isLocalBlock(block)
+}
+
+// SetEtherbase sets the mining reward address.
+func (s *Ethereum) SetEtherbase(etherbase common.Address) {
+ s.lock.Lock()
+ s.etherbase = etherbase
+ s.lock.Unlock()
+
+ s.miner.SetEtherbase(etherbase)
+}
+
+// StartMining starts the miner with the given number of CPU threads. If mining
+// is already running, this method adjust the number of threads allowed to use
+// and updates the minimum price required by the transaction pool.
+func (s *Ethereum) StartMining(threads int) error {
+ // Update the thread count within the consensus engine
+ type threaded interface {
+ SetThreads(threads int)
+ }
+ if th, ok := s.engine.(threaded); ok {
+ log.Info("Updated mining threads", "threads", threads)
+ if threads == 0 {
+ threads = -1 // Disable the miner from within
+ }
+ th.SetThreads(threads)
+ }
+ // If the miner was not running, initialize it
+ if !s.IsMining() {
+ // Propagate the initial price point to the transaction pool
+ s.lock.RLock()
+ price := s.gasPrice
+ s.lock.RUnlock()
+ s.txPool.SetGasPrice(price)
+
+ // Configure the local mining address
+ eb, err := s.Etherbase()
+ if err != nil {
+ log.Error("Cannot start mining without etherbase", "err", err)
+ return fmt.Errorf("etherbase missing: %v", err)
+ }
+ if clique, ok := s.engine.(*clique.Clique); ok {
+ wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
+ if wallet == nil || err != nil {
+ log.Error("Etherbase account unavailable locally", "err", err)
+ return fmt.Errorf("signer missing: %v", err)
+ }
+ clique.Authorize(eb, wallet.SignData)
+ }
+ // If mining is started, we can disable the transaction rejection mechanism
+ // introduced to speed sync times.
+ atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
+
+ go s.miner.Start(eb)
+ }
+ return nil
+}
+
+// StopMining terminates the miner, both at the consensus engine level as well as
+// at the block creation level.
+func (s *Ethereum) StopMining() {
+ // Update the thread count within the consensus engine
+ type threaded interface {
+ SetThreads(threads int)
+ }
+ if th, ok := s.engine.(threaded); ok {
+ th.SetThreads(-1)
+ }
+ // Stop the block creating itself
+ s.miner.Stop()
+}
+
+func (s *Ethereum) IsMining() bool { return s.miner.Mining() }
+func (s *Ethereum) Miner() *miner.Miner { return s.miner }
+
+func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
+func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
+func (s *Ethereum) TxPool() *core.TxPool { return s.txPool }
+func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
+func (s *Ethereum) Engine() consensus.Engine { return s.engine }
+func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
+func (s *Ethereum) IsListening() bool { return true } // Always listening
+func (s *Ethereum) EthVersion() int { return int(ProtocolVersions[0]) }
+func (s *Ethereum) NetVersion() uint64 { return s.networkID }
+func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
+func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 }
+func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning }
+
+// Protocols implements node.Service, returning all the currently configured
+// network protocols to start.
+func (s *Ethereum) Protocols() []p2p.Protocol {
+ protos := make([]p2p.Protocol, len(ProtocolVersions))
+ for i, vsn := range ProtocolVersions {
+ protos[i] = s.protocolManager.makeProtocol(vsn)
+ protos[i].Attributes = []enr.Entry{s.currentEthEntry()}
+ }
+ if s.lesServer != nil {
+ protos = append(protos, s.lesServer.Protocols()...)
+ }
+ return protos
+}
+
+// Start implements node.Service, starting all internal goroutines needed by the
+// Ethereum protocol implementation.
+func (s *Ethereum) Start(srvr *p2p.Server) error {
+ s.startEthEntryUpdate(srvr.LocalNode())
+
+ // Start the bloom bits servicing goroutines
+ s.startBloomHandlers(params.BloomBitsBlocks)
+
+ // Start the RPC service
+ s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.NetVersion())
+
+ // Figure out a max peers count based on the server limits
+ maxPeers := srvr.MaxPeers
+ if s.config.LightServ > 0 {
+ if s.config.LightPeers >= srvr.MaxPeers {
+ return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, srvr.MaxPeers)
+ }
+ maxPeers -= s.config.LightPeers
+ }
+ // Start the networking layer and the light server if requested
+ s.protocolManager.Start(maxPeers)
+ if s.lesServer != nil {
+ s.lesServer.Start(srvr)
+ }
+ return nil
+}
+
+// Stop implements node.Service, terminating all internal goroutines used by the
+// Ethereum protocol.
+func (s *Ethereum) Stop() error {
+ s.bloomIndexer.Close()
+ s.blockchain.Stop()
+ s.engine.Close()
+ s.protocolManager.Stop()
+ if s.lesServer != nil {
+ s.lesServer.Stop()
+ }
+ s.txPool.Stop()
+ s.miner.Stop()
+ s.eventMux.Stop()
+
+ s.chainDb.Close()
+ close(s.shutdownChan)
+ return nil
+}
diff --git a/eth/bloombits.go b/eth/bloombits.go
new file mode 100644
index 0000000..9a31997
--- /dev/null
+++ b/eth/bloombits.go
@@ -0,0 +1,138 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "context"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/bitutil"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/bloombits"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/ethdb"
+)
+
+const (
+ // bloomServiceThreads is the number of goroutines used globally by an Ethereum
+ // instance to service bloombits lookups for all running filters.
+ bloomServiceThreads = 16
+
+ // bloomFilterThreads is the number of goroutines used locally per filter to
+ // multiplex requests onto the global servicing goroutines.
+ bloomFilterThreads = 3
+
+ // bloomRetrievalBatch is the maximum number of bloom bit retrievals to service
+ // in a single batch.
+ bloomRetrievalBatch = 16
+
+ // bloomRetrievalWait is the maximum time to wait for enough bloom bit requests
+ // to accumulate request an entire batch (avoiding hysteresis).
+ bloomRetrievalWait = time.Duration(0)
+)
+
+// startBloomHandlers starts a batch of goroutines to accept bloom bit database
+// retrievals from possibly a range of filters and serving the data to satisfy.
+func (eth *Ethereum) startBloomHandlers(sectionSize uint64) {
+ for i := 0; i < bloomServiceThreads; i++ {
+ go func() {
+ for {
+ select {
+ case <-eth.shutdownChan:
+ return
+
+ case request := <-eth.bloomRequests:
+ task := <-request
+ task.Bitsets = make([][]byte, len(task.Sections))
+ for i, section := range task.Sections {
+ head := rawdb.ReadCanonicalHash(eth.chainDb, (section+1)*sectionSize-1)
+ if compVector, err := rawdb.ReadBloomBits(eth.chainDb, task.Bit, section, head); err == nil {
+ if blob, err := bitutil.DecompressBytes(compVector, int(sectionSize/8)); err == nil {
+ task.Bitsets[i] = blob
+ } else {
+ task.Error = err
+ }
+ } else {
+ task.Error = err
+ }
+ }
+ request <- task
+ }
+ }
+ }()
+ }
+}
+
+const (
+ // bloomThrottling is the time to wait between processing two consecutive index
+ // sections. It's useful during chain upgrades to prevent disk overload.
+ bloomThrottling = 100 * time.Millisecond
+)
+
+// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index
+// for the Ethereum header bloom filters, permitting blazing fast filtering.
+type BloomIndexer struct {
+ size uint64 // section size to generate bloombits for
+ db ethdb.Database // database instance to write index data and metadata into
+ gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index
+ section uint64 // Section is the section number being processed currently
+ head common.Hash // Head is the hash of the last header processed
+}
+
+// NewBloomIndexer returns a chain indexer that generates bloom bits data for the
+// canonical chain for fast logs filtering.
+func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *core.ChainIndexer {
+ backend := &BloomIndexer{
+ db: db,
+ size: size,
+ }
+ table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix))
+
+ return core.NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits")
+}
+
+// Reset implements core.ChainIndexerBackend, starting a new bloombits index
+// section.
+func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
+ gen, err := bloombits.NewGenerator(uint(b.size))
+ b.gen, b.section, b.head = gen, section, common.Hash{}
+ return err
+}
+
+// Process implements core.ChainIndexerBackend, adding a new header's bloom into
+// the index.
+func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error {
+ b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom)
+ b.head = header.Hash()
+ return nil
+}
+
+// Commit implements core.ChainIndexerBackend, finalizing the bloom section and
+// writing it out into the database.
+func (b *BloomIndexer) Commit() error {
+ batch := b.db.NewBatch()
+ for i := 0; i < types.BloomBitLength; i++ {
+ bits, err := b.gen.Bitset(uint(i))
+ if err != nil {
+ return err
+ }
+ rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits))
+ }
+ return batch.Write()
+}
diff --git a/eth/config.go b/eth/config.go
new file mode 100644
index 0000000..6887872
--- /dev/null
+++ b/eth/config.go
@@ -0,0 +1,157 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "math/big"
+ "os"
+ "os/user"
+ "path/filepath"
+ "runtime"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/gasprice"
+ "github.com/ethereum/go-ethereum/miner"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// DefaultConfig contains default settings for use on the Ethereum main net.
+var DefaultConfig = Config{
+ SyncMode: downloader.FastSync,
+ Ethash: ethash.Config{
+ CacheDir: "ethash",
+ CachesInMem: 2,
+ CachesOnDisk: 3,
+ DatasetsInMem: 1,
+ DatasetsOnDisk: 2,
+ },
+ NetworkId: 1,
+ LightPeers: 100,
+ UltraLightFraction: 75,
+ DatabaseCache: 512,
+ TrieCleanCache: 256,
+ TrieDirtyCache: 256,
+ TrieTimeout: 60 * time.Minute,
+ Miner: miner.Config{
+ GasFloor: 8000000,
+ GasCeil: 8000000,
+ GasPrice: big.NewInt(params.GWei),
+ Recommit: 3 * time.Second,
+ },
+ TxPool: core.DefaultTxPoolConfig,
+ GPO: gasprice.Config{
+ Blocks: 20,
+ Percentile: 60,
+ },
+}
+
+func init() {
+ home := os.Getenv("HOME")
+ if home == "" {
+ if user, err := user.Current(); err == nil {
+ home = user.HomeDir
+ }
+ }
+ if runtime.GOOS == "darwin" {
+ DefaultConfig.Ethash.DatasetDir = filepath.Join(home, "Library", "Ethash")
+ } else if runtime.GOOS == "windows" {
+ localappdata := os.Getenv("LOCALAPPDATA")
+ if localappdata != "" {
+ DefaultConfig.Ethash.DatasetDir = filepath.Join(localappdata, "Ethash")
+ } else {
+ DefaultConfig.Ethash.DatasetDir = filepath.Join(home, "AppData", "Local", "Ethash")
+ }
+ } else {
+ DefaultConfig.Ethash.DatasetDir = filepath.Join(home, ".ethash")
+ }
+}
+
+//go:generate gencodec -type Config -formats toml -out gen_config.go
+
+type Config struct {
+ // The genesis block, which is inserted if the database is empty.
+ // If nil, the Ethereum main net block is used.
+ Genesis *core.Genesis `toml:",omitempty"`
+
+ // Protocol options
+ NetworkId uint64 // Network ID to use for selecting peers to connect to
+ SyncMode downloader.SyncMode
+
+ NoPruning bool // Whether to disable pruning and flush everything to disk
+ NoPrefetch bool // Whether to disable prefetching and only load state on demand
+
+ // Whitelist of required block number -> hash values to accept
+ Whitelist map[uint64]common.Hash `toml:"-"`
+
+ // Light client options
+ LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
+ LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers
+ LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers
+ LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
+
+ // Ultra Light client options
+ UltraLightServers []string `toml:",omitempty"` // List of trusted ultra light servers
+ UltraLightFraction int `toml:",omitempty"` // Percentage of trusted servers to accept an announcement
+ UltraLightOnlyAnnounce bool `toml:",omitempty"` // Whether to only announce headers, or also serve them
+
+ // Database options
+ SkipBcVersionCheck bool `toml:"-"`
+ DatabaseHandles int `toml:"-"`
+ DatabaseCache int
+ DatabaseFreezer string
+
+ TrieCleanCache int
+ TrieDirtyCache int
+ TrieTimeout time.Duration
+
+ // Mining options
+ Miner miner.Config
+
+ // Ethash options
+ Ethash ethash.Config
+
+ // Transaction pool options
+ TxPool core.TxPoolConfig
+
+ // Gas Price Oracle options
+ GPO gasprice.Config
+
+ // Enables tracking of SHA3 preimages in the VM
+ EnablePreimageRecording bool
+
+ // Miscellaneous options
+ DocRoot string `toml:"-"`
+
+ // Type of the EWASM interpreter ("" for default)
+ EWASMInterpreter string
+
+ // Type of the EVM interpreter ("" for default)
+ EVMInterpreter string
+
+ // RPCGasCap is the global gas cap for eth-call variants.
+ RPCGasCap *big.Int `toml:",omitempty"`
+
+ // Checkpoint is a hardcoded checkpoint which can be nil.
+ Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
+
+ // CheckpointOracle is the configuration for checkpoint oracle.
+ CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
+}
diff --git a/eth/enr_entry.go b/eth/enr_entry.go
new file mode 100644
index 0000000..d9e7b95
--- /dev/null
+++ b/eth/enr_entry.go
@@ -0,0 +1,61 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/forkid"
+ "github.com/ethereum/go-ethereum/p2p/enode"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+// ethEntry is the "eth" ENR entry which advertises eth protocol
+// on the discovery network.
+type ethEntry struct {
+ ForkID forkid.ID // Fork identifier per EIP-2124
+
+ // Ignore additional fields (for forward compatibility).
+ Rest []rlp.RawValue `rlp:"tail"`
+}
+
+// ENRKey implements enr.Entry.
+func (e ethEntry) ENRKey() string {
+ return "eth"
+}
+
+func (eth *Ethereum) startEthEntryUpdate(ln *enode.LocalNode) {
+ var newHead = make(chan core.ChainHeadEvent, 10)
+ sub := eth.blockchain.SubscribeChainHeadEvent(newHead)
+
+ go func() {
+ defer sub.Unsubscribe()
+ for {
+ select {
+ case <-newHead:
+ ln.Set(eth.currentEthEntry())
+ case <-sub.Err():
+ // Would be nice to sync with eth.Stop, but there is no
+ // good way to do that.
+ return
+ }
+ }
+ }()
+}
+
+func (eth *Ethereum) currentEthEntry() *ethEntry {
+ return &ethEntry{ForkID: forkid.NewID(eth.blockchain)}
+}
diff --git a/eth/gen_config.go b/eth/gen_config.go
new file mode 100644
index 0000000..bc4b55b
--- /dev/null
+++ b/eth/gen_config.go
@@ -0,0 +1,221 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package eth
+
+import (
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/gasprice"
+ "github.com/ethereum/go-ethereum/miner"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// MarshalTOML marshals as TOML.
+func (c Config) MarshalTOML() (interface{}, error) {
+ type Config struct {
+ Genesis *core.Genesis `toml:",omitempty"`
+ NetworkId uint64
+ SyncMode downloader.SyncMode
+ NoPruning bool
+ NoPrefetch bool
+ Whitelist map[uint64]common.Hash `toml:"-"`
+ LightServ int `toml:",omitempty"`
+ LightIngress int `toml:",omitempty"`
+ LightEgress int `toml:",omitempty"`
+ LightPeers int `toml:",omitempty"`
+ UltraLightServers []string `toml:",omitempty"`
+ UltraLightFraction int `toml:",omitempty"`
+ UltraLightOnlyAnnounce bool `toml:",omitempty"`
+ SkipBcVersionCheck bool `toml:"-"`
+ DatabaseHandles int `toml:"-"`
+ DatabaseCache int
+ DatabaseFreezer string
+ TrieCleanCache int
+ TrieDirtyCache int
+ TrieTimeout time.Duration
+ Miner miner.Config
+ Ethash ethash.Config
+ TxPool core.TxPoolConfig
+ GPO gasprice.Config
+ EnablePreimageRecording bool
+ DocRoot string `toml:"-"`
+ EWASMInterpreter string
+ EVMInterpreter string
+ RPCGasCap *big.Int `toml:",omitempty"`
+ Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
+ CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
+ }
+ var enc Config
+ enc.Genesis = c.Genesis
+ enc.NetworkId = c.NetworkId
+ enc.SyncMode = c.SyncMode
+ enc.NoPruning = c.NoPruning
+ enc.NoPrefetch = c.NoPrefetch
+ enc.Whitelist = c.Whitelist
+ enc.LightServ = c.LightServ
+ enc.LightIngress = c.LightIngress
+ enc.LightEgress = c.LightEgress
+ enc.LightPeers = c.LightPeers
+ enc.UltraLightServers = c.UltraLightServers
+ enc.UltraLightFraction = c.UltraLightFraction
+ enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce
+ enc.SkipBcVersionCheck = c.SkipBcVersionCheck
+ enc.DatabaseHandles = c.DatabaseHandles
+ enc.DatabaseCache = c.DatabaseCache
+ enc.DatabaseFreezer = c.DatabaseFreezer
+ enc.TrieCleanCache = c.TrieCleanCache
+ enc.TrieDirtyCache = c.TrieDirtyCache
+ enc.TrieTimeout = c.TrieTimeout
+ enc.Miner = c.Miner
+ enc.Ethash = c.Ethash
+ enc.TxPool = c.TxPool
+ enc.GPO = c.GPO
+ enc.EnablePreimageRecording = c.EnablePreimageRecording
+ enc.DocRoot = c.DocRoot
+ enc.EWASMInterpreter = c.EWASMInterpreter
+ enc.EVMInterpreter = c.EVMInterpreter
+ enc.RPCGasCap = c.RPCGasCap
+ enc.Checkpoint = c.Checkpoint
+ enc.CheckpointOracle = c.CheckpointOracle
+ return &enc, nil
+}
+
+// UnmarshalTOML unmarshals from TOML.
+func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
+ type Config struct {
+ Genesis *core.Genesis `toml:",omitempty"`
+ NetworkId *uint64
+ SyncMode *downloader.SyncMode
+ NoPruning *bool
+ NoPrefetch *bool
+ Whitelist map[uint64]common.Hash `toml:"-"`
+ LightServ *int `toml:",omitempty"`
+ LightIngress *int `toml:",omitempty"`
+ LightEgress *int `toml:",omitempty"`
+ LightPeers *int `toml:",omitempty"`
+ UltraLightServers []string `toml:",omitempty"`
+ UltraLightFraction *int `toml:",omitempty"`
+ UltraLightOnlyAnnounce *bool `toml:",omitempty"`
+ SkipBcVersionCheck *bool `toml:"-"`
+ DatabaseHandles *int `toml:"-"`
+ DatabaseCache *int
+ DatabaseFreezer *string
+ TrieCleanCache *int
+ TrieDirtyCache *int
+ TrieTimeout *time.Duration
+ Miner *miner.Config
+ Ethash *ethash.Config
+ TxPool *core.TxPoolConfig
+ GPO *gasprice.Config
+ EnablePreimageRecording *bool
+ DocRoot *string `toml:"-"`
+ EWASMInterpreter *string
+ EVMInterpreter *string
+ RPCGasCap *big.Int `toml:",omitempty"`
+ Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
+ CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
+ }
+ var dec Config
+ if err := unmarshal(&dec); err != nil {
+ return err
+ }
+ if dec.Genesis != nil {
+ c.Genesis = dec.Genesis
+ }
+ if dec.NetworkId != nil {
+ c.NetworkId = *dec.NetworkId
+ }
+ if dec.SyncMode != nil {
+ c.SyncMode = *dec.SyncMode
+ }
+ if dec.NoPruning != nil {
+ c.NoPruning = *dec.NoPruning
+ }
+ if dec.NoPrefetch != nil {
+ c.NoPrefetch = *dec.NoPrefetch
+ }
+ if dec.Whitelist != nil {
+ c.Whitelist = dec.Whitelist
+ }
+ if dec.LightServ != nil {
+ c.LightServ = *dec.LightServ
+ }
+ if dec.LightIngress != nil {
+ c.LightIngress = *dec.LightIngress
+ }
+ if dec.LightEgress != nil {
+ c.LightEgress = *dec.LightEgress
+ }
+ if dec.LightPeers != nil {
+ c.LightPeers = *dec.LightPeers
+ }
+ if dec.UltraLightServers != nil {
+ c.UltraLightServers = dec.UltraLightServers
+ }
+ if dec.UltraLightFraction != nil {
+ c.UltraLightFraction = *dec.UltraLightFraction
+ }
+ if dec.UltraLightOnlyAnnounce != nil {
+ c.UltraLightOnlyAnnounce = *dec.UltraLightOnlyAnnounce
+ }
+ if dec.SkipBcVersionCheck != nil {
+ c.SkipBcVersionCheck = *dec.SkipBcVersionCheck
+ }
+ if dec.DatabaseHandles != nil {
+ c.DatabaseHandles = *dec.DatabaseHandles
+ }
+ if dec.DatabaseCache != nil {
+ c.DatabaseCache = *dec.DatabaseCache
+ }
+ if dec.DatabaseFreezer != nil {
+ c.DatabaseFreezer = *dec.DatabaseFreezer
+ }
+ if dec.TrieCleanCache != nil {
+ c.TrieCleanCache = *dec.TrieCleanCache
+ }
+ if dec.TrieDirtyCache != nil {
+ c.TrieDirtyCache = *dec.TrieDirtyCache
+ }
+ if dec.TrieTimeout != nil {
+ c.TrieTimeout = *dec.TrieTimeout
+ }
+ if dec.Miner != nil {
+ c.Miner = *dec.Miner
+ }
+ if dec.Ethash != nil {
+ c.Ethash = *dec.Ethash
+ }
+ if dec.TxPool != nil {
+ c.TxPool = *dec.TxPool
+ }
+ if dec.GPO != nil {
+ c.GPO = *dec.GPO
+ }
+ if dec.EnablePreimageRecording != nil {
+ c.EnablePreimageRecording = *dec.EnablePreimageRecording
+ }
+ if dec.DocRoot != nil {
+ c.DocRoot = *dec.DocRoot
+ }
+ if dec.EWASMInterpreter != nil {
+ c.EWASMInterpreter = *dec.EWASMInterpreter
+ }
+ if dec.EVMInterpreter != nil {
+ c.EVMInterpreter = *dec.EVMInterpreter
+ }
+ if dec.RPCGasCap != nil {
+ c.RPCGasCap = dec.RPCGasCap
+ }
+ if dec.Checkpoint != nil {
+ c.Checkpoint = dec.Checkpoint
+ }
+ if dec.CheckpointOracle != nil {
+ c.CheckpointOracle = dec.CheckpointOracle
+ }
+ return nil
+}
diff --git a/eth/handler.go b/eth/handler.go
new file mode 100644
index 0000000..4ce2d1c
--- /dev/null
+++ b/eth/handler.go
@@ -0,0 +1,844 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math"
+ "math/big"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/fetcher"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/p2p/enode"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/trie"
+)
+
+const (
+ softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
+ estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
+
+ // txChanSize is the size of channel listening to NewTxsEvent.
+ // The number is referenced from the size of tx pool.
+ txChanSize = 4096
+
+ // minimim number of peers to broadcast new blocks to
+ minBroadcastPeers = 4
+)
+
+var (
+ syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge
+)
+
+func errResp(code errCode, format string, v ...interface{}) error {
+ return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
+}
+
+type ProtocolManager struct {
+ networkID uint64
+
+ fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks)
+ acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing)
+
+ checkpointNumber uint64 // Block number for the sync progress validator to cross reference
+ checkpointHash common.Hash // Block hash for the sync progress validator to cross reference
+
+ txpool txPool
+ blockchain *core.BlockChain
+ maxPeers int
+
+ downloader *downloader.Downloader
+ fetcher *fetcher.Fetcher
+ peers *peerSet
+
+ eventMux *event.TypeMux
+ txsCh chan core.NewTxsEvent
+ txsSub event.Subscription
+ minedBlockSub *event.TypeMuxSubscription
+
+ whitelist map[uint64]common.Hash
+
+ // channels for fetcher, syncer, txsyncLoop
+ newPeerCh chan *peer
+ txsyncCh chan *txsync
+ quitSync chan struct{}
+ noMorePeers chan struct{}
+
+ // wait group is used for graceful shutdowns during downloading
+ // and processing
+ wg sync.WaitGroup
+}
+
+// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
+// with the Ethereum network.
+func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash) (*ProtocolManager, error) {
+ // Create the protocol manager with the base fields
+ manager := &ProtocolManager{
+ networkID: networkID,
+ eventMux: mux,
+ txpool: txpool,
+ blockchain: blockchain,
+ peers: newPeerSet(),
+ whitelist: whitelist,
+ newPeerCh: make(chan *peer),
+ noMorePeers: make(chan struct{}),
+ txsyncCh: make(chan *txsync),
+ quitSync: make(chan struct{}),
+ }
+ if mode == downloader.FullSync {
+ // The database seems empty as the current block is the genesis. Yet the fast
+ // block is ahead, so fast sync was enabled for this node at a certain point.
+ // The scenarios where this can happen is
+ // * if the user manually (or via a bad block) rolled back a fast sync node
+ // below the sync point.
+ // * the last fast sync is not finished while user specifies a full sync this
+ // time. But we don't have any recent state for full sync.
+ // In these cases however it's safe to reenable fast sync.
+ fullBlock, fastBlock := blockchain.CurrentBlock(), blockchain.CurrentFastBlock()
+ if fullBlock.NumberU64() == 0 && fastBlock.NumberU64() > 0 {
+ manager.fastSync = uint32(1)
+ log.Warn("Switch sync mode from full sync to fast sync")
+ }
+ } else {
+ if blockchain.CurrentBlock().NumberU64() > 0 {
+ // Print warning log if database is not empty to run fast sync.
+ log.Warn("Switch sync mode from fast sync to full sync")
+ } else {
+ // If fast sync was requested and our database is empty, grant it
+ manager.fastSync = uint32(1)
+ }
+ }
+ // If we have trusted checkpoints, enforce them on the chain
+ if checkpoint != nil {
+ manager.checkpointNumber = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1
+ manager.checkpointHash = checkpoint.SectionHead
+ }
+
+ // Construct the downloader (long sync) and its backing state bloom if fast
+ // sync is requested. The downloader is responsible for deallocating the state
+ // bloom when it's done.
+ var stateBloom *trie.SyncBloom
+ if atomic.LoadUint32(&manager.fastSync) == 1 {
+ stateBloom = trie.NewSyncBloom(uint64(cacheLimit), chaindb)
+ }
+ manager.downloader = downloader.New(manager.checkpointNumber, chaindb, stateBloom, manager.eventMux, blockchain, nil, manager.removePeer)
+
+ // Construct the fetcher (short sync)
+ validator := func(header *types.Header) error {
+ return engine.VerifyHeader(blockchain, header, true)
+ }
+ heighter := func() uint64 {
+ return blockchain.CurrentBlock().NumberU64()
+ }
+ inserter := func(blocks types.Blocks) (int, error) {
+ // If sync hasn't reached the checkpoint yet, deny importing weird blocks.
+ //
+ // Ideally we would also compare the head block's timestamp and similarly reject
+ // the propagated block if the head is too old. Unfortunately there is a corner
+ // case when starting new networks, where the genesis might be ancient (0 unix)
+ // which would prevent full nodes from accepting it.
+ if manager.blockchain.CurrentBlock().NumberU64() < manager.checkpointNumber {
+ log.Warn("Unsynced yet, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
+ return 0, nil
+ }
+ // If fast sync is running, deny importing weird blocks. This is a problematic
+ // clause when starting up a new network, because fast-syncing miners might not
+ // accept each others' blocks until a restart. Unfortunately we haven't figured
+ // out a way yet where nodes can decide unilaterally whether the network is new
+ // or not. This should be fixed if we figure out a solution.
+ if atomic.LoadUint32(&manager.fastSync) == 1 {
+ log.Warn("Fast syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
+ return 0, nil
+ }
+ n, err := manager.blockchain.InsertChain(blocks)
+ if err == nil {
+ atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
+ }
+ return n, err
+ }
+ manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer)
+
+ return manager, nil
+}
+
+func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol {
+ length, ok := protocolLengths[version]
+ if !ok {
+ panic("makeProtocol for unknown version")
+ }
+
+ return p2p.Protocol{
+ Name: protocolName,
+ Version: version,
+ Length: length,
+ Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
+ peer := pm.newPeer(int(version), p, rw)
+ select {
+ case pm.newPeerCh <- peer:
+ pm.wg.Add(1)
+ defer pm.wg.Done()
+ return pm.handle(peer)
+ case <-pm.quitSync:
+ return p2p.DiscQuitting
+ }
+ },
+ NodeInfo: func() interface{} {
+ return pm.NodeInfo()
+ },
+ PeerInfo: func(id enode.ID) interface{} {
+ if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
+ return p.Info()
+ }
+ return nil
+ },
+ }
+}
+
+func (pm *ProtocolManager) removePeer(id string) {
+ // Short circuit if the peer was already removed
+ peer := pm.peers.Peer(id)
+ if peer == nil {
+ return
+ }
+ log.Debug("Removing Ethereum peer", "peer", id)
+
+ // Unregister the peer from the downloader and Ethereum peer set
+ pm.downloader.UnregisterPeer(id)
+ if err := pm.peers.Unregister(id); err != nil {
+ log.Error("Peer removal failed", "peer", id, "err", err)
+ }
+ // Hard disconnect at the networking layer
+ if peer != nil {
+ peer.Peer.Disconnect(p2p.DiscUselessPeer)
+ }
+}
+
+func (pm *ProtocolManager) Start(maxPeers int) {
+ pm.maxPeers = maxPeers
+
+ // broadcast transactions
+ pm.txsCh = make(chan core.NewTxsEvent, txChanSize)
+ pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh)
+ go pm.txBroadcastLoop()
+
+ // broadcast mined blocks
+ pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{})
+ go pm.minedBroadcastLoop()
+
+ // start sync handlers
+ go pm.syncer()
+ go pm.txsyncLoop()
+}
+
+func (pm *ProtocolManager) Stop() {
+ log.Info("Stopping Ethereum protocol")
+
+ pm.txsSub.Unsubscribe() // quits txBroadcastLoop
+ pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop
+
+ // Quit the sync loop.
+ // After this send has completed, no new peers will be accepted.
+ pm.noMorePeers <- struct{}{}
+
+ // Quit fetcher, txsyncLoop.
+ close(pm.quitSync)
+
+ // Disconnect existing sessions.
+ // This also closes the gate for any new registrations on the peer set.
+ // sessions which are already established but not added to pm.peers yet
+ // will exit when they try to register.
+ pm.peers.Close()
+
+ // Wait for all peer handler goroutines and the loops to come down.
+ pm.wg.Wait()
+
+ log.Info("Ethereum protocol stopped")
+}
+
+func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
+ return newPeer(pv, p, newMeteredMsgWriter(rw))
+}
+
+// handle is the callback invoked to manage the life cycle of an eth peer. When
+// this function terminates, the peer is disconnected.
+func (pm *ProtocolManager) handle(p *peer) error {
+ // Ignore maxPeers if this is a trusted peer
+ if pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted {
+ return p2p.DiscTooManyPeers
+ }
+ p.Log().Debug("Ethereum peer connected", "name", p.Name())
+
+ // Execute the Ethereum handshake
+ var (
+ genesis = pm.blockchain.Genesis()
+ head = pm.blockchain.CurrentHeader()
+ hash = head.Hash()
+ number = head.Number.Uint64()
+ td = pm.blockchain.GetTd(hash, number)
+ )
+ if err := p.Handshake(pm.networkID, td, hash, genesis.Hash()); err != nil {
+ p.Log().Debug("Ethereum handshake failed", "err", err)
+ return err
+ }
+ if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
+ rw.Init(p.version)
+ }
+ // Register the peer locally
+ if err := pm.peers.Register(p); err != nil {
+ p.Log().Error("Ethereum peer registration failed", "err", err)
+ return err
+ }
+ defer pm.removePeer(p.id)
+
+ // Register the peer in the downloader. If the downloader considers it banned, we disconnect
+ if err := pm.downloader.RegisterPeer(p.id, p.version, p); err != nil {
+ return err
+ }
+ // Propagate existing transactions. new transactions appearing
+ // after this will be sent via broadcasts.
+ pm.syncTransactions(p)
+
+ // If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse)
+ if pm.checkpointHash != (common.Hash{}) {
+ // Request the peer's checkpoint header for chain height/weight validation
+ if err := p.RequestHeadersByNumber(pm.checkpointNumber, 1, 0, false); err != nil {
+ return err
+ }
+ // Start a timer to disconnect if the peer doesn't reply in time
+ p.syncDrop = time.AfterFunc(syncChallengeTimeout, func() {
+ p.Log().Warn("Checkpoint challenge timed out, dropping", "addr", p.RemoteAddr(), "type", p.Name())
+ pm.removePeer(p.id)
+ })
+ // Make sure it's cleaned up if the peer dies off
+ defer func() {
+ if p.syncDrop != nil {
+ p.syncDrop.Stop()
+ p.syncDrop = nil
+ }
+ }()
+ }
+ // If we have any explicit whitelist block hashes, request them
+ for number := range pm.whitelist {
+ if err := p.RequestHeadersByNumber(number, 1, 0, false); err != nil {
+ return err
+ }
+ }
+ // Handle incoming messages until the connection is torn down
+ for {
+ if err := pm.handleMsg(p); err != nil {
+ p.Log().Debug("Ethereum message handling failed", "err", err)
+ return err
+ }
+ }
+}
+
+// handleMsg is invoked whenever an inbound message is received from a remote
+// peer. The remote connection is torn down upon returning any error.
+func (pm *ProtocolManager) handleMsg(p *peer) error {
+ // Read the next message from the remote peer, and ensure it's fully consumed
+ msg, err := p.rw.ReadMsg()
+ if err != nil {
+ return err
+ }
+ if msg.Size > protocolMaxMsgSize {
+ return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
+ }
+ defer msg.Discard()
+
+ // Handle the message depending on its contents
+ switch {
+ case msg.Code == StatusMsg:
+ // Status messages should never arrive after the handshake
+ return errResp(ErrExtraStatusMsg, "uncontrolled status message")
+
+ // Block header query, collect the requested headers and reply
+ case msg.Code == GetBlockHeadersMsg:
+ // Decode the complex header query
+ var query getBlockHeadersData
+ if err := msg.Decode(&query); err != nil {
+ return errResp(ErrDecode, "%v: %v", msg, err)
+ }
+ hashMode := query.Origin.Hash != (common.Hash{})
+ first := true
+ maxNonCanonical := uint64(100)
+
+ // Gather headers until the fetch or network limits is reached
+ var (
+ bytes common.StorageSize
+ headers []*types.Header
+ unknown bool
+ )
+ for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && len(headers) < downloader.MaxHeaderFetch {
+ // Retrieve the next header satisfying the query
+ var origin *types.Header
+ if hashMode {
+ if first {
+ first = false
+ origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash)
+ if origin != nil {
+ query.Origin.Number = origin.Number.Uint64()
+ }
+ } else {
+ origin = pm.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number)
+ }
+ } else {
+ origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number)
+ }
+ if origin == nil {
+ break
+ }
+ headers = append(headers, origin)
+ bytes += estHeaderRlpSize
+
+ // Advance to the next header of the query
+ switch {
+ case hashMode && query.Reverse:
+ // Hash based traversal towards the genesis block
+ ancestor := query.Skip + 1
+ if ancestor == 0 {
+ unknown = true
+ } else {
+ query.Origin.Hash, query.Origin.Number = pm.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical)
+ unknown = (query.Origin.Hash == common.Hash{})
+ }
+ case hashMode && !query.Reverse:
+ // Hash based traversal towards the leaf block
+ var (
+ current = origin.Number.Uint64()
+ next = current + query.Skip + 1
+ )
+ if next <= current {
+ infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ")
+ p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
+ unknown = true
+ } else {
+ if header := pm.blockchain.GetHeaderByNumber(next); header != nil {
+ nextHash := header.Hash()
+ expOldHash, _ := pm.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
+ if expOldHash == query.Origin.Hash {
+ query.Origin.Hash, query.Origin.Number = nextHash, next
+ } else {
+ unknown = true
+ }
+ } else {
+ unknown = true
+ }
+ }
+ case query.Reverse:
+ // Number based traversal towards the genesis block
+ if query.Origin.Number >= query.Skip+1 {
+ query.Origin.Number -= query.Skip + 1
+ } else {
+ unknown = true
+ }
+
+ case !query.Reverse:
+ // Number based traversal towards the leaf block
+ query.Origin.Number += query.Skip + 1
+ }
+ }
+ return p.SendBlockHeaders(headers)
+
+ case msg.Code == BlockHeadersMsg:
+ // A batch of headers arrived to one of our previous requests
+ var headers []*types.Header
+ if err := msg.Decode(&headers); err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ // If no headers were received, but we're expencting a checkpoint header, consider it that
+ if len(headers) == 0 && p.syncDrop != nil {
+ // Stop the timer either way, decide later to drop or not
+ p.syncDrop.Stop()
+ p.syncDrop = nil
+
+ // If we're doing a fast sync, we must enforce the checkpoint block to avoid
+ // eclipse attacks. Unsynced nodes are welcome to connect after we're done
+ // joining the network
+ if atomic.LoadUint32(&pm.fastSync) == 1 {
+ p.Log().Warn("Dropping unsynced node during fast sync", "addr", p.RemoteAddr(), "type", p.Name())
+ return errors.New("unsynced node cannot serve fast sync")
+ }
+ }
+ // Filter out any explicitly requested headers, deliver the rest to the downloader
+ filter := len(headers) == 1
+ if filter {
+ // If it's a potential sync progress check, validate the content and advertised chain weight
+ if p.syncDrop != nil && headers[0].Number.Uint64() == pm.checkpointNumber {
+ // Disable the sync drop timer
+ p.syncDrop.Stop()
+ p.syncDrop = nil
+
+ // Validate the header and either drop the peer or continue
+ if headers[0].Hash() != pm.checkpointHash {
+ return errors.New("checkpoint hash mismatch")
+ }
+ return nil
+ }
+ // Otherwise if it's a whitelisted block, validate against the set
+ if want, ok := pm.whitelist[headers[0].Number.Uint64()]; ok {
+ if hash := headers[0].Hash(); want != hash {
+ p.Log().Info("Whitelist mismatch, dropping peer", "number", headers[0].Number.Uint64(), "hash", hash, "want", want)
+ return errors.New("whitelist block mismatch")
+ }
+ p.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want)
+ }
+ // Irrelevant of the fork checks, send the header to the fetcher just in case
+ headers = pm.fetcher.FilterHeaders(p.id, headers, time.Now())
+ }
+ if len(headers) > 0 || !filter {
+ err := pm.downloader.DeliverHeaders(p.id, headers)
+ if err != nil {
+ log.Debug("Failed to deliver headers", "err", err)
+ }
+ }
+
+ case msg.Code == GetBlockBodiesMsg:
+ // Decode the retrieval message
+ msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
+ if _, err := msgStream.List(); err != nil {
+ return err
+ }
+ // Gather blocks until the fetch or network limits is reached
+ var (
+ hash common.Hash
+ bytes int
+ bodies []rlp.RawValue
+ )
+ for bytes < softResponseLimit && len(bodies) < downloader.MaxBlockFetch {
+ // Retrieve the hash of the next block
+ if err := msgStream.Decode(&hash); err == rlp.EOL {
+ break
+ } else if err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ // Retrieve the requested block body, stopping if enough was found
+ if data := pm.blockchain.GetBodyRLP(hash); len(data) != 0 {
+ bodies = append(bodies, data)
+ bytes += len(data)
+ }
+ }
+ return p.SendBlockBodiesRLP(bodies)
+
+ case msg.Code == BlockBodiesMsg:
+ // A batch of block bodies arrived to one of our previous requests
+ var request blockBodiesData
+ if err := msg.Decode(&request); err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ // Deliver them all to the downloader for queuing
+ transactions := make([][]*types.Transaction, len(request))
+ uncles := make([][]*types.Header, len(request))
+
+ for i, body := range request {
+ transactions[i] = body.Transactions
+ uncles[i] = body.Uncles
+ }
+ // Filter out any explicitly requested bodies, deliver the rest to the downloader
+ filter := len(transactions) > 0 || len(uncles) > 0
+ if filter {
+ transactions, uncles = pm.fetcher.FilterBodies(p.id, transactions, uncles, time.Now())
+ }
+ if len(transactions) > 0 || len(uncles) > 0 || !filter {
+ err := pm.downloader.DeliverBodies(p.id, transactions, uncles)
+ if err != nil {
+ log.Debug("Failed to deliver bodies", "err", err)
+ }
+ }
+
+ case p.version >= eth63 && msg.Code == GetNodeDataMsg:
+ // Decode the retrieval message
+ msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
+ if _, err := msgStream.List(); err != nil {
+ return err
+ }
+ // Gather state data until the fetch or network limits is reached
+ var (
+ hash common.Hash
+ bytes int
+ data [][]byte
+ )
+ for bytes < softResponseLimit && len(data) < downloader.MaxStateFetch {
+ // Retrieve the hash of the next state entry
+ if err := msgStream.Decode(&hash); err == rlp.EOL {
+ break
+ } else if err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ // Retrieve the requested state entry, stopping if enough was found
+ if entry, err := pm.blockchain.TrieNode(hash); err == nil {
+ data = append(data, entry)
+ bytes += len(entry)
+ }
+ }
+ return p.SendNodeData(data)
+
+ case p.version >= eth63 && msg.Code == NodeDataMsg:
+ // A batch of node state data arrived to one of our previous requests
+ var data [][]byte
+ if err := msg.Decode(&data); err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ // Deliver all to the downloader
+ if err := pm.downloader.DeliverNodeData(p.id, data); err != nil {
+ log.Debug("Failed to deliver node state data", "err", err)
+ }
+
+ case p.version >= eth63 && msg.Code == GetReceiptsMsg:
+ // Decode the retrieval message
+ msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
+ if _, err := msgStream.List(); err != nil {
+ return err
+ }
+ // Gather state data until the fetch or network limits is reached
+ var (
+ hash common.Hash
+ bytes int
+ receipts []rlp.RawValue
+ )
+ for bytes < softResponseLimit && len(receipts) < downloader.MaxReceiptFetch {
+ // Retrieve the hash of the next block
+ if err := msgStream.Decode(&hash); err == rlp.EOL {
+ break
+ } else if err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ // Retrieve the requested block's receipts, skipping if unknown to us
+ results := pm.blockchain.GetReceiptsByHash(hash)
+ if results == nil {
+ if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
+ continue
+ }
+ }
+ // If known, encode and queue for response packet
+ if encoded, err := rlp.EncodeToBytes(results); err != nil {
+ log.Error("Failed to encode receipt", "err", err)
+ } else {
+ receipts = append(receipts, encoded)
+ bytes += len(encoded)
+ }
+ }
+ return p.SendReceiptsRLP(receipts)
+
+ case p.version >= eth63 && msg.Code == ReceiptsMsg:
+ // A batch of receipts arrived to one of our previous requests
+ var receipts [][]*types.Receipt
+ if err := msg.Decode(&receipts); err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ // Deliver all to the downloader
+ if err := pm.downloader.DeliverReceipts(p.id, receipts); err != nil {
+ log.Debug("Failed to deliver receipts", "err", err)
+ }
+
+ case msg.Code == NewBlockHashesMsg:
+ var announces newBlockHashesData
+ if err := msg.Decode(&announces); err != nil {
+ return errResp(ErrDecode, "%v: %v", msg, err)
+ }
+ // Mark the hashes as present at the remote node
+ for _, block := range announces {
+ p.MarkBlock(block.Hash)
+ }
+ // Schedule all the unknown hashes for retrieval
+ unknown := make(newBlockHashesData, 0, len(announces))
+ for _, block := range announces {
+ if !pm.blockchain.HasBlock(block.Hash, block.Number) {
+ unknown = append(unknown, block)
+ }
+ }
+ for _, block := range unknown {
+ pm.fetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies)
+ }
+
+ case msg.Code == NewBlockMsg:
+ // Retrieve and decode the propagated block
+ var request newBlockData
+ if err := msg.Decode(&request); err != nil {
+ return errResp(ErrDecode, "%v: %v", msg, err)
+ }
+ if err := request.sanityCheck(); err != nil {
+ return err
+ }
+ request.Block.ReceivedAt = msg.ReceivedAt
+ request.Block.ReceivedFrom = p
+
+ // Mark the peer as owning the block and schedule it for import
+ p.MarkBlock(request.Block.Hash())
+ pm.fetcher.Enqueue(p.id, request.Block)
+
+ // Assuming the block is importable by the peer, but possibly not yet done so,
+ // calculate the head hash and TD that the peer truly must have.
+ var (
+ trueHead = request.Block.ParentHash()
+ trueTD = new(big.Int).Sub(request.TD, request.Block.Difficulty())
+ )
+ // Update the peer's total difficulty if better than the previous
+ if _, td := p.Head(); trueTD.Cmp(td) > 0 {
+ p.SetHead(trueHead, trueTD)
+
+ // Schedule a sync if above ours. Note, this will not fire a sync for a gap of
+ // a single block (as the true TD is below the propagated block), however this
+ // scenario should easily be covered by the fetcher.
+ currentBlock := pm.blockchain.CurrentBlock()
+ if trueTD.Cmp(pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())) > 0 {
+ go pm.synchronise(p)
+ }
+ }
+
+ case msg.Code == TxMsg:
+ // Transactions arrived, make sure we have a valid and fresh chain to handle them
+ if atomic.LoadUint32(&pm.acceptTxs) == 0 {
+ break
+ }
+ // Transactions can be processed, parse all of them and deliver to the pool
+ var txs []*types.Transaction
+ if err := msg.Decode(&txs); err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ for i, tx := range txs {
+ // Validate and mark the remote transaction
+ if tx == nil {
+ return errResp(ErrDecode, "transaction %d is nil", i)
+ }
+ p.MarkTransaction(tx.Hash())
+ }
+ pm.txpool.AddRemotes(txs)
+
+ default:
+ return errResp(ErrInvalidMsgCode, "%v", msg.Code)
+ }
+ return nil
+}
+
+// BroadcastBlock will either propagate a block to a subset of it's peers, or
+// will only announce it's availability (depending what's requested).
+func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
+ hash := block.Hash()
+ peers := pm.peers.PeersWithoutBlock(hash)
+
+ // If propagation is requested, send to a subset of the peer
+ if propagate {
+ // Calculate the TD of the block (it's not imported yet, so block.Td is not valid)
+ var td *big.Int
+ if parent := pm.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil {
+ td = new(big.Int).Add(block.Difficulty(), pm.blockchain.GetTd(block.ParentHash(), block.NumberU64()-1))
+ } else {
+ log.Error("Propagating dangling block", "number", block.Number(), "hash", hash)
+ return
+ }
+ // Send the block to a subset of our peers
+ transferLen := int(math.Sqrt(float64(len(peers))))
+ if transferLen < minBroadcastPeers {
+ transferLen = minBroadcastPeers
+ }
+ if transferLen > len(peers) {
+ transferLen = len(peers)
+ }
+ transfer := peers[:transferLen]
+ for _, peer := range transfer {
+ peer.AsyncSendNewBlock(block, td)
+ }
+ log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
+ return
+ }
+ // Otherwise if the block is indeed in out own chain, announce it
+ if pm.blockchain.HasBlock(hash, block.NumberU64()) {
+ for _, peer := range peers {
+ peer.AsyncSendNewBlockHash(block)
+ }
+ log.Trace("Announced block", "hash", hash, "recipients", len(peers), "duration", common.PrettyDuration(time.Since(block.ReceivedAt)))
+ }
+}
+
+// BroadcastTxs will propagate a batch of transactions to all peers which are not known to
+// already have the given transaction.
+func (pm *ProtocolManager) BroadcastTxs(txs types.Transactions) {
+ var txset = make(map[*peer]types.Transactions)
+
+ // Broadcast transactions to a batch of peers not knowing about it
+ for _, tx := range txs {
+ peers := pm.peers.PeersWithoutTx(tx.Hash())
+ for _, peer := range peers {
+ txset[peer] = append(txset[peer], tx)
+ }
+ log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers))
+ }
+ // FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
+ for peer, txs := range txset {
+ peer.AsyncSendTransactions(txs)
+ }
+}
+
+// Mined broadcast loop
+func (pm *ProtocolManager) minedBroadcastLoop() {
+ // automatically stops if unsubscribe
+ for obj := range pm.minedBlockSub.Chan() {
+ if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok {
+ pm.BroadcastBlock(ev.Block, true) // First propagate block to peers
+ pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest
+ }
+ }
+}
+
+func (pm *ProtocolManager) txBroadcastLoop() {
+ for {
+ select {
+ case event := <-pm.txsCh:
+ pm.BroadcastTxs(event.Txs)
+
+ // Err() channel will be closed when unsubscribing.
+ case <-pm.txsSub.Err():
+ return
+ }
+ }
+}
+
+// NodeInfo represents a short summary of the Ethereum sub-protocol metadata
+// known about the host peer.
+type NodeInfo struct {
+ Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4)
+ Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
+ Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
+ Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules
+ Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block
+}
+
+// NodeInfo retrieves some protocol metadata about the running host node.
+func (pm *ProtocolManager) NodeInfo() *NodeInfo {
+ currentBlock := pm.blockchain.CurrentBlock()
+ return &NodeInfo{
+ Network: pm.networkID,
+ Difficulty: pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()),
+ Genesis: pm.blockchain.Genesis().Hash(),
+ Config: pm.blockchain.Config(),
+ Head: currentBlock.Hash(),
+ }
+}
diff --git a/eth/metrics.go b/eth/metrics.go
new file mode 100644
index 0000000..0533a2a
--- /dev/null
+++ b/eth/metrics.go
@@ -0,0 +1,139 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "github.com/ethereum/go-ethereum/metrics"
+ "github.com/ethereum/go-ethereum/p2p"
+)
+
+var (
+ propTxnInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/packets", nil)
+ propTxnInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/in/traffic", nil)
+ propTxnOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/packets", nil)
+ propTxnOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/txns/out/traffic", nil)
+ propHashInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/packets", nil)
+ propHashInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/in/traffic", nil)
+ propHashOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/packets", nil)
+ propHashOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/hashes/out/traffic", nil)
+ propBlockInPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/packets", nil)
+ propBlockInTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/in/traffic", nil)
+ propBlockOutPacketsMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/packets", nil)
+ propBlockOutTrafficMeter = metrics.NewRegisteredMeter("eth/prop/blocks/out/traffic", nil)
+ reqHeaderInPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/in/packets", nil)
+ reqHeaderInTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/in/traffic", nil)
+ reqHeaderOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/headers/out/packets", nil)
+ reqHeaderOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/headers/out/traffic", nil)
+ reqBodyInPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/packets", nil)
+ reqBodyInTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/in/traffic", nil)
+ reqBodyOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/packets", nil)
+ reqBodyOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/bodies/out/traffic", nil)
+ reqStateInPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/in/packets", nil)
+ reqStateInTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/in/traffic", nil)
+ reqStateOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/states/out/packets", nil)
+ reqStateOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/states/out/traffic", nil)
+ reqReceiptInPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/packets", nil)
+ reqReceiptInTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/in/traffic", nil)
+ reqReceiptOutPacketsMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/packets", nil)
+ reqReceiptOutTrafficMeter = metrics.NewRegisteredMeter("eth/req/receipts/out/traffic", nil)
+ miscInPacketsMeter = metrics.NewRegisteredMeter("eth/misc/in/packets", nil)
+ miscInTrafficMeter = metrics.NewRegisteredMeter("eth/misc/in/traffic", nil)
+ miscOutPacketsMeter = metrics.NewRegisteredMeter("eth/misc/out/packets", nil)
+ miscOutTrafficMeter = metrics.NewRegisteredMeter("eth/misc/out/traffic", nil)
+)
+
+// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
+// accumulating the above defined metrics based on the data stream contents.
+type meteredMsgReadWriter struct {
+ p2p.MsgReadWriter // Wrapped message stream to meter
+ version int // Protocol version to select correct meters
+}
+
+// newMeteredMsgWriter wraps a p2p MsgReadWriter with metering support. If the
+// metrics system is disabled, this function returns the original object.
+func newMeteredMsgWriter(rw p2p.MsgReadWriter) p2p.MsgReadWriter {
+ if !metrics.Enabled {
+ return rw
+ }
+ return &meteredMsgReadWriter{MsgReadWriter: rw}
+}
+
+// Init sets the protocol version used by the stream to know which meters to
+// increment in case of overlapping message ids between protocol versions.
+func (rw *meteredMsgReadWriter) Init(version int) {
+ rw.version = version
+}
+
+func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) {
+ // Read the message and short circuit in case of an error
+ msg, err := rw.MsgReadWriter.ReadMsg()
+ if err != nil {
+ return msg, err
+ }
+ // Account for the data traffic
+ packets, traffic := miscInPacketsMeter, miscInTrafficMeter
+ switch {
+ case msg.Code == BlockHeadersMsg:
+ packets, traffic = reqHeaderInPacketsMeter, reqHeaderInTrafficMeter
+ case msg.Code == BlockBodiesMsg:
+ packets, traffic = reqBodyInPacketsMeter, reqBodyInTrafficMeter
+
+ case rw.version >= eth63 && msg.Code == NodeDataMsg:
+ packets, traffic = reqStateInPacketsMeter, reqStateInTrafficMeter
+ case rw.version >= eth63 && msg.Code == ReceiptsMsg:
+ packets, traffic = reqReceiptInPacketsMeter, reqReceiptInTrafficMeter
+
+ case msg.Code == NewBlockHashesMsg:
+ packets, traffic = propHashInPacketsMeter, propHashInTrafficMeter
+ case msg.Code == NewBlockMsg:
+ packets, traffic = propBlockInPacketsMeter, propBlockInTrafficMeter
+ case msg.Code == TxMsg:
+ packets, traffic = propTxnInPacketsMeter, propTxnInTrafficMeter
+ }
+ packets.Mark(1)
+ traffic.Mark(int64(msg.Size))
+
+ return msg, err
+}
+
+func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error {
+ // Account for the data traffic
+ packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter
+ switch {
+ case msg.Code == BlockHeadersMsg:
+ packets, traffic = reqHeaderOutPacketsMeter, reqHeaderOutTrafficMeter
+ case msg.Code == BlockBodiesMsg:
+ packets, traffic = reqBodyOutPacketsMeter, reqBodyOutTrafficMeter
+
+ case rw.version >= eth63 && msg.Code == NodeDataMsg:
+ packets, traffic = reqStateOutPacketsMeter, reqStateOutTrafficMeter
+ case rw.version >= eth63 && msg.Code == ReceiptsMsg:
+ packets, traffic = reqReceiptOutPacketsMeter, reqReceiptOutTrafficMeter
+
+ case msg.Code == NewBlockHashesMsg:
+ packets, traffic = propHashOutPacketsMeter, propHashOutTrafficMeter
+ case msg.Code == NewBlockMsg:
+ packets, traffic = propBlockOutPacketsMeter, propBlockOutTrafficMeter
+ case msg.Code == TxMsg:
+ packets, traffic = propTxnOutPacketsMeter, propTxnOutTrafficMeter
+ }
+ packets.Mark(1)
+ traffic.Mark(int64(msg.Size))
+
+ // Send the packet to the p2p layer
+ return rw.MsgReadWriter.WriteMsg(msg)
+}
diff --git a/eth/peer.go b/eth/peer.go
new file mode 100644
index 0000000..814c787
--- /dev/null
+++ b/eth/peer.go
@@ -0,0 +1,546 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+ "sync"
+ "time"
+
+ mapset "github.com/deckarep/golang-set"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+var (
+ errClosed = errors.New("peer set is closed")
+ errAlreadyRegistered = errors.New("peer is already registered")
+ errNotRegistered = errors.New("peer is not registered")
+)
+
+const (
+ maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
+ maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS)
+
+ // maxQueuedTxs is the maximum number of transaction lists to queue up before
+ // dropping broadcasts. This is a sensitive number as a transaction list might
+ // contain a single transaction, or thousands.
+ maxQueuedTxs = 128
+
+ // maxQueuedProps is the maximum number of block propagations to queue up before
+ // dropping broadcasts. There's not much point in queueing stale blocks, so a few
+ // that might cover uncles should be enough.
+ maxQueuedProps = 4
+
+ // maxQueuedAnns is the maximum number of block announcements to queue up before
+ // dropping broadcasts. Similarly to block propagations, there's no point to queue
+ // above some healthy uncle limit, so use that.
+ maxQueuedAnns = 4
+
+ handshakeTimeout = 5 * time.Second
+)
+
+// PeerInfo represents a short summary of the Ethereum sub-protocol metadata known
+// about a connected peer.
+type PeerInfo struct {
+ Version int `json:"version"` // Ethereum protocol version negotiated
+ Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain
+ Head string `json:"head"` // SHA3 hash of the peer's best owned block
+}
+
+// propEvent is a block propagation, waiting for its turn in the broadcast queue.
+type propEvent struct {
+ block *types.Block
+ td *big.Int
+}
+
+type peer struct {
+ id string
+
+ *p2p.Peer
+ rw p2p.MsgReadWriter
+
+ version int // Protocol version negotiated
+ syncDrop *time.Timer // Timed connection dropper if sync progress isn't validated in time
+
+ head common.Hash
+ td *big.Int
+ lock sync.RWMutex
+
+ knownTxs mapset.Set // Set of transaction hashes known to be known by this peer
+ knownBlocks mapset.Set // Set of block hashes known to be known by this peer
+ queuedTxs chan []*types.Transaction // Queue of transactions to broadcast to the peer
+ queuedProps chan *propEvent // Queue of blocks to broadcast to the peer
+ queuedAnns chan *types.Block // Queue of blocks to announce to the peer
+ term chan struct{} // Termination channel to stop the broadcaster
+}
+
+func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
+ return &peer{
+ Peer: p,
+ rw: rw,
+ version: version,
+ id: fmt.Sprintf("%x", p.ID().Bytes()[:8]),
+ knownTxs: mapset.NewSet(),
+ knownBlocks: mapset.NewSet(),
+ queuedTxs: make(chan []*types.Transaction, maxQueuedTxs),
+ queuedProps: make(chan *propEvent, maxQueuedProps),
+ queuedAnns: make(chan *types.Block, maxQueuedAnns),
+ term: make(chan struct{}),
+ }
+}
+
+// broadcast is a write loop that multiplexes block propagations, announcements
+// and transaction broadcasts into the remote peer. The goal is to have an async
+// writer that does not lock up node internals.
+func (p *peer) broadcast() {
+ for {
+ select {
+ case txs := <-p.queuedTxs:
+ if err := p.SendTransactions(txs); err != nil {
+ return
+ }
+ p.Log().Trace("Broadcast transactions", "count", len(txs))
+
+ case prop := <-p.queuedProps:
+ if err := p.SendNewBlock(prop.block, prop.td); err != nil {
+ return
+ }
+ p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
+
+ case block := <-p.queuedAnns:
+ if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
+ return
+ }
+ p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
+
+ case <-p.term:
+ return
+ }
+ }
+}
+
+// close signals the broadcast goroutine to terminate.
+func (p *peer) close() {
+ close(p.term)
+}
+
+// Info gathers and returns a collection of metadata known about a peer.
+func (p *peer) Info() *PeerInfo {
+ hash, td := p.Head()
+
+ return &PeerInfo{
+ Version: p.version,
+ Difficulty: td,
+ Head: hash.Hex(),
+ }
+}
+
+// Head retrieves a copy of the current head hash and total difficulty of the
+// peer.
+func (p *peer) Head() (hash common.Hash, td *big.Int) {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+
+ copy(hash[:], p.head[:])
+ return hash, new(big.Int).Set(p.td)
+}
+
+// SetHead updates the head hash and total difficulty of the peer.
+func (p *peer) SetHead(hash common.Hash, td *big.Int) {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ copy(p.head[:], hash[:])
+ p.td.Set(td)
+}
+
+// MarkBlock marks a block as known for the peer, ensuring that the block will
+// never be propagated to this particular peer.
+func (p *peer) MarkBlock(hash common.Hash) {
+ // If we reached the memory allowance, drop a previously known block hash
+ for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+ p.knownBlocks.Pop()
+ }
+ p.knownBlocks.Add(hash)
+}
+
+// MarkTransaction marks a transaction as known for the peer, ensuring that it
+// will never be propagated to this particular peer.
+func (p *peer) MarkTransaction(hash common.Hash) {
+ // If we reached the memory allowance, drop a previously known transaction hash
+ for p.knownTxs.Cardinality() >= maxKnownTxs {
+ p.knownTxs.Pop()
+ }
+ p.knownTxs.Add(hash)
+}
+
+// SendTransactions sends transactions to the peer and includes the hashes
+// in its transaction hash set for future reference.
+func (p *peer) SendTransactions(txs types.Transactions) error {
+ // Mark all the transactions as known, but ensure we don't overflow our limits
+ for _, tx := range txs {
+ p.knownTxs.Add(tx.Hash())
+ }
+ for p.knownTxs.Cardinality() >= maxKnownTxs {
+ p.knownTxs.Pop()
+ }
+ return p2p.Send(p.rw, TxMsg, txs)
+}
+
+// AsyncSendTransactions queues list of transactions propagation to a remote
+// peer. If the peer's broadcast queue is full, the event is silently dropped.
+func (p *peer) AsyncSendTransactions(txs []*types.Transaction) {
+ select {
+ case p.queuedTxs <- txs:
+ // Mark all the transactions as known, but ensure we don't overflow our limits
+ for _, tx := range txs {
+ p.knownTxs.Add(tx.Hash())
+ }
+ for p.knownTxs.Cardinality() >= maxKnownTxs {
+ p.knownTxs.Pop()
+ }
+ default:
+ p.Log().Debug("Dropping transaction propagation", "count", len(txs))
+ }
+}
+
+// SendNewBlockHashes announces the availability of a number of blocks through
+// a hash notification.
+func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
+ // Mark all the block hashes as known, but ensure we don't overflow our limits
+ for _, hash := range hashes {
+ p.knownBlocks.Add(hash)
+ }
+ for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+ p.knownBlocks.Pop()
+ }
+ request := make(newBlockHashesData, len(hashes))
+ for i := 0; i < len(hashes); i++ {
+ request[i].Hash = hashes[i]
+ request[i].Number = numbers[i]
+ }
+ return p2p.Send(p.rw, NewBlockHashesMsg, request)
+}
+
+// AsyncSendNewBlockHash queues the availability of a block for propagation to a
+// remote peer. If the peer's broadcast queue is full, the event is silently
+// dropped.
+func (p *peer) AsyncSendNewBlockHash(block *types.Block) {
+ select {
+ case p.queuedAnns <- block:
+ // Mark all the block hash as known, but ensure we don't overflow our limits
+ p.knownBlocks.Add(block.Hash())
+ for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+ p.knownBlocks.Pop()
+ }
+ default:
+ p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
+ }
+}
+
+// SendNewBlock propagates an entire block to a remote peer.
+func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
+ // Mark all the block hash as known, but ensure we don't overflow our limits
+ p.knownBlocks.Add(block.Hash())
+ for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+ p.knownBlocks.Pop()
+ }
+ return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td})
+}
+
+// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
+// the peer's broadcast queue is full, the event is silently dropped.
+func (p *peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
+ select {
+ case p.queuedProps <- &propEvent{block: block, td: td}:
+ // Mark all the block hash as known, but ensure we don't overflow our limits
+ p.knownBlocks.Add(block.Hash())
+ for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+ p.knownBlocks.Pop()
+ }
+ default:
+ p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
+ }
+}
+
+// SendBlockHeaders sends a batch of block headers to the remote peer.
+func (p *peer) SendBlockHeaders(headers []*types.Header) error {
+ return p2p.Send(p.rw, BlockHeadersMsg, headers)
+}
+
+// SendBlockBodies sends a batch of block contents to the remote peer.
+func (p *peer) SendBlockBodies(bodies []*blockBody) error {
+ return p2p.Send(p.rw, BlockBodiesMsg, blockBodiesData(bodies))
+}
+
+// SendBlockBodiesRLP sends a batch of block contents to the remote peer from
+// an already RLP encoded format.
+func (p *peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error {
+ return p2p.Send(p.rw, BlockBodiesMsg, bodies)
+}
+
+// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the
+// hashes requested.
+func (p *peer) SendNodeData(data [][]byte) error {
+ return p2p.Send(p.rw, NodeDataMsg, data)
+}
+
+// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the
+// ones requested from an already RLP encoded format.
+func (p *peer) SendReceiptsRLP(receipts []rlp.RawValue) error {
+ return p2p.Send(p.rw, ReceiptsMsg, receipts)
+}
+
+// RequestOneHeader is a wrapper around the header query functions to fetch a
+// single header. It is used solely by the fetcher.
+func (p *peer) RequestOneHeader(hash common.Hash) error {
+ p.Log().Debug("Fetching single header", "hash", hash)
+ return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), Reverse: false})
+}
+
+// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
+// specified header query, based on the hash of an origin block.
+func (p *peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error {
+ p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse)
+ return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse})
+}
+
+// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the
+// specified header query, based on the number of an origin block.
+func (p *peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error {
+ p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse)
+ return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse})
+}
+
+// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes
+// specified.
+func (p *peer) RequestBodies(hashes []common.Hash) error {
+ p.Log().Debug("Fetching batch of block bodies", "count", len(hashes))
+ return p2p.Send(p.rw, GetBlockBodiesMsg, hashes)
+}
+
+// RequestNodeData fetches a batch of arbitrary data from a node's known state
+// data, corresponding to the specified hashes.
+func (p *peer) RequestNodeData(hashes []common.Hash) error {
+ p.Log().Debug("Fetching batch of state data", "count", len(hashes))
+ return p2p.Send(p.rw, GetNodeDataMsg, hashes)
+}
+
+// RequestReceipts fetches a batch of transaction receipts from a remote node.
+func (p *peer) RequestReceipts(hashes []common.Hash) error {
+ p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
+ return p2p.Send(p.rw, GetReceiptsMsg, hashes)
+}
+
+// Handshake executes the eth protocol handshake, negotiating version number,
+// network IDs, difficulties, head and genesis blocks.
+func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash) error {
+ // Send out own handshake in a new thread
+ errc := make(chan error, 2)
+ var status statusData // safe to read after two values have been received from errc
+
+ go func() {
+ errc <- p2p.Send(p.rw, StatusMsg, &statusData{
+ ProtocolVersion: uint32(p.version),
+ NetworkId: network,
+ TD: td,
+ CurrentBlock: head,
+ GenesisBlock: genesis,
+ })
+ }()
+ go func() {
+ errc <- p.readStatus(network, &status, genesis)
+ }()
+ timeout := time.NewTimer(handshakeTimeout)
+ defer timeout.Stop()
+ for i := 0; i < 2; i++ {
+ select {
+ case err := <-errc:
+ if err != nil {
+ return err
+ }
+ case <-timeout.C:
+ return p2p.DiscReadTimeout
+ }
+ }
+ p.td, p.head = status.TD, status.CurrentBlock
+ return nil
+}
+
+func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash) (err error) {
+ msg, err := p.rw.ReadMsg()
+ if err != nil {
+ return err
+ }
+ if msg.Code != StatusMsg {
+ return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
+ }
+ if msg.Size > protocolMaxMsgSize {
+ return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
+ }
+ // Decode the handshake and make sure everything matches
+ if err := msg.Decode(&status); err != nil {
+ return errResp(ErrDecode, "msg %v: %v", msg, err)
+ }
+ if status.GenesisBlock != genesis {
+ return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8])
+ }
+ if status.NetworkId != network {
+ return errResp(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, network)
+ }
+ if int(status.ProtocolVersion) != p.version {
+ return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version)
+ }
+ return nil
+}
+
+// String implements fmt.Stringer.
+func (p *peer) String() string {
+ return fmt.Sprintf("Peer %s [%s]", p.id,
+ fmt.Sprintf("eth/%2d", p.version),
+ )
+}
+
+// peerSet represents the collection of active peers currently participating in
+// the Ethereum sub-protocol.
+type peerSet struct {
+ peers map[string]*peer
+ lock sync.RWMutex
+ closed bool
+}
+
+// newPeerSet creates a new peer set to track the active participants.
+func newPeerSet() *peerSet {
+ return &peerSet{
+ peers: make(map[string]*peer),
+ }
+}
+
+// Register injects a new peer into the working set, or returns an error if the
+// peer is already known. If a new peer it registered, its broadcast loop is also
+// started.
+func (ps *peerSet) Register(p *peer) error {
+ ps.lock.Lock()
+ defer ps.lock.Unlock()
+
+ if ps.closed {
+ return errClosed
+ }
+ if _, ok := ps.peers[p.id]; ok {
+ return errAlreadyRegistered
+ }
+ ps.peers[p.id] = p
+ go p.broadcast()
+
+ return nil
+}
+
+// Unregister removes a remote peer from the active set, disabling any further
+// actions to/from that particular entity.
+func (ps *peerSet) Unregister(id string) error {
+ ps.lock.Lock()
+ defer ps.lock.Unlock()
+
+ p, ok := ps.peers[id]
+ if !ok {
+ return errNotRegistered
+ }
+ delete(ps.peers, id)
+ p.close()
+
+ return nil
+}
+
+// Peer retrieves the registered peer with the given id.
+func (ps *peerSet) Peer(id string) *peer {
+ ps.lock.RLock()
+ defer ps.lock.RUnlock()
+
+ return ps.peers[id]
+}
+
+// Len returns if the current number of peers in the set.
+func (ps *peerSet) Len() int {
+ ps.lock.RLock()
+ defer ps.lock.RUnlock()
+
+ return len(ps.peers)
+}
+
+// PeersWithoutBlock retrieves a list of peers that do not have a given block in
+// their set of known hashes.
+func (ps *peerSet) PeersWithoutBlock(hash common.Hash) []*peer {
+ ps.lock.RLock()
+ defer ps.lock.RUnlock()
+
+ list := make([]*peer, 0, len(ps.peers))
+ for _, p := range ps.peers {
+ if !p.knownBlocks.Contains(hash) {
+ list = append(list, p)
+ }
+ }
+ return list
+}
+
+// PeersWithoutTx retrieves a list of peers that do not have a given transaction
+// in their set of known hashes.
+func (ps *peerSet) PeersWithoutTx(hash common.Hash) []*peer {
+ ps.lock.RLock()
+ defer ps.lock.RUnlock()
+
+ list := make([]*peer, 0, len(ps.peers))
+ for _, p := range ps.peers {
+ if !p.knownTxs.Contains(hash) {
+ list = append(list, p)
+ }
+ }
+ return list
+}
+
+// BestPeer retrieves the known peer with the currently highest total difficulty.
+func (ps *peerSet) BestPeer() *peer {
+ ps.lock.RLock()
+ defer ps.lock.RUnlock()
+
+ var (
+ bestPeer *peer
+ bestTd *big.Int
+ )
+ for _, p := range ps.peers {
+ if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
+ bestPeer, bestTd = p, td
+ }
+ }
+ return bestPeer
+}
+
+// Close disconnects all peers.
+// No new peers can be registered after Close has returned.
+func (ps *peerSet) Close() {
+ ps.lock.Lock()
+ defer ps.lock.Unlock()
+
+ for _, p := range ps.peers {
+ p.Disconnect(p2p.DiscQuitting)
+ }
+ ps.closed = true
+}
diff --git a/eth/protocol.go b/eth/protocol.go
new file mode 100644
index 0000000..de0c979
--- /dev/null
+++ b/eth/protocol.go
@@ -0,0 +1,196 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "fmt"
+ "io"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+// Constants to match up protocol versions and messages
+const (
+ eth62 = 62
+ eth63 = 63
+)
+
+// protocolName is the official short name of the protocol used during capability negotiation.
+const protocolName = "eth"
+
+// ProtocolVersions are the supported versions of the eth protocol (first is primary).
+var ProtocolVersions = []uint{eth63}
+
+// protocolLengths are the number of implemented message corresponding to different protocol versions.
+var protocolLengths = map[uint]uint64{eth63: 17, eth62: 8}
+
+const protocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
+
+// eth protocol message codes
+const (
+ // Protocol messages belonging to eth/62
+ StatusMsg = 0x00
+ NewBlockHashesMsg = 0x01
+ TxMsg = 0x02
+ GetBlockHeadersMsg = 0x03
+ BlockHeadersMsg = 0x04
+ GetBlockBodiesMsg = 0x05
+ BlockBodiesMsg = 0x06
+ NewBlockMsg = 0x07
+
+ // Protocol messages belonging to eth/63
+ GetNodeDataMsg = 0x0d
+ NodeDataMsg = 0x0e
+ GetReceiptsMsg = 0x0f
+ ReceiptsMsg = 0x10
+)
+
+type errCode int
+
+const (
+ ErrMsgTooLarge = iota
+ ErrDecode
+ ErrInvalidMsgCode
+ ErrProtocolVersionMismatch
+ ErrNetworkIdMismatch
+ ErrGenesisBlockMismatch
+ ErrNoStatusMsg
+ ErrExtraStatusMsg
+ ErrSuspendedPeer
+)
+
+func (e errCode) String() string {
+ return errorToString[int(e)]
+}
+
+// XXX change once legacy code is out
+var errorToString = map[int]string{
+ ErrMsgTooLarge: "Message too long",
+ ErrDecode: "Invalid message",
+ ErrInvalidMsgCode: "Invalid message code",
+ ErrProtocolVersionMismatch: "Protocol version mismatch",
+ ErrNetworkIdMismatch: "NetworkId mismatch",
+ ErrGenesisBlockMismatch: "Genesis block mismatch",
+ ErrNoStatusMsg: "No status message",
+ ErrExtraStatusMsg: "Extra status message",
+ ErrSuspendedPeer: "Suspended peer",
+}
+
+type txPool interface {
+ // AddRemotes should add the given transactions to the pool.
+ AddRemotes([]*types.Transaction) []error
+
+ // Pending should return pending transactions.
+ // The slice should be modifiable by the caller.
+ Pending() (map[common.Address]types.Transactions, error)
+
+ // SubscribeNewTxsEvent should return an event subscription of
+ // NewTxsEvent and send events to the given channel.
+ SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
+}
+
+// statusData is the network packet for the status message.
+type statusData struct {
+ ProtocolVersion uint32
+ NetworkId uint64
+ TD *big.Int
+ CurrentBlock common.Hash
+ GenesisBlock common.Hash
+}
+
+// newBlockHashesData is the network packet for the block announcements.
+type newBlockHashesData []struct {
+ Hash common.Hash // Hash of one particular block being announced
+ Number uint64 // Number of one particular block being announced
+}
+
+// getBlockHeadersData represents a block header query.
+type getBlockHeadersData struct {
+ Origin hashOrNumber // Block from which to retrieve headers
+ Amount uint64 // Maximum number of headers to retrieve
+ Skip uint64 // Blocks to skip between consecutive headers
+ Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis)
+}
+
+// hashOrNumber is a combined field for specifying an origin block.
+type hashOrNumber struct {
+ Hash common.Hash // Block hash from which to retrieve headers (excludes Number)
+ Number uint64 // Block hash from which to retrieve headers (excludes Hash)
+}
+
+// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the
+// two contained union fields.
+func (hn *hashOrNumber) EncodeRLP(w io.Writer) error {
+ if hn.Hash == (common.Hash{}) {
+ return rlp.Encode(w, hn.Number)
+ }
+ if hn.Number != 0 {
+ return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
+ }
+ return rlp.Encode(w, hn.Hash)
+}
+
+// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents
+// into either a block hash or a block number.
+func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error {
+ _, size, _ := s.Kind()
+ origin, err := s.Raw()
+ if err == nil {
+ switch {
+ case size == 32:
+ err = rlp.DecodeBytes(origin, &hn.Hash)
+ case size <= 8:
+ err = rlp.DecodeBytes(origin, &hn.Number)
+ default:
+ err = fmt.Errorf("invalid input size %d for origin", size)
+ }
+ }
+ return err
+}
+
+// newBlockData is the network packet for the block propagation message.
+type newBlockData struct {
+ Block *types.Block
+ TD *big.Int
+}
+
+// sanityCheck verifies that the values are reasonable, as a DoS protection
+func (request *newBlockData) sanityCheck() error {
+ if err := request.Block.SanityCheck(); err != nil {
+ return err
+ }
+ //TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
+ // larger, it will still fit within 100 bits
+ if tdlen := request.TD.BitLen(); tdlen > 100 {
+ return fmt.Errorf("too large block TD: bitlen %d", tdlen)
+ }
+ return nil
+}
+
+// blockBody represents the data content of a single block.
+type blockBody struct {
+ Transactions []*types.Transaction // Transactions contained within a block
+ Uncles []*types.Header // Uncles contained within a block
+}
+
+// blockBodiesData is the network packet for block content distribution.
+type blockBodiesData []*blockBody
diff --git a/eth/sync.go b/eth/sync.go
new file mode 100644
index 0000000..9e180ee
--- /dev/null
+++ b/eth/sync.go
@@ -0,0 +1,216 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package eth
+
+import (
+ "math/rand"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+const (
+ forceSyncCycle = 10 * time.Second // Time interval to force syncs, even if few peers are available
+ minDesiredPeerCount = 5 // Amount of peers desired to start syncing
+
+ // This is the target size for the packs of transactions sent by txsyncLoop.
+ // A pack can get larger than this if a single transactions exceeds this size.
+ txsyncPackSize = 100 * 1024
+)
+
+type txsync struct {
+ p *peer
+ txs []*types.Transaction
+}
+
+// syncTransactions starts sending all currently pending transactions to the given peer.
+func (pm *ProtocolManager) syncTransactions(p *peer) {
+ var txs types.Transactions
+ pending, _ := pm.txpool.Pending()
+ for _, batch := range pending {
+ txs = append(txs, batch...)
+ }
+ if len(txs) == 0 {
+ return
+ }
+ select {
+ case pm.txsyncCh <- &txsync{p, txs}:
+ case <-pm.quitSync:
+ }
+}
+
+// txsyncLoop takes care of the initial transaction sync for each new
+// connection. When a new peer appears, we relay all currently pending
+// transactions. In order to minimise egress bandwidth usage, we send
+// the transactions in small packs to one peer at a time.
+func (pm *ProtocolManager) txsyncLoop() {
+ var (
+ pending = make(map[enode.ID]*txsync)
+ sending = false // whether a send is active
+ pack = new(txsync) // the pack that is being sent
+ done = make(chan error, 1) // result of the send
+ )
+
+ // send starts a sending a pack of transactions from the sync.
+ send := func(s *txsync) {
+ // Fill pack with transactions up to the target size.
+ size := common.StorageSize(0)
+ pack.p = s.p
+ pack.txs = pack.txs[:0]
+ for i := 0; i < len(s.txs) && size < txsyncPackSize; i++ {
+ pack.txs = append(pack.txs, s.txs[i])
+ size += s.txs[i].Size()
+ }
+ // Remove the transactions that will be sent.
+ s.txs = s.txs[:copy(s.txs, s.txs[len(pack.txs):])]
+ if len(s.txs) == 0 {
+ delete(pending, s.p.ID())
+ }
+ // Send the pack in the background.
+ s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size)
+ sending = true
+ go func() { done <- pack.p.SendTransactions(pack.txs) }()
+ }
+
+ // pick chooses the next pending sync.
+ pick := func() *txsync {
+ if len(pending) == 0 {
+ return nil
+ }
+ n := rand.Intn(len(pending)) + 1
+ for _, s := range pending {
+ if n--; n == 0 {
+ return s
+ }
+ }
+ return nil
+ }
+
+ for {
+ select {
+ case s := <-pm.txsyncCh:
+ pending[s.p.ID()] = s
+ if !sending {
+ send(s)
+ }
+ case err := <-done:
+ sending = false
+ // Stop tracking peers that cause send failures.
+ if err != nil {
+ pack.p.Log().Debug("Transaction send failed", "err", err)
+ delete(pending, pack.p.ID())
+ }
+ // Schedule the next send.
+ if s := pick(); s != nil {
+ send(s)
+ }
+ case <-pm.quitSync:
+ return
+ }
+ }
+}
+
+// syncer is responsible for periodically synchronising with the network, both
+// downloading hashes and blocks as well as handling the announcement handler.
+func (pm *ProtocolManager) syncer() {
+ // Start and ensure cleanup of sync mechanisms
+ pm.fetcher.Start()
+ defer pm.fetcher.Stop()
+ defer pm.downloader.Terminate()
+
+ // Wait for different events to fire synchronisation operations
+ forceSync := time.NewTicker(forceSyncCycle)
+ defer forceSync.Stop()
+
+ for {
+ select {
+ case <-pm.newPeerCh:
+ // Make sure we have peers to select from, then sync
+ if pm.peers.Len() < minDesiredPeerCount {
+ break
+ }
+ go pm.synchronise(pm.peers.BestPeer())
+
+ case <-forceSync.C:
+ // Force a sync even if not enough peers are present
+ go pm.synchronise(pm.peers.BestPeer())
+
+ case <-pm.noMorePeers:
+ return
+ }
+ }
+}
+
+// synchronise tries to sync up our local block chain with a remote peer.
+func (pm *ProtocolManager) synchronise(peer *peer) {
+ // Short circuit if no peers are available
+ if peer == nil {
+ return
+ }
+ // Make sure the peer's TD is higher than our own
+ currentBlock := pm.blockchain.CurrentBlock()
+ td := pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
+
+ pHead, pTd := peer.Head()
+ if pTd.Cmp(td) <= 0 {
+ return
+ }
+ // Otherwise try to sync with the downloader
+ mode := downloader.FullSync
+ if atomic.LoadUint32(&pm.fastSync) == 1 {
+ // Fast sync was explicitly requested, and explicitly granted
+ mode = downloader.FastSync
+ }
+ if mode == downloader.FastSync {
+ // Make sure the peer's total difficulty we are synchronizing is higher.
+ if pm.blockchain.GetTdByHash(pm.blockchain.CurrentFastBlock().Hash()).Cmp(pTd) >= 0 {
+ return
+ }
+ }
+ // Run the sync cycle, and disable fast sync if we've went past the pivot block
+ if err := pm.downloader.Synchronise(peer.id, pHead, pTd, mode); err != nil {
+ return
+ }
+ if atomic.LoadUint32(&pm.fastSync) == 1 {
+ log.Info("Fast sync complete, auto disabling")
+ atomic.StoreUint32(&pm.fastSync, 0)
+ }
+ // If we've successfully finished a sync cycle and passed any required checkpoint,
+ // enable accepting transactions from the network.
+ head := pm.blockchain.CurrentBlock()
+ if head.NumberU64() >= pm.checkpointNumber {
+ // Checkpoint passed, sanity check the timestamp to have a fallback mechanism
+ // for non-checkpointed (number = 0) private networks.
+ if head.Time() >= uint64(time.Now().AddDate(0, -1, 0).Unix()) {
+ atomic.StoreUint32(&pm.acceptTxs, 1)
+ }
+ }
+ if head.NumberU64() > 0 {
+ // We've completed a sync cycle, notify all peers of new state. This path is
+ // essential in star-topology networks where a gateway node needs to notify
+ // all its out-of-date peers of the availability of a new block. This failure
+ // scenario will most often crop up in private and hackathon networks with
+ // degenerate connectivity, but it should be healthy for the mainnet too to
+ // more reliably update peers or the local TD state.
+ go pm.BroadcastBlock(head, false)
+ }
+}
diff --git a/internal/ethapi/addrlock.go b/internal/ethapi/addrlock.go
new file mode 100644
index 0000000..61ddff6
--- /dev/null
+++ b/internal/ethapi/addrlock.go
@@ -0,0 +1,53 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package ethapi
+
+import (
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type AddrLocker struct {
+ mu sync.Mutex
+ locks map[common.Address]*sync.Mutex
+}
+
+// lock returns the lock of the given address.
+func (l *AddrLocker) lock(address common.Address) *sync.Mutex {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ if l.locks == nil {
+ l.locks = make(map[common.Address]*sync.Mutex)
+ }
+ if _, ok := l.locks[address]; !ok {
+ l.locks[address] = new(sync.Mutex)
+ }
+ return l.locks[address]
+}
+
+// LockAddr locks an account's mutex. This is used to prevent another tx getting the
+// same nonce until the lock is released. The mutex prevents the (an identical nonce) from
+// being read again during the time that the first transaction is being signed.
+func (l *AddrLocker) LockAddr(address common.Address) {
+ l.lock(address).Lock()
+}
+
+// UnlockAddr unlocks the mutex of the given account.
+func (l *AddrLocker) UnlockAddr(address common.Address) {
+ l.lock(address).Unlock()
+}
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
new file mode 100644
index 0000000..05204e5
--- /dev/null
+++ b/internal/ethapi/api.go
@@ -0,0 +1,1767 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package ethapi
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "math/big"
+ "strings"
+ "time"
+
+ "github.com/davecgh/go-spew/spew"
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
+ "github.com/ethereum/go-ethereum/accounts/scwallet"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/consensus/clique"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/tyler-smith/go-bip39"
+)
+
+const (
+ defaultGasPrice = params.GWei
+)
+
+// PublicEthereumAPI provides an API to access Ethereum related information.
+// It offers only methods that operate on public data that is freely available to anyone.
+type PublicEthereumAPI struct {
+ b Backend
+}
+
+// NewPublicEthereumAPI creates a new Ethereum protocol API.
+func NewPublicEthereumAPI(b Backend) *PublicEthereumAPI {
+ return &PublicEthereumAPI{b}
+}
+
+// GasPrice returns a suggestion for a gas price.
+func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
+ price, err := s.b.SuggestPrice(ctx)
+ return (*hexutil.Big)(price), err
+}
+
+// ProtocolVersion returns the current Ethereum protocol version this node supports
+func (s *PublicEthereumAPI) ProtocolVersion() hexutil.Uint {
+ return hexutil.Uint(s.b.ProtocolVersion())
+}
+
+// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
+// yet received the latest block headers from its pears. In case it is synchronizing:
+// - startingBlock: block number this node started to synchronise from
+// - currentBlock: block number this node is currently importing
+// - highestBlock: block number of the highest block header this node has received from peers
+// - pulledStates: number of state entries processed until now
+// - knownStates: number of known state entries that still need to be pulled
+func (s *PublicEthereumAPI) Syncing() (interface{}, error) {
+ progress := s.b.Downloader().Progress()
+
+ // Return not syncing if the synchronisation already completed
+ if progress.CurrentBlock >= progress.HighestBlock {
+ return false, nil
+ }
+ // Otherwise gather the block sync stats
+ return map[string]interface{}{
+ "startingBlock": hexutil.Uint64(progress.StartingBlock),
+ "currentBlock": hexutil.Uint64(progress.CurrentBlock),
+ "highestBlock": hexutil.Uint64(progress.HighestBlock),
+ "pulledStates": hexutil.Uint64(progress.PulledStates),
+ "knownStates": hexutil.Uint64(progress.KnownStates),
+ }, nil
+}
+
+// PublicTxPoolAPI offers and API for the transaction pool. It only operates on data that is non confidential.
+type PublicTxPoolAPI struct {
+ b Backend
+}
+
+// NewPublicTxPoolAPI creates a new tx pool service that gives information about the transaction pool.
+func NewPublicTxPoolAPI(b Backend) *PublicTxPoolAPI {
+ return &PublicTxPoolAPI{b}
+}
+
+// Content returns the transactions contained within the transaction pool.
+func (s *PublicTxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
+ content := map[string]map[string]map[string]*RPCTransaction{
+ "pending": make(map[string]map[string]*RPCTransaction),
+ "queued": make(map[string]map[string]*RPCTransaction),
+ }
+ pending, queue := s.b.TxPoolContent()
+
+ // Flatten the pending transactions
+ for account, txs := range pending {
+ dump := make(map[string]*RPCTransaction)
+ for _, tx := range txs {
+ dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx)
+ }
+ content["pending"][account.Hex()] = dump
+ }
+ // Flatten the queued transactions
+ for account, txs := range queue {
+ dump := make(map[string]*RPCTransaction)
+ for _, tx := range txs {
+ dump[fmt.Sprintf("%d", tx.Nonce())] = newRPCPendingTransaction(tx)
+ }
+ content["queued"][account.Hex()] = dump
+ }
+ return content
+}
+
+// Status returns the number of pending and queued transaction in the pool.
+func (s *PublicTxPoolAPI) Status() map[string]hexutil.Uint {
+ pending, queue := s.b.Stats()
+ return map[string]hexutil.Uint{
+ "pending": hexutil.Uint(pending),
+ "queued": hexutil.Uint(queue),
+ }
+}
+
+// Inspect retrieves the content of the transaction pool and flattens it into an
+// easily inspectable list.
+func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string {
+ content := map[string]map[string]map[string]string{
+ "pending": make(map[string]map[string]string),
+ "queued": make(map[string]map[string]string),
+ }
+ pending, queue := s.b.TxPoolContent()
+
+ // Define a formatter to flatten a transaction into a string
+ var format = func(tx *types.Transaction) string {
+ if to := tx.To(); to != nil {
+ return fmt.Sprintf("%s: %v wei + %v gas × %v wei", tx.To().Hex(), tx.Value(), tx.Gas(), tx.GasPrice())
+ }
+ return fmt.Sprintf("contract creation: %v wei + %v gas × %v wei", tx.Value(), tx.Gas(), tx.GasPrice())
+ }
+ // Flatten the pending transactions
+ for account, txs := range pending {
+ dump := make(map[string]string)
+ for _, tx := range txs {
+ dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx)
+ }
+ content["pending"][account.Hex()] = dump
+ }
+ // Flatten the queued transactions
+ for account, txs := range queue {
+ dump := make(map[string]string)
+ for _, tx := range txs {
+ dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx)
+ }
+ content["queued"][account.Hex()] = dump
+ }
+ return content
+}
+
+// PublicAccountAPI provides an API to access accounts managed by this node.
+// It offers only methods that can retrieve accounts.
+type PublicAccountAPI struct {
+ am *accounts.Manager
+}
+
+// NewPublicAccountAPI creates a new PublicAccountAPI.
+func NewPublicAccountAPI(am *accounts.Manager) *PublicAccountAPI {
+ return &PublicAccountAPI{am: am}
+}
+
+// Accounts returns the collection of accounts this node manages
+func (s *PublicAccountAPI) Accounts() []common.Address {
+ return s.am.Accounts()
+}
+
+// PrivateAccountAPI provides an API to access accounts managed by this node.
+// It offers methods to create, (un)lock en list accounts. Some methods accept
+// passwords and are therefore considered private by default.
+type PrivateAccountAPI struct {
+ am *accounts.Manager
+ nonceLock *AddrLocker
+ b Backend
+}
+
+// NewPrivateAccountAPI create a new PrivateAccountAPI.
+func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
+ return &PrivateAccountAPI{
+ am: b.AccountManager(),
+ nonceLock: nonceLock,
+ b: b,
+ }
+}
+
+// listAccounts will return a list of addresses for accounts this node manages.
+func (s *PrivateAccountAPI) ListAccounts() []common.Address {
+ return s.am.Accounts()
+}
+
+// rawWallet is a JSON representation of an accounts.Wallet interface, with its
+// data contents extracted into plain fields.
+type rawWallet struct {
+ URL string `json:"url"`
+ Status string `json:"status"`
+ Failure string `json:"failure,omitempty"`
+ Accounts []accounts.Account `json:"accounts,omitempty"`
+}
+
+// ListWallets will return a list of wallets this node manages.
+func (s *PrivateAccountAPI) ListWallets() []rawWallet {
+ wallets := make([]rawWallet, 0) // return [] instead of nil if empty
+ for _, wallet := range s.am.Wallets() {
+ status, failure := wallet.Status()
+
+ raw := rawWallet{
+ URL: wallet.URL().String(),
+ Status: status,
+ Accounts: wallet.Accounts(),
+ }
+ if failure != nil {
+ raw.Failure = failure.Error()
+ }
+ wallets = append(wallets, raw)
+ }
+ return wallets
+}
+
+// OpenWallet initiates a hardware wallet opening procedure, establishing a USB
+// connection and attempting to authenticate via the provided passphrase. Note,
+// the method may return an extra challenge requiring a second open (e.g. the
+// Trezor PIN matrix challenge).
+func (s *PrivateAccountAPI) OpenWallet(url string, passphrase *string) error {
+ wallet, err := s.am.Wallet(url)
+ if err != nil {
+ return err
+ }
+ pass := ""
+ if passphrase != nil {
+ pass = *passphrase
+ }
+ return wallet.Open(pass)
+}
+
+// DeriveAccount requests a HD wallet to derive a new account, optionally pinning
+// it for later reuse.
+func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
+ wallet, err := s.am.Wallet(url)
+ if err != nil {
+ return accounts.Account{}, err
+ }
+ derivPath, err := accounts.ParseDerivationPath(path)
+ if err != nil {
+ return accounts.Account{}, err
+ }
+ if pin == nil {
+ pin = new(bool)
+ }
+ return wallet.Derive(derivPath, *pin)
+}
+
+// NewAccount will create a new account and returns the address for the new account.
+func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
+ acc, err := fetchKeystore(s.am).NewAccount(password)
+ if err == nil {
+ log.Info("Your new key was generated", "address", acc.Address)
+ log.Warn("Please backup your key file!", "path", acc.URL.Path)
+ log.Warn("Please remember your password!")
+ return acc.Address, nil
+ }
+ return common.Address{}, err
+}
+
+// fetchKeystore retrives the encrypted keystore from the account manager.
+func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
+ return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+}
+
+// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
+// encrypting it with the passphrase.
+func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
+ key, err := crypto.HexToECDSA(privkey)
+ if err != nil {
+ return common.Address{}, err
+ }
+ acc, err := fetchKeystore(s.am).ImportECDSA(key, password)
+ return acc.Address, err
+}
+
+// UnlockAccount will unlock the account associated with the given address with
+// the given password for duration seconds. If duration is nil it will use a
+// default of 300 seconds. It returns an indication if the account was unlocked.
+func (s *PrivateAccountAPI) UnlockAccount(ctx context.Context, addr common.Address, password string, duration *uint64) (bool, error) {
+ // When the API is exposed by external RPC(http, ws etc), unless the user
+ // explicitly specifies to allow the insecure account unlocking, otherwise
+ // it is disabled.
+ if s.b.ExtRPCEnabled() && !s.b.AccountManager().Config().InsecureUnlockAllowed {
+ return false, errors.New("account unlock with HTTP access is forbidden")
+ }
+
+ const max = uint64(time.Duration(math.MaxInt64) / time.Second)
+ var d time.Duration
+ if duration == nil {
+ d = 300 * time.Second
+ } else if *duration > max {
+ return false, errors.New("unlock duration too large")
+ } else {
+ d = time.Duration(*duration) * time.Second
+ }
+ err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d)
+ if err != nil {
+ log.Warn("Failed account unlock attempt", "address", addr, "err", err)
+ }
+ return err == nil, err
+}
+
+// LockAccount will lock the account associated with the given address when it's unlocked.
+func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
+ return fetchKeystore(s.am).Lock(addr) == nil
+}
+
+// signTransaction sets defaults and signs the given transaction
+// NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
+// and release it after the transaction has been submitted to the tx pool
+func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArgs, passwd string) (*types.Transaction, error) {
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: args.From}
+ wallet, err := s.am.Find(account)
+ if err != nil {
+ return nil, err
+ }
+ // Set some sanity defaults and terminate on failure
+ if err := args.setDefaults(ctx, s.b); err != nil {
+ return nil, err
+ }
+ // Assemble the transaction and sign with the wallet
+ tx := args.toTransaction()
+
+ return wallet.SignTxWithPassphrase(account, passwd, tx, s.b.ChainConfig().ChainID)
+}
+
+// SendTransaction will create a transaction from the given arguments and
+// tries to sign it with the key associated with args.To. If the given passwd isn't
+// able to decrypt the key it fails.
+func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
+ if args.Nonce == nil {
+ // Hold the addresse's mutex around signing to prevent concurrent assignment of
+ // the same nonce to multiple accounts.
+ s.nonceLock.LockAddr(args.From)
+ defer s.nonceLock.UnlockAddr(args.From)
+ }
+ signed, err := s.signTransaction(ctx, &args, passwd)
+ if err != nil {
+ log.Warn("Failed transaction send attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
+ return common.Hash{}, err
+ }
+ return SubmitTransaction(ctx, s.b, signed)
+}
+
+// SignTransaction will create a transaction from the given arguments and
+// tries to sign it with the key associated with args.To. If the given passwd isn't
+// able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast
+// to other nodes
+func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, passwd string) (*SignTransactionResult, error) {
+ // No need to obtain the noncelock mutex, since we won't be sending this
+ // tx into the transaction pool, but right back to the user
+ if args.Gas == nil {
+ return nil, fmt.Errorf("gas not specified")
+ }
+ if args.GasPrice == nil {
+ return nil, fmt.Errorf("gasPrice not specified")
+ }
+ if args.Nonce == nil {
+ return nil, fmt.Errorf("nonce not specified")
+ }
+ signed, err := s.signTransaction(ctx, &args, passwd)
+ if err != nil {
+ log.Warn("Failed transaction sign attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
+ return nil, err
+ }
+ data, err := rlp.EncodeToBytes(signed)
+ if err != nil {
+ return nil, err
+ }
+ return &SignTransactionResult{data, signed}, nil
+}
+
+// Sign calculates an Ethereum ECDSA signature for:
+// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
+//
+// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
+// where the V value will be 27 or 28 for legacy reasons.
+//
+// The key used to calculate the signature is decrypted with the given password.
+//
+// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
+func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: addr}
+
+ wallet, err := s.b.AccountManager().Find(account)
+ if err != nil {
+ return nil, err
+ }
+ // Assemble sign the data with the wallet
+ signature, err := wallet.SignTextWithPassphrase(account, passwd, data)
+ if err != nil {
+ log.Warn("Failed data sign attempt", "address", addr, "err", err)
+ return nil, err
+ }
+ signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
+ return signature, nil
+}
+
+// EcRecover returns the address for the account that was used to create the signature.
+// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
+// the address of:
+// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
+// addr = ecrecover(hash, signature)
+//
+// Note, the signature must conform to the secp256k1 curve R, S and V values, where
+// the V value must be 27 or 28 for legacy reasons.
+//
+// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
+func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
+ if len(sig) != 65 {
+ return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
+ }
+ if sig[64] != 27 && sig[64] != 28 {
+ return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
+ }
+ sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
+
+ rpk, err := crypto.SigToPub(accounts.TextHash(data), sig)
+ if err != nil {
+ return common.Address{}, err
+ }
+ return crypto.PubkeyToAddress(*rpk), nil
+}
+
+// SignAndSendTransaction was renamed to SendTransaction. This method is deprecated
+// and will be removed in the future. It primary goal is to give clients time to update.
+func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
+ return s.SendTransaction(ctx, args, passwd)
+}
+
+// InitializeWallet initializes a new wallet at the provided URL, by generating and returning a new private key.
+func (s *PrivateAccountAPI) InitializeWallet(ctx context.Context, url string) (string, error) {
+ wallet, err := s.am.Wallet(url)
+ if err != nil {
+ return "", err
+ }
+
+ entropy, err := bip39.NewEntropy(256)
+ if err != nil {
+ return "", err
+ }
+
+ mnemonic, err := bip39.NewMnemonic(entropy)
+ if err != nil {
+ return "", err
+ }
+
+ seed := bip39.NewSeed(mnemonic, "")
+
+ switch wallet := wallet.(type) {
+ case *scwallet.Wallet:
+ return mnemonic, wallet.Initialize(seed)
+ default:
+ return "", fmt.Errorf("Specified wallet does not support initialization")
+ }
+}
+
+// Unpair deletes a pairing between wallet and geth.
+func (s *PrivateAccountAPI) Unpair(ctx context.Context, url string, pin string) error {
+ wallet, err := s.am.Wallet(url)
+ if err != nil {
+ return err
+ }
+
+ switch wallet := wallet.(type) {
+ case *scwallet.Wallet:
+ return wallet.Unpair([]byte(pin))
+ default:
+ return fmt.Errorf("Specified wallet does not support pairing")
+ }
+}
+
+// PublicBlockChainAPI provides an API to access the Ethereum blockchain.
+// It offers only methods that operate on public data that is freely available to anyone.
+type PublicBlockChainAPI struct {
+ b Backend
+}
+
+// NewPublicBlockChainAPI creates a new Ethereum blockchain API.
+func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI {
+ return &PublicBlockChainAPI{b}
+}
+
+// ChainId returns the chainID value for transaction replay protection.
+func (s *PublicBlockChainAPI) ChainId() *hexutil.Big {
+ return (*hexutil.Big)(s.b.ChainConfig().ChainID)
+}
+
+// BlockNumber returns the block number of the chain head.
+func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 {
+ header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
+ return hexutil.Uint64(header.Number.Uint64())
+}
+
+// GetBalance returns the amount of wei for the given address in the state of the
+// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
+// block numbers are also allowed.
+func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Big, error) {
+ state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+ if state == nil || err != nil {
+ return nil, err
+ }
+ return (*hexutil.Big)(state.GetBalance(address)), state.Error()
+}
+
+// Result structs for GetProof
+type AccountResult struct {
+ Address common.Address `json:"address"`
+ AccountProof []string `json:"accountProof"`
+ Balance *hexutil.Big `json:"balance"`
+ CodeHash common.Hash `json:"codeHash"`
+ Nonce hexutil.Uint64 `json:"nonce"`
+ StorageHash common.Hash `json:"storageHash"`
+ StorageProof []StorageResult `json:"storageProof"`
+}
+type StorageResult struct {
+ Key string `json:"key"`
+ Value *hexutil.Big `json:"value"`
+ Proof []string `json:"proof"`
+}
+
+// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
+func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumber) (*AccountResult, error) {
+ state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+ if state == nil || err != nil {
+ return nil, err
+ }
+
+ storageTrie := state.StorageTrie(address)
+ storageHash := types.EmptyRootHash
+ codeHash := state.GetCodeHash(address)
+ storageProof := make([]StorageResult, len(storageKeys))
+
+ // if we have a storageTrie, (which means the account exists), we can update the storagehash
+ if storageTrie != nil {
+ storageHash = storageTrie.Hash()
+ } else {
+ // no storageTrie means the account does not exist, so the codeHash is the hash of an empty bytearray.
+ codeHash = crypto.Keccak256Hash(nil)
+ }
+
+ // create the proof for the storageKeys
+ for i, key := range storageKeys {
+ if storageTrie != nil {
+ proof, storageError := state.GetStorageProof(address, common.HexToHash(key))
+ if storageError != nil {
+ return nil, storageError
+ }
+ storageProof[i] = StorageResult{key, (*hexutil.Big)(state.GetState(address, common.HexToHash(key)).Big()), common.ToHexArray(proof)}
+ } else {
+ storageProof[i] = StorageResult{key, &hexutil.Big{}, []string{}}
+ }
+ }
+
+ // create the accountProof
+ accountProof, proofErr := state.GetProof(address)
+ if proofErr != nil {
+ return nil, proofErr
+ }
+
+ return &AccountResult{
+ Address: address,
+ AccountProof: common.ToHexArray(accountProof),
+ Balance: (*hexutil.Big)(state.GetBalance(address)),
+ CodeHash: codeHash,
+ Nonce: hexutil.Uint64(state.GetNonce(address)),
+ StorageHash: storageHash,
+ StorageProof: storageProof,
+ }, state.Error()
+}
+
+// GetHeaderByNumber returns the requested canonical block header.
+// * When blockNr is -1 the chain head is returned.
+// * When blockNr is -2 the pending chain head is returned.
+func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
+ header, err := s.b.HeaderByNumber(ctx, number)
+ if header != nil && err == nil {
+ response := s.rpcMarshalHeader(header)
+ if number == rpc.PendingBlockNumber {
+ // Pending header need to nil out a few fields
+ for _, field := range []string{"hash", "nonce", "miner"} {
+ response[field] = nil
+ }
+ }
+ return response, err
+ }
+ return nil, err
+}
+
+// GetHeaderByHash returns the requested header by hash.
+func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} {
+ header, _ := s.b.HeaderByHash(ctx, hash)
+ if header != nil {
+ return s.rpcMarshalHeader(header)
+ }
+ return nil
+}
+
+// GetBlockByNumber returns the requested canonical block.
+// * When blockNr is -1 the chain head is returned.
+// * When blockNr is -2 the pending chain head is returned.
+// * When fullTx is true all transactions in the block are returned, otherwise
+// only the transaction hash is returned.
+func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
+ block, err := s.b.BlockByNumber(ctx, number)
+ if block != nil && err == nil {
+ response, err := s.rpcMarshalBlock(block, true, fullTx)
+ if err == nil && number == rpc.PendingBlockNumber {
+ // Pending blocks need to nil out a few fields
+ for _, field := range []string{"hash", "nonce", "miner"} {
+ response[field] = nil
+ }
+ }
+ return response, err
+ }
+ return nil, err
+}
+
+// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full
+// detail, otherwise only the transaction hash is returned.
+func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
+ block, err := s.b.BlockByHash(ctx, hash)
+ if block != nil {
+ return s.rpcMarshalBlock(block, true, fullTx)
+ }
+ return nil, err
+}
+
+// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true
+// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned.
+func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) {
+ block, err := s.b.BlockByNumber(ctx, blockNr)
+ if block != nil {
+ uncles := block.Uncles()
+ if index >= hexutil.Uint(len(uncles)) {
+ log.Debug("Requested uncle not found", "number", blockNr, "hash", block.Hash(), "index", index)
+ return nil, nil
+ }
+ block = types.NewBlockWithHeader(uncles[index])
+ return s.rpcMarshalBlock(block, false, false)
+ }
+ return nil, err
+}
+
+// GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index. When fullTx is true
+// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned.
+func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (map[string]interface{}, error) {
+ block, err := s.b.BlockByHash(ctx, blockHash)
+ if block != nil {
+ uncles := block.Uncles()
+ if index >= hexutil.Uint(len(uncles)) {
+ log.Debug("Requested uncle not found", "number", block.Number(), "hash", blockHash, "index", index)
+ return nil, nil
+ }
+ block = types.NewBlockWithHeader(uncles[index])
+ return s.rpcMarshalBlock(block, false, false)
+ }
+ return nil, err
+}
+
+// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
+func (s *PublicBlockChainAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
+ if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
+ n := hexutil.Uint(len(block.Uncles()))
+ return &n
+ }
+ return nil
+}
+
+// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
+func (s *PublicBlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
+ if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
+ n := hexutil.Uint(len(block.Uncles()))
+ return &n
+ }
+ return nil
+}
+
+// GetCode returns the code stored at the given address in the state for the given block number.
+func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
+ state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+ if state == nil || err != nil {
+ return nil, err
+ }
+ code := state.GetCode(address)
+ return code, state.Error()
+}
+
+// GetStorageAt returns the storage from the state at the given address, key and
+// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
+// numbers are also allowed.
+func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, key string, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
+ state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+ if state == nil || err != nil {
+ return nil, err
+ }
+ res := state.GetState(address, common.HexToHash(key))
+ return res[:], state.Error()
+}
+
+// CallArgs represents the arguments for a call.
+type CallArgs struct {
+ From *common.Address `json:"from"`
+ To *common.Address `json:"to"`
+ Gas *hexutil.Uint64 `json:"gas"`
+ GasPrice *hexutil.Big `json:"gasPrice"`
+ Value *hexutil.Big `json:"value"`
+ Data *hexutil.Bytes `json:"data"`
+}
+
+// account indicates the overriding fields of account during the execution of
+// a message call.
+// Note, state and stateDiff can't be specified at the same time. If state is
+// set, message execution will only use the data in the given state. Otherwise
+// if statDiff is set, all diff will be applied first and then execute the call
+// message.
+type account struct {
+ Nonce *hexutil.Uint64 `json:"nonce"`
+ Code *hexutil.Bytes `json:"code"`
+ Balance **hexutil.Big `json:"balance"`
+ State *map[common.Hash]common.Hash `json:"state"`
+ StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
+}
+
+func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
+ defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
+
+ state, header, err := b.StateAndHeaderByNumber(ctx, blockNr)
+ if state == nil || err != nil {
+ return nil, 0, false, err
+ }
+ // Set sender address or use a default if none specified
+ var addr common.Address
+ if args.From == nil {
+ if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
+ if accounts := wallets[0].Accounts(); len(accounts) > 0 {
+ addr = accounts[0].Address
+ }
+ }
+ } else {
+ addr = *args.From
+ }
+ // Override the fields of specified contracts before execution.
+ for addr, account := range overrides {
+ // Override account nonce.
+ if account.Nonce != nil {
+ state.SetNonce(addr, uint64(*account.Nonce))
+ }
+ // Override account(contract) code.
+ if account.Code != nil {
+ state.SetCode(addr, *account.Code)
+ }
+ // Override account balance.
+ if account.Balance != nil {
+ state.SetBalance(addr, (*big.Int)(*account.Balance))
+ }
+ if account.State != nil && account.StateDiff != nil {
+ return nil, 0, false, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
+ }
+ // Replace entire state if caller requires.
+ if account.State != nil {
+ state.SetStorage(addr, *account.State)
+ }
+ // Apply state diff into specified accounts.
+ if account.StateDiff != nil {
+ for key, value := range *account.StateDiff {
+ state.SetState(addr, key, value)
+ }
+ }
+ }
+ // Set default gas & gas price if none were set
+ gas := uint64(math.MaxUint64 / 2)
+ if args.Gas != nil {
+ gas = uint64(*args.Gas)
+ }
+ if globalGasCap != nil && globalGasCap.Uint64() < gas {
+ log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
+ gas = globalGasCap.Uint64()
+ }
+ gasPrice := new(big.Int).SetUint64(defaultGasPrice)
+ if args.GasPrice != nil {
+ gasPrice = args.GasPrice.ToInt()
+ }
+
+ value := new(big.Int)
+ if args.Value != nil {
+ value = args.Value.ToInt()
+ }
+
+ var data []byte
+ if args.Data != nil {
+ data = []byte(*args.Data)
+ }
+
+ // Create new call message
+ msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false)
+
+ // Setup context so it may be cancelled the call has completed
+ // or, in case of unmetered gas, setup a context with a timeout.
+ var cancel context.CancelFunc
+ if timeout > 0 {
+ ctx, cancel = context.WithTimeout(ctx, timeout)
+ } else {
+ ctx, cancel = context.WithCancel(ctx)
+ }
+ // Make sure the context is cancelled when the call has completed
+ // this makes sure resources are cleaned up.
+ defer cancel()
+
+ // Get a new instance of the EVM.
+ evm, vmError, err := b.GetEVM(ctx, msg, state, header)
+ if err != nil {
+ return nil, 0, false, err
+ }
+ // Wait for the context to be done and cancel the evm. Even if the
+ // EVM has finished, cancelling may be done (repeatedly)
+ go func() {
+ <-ctx.Done()
+ evm.Cancel()
+ }()
+
+ // Setup the gas pool (also for unmetered requests)
+ // and apply the message.
+ gp := new(core.GasPool).AddGas(math.MaxUint64)
+ res, gas, failed, err := core.ApplyMessage(evm, msg, gp)
+ if err := vmError(); err != nil {
+ return nil, 0, false, err
+ }
+ // If the timer caused an abort, return an appropriate error message
+ if evm.Cancelled() {
+ return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout)
+ }
+ return res, gas, failed, err
+}
+
+// Call executes the given transaction on the state for the given block number.
+//
+// Additionally, the caller can specify a batch of contract for fields overriding.
+//
+// Note, this function doesn't make and changes in the state/blockchain and is
+// useful to execute and retrieve values.
+func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, overrides *map[common.Address]account) (hexutil.Bytes, error) {
+ var accounts map[common.Address]account
+ if overrides != nil {
+ accounts = *overrides
+ }
+ result, _, _, err := DoCall(ctx, s.b, args, blockNr, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
+ return (hexutil.Bytes)(result), err
+}
+
+func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, gasCap *big.Int) (hexutil.Uint64, error) {
+ // Binary search the gas requirement, as it may be higher than the amount used
+ var (
+ lo uint64 = params.TxGas - 1
+ hi uint64
+ cap uint64
+ )
+ if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
+ hi = uint64(*args.Gas)
+ } else {
+ // Retrieve the block to act as the gas ceiling
+ block, err := b.BlockByNumber(ctx, blockNr)
+ if err != nil {
+ return 0, err
+ }
+ hi = block.GasLimit()
+ }
+ if gasCap != nil && hi > gasCap.Uint64() {
+ log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
+ hi = gasCap.Uint64()
+ }
+ cap = hi
+
+ // Create a helper to check if a gas allowance results in an executable transaction
+ executable := func(gas uint64) bool {
+ args.Gas = (*hexutil.Uint64)(&gas)
+
+ _, _, failed, err := DoCall(ctx, b, args, rpc.PendingBlockNumber, nil, vm.Config{}, 0, gasCap)
+ if err != nil || failed {
+ return false
+ }
+ return true
+ }
+ // Execute the binary search and hone in on an executable gas limit
+ for lo+1 < hi {
+ mid := (hi + lo) / 2
+ if !executable(mid) {
+ lo = mid
+ } else {
+ hi = mid
+ }
+ }
+ // Reject the transaction as invalid if it still fails at the highest allowance
+ if hi == cap {
+ if !executable(hi) {
+ return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", cap)
+ }
+ }
+ return hexutil.Uint64(hi), nil
+}
+
+// EstimateGas returns an estimate of the amount of gas needed to execute the
+// given transaction against the current pending block.
+func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) {
+ return DoEstimateGas(ctx, s.b, args, rpc.PendingBlockNumber, s.b.RPCGasCap())
+}
+
+// ExecutionResult groups all structured logs emitted by the EVM
+// while replaying a transaction in debug mode as well as transaction
+// execution status, the amount of gas used and the return value
+type ExecutionResult struct {
+ Gas uint64 `json:"gas"`
+ Failed bool `json:"failed"`
+ ReturnValue string `json:"returnValue"`
+ StructLogs []StructLogRes `json:"structLogs"`
+}
+
+// StructLogRes stores a structured log emitted by the EVM while replaying a
+// transaction in debug mode
+type StructLogRes struct {
+ Pc uint64 `json:"pc"`
+ Op string `json:"op"`
+ Gas uint64 `json:"gas"`
+ GasCost uint64 `json:"gasCost"`
+ Depth int `json:"depth"`
+ Error error `json:"error,omitempty"`
+ Stack *[]string `json:"stack,omitempty"`
+ Memory *[]string `json:"memory,omitempty"`
+ Storage *map[string]string `json:"storage,omitempty"`
+}
+
+// FormatLogs formats EVM returned structured logs for json output
+func FormatLogs(logs []vm.StructLog) []StructLogRes {
+ formatted := make([]StructLogRes, len(logs))
+ for index, trace := range logs {
+ formatted[index] = StructLogRes{
+ Pc: trace.Pc,
+ Op: trace.Op.String(),
+ Gas: trace.Gas,
+ GasCost: trace.GasCost,
+ Depth: trace.Depth,
+ Error: trace.Err,
+ }
+ if trace.Stack != nil {
+ stack := make([]string, len(trace.Stack))
+ for i, stackValue := range trace.Stack {
+ stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32))
+ }
+ formatted[index].Stack = &stack
+ }
+ if trace.Memory != nil {
+ memory := make([]string, 0, (len(trace.Memory)+31)/32)
+ for i := 0; i+32 <= len(trace.Memory); i += 32 {
+ memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
+ }
+ formatted[index].Memory = &memory
+ }
+ if trace.Storage != nil {
+ storage := make(map[string]string)
+ for i, storageValue := range trace.Storage {
+ storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
+ }
+ formatted[index].Storage = &storage
+ }
+ }
+ return formatted
+}
+
+// RPCMarshalHeader converts the given header to the RPC output .
+func RPCMarshalHeader(head *types.Header) map[string]interface{} {
+ return map[string]interface{}{
+ "number": (*hexutil.Big)(head.Number),
+ "hash": head.Hash(),
+ "parentHash": head.ParentHash,
+ "nonce": head.Nonce,
+ "mixHash": head.MixDigest,
+ "sha3Uncles": head.UncleHash,
+ "logsBloom": head.Bloom,
+ "stateRoot": head.Root,
+ "miner": head.Coinbase,
+ "difficulty": (*hexutil.Big)(head.Difficulty),
+ "extraData": hexutil.Bytes(head.Extra),
+ "size": hexutil.Uint64(head.Size()),
+ "gasLimit": hexutil.Uint64(head.GasLimit),
+ "gasUsed": hexutil.Uint64(head.GasUsed),
+ "timestamp": hexutil.Uint64(head.Time),
+ "transactionsRoot": head.TxHash,
+ "receiptsRoot": head.ReceiptHash,
+ }
+}
+
+// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
+// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain
+// transaction hashes.
+func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
+ fields := RPCMarshalHeader(block.Header())
+ fields["size"] = hexutil.Uint64(block.Size())
+
+ if inclTx {
+ formatTx := func(tx *types.Transaction) (interface{}, error) {
+ return tx.Hash(), nil
+ }
+ if fullTx {
+ formatTx = func(tx *types.Transaction) (interface{}, error) {
+ return newRPCTransactionFromBlockHash(block, tx.Hash()), nil
+ }
+ }
+ txs := block.Transactions()
+ transactions := make([]interface{}, len(txs))
+ var err error
+ for i, tx := range txs {
+ if transactions[i], err = formatTx(tx); err != nil {
+ return nil, err
+ }
+ }
+ fields["transactions"] = transactions
+ }
+ uncles := block.Uncles()
+ uncleHashes := make([]common.Hash, len(uncles))
+ for i, uncle := range uncles {
+ uncleHashes[i] = uncle.Hash()
+ }
+ fields["uncles"] = uncleHashes
+
+ return fields, nil
+}
+
+// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires
+// a `PublicBlockchainAPI`.
+func (s *PublicBlockChainAPI) rpcMarshalHeader(header *types.Header) map[string]interface{} {
+ fields := RPCMarshalHeader(header)
+ fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(header.Hash()))
+ return fields
+}
+
+// rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires
+// a `PublicBlockchainAPI`.
+func (s *PublicBlockChainAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
+ fields, err := RPCMarshalBlock(b, inclTx, fullTx)
+ if err != nil {
+ return nil, err
+ }
+ fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(b.Hash()))
+ return fields, err
+}
+
+// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
+type RPCTransaction struct {
+ BlockHash *common.Hash `json:"blockHash"`
+ BlockNumber *hexutil.Big `json:"blockNumber"`
+ From common.Address `json:"from"`
+ Gas hexutil.Uint64 `json:"gas"`
+ GasPrice *hexutil.Big `json:"gasPrice"`
+ Hash common.Hash `json:"hash"`
+ Input hexutil.Bytes `json:"input"`
+ Nonce hexutil.Uint64 `json:"nonce"`
+ To *common.Address `json:"to"`
+ TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
+ Value *hexutil.Big `json:"value"`
+ V *hexutil.Big `json:"v"`
+ R *hexutil.Big `json:"r"`
+ S *hexutil.Big `json:"s"`
+}
+
+// newRPCTransaction returns a transaction that will serialize to the RPC
+// representation, with the given location metadata set (if available).
+func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction {
+ var signer types.Signer = types.FrontierSigner{}
+ if tx.Protected() {
+ signer = types.NewEIP155Signer(tx.ChainId())
+ }
+ from, _ := types.Sender(signer, tx)
+ v, r, s := tx.RawSignatureValues()
+
+ result := &RPCTransaction{
+ From: from,
+ Gas: hexutil.Uint64(tx.Gas()),
+ GasPrice: (*hexutil.Big)(tx.GasPrice()),
+ Hash: tx.Hash(),
+ Input: hexutil.Bytes(tx.Data()),
+ Nonce: hexutil.Uint64(tx.Nonce()),
+ To: tx.To(),
+ Value: (*hexutil.Big)(tx.Value()),
+ V: (*hexutil.Big)(v),
+ R: (*hexutil.Big)(r),
+ S: (*hexutil.Big)(s),
+ }
+ if blockHash != (common.Hash{}) {
+ result.BlockHash = &blockHash
+ result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
+ result.TransactionIndex = (*hexutil.Uint64)(&index)
+ }
+ return result
+}
+
+// newRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation
+func newRPCPendingTransaction(tx *types.Transaction) *RPCTransaction {
+ return newRPCTransaction(tx, common.Hash{}, 0, 0)
+}
+
+// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
+func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction {
+ txs := b.Transactions()
+ if index >= uint64(len(txs)) {
+ return nil
+ }
+ return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index)
+}
+
+// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
+func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.Bytes {
+ txs := b.Transactions()
+ if index >= uint64(len(txs)) {
+ return nil
+ }
+ blob, _ := rlp.EncodeToBytes(txs[index])
+ return blob
+}
+
+// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation.
+func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction {
+ for idx, tx := range b.Transactions() {
+ if tx.Hash() == hash {
+ return newRPCTransactionFromBlockIndex(b, uint64(idx))
+ }
+ }
+ return nil
+}
+
+// PublicTransactionPoolAPI exposes methods for the RPC interface
+type PublicTransactionPoolAPI struct {
+ b Backend
+ nonceLock *AddrLocker
+}
+
+// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
+func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI {
+ return &PublicTransactionPoolAPI{b, nonceLock}
+}
+
+// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
+func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
+ if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
+ n := hexutil.Uint(len(block.Transactions()))
+ return &n
+ }
+ return nil
+}
+
+// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
+func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
+ if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
+ n := hexutil.Uint(len(block.Transactions()))
+ return &n
+ }
+ return nil
+}
+
+// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
+func (s *PublicTransactionPoolAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
+ if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
+ return newRPCTransactionFromBlockIndex(block, uint64(index))
+ }
+ return nil
+}
+
+// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
+func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
+ if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
+ return newRPCTransactionFromBlockIndex(block, uint64(index))
+ }
+ return nil
+}
+
+// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.
+func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes {
+ if block, _ := s.b.BlockByNumber(ctx, blockNr); block != nil {
+ return newRPCRawTransactionFromBlockIndex(block, uint64(index))
+ }
+ return nil
+}
+
+// GetRawTransactionByBlockHashAndIndex returns the bytes of the transaction for the given block hash and index.
+func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes {
+ if block, _ := s.b.BlockByHash(ctx, blockHash); block != nil {
+ return newRPCRawTransactionFromBlockIndex(block, uint64(index))
+ }
+ return nil
+}
+
+// GetTransactionCount returns the number of transactions the given address has sent for the given block number
+func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Uint64, error) {
+ // Ask transaction pool for the nonce which includes pending transactions
+ if blockNr == rpc.PendingBlockNumber {
+ nonce, err := s.b.GetPoolNonce(ctx, address)
+ if err != nil {
+ return nil, err
+ }
+ return (*hexutil.Uint64)(&nonce), nil
+ }
+ // Resolve block number and use its state to ask for the nonce
+ state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
+ if state == nil || err != nil {
+ return nil, err
+ }
+ nonce := state.GetNonce(address)
+ return (*hexutil.Uint64)(&nonce), state.Error()
+}
+
+// GetTransactionByHash returns the transaction for the given hash
+func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
+ // Try to return an already finalized transaction
+ tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
+ if err != nil {
+ return nil, err
+ }
+ if tx != nil {
+ return newRPCTransaction(tx, blockHash, blockNumber, index), nil
+ }
+ // No finalized transaction, try to retrieve it from the pool
+ if tx := s.b.GetPoolTransaction(hash); tx != nil {
+ return newRPCPendingTransaction(tx), nil
+ }
+
+ // Transaction unknown, return as such
+ return nil, nil
+}
+
+// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
+func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
+ // Retrieve a finalized transaction, or a pooled otherwise
+ tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
+ if err != nil {
+ return nil, err
+ }
+ if tx == nil {
+ if tx = s.b.GetPoolTransaction(hash); tx == nil {
+ // Transaction not found anywhere, abort
+ return nil, nil
+ }
+ }
+ // Serialize to RLP and return
+ return rlp.EncodeToBytes(tx)
+}
+
+// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
+func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
+ tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash)
+ if tx == nil {
+ return nil, nil
+ }
+ receipts, err := s.b.GetReceipts(ctx, blockHash)
+ if err != nil {
+ return nil, err
+ }
+ if len(receipts) <= int(index) {
+ return nil, nil
+ }
+ receipt := receipts[index]
+
+ var signer types.Signer = types.FrontierSigner{}
+ if tx.Protected() {
+ signer = types.NewEIP155Signer(tx.ChainId())
+ }
+ from, _ := types.Sender(signer, tx)
+
+ fields := map[string]interface{}{
+ "blockHash": blockHash,
+ "blockNumber": hexutil.Uint64(blockNumber),
+ "transactionHash": hash,
+ "transactionIndex": hexutil.Uint64(index),
+ "from": from,
+ "to": tx.To(),
+ "gasUsed": hexutil.Uint64(receipt.GasUsed),
+ "cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
+ "contractAddress": nil,
+ "logs": receipt.Logs,
+ "logsBloom": receipt.Bloom,
+ }
+
+ // Assign receipt status or post state.
+ if len(receipt.PostState) > 0 {
+ fields["root"] = hexutil.Bytes(receipt.PostState)
+ } else {
+ fields["status"] = hexutil.Uint(receipt.Status)
+ }
+ if receipt.Logs == nil {
+ fields["logs"] = [][]*types.Log{}
+ }
+ // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
+ if receipt.ContractAddress != (common.Address{}) {
+ fields["contractAddress"] = receipt.ContractAddress
+ }
+ return fields, nil
+}
+
+// sign is a helper function that signs a transaction with the private key of the given address.
+func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: addr}
+
+ wallet, err := s.b.AccountManager().Find(account)
+ if err != nil {
+ return nil, err
+ }
+ // Request the wallet to sign the transaction
+ return wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
+}
+
+// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
+type SendTxArgs struct {
+ From common.Address `json:"from"`
+ To *common.Address `json:"to"`
+ Gas *hexutil.Uint64 `json:"gas"`
+ GasPrice *hexutil.Big `json:"gasPrice"`
+ Value *hexutil.Big `json:"value"`
+ Nonce *hexutil.Uint64 `json:"nonce"`
+ // We accept "data" and "input" for backwards-compatibility reasons. "input" is the
+ // newer name and should be preferred by clients.
+ Data *hexutil.Bytes `json:"data"`
+ Input *hexutil.Bytes `json:"input"`
+}
+
+// setDefaults is a helper function that fills in default values for unspecified tx fields.
+func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
+ if args.GasPrice == nil {
+ price, err := b.SuggestPrice(ctx)
+ if err != nil {
+ return err
+ }
+ args.GasPrice = (*hexutil.Big)(price)
+ }
+ if args.Value == nil {
+ args.Value = new(hexutil.Big)
+ }
+ if args.Nonce == nil {
+ nonce, err := b.GetPoolNonce(ctx, args.From)
+ if err != nil {
+ return err
+ }
+ args.Nonce = (*hexutil.Uint64)(&nonce)
+ }
+ if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
+ return errors.New(`Both "data" and "input" are set and not equal. Please use "input" to pass transaction call data.`)
+ }
+ if args.To == nil {
+ // Contract creation
+ var input []byte
+ if args.Data != nil {
+ input = *args.Data
+ } else if args.Input != nil {
+ input = *args.Input
+ }
+ if len(input) == 0 {
+ return errors.New(`contract creation without any data provided`)
+ }
+ }
+ // Estimate the gas usage if necessary.
+ if args.Gas == nil {
+ // For backwards-compatibility reason, we try both input and data
+ // but input is preferred.
+ input := args.Input
+ if input == nil {
+ input = args.Data
+ }
+ callArgs := CallArgs{
+ From: &args.From, // From shouldn't be nil
+ To: args.To,
+ GasPrice: args.GasPrice,
+ Value: args.Value,
+ Data: input,
+ }
+ estimated, err := DoEstimateGas(ctx, b, callArgs, rpc.PendingBlockNumber, b.RPCGasCap())
+ if err != nil {
+ return err
+ }
+ args.Gas = &estimated
+ log.Trace("Estimate gas usage automatically", "gas", args.Gas)
+ }
+ return nil
+}
+
+func (args *SendTxArgs) toTransaction() *types.Transaction {
+ var input []byte
+ if args.Input != nil {
+ input = *args.Input
+ } else if args.Data != nil {
+ input = *args.Data
+ }
+ if args.To == nil {
+ return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
+ }
+ return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
+}
+
+// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
+func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
+ if err := b.SendTx(ctx, tx); err != nil {
+ return common.Hash{}, err
+ }
+ if tx.To() == nil {
+ signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
+ from, err := types.Sender(signer, tx)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ addr := crypto.CreateAddress(from, tx.Nonce())
+ log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())
+ } else {
+ log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To())
+ }
+ return tx.Hash(), nil
+}
+
+// SendTransaction creates a transaction for the given argument, sign it and submit it to the
+// transaction pool.
+func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: args.From}
+
+ wallet, err := s.b.AccountManager().Find(account)
+ if err != nil {
+ return common.Hash{}, err
+ }
+
+ if args.Nonce == nil {
+ // Hold the addresse's mutex around signing to prevent concurrent assignment of
+ // the same nonce to multiple accounts.
+ s.nonceLock.LockAddr(args.From)
+ defer s.nonceLock.UnlockAddr(args.From)
+ }
+
+ // Set some sanity defaults and terminate on failure
+ if err := args.setDefaults(ctx, s.b); err != nil {
+ return common.Hash{}, err
+ }
+ // Assemble the transaction and sign with the wallet
+ tx := args.toTransaction()
+
+ signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return SubmitTransaction(ctx, s.b, signed)
+}
+
+// SendRawTransaction will add the signed transaction to the transaction pool.
+// The sender is responsible for signing the transaction and using the correct nonce.
+func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
+ tx := new(types.Transaction)
+ if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
+ return common.Hash{}, err
+ }
+ return SubmitTransaction(ctx, s.b, tx)
+}
+
+// Sign calculates an ECDSA signature for:
+// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message).
+//
+// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
+// where the V value will be 27 or 28 for legacy reasons.
+//
+// The account associated with addr must be unlocked.
+//
+// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
+func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: addr}
+
+ wallet, err := s.b.AccountManager().Find(account)
+ if err != nil {
+ return nil, err
+ }
+ // Sign the requested hash with the wallet
+ signature, err := wallet.SignText(account, data)
+ if err == nil {
+ signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
+ }
+ return signature, err
+}
+
+// SignTransactionResult represents a RLP encoded signed transaction.
+type SignTransactionResult struct {
+ Raw hexutil.Bytes `json:"raw"`
+ Tx *types.Transaction `json:"tx"`
+}
+
+// SignTransaction will sign the given transaction with the from account.
+// The node needs to have the private key of the account corresponding with
+// the given from address and it needs to be unlocked.
+func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
+ if args.Gas == nil {
+ return nil, fmt.Errorf("gas not specified")
+ }
+ if args.GasPrice == nil {
+ return nil, fmt.Errorf("gasPrice not specified")
+ }
+ if args.Nonce == nil {
+ return nil, fmt.Errorf("nonce not specified")
+ }
+ if err := args.setDefaults(ctx, s.b); err != nil {
+ return nil, err
+ }
+ tx, err := s.sign(args.From, args.toTransaction())
+ if err != nil {
+ return nil, err
+ }
+ data, err := rlp.EncodeToBytes(tx)
+ if err != nil {
+ return nil, err
+ }
+ return &SignTransactionResult{data, tx}, nil
+}
+
+// PendingTransactions returns the transactions that are in the transaction pool
+// and have a from address that is one of the accounts this node manages.
+func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) {
+ pending, err := s.b.GetPoolTransactions()
+ if err != nil {
+ return nil, err
+ }
+ accounts := make(map[common.Address]struct{})
+ for _, wallet := range s.b.AccountManager().Wallets() {
+ for _, account := range wallet.Accounts() {
+ accounts[account.Address] = struct{}{}
+ }
+ }
+ transactions := make([]*RPCTransaction, 0, len(pending))
+ for _, tx := range pending {
+ var signer types.Signer = types.HomesteadSigner{}
+ if tx.Protected() {
+ signer = types.NewEIP155Signer(tx.ChainId())
+ }
+ from, _ := types.Sender(signer, tx)
+ if _, exists := accounts[from]; exists {
+ transactions = append(transactions, newRPCPendingTransaction(tx))
+ }
+ }
+ return transactions, nil
+}
+
+// Resend accepts an existing transaction and a new gas price and limit. It will remove
+// the given transaction from the pool and reinsert it with the new gas price and limit.
+func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
+ if sendArgs.Nonce == nil {
+ return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec")
+ }
+ if err := sendArgs.setDefaults(ctx, s.b); err != nil {
+ return common.Hash{}, err
+ }
+ matchTx := sendArgs.toTransaction()
+ pending, err := s.b.GetPoolTransactions()
+ if err != nil {
+ return common.Hash{}, err
+ }
+
+ for _, p := range pending {
+ var signer types.Signer = types.HomesteadSigner{}
+ if p.Protected() {
+ signer = types.NewEIP155Signer(p.ChainId())
+ }
+ wantSigHash := signer.Hash(matchTx)
+
+ if pFrom, err := types.Sender(signer, p); err == nil && pFrom == sendArgs.From && signer.Hash(p) == wantSigHash {
+ // Match. Re-sign and send the transaction.
+ if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
+ sendArgs.GasPrice = gasPrice
+ }
+ if gasLimit != nil && *gasLimit != 0 {
+ sendArgs.Gas = gasLimit
+ }
+ signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction())
+ if err != nil {
+ return common.Hash{}, err
+ }
+ if err = s.b.SendTx(ctx, signedTx); err != nil {
+ return common.Hash{}, err
+ }
+ return signedTx.Hash(), nil
+ }
+ }
+
+ return common.Hash{}, fmt.Errorf("Transaction %#x not found", matchTx.Hash())
+}
+
+// PublicDebugAPI is the collection of Ethereum APIs exposed over the public
+// debugging endpoint.
+type PublicDebugAPI struct {
+ b Backend
+}
+
+// NewPublicDebugAPI creates a new API definition for the public debug methods
+// of the Ethereum service.
+func NewPublicDebugAPI(b Backend) *PublicDebugAPI {
+ return &PublicDebugAPI{b: b}
+}
+
+// GetBlockRlp retrieves the RLP encoded for of a single block.
+func (api *PublicDebugAPI) GetBlockRlp(ctx context.Context, number uint64) (string, error) {
+ block, _ := api.b.BlockByNumber(ctx, rpc.BlockNumber(number))
+ if block == nil {
+ return "", fmt.Errorf("block #%d not found", number)
+ }
+ encoded, err := rlp.EncodeToBytes(block)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%x", encoded), nil
+}
+
+// TestSignCliqueBlock fetches the given block number, and attempts to sign it as a clique header with the
+// given address, returning the address of the recovered signature
+//
+// This is a temporary method to debug the externalsigner integration,
+// TODO: Remove this method when the integration is mature
+func (api *PublicDebugAPI) TestSignCliqueBlock(ctx context.Context, address common.Address, number uint64) (common.Address, error) {
+ block, _ := api.b.BlockByNumber(ctx, rpc.BlockNumber(number))
+ if block == nil {
+ return common.Address{}, fmt.Errorf("block #%d not found", number)
+ }
+ header := block.Header()
+ header.Extra = make([]byte, 32+65)
+ encoded := clique.CliqueRLP(header)
+
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: address}
+ wallet, err := api.b.AccountManager().Find(account)
+ if err != nil {
+ return common.Address{}, err
+ }
+
+ signature, err := wallet.SignData(account, accounts.MimetypeClique, encoded)
+ if err != nil {
+ return common.Address{}, err
+ }
+ sealHash := clique.SealHash(header).Bytes()
+ log.Info("test signing of clique block",
+ "Sealhash", fmt.Sprintf("%x", sealHash),
+ "signature", fmt.Sprintf("%x", signature))
+ pubkey, err := crypto.Ecrecover(sealHash, signature)
+ if err != nil {
+ return common.Address{}, err
+ }
+ var signer common.Address
+ copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
+
+ return signer, nil
+}
+
+// PrintBlock retrieves a block and returns its pretty printed form.
+func (api *PublicDebugAPI) PrintBlock(ctx context.Context, number uint64) (string, error) {
+ block, _ := api.b.BlockByNumber(ctx, rpc.BlockNumber(number))
+ if block == nil {
+ return "", fmt.Errorf("block #%d not found", number)
+ }
+ return spew.Sdump(block), nil
+}
+
+// SeedHash retrieves the seed hash of a block.
+func (api *PublicDebugAPI) SeedHash(ctx context.Context, number uint64) (string, error) {
+ block, _ := api.b.BlockByNumber(ctx, rpc.BlockNumber(number))
+ if block == nil {
+ return "", fmt.Errorf("block #%d not found", number)
+ }
+ return fmt.Sprintf("0x%x", ethash.SeedHash(number)), nil
+}
+
+// PrivateDebugAPI is the collection of Ethereum APIs exposed over the private
+// debugging endpoint.
+type PrivateDebugAPI struct {
+ b Backend
+}
+
+// NewPrivateDebugAPI creates a new API definition for the private debug methods
+// of the Ethereum service.
+func NewPrivateDebugAPI(b Backend) *PrivateDebugAPI {
+ return &PrivateDebugAPI{b: b}
+}
+
+// ChaindbProperty returns leveldb properties of the key-value database.
+func (api *PrivateDebugAPI) ChaindbProperty(property string) (string, error) {
+ if property == "" {
+ property = "leveldb.stats"
+ } else if !strings.HasPrefix(property, "leveldb.") {
+ property = "leveldb." + property
+ }
+ return api.b.ChainDb().Stat(property)
+}
+
+// ChaindbCompact flattens the entire key-value database into a single level,
+// removing all unused slots and merging all keys.
+func (api *PrivateDebugAPI) ChaindbCompact() error {
+ for b := byte(0); b < 255; b++ {
+ log.Info("Compacting chain database", "range", fmt.Sprintf("0x%0.2X-0x%0.2X", b, b+1))
+ if err := api.b.ChainDb().Compact([]byte{b}, []byte{b + 1}); err != nil {
+ log.Error("Database compaction failed", "err", err)
+ return err
+ }
+ }
+ return nil
+}
+
+// SetHead rewinds the head of the blockchain to a previous block.
+func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) {
+ api.b.SetHead(uint64(number))
+}
+
+// PublicNetAPI offers network related RPC methods
+type PublicNetAPI struct {
+ net *p2p.Server
+ networkVersion uint64
+}
+
+// NewPublicNetAPI creates a new net API instance.
+func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI {
+ return &PublicNetAPI{net, networkVersion}
+}
+
+// Listening returns an indication if the node is listening for network connections.
+func (s *PublicNetAPI) Listening() bool {
+ return true // always listening
+}
+
+// PeerCount returns the number of connected peers
+func (s *PublicNetAPI) PeerCount() hexutil.Uint {
+ return hexutil.Uint(s.net.PeerCount())
+}
+
+// Version returns the current ethereum protocol version.
+func (s *PublicNetAPI) Version() string {
+ return fmt.Sprintf("%d", s.networkVersion)
+}
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
new file mode 100644
index 0000000..06c6db3
--- /dev/null
+++ b/internal/ethapi/backend.go
@@ -0,0 +1,130 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Package ethapi implements the general Ethereum API functions.
+package ethapi
+
+import (
+ "context"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/bloombits"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+// Backend interface provides the common API services (that are provided by
+// both full and light clients) with access to necessary functions.
+type Backend interface {
+ // General Ethereum API
+ Downloader() *downloader.Downloader
+ ProtocolVersion() int
+ SuggestPrice(ctx context.Context) (*big.Int, error)
+ ChainDb() ethdb.Database
+ EventMux() *event.TypeMux
+ AccountManager() *accounts.Manager
+ ExtRPCEnabled() bool
+ RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection
+
+ // Blockchain API
+ SetHead(number uint64)
+ HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
+ HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
+ BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
+ BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
+ StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error)
+ GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
+ GetTd(hash common.Hash) *big.Int
+ GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error)
+ SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
+ SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
+ SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
+
+ // Transaction pool API
+ SendTx(ctx context.Context, signedTx *types.Transaction) error
+ GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
+ GetPoolTransactions() (types.Transactions, error)
+ GetPoolTransaction(txHash common.Hash) *types.Transaction
+ GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
+ Stats() (pending int, queued int)
+ TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)
+ SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
+
+ // Filter API
+ BloomStatus() (uint64, uint64)
+ GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error)
+ ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
+ SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
+ SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
+
+ ChainConfig() *params.ChainConfig
+ CurrentBlock() *types.Block
+}
+
+func GetAPIs(apiBackend Backend) []rpc.API {
+ nonceLock := new(AddrLocker)
+ return []rpc.API{
+ {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: NewPublicEthereumAPI(apiBackend),
+ Public: true,
+ }, {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: NewPublicBlockChainAPI(apiBackend),
+ Public: true,
+ }, {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: NewPublicTransactionPoolAPI(apiBackend, nonceLock),
+ Public: true,
+ }, {
+ Namespace: "txpool",
+ Version: "1.0",
+ Service: NewPublicTxPoolAPI(apiBackend),
+ Public: true,
+ }, {
+ Namespace: "debug",
+ Version: "1.0",
+ Service: NewPublicDebugAPI(apiBackend),
+ Public: true,
+ }, {
+ Namespace: "debug",
+ Version: "1.0",
+ Service: NewPrivateDebugAPI(apiBackend),
+ }, {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: NewPublicAccountAPI(apiBackend.AccountManager()),
+ Public: true,
+ }, {
+ Namespace: "personal",
+ Version: "1.0",
+ Service: NewPrivateAccountAPI(apiBackend, nonceLock),
+ Public: false,
+ },
+ }
+}
diff --git a/miner/miner.go b/miner/miner.go
new file mode 100644
index 0000000..b342bb8
--- /dev/null
+++ b/miner/miner.go
@@ -0,0 +1,51 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Package miner implements Ethereum block creation and mining.
+package miner
+
+import (
+
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/params"
+ eminer "github.com/ethereum/go-ethereum/miner"
+)
+
+// Backend wraps all methods required for mining.
+type Backend interface {
+ BlockChain() *core.BlockChain
+ TxPool() *core.TxPool
+}
+
+// Config is the configuration parameters of mining.
+type Config = eminer.Config
+
+type Worker = worker
+
+func NewWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(*types.Block) bool) *Worker {
+ return newWorker(config, chainConfig, engine, eth, mux, isLocalBlock)
+}
+
+func (self *Worker) Start() {
+ self.start()
+}
+
+func (self *Worker) Stop() {
+ self.stop()
+}
diff --git a/miner/unconfirmed.go b/miner/unconfirmed.go
new file mode 100644
index 0000000..3a176e8
--- /dev/null
+++ b/miner/unconfirmed.go
@@ -0,0 +1,136 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package miner
+
+import (
+ "container/ring"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+// chainRetriever is used by the unconfirmed block set to verify whether a previously
+// mined block is part of the canonical chain or not.
+type chainRetriever interface {
+ // GetHeaderByNumber retrieves the canonical header associated with a block number.
+ GetHeaderByNumber(number uint64) *types.Header
+
+ // GetBlockByNumber retrieves the canonical block associated with a block number.
+ GetBlockByNumber(number uint64) *types.Block
+}
+
+// unconfirmedBlock is a small collection of metadata about a locally mined block
+// that is placed into a unconfirmed set for canonical chain inclusion tracking.
+type unconfirmedBlock struct {
+ index uint64
+ hash common.Hash
+}
+
+// unconfirmedBlocks implements a data structure to maintain locally mined blocks
+// have not yet reached enough maturity to guarantee chain inclusion. It is
+// used by the miner to provide logs to the user when a previously mined block
+// has a high enough guarantee to not be reorged out of the canonical chain.
+type unconfirmedBlocks struct {
+ chain chainRetriever // Blockchain to verify canonical status through
+ depth uint // Depth after which to discard previous blocks
+ blocks *ring.Ring // Block infos to allow canonical chain cross checks
+ lock sync.RWMutex // Protects the fields from concurrent access
+}
+
+// newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks.
+func newUnconfirmedBlocks(chain chainRetriever, depth uint) *unconfirmedBlocks {
+ return &unconfirmedBlocks{
+ chain: chain,
+ depth: depth,
+ }
+}
+
+// Insert adds a new block to the set of unconfirmed ones.
+func (set *unconfirmedBlocks) Insert(index uint64, hash common.Hash) {
+ // If a new block was mined locally, shift out any old enough blocks
+ set.Shift(index)
+
+ // Create the new item as its own ring
+ item := ring.New(1)
+ item.Value = &unconfirmedBlock{
+ index: index,
+ hash: hash,
+ }
+ // Set as the initial ring or append to the end
+ set.lock.Lock()
+ defer set.lock.Unlock()
+
+ if set.blocks == nil {
+ set.blocks = item
+ } else {
+ set.blocks.Move(-1).Link(item)
+ }
+ // Display a log for the user to notify of a new mined block unconfirmed
+ log.Info("🔨 mined potential block", "number", index, "hash", hash)
+}
+
+// Shift drops all unconfirmed blocks from the set which exceed the unconfirmed sets depth
+// allowance, checking them against the canonical chain for inclusion or staleness
+// report.
+func (set *unconfirmedBlocks) Shift(height uint64) {
+ set.lock.Lock()
+ defer set.lock.Unlock()
+
+ for set.blocks != nil {
+ // Retrieve the next unconfirmed block and abort if too fresh
+ next := set.blocks.Value.(*unconfirmedBlock)
+ if next.index+uint64(set.depth) > height {
+ break
+ }
+ // Block seems to exceed depth allowance, check for canonical status
+ header := set.chain.GetHeaderByNumber(next.index)
+ switch {
+ case header == nil:
+ log.Warn("Failed to retrieve header of mined block", "number", next.index, "hash", next.hash)
+ case header.Hash() == next.hash:
+ log.Info("🔗 block reached canonical chain", "number", next.index, "hash", next.hash)
+ default:
+ // Block is not canonical, check whether we have an uncle or a lost block
+ included := false
+ for number := next.index; !included && number < next.index+uint64(set.depth) && number <= height; number++ {
+ if block := set.chain.GetBlockByNumber(number); block != nil {
+ for _, uncle := range block.Uncles() {
+ if uncle.Hash() == next.hash {
+ included = true
+ break
+ }
+ }
+ }
+ }
+ if included {
+ log.Info("⑂ block became an uncle", "number", next.index, "hash", next.hash)
+ } else {
+ log.Info("😱 block lost", "number", next.index, "hash", next.hash)
+ }
+ }
+ // Drop the block out of the ring
+ if set.blocks.Value == set.blocks.Next().Value {
+ set.blocks = nil
+ } else {
+ set.blocks = set.blocks.Move(-1)
+ set.blocks.Unlink(1)
+ set.blocks = set.blocks.Move(1)
+ }
+ }
+}
diff --git a/miner/worker.go b/miner/worker.go
new file mode 100644
index 0000000..edfd3f9
--- /dev/null
+++ b/miner/worker.go
@@ -0,0 +1,1002 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+//
+// NOTE: this piece of code is adopted from
+// github.com/ethereum/go-ethereum/miner/worker.go,
+// modified by Ted Yin.
+// The modification is also licensed under the same LGPL.
+
+package miner
+
+import (
+ "bytes"
+ "errors"
+ "math/big"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ mapset "github.com/deckarep/golang-set"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/consensus/misc"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+const (
+ // resultQueueSize is the size of channel listening to sealing result.
+ resultQueueSize = 10
+
+ // txChanSize is the size of channel listening to NewTxsEvent.
+ // The number is referenced from the size of tx pool.
+ txChanSize = 4096
+
+ // chainHeadChanSize is the size of channel listening to ChainHeadEvent.
+ chainHeadChanSize = 10
+
+ // chainSideChanSize is the size of channel listening to ChainSideEvent.
+ chainSideChanSize = 10
+
+ // resubmitAdjustChanSize is the size of resubmitting interval adjustment channel.
+ resubmitAdjustChanSize = 10
+
+ // miningLogAtDepth is the number of confirmations before logging successful mining.
+ miningLogAtDepth = 7
+
+ // minRecommitInterval is the minimal time interval to recreate the mining block with
+ // any newly arrived transactions.
+ minRecommitInterval = 1 * time.Second
+
+ // maxRecommitInterval is the maximum time interval to recreate the mining block with
+ // any newly arrived transactions.
+ maxRecommitInterval = 15 * time.Second
+
+ // intervalAdjustRatio is the impact a single interval adjustment has on sealing work
+ // resubmitting interval.
+ intervalAdjustRatio = 0.1
+
+ // intervalAdjustBias is applied during the new resubmit interval calculation in favor of
+ // increasing upper limit or decreasing lower limit so that the limit can be reachable.
+ intervalAdjustBias = 200 * 1000.0 * 1000.0
+
+ // staleThreshold is the maximum depth of the acceptable stale block.
+ staleThreshold = 7
+)
+
+// environment is the worker's current environment and holds all of the current state information.
+type environment struct {
+ signer types.Signer
+
+ state *state.StateDB // apply state changes here
+ ancestors mapset.Set // ancestor set (used for checking uncle parent validity)
+ family mapset.Set // family set (used for checking uncle invalidity)
+ uncles mapset.Set // uncle set
+ tcount int // tx count in cycle
+ gasPool *core.GasPool // available gas used to pack transactions
+
+ header *types.Header
+ txs []*types.Transaction
+ receipts []*types.Receipt
+}
+
+// task contains all information for consensus engine sealing and result submitting.
+type task struct {
+ receipts []*types.Receipt
+ state *state.StateDB
+ block *types.Block
+ createdAt time.Time
+}
+
+const (
+ commitInterruptNone int32 = iota
+ commitInterruptNewHead
+ commitInterruptResubmit
+)
+
+// newWorkReq represents a request for new sealing work submitting with relative interrupt notifier.
+type newWorkReq struct {
+ interrupt *int32
+ noempty bool
+ timestamp int64
+}
+
+// intervalAdjust represents a resubmitting interval adjustment.
+type intervalAdjust struct {
+ ratio float64
+ inc bool
+}
+
+// worker is the main object which takes care of submitting new work to consensus engine
+// and gathering the sealing result.
+type worker struct {
+ config *Config
+ chainConfig *params.ChainConfig
+ engine consensus.Engine
+ eth Backend
+ chain *core.BlockChain
+
+ // Subscriptions
+ mux *event.TypeMux
+ txsCh chan core.NewTxsEvent
+ txsSub event.Subscription
+ chainHeadCh chan core.ChainHeadEvent
+ chainHeadSub event.Subscription
+ chainSideCh chan core.ChainSideEvent
+ chainSideSub event.Subscription
+
+ // Channels
+ newWorkCh chan *newWorkReq
+ taskCh chan *task
+ resultCh chan *types.Block
+ startCh chan struct{}
+ exitCh chan struct{}
+ resubmitIntervalCh chan time.Duration
+ resubmitAdjustCh chan *intervalAdjust
+
+ current *environment // An environment for current running cycle.
+ localUncles map[common.Hash]*types.Block // A set of side blocks generated locally as the possible uncle blocks.
+ remoteUncles map[common.Hash]*types.Block // A set of side blocks as the possible uncle blocks.
+ unconfirmed *unconfirmedBlocks // A set of locally mined blocks pending canonicalness confirmations.
+
+ mu sync.RWMutex // The lock used to protect the coinbase and extra fields
+ coinbase common.Address
+ extra []byte
+
+ pendingMu sync.RWMutex
+ pendingTasks map[common.Hash]*task
+
+ snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot
+ snapshotBlock *types.Block
+ snapshotState *state.StateDB
+
+ // atomic status counters
+ running int32 // The indicator whether the consensus engine is running or not.
+ newTxs int32 // New arrival transaction count since last sealing work submitting.
+
+ // External functions
+ isLocalBlock func(block *types.Block) bool // Function used to determine whether the specified block is mined by local miner.
+
+ // Test hooks
+ newTaskHook func(*task) // Method to call upon receiving a new sealing task.
+ skipSealHook func(*task) bool // Method to decide whether skipping the sealing.
+ fullTaskHook func() // Method to call before pushing the full sealing task.
+ resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval.
+}
+
+func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, isLocalBlock func(*types.Block) bool) *worker {
+ worker := &worker{
+ config: config,
+ chainConfig: chainConfig,
+ engine: engine,
+ eth: eth,
+ mux: mux,
+ chain: eth.BlockChain(),
+ isLocalBlock: isLocalBlock,
+ localUncles: make(map[common.Hash]*types.Block),
+ remoteUncles: make(map[common.Hash]*types.Block),
+ unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
+ pendingTasks: make(map[common.Hash]*task),
+ txsCh: make(chan core.NewTxsEvent, txChanSize),
+ chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
+ chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
+ newWorkCh: make(chan *newWorkReq),
+ taskCh: make(chan *task),
+ resultCh: make(chan *types.Block, resultQueueSize),
+ exitCh: make(chan struct{}),
+ startCh: make(chan struct{}, 1),
+ resubmitIntervalCh: make(chan time.Duration),
+ resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
+ }
+ // Subscribe NewTxsEvent for tx pool
+ worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
+ // Subscribe events for blockchain
+ worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
+ worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
+
+ // Sanitize recommit interval if the user-specified one is too short.
+ recommit := worker.config.Recommit
+ if recommit < minRecommitInterval {
+ log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval)
+ recommit = minRecommitInterval
+ }
+
+ go worker.mainLoop()
+ go worker.newWorkLoop(recommit)
+ go worker.resultLoop()
+ go worker.taskLoop()
+
+ // Submit first work to initialize pending state.
+ worker.startCh <- struct{}{}
+
+ return worker
+}
+
+// setEtherbase sets the etherbase used to initialize the block coinbase field.
+func (w *worker) setEtherbase(addr common.Address) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ w.coinbase = addr
+}
+
+// setExtra sets the content used to initialize the block extra field.
+func (w *worker) setExtra(extra []byte) {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ w.extra = extra
+}
+
+// setRecommitInterval updates the interval for miner sealing work recommitting.
+func (w *worker) setRecommitInterval(interval time.Duration) {
+ w.resubmitIntervalCh <- interval
+}
+
+// pending returns the pending state and corresponding block.
+func (w *worker) pending() (*types.Block, *state.StateDB) {
+ // return a snapshot to avoid contention on currentMu mutex
+ w.snapshotMu.RLock()
+ defer w.snapshotMu.RUnlock()
+ if w.snapshotState == nil {
+ return nil, nil
+ }
+ return w.snapshotBlock, w.snapshotState.Copy()
+}
+
+// pendingBlock returns pending block.
+func (w *worker) pendingBlock() *types.Block {
+ // return a snapshot to avoid contention on currentMu mutex
+ w.snapshotMu.RLock()
+ defer w.snapshotMu.RUnlock()
+ return w.snapshotBlock
+}
+
+// start sets the running status as 1 and triggers new work submitting.
+func (w *worker) start() {
+ atomic.StoreInt32(&w.running, 1)
+ w.startCh <- struct{}{}
+}
+
+// stop sets the running status as 0.
+func (w *worker) stop() {
+ atomic.StoreInt32(&w.running, 0)
+}
+
+// isRunning returns an indicator whether worker is running or not.
+func (w *worker) isRunning() bool {
+ return atomic.LoadInt32(&w.running) == 1
+}
+
+// close terminates all background threads maintained by the worker.
+// Note the worker does not support being closed multiple times.
+func (w *worker) close() {
+ close(w.exitCh)
+}
+
+// newWorkLoop is a standalone goroutine to submit new mining work upon received events.
+func (w *worker) newWorkLoop(recommit time.Duration) {
+ var (
+ interrupt *int32
+ minRecommit = recommit // minimal resubmit interval specified by user.
+ timestamp int64 // timestamp for each round of mining.
+ )
+
+ timer := time.NewTimer(0)
+ <-timer.C // discard the initial tick
+
+ // commit aborts in-flight transaction execution with given signal and resubmits a new one.
+ commit := func(noempty bool, s int32) {
+ if interrupt != nil {
+ atomic.StoreInt32(interrupt, s)
+ }
+ interrupt = new(int32)
+ w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}
+ timer.Reset(recommit)
+ atomic.StoreInt32(&w.newTxs, 0)
+ }
+ // recalcRecommit recalculates the resubmitting interval upon feedback.
+ recalcRecommit := func(target float64, inc bool) {
+ var (
+ prev = float64(recommit.Nanoseconds())
+ next float64
+ )
+ if inc {
+ next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target+intervalAdjustBias)
+ // Recap if interval is larger than the maximum time interval
+ if next > float64(maxRecommitInterval.Nanoseconds()) {
+ next = float64(maxRecommitInterval.Nanoseconds())
+ }
+ } else {
+ next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target-intervalAdjustBias)
+ // Recap if interval is less than the user specified minimum
+ if next < float64(minRecommit.Nanoseconds()) {
+ next = float64(minRecommit.Nanoseconds())
+ }
+ }
+ recommit = time.Duration(int64(next))
+ }
+ // clearPending cleans the stale pending tasks.
+ clearPending := func(number uint64) {
+ w.pendingMu.Lock()
+ for h, t := range w.pendingTasks {
+ if t.block.NumberU64()+staleThreshold <= number {
+ delete(w.pendingTasks, h)
+ }
+ }
+ w.pendingMu.Unlock()
+ }
+
+ for {
+ select {
+ case <-w.startCh:
+ clearPending(w.chain.CurrentBlock().NumberU64())
+ timestamp = time.Now().Unix()
+ commit(false, commitInterruptNewHead)
+
+ case head := <-w.chainHeadCh:
+ clearPending(head.Block.NumberU64())
+ timestamp = time.Now().Unix()
+ commit(false, commitInterruptNewHead)
+
+ case <-timer.C:
+ // If mining is running resubmit a new work cycle periodically to pull in
+ // higher priced transactions. Disable this overhead for pending blocks.
+ if w.isRunning() && (w.chainConfig.Clique == nil || w.chainConfig.Clique.Period > 0) {
+ // Short circuit if no new transaction arrives.
+ if atomic.LoadInt32(&w.newTxs) == 0 {
+ timer.Reset(recommit)
+ continue
+ }
+ commit(true, commitInterruptResubmit)
+ }
+
+ case interval := <-w.resubmitIntervalCh:
+ // Adjust resubmit interval explicitly by user.
+ if interval < minRecommitInterval {
+ log.Warn("Sanitizing miner recommit interval", "provided", interval, "updated", minRecommitInterval)
+ interval = minRecommitInterval
+ }
+ log.Info("Miner recommit interval update", "from", minRecommit, "to", interval)
+ minRecommit, recommit = interval, interval
+
+ if w.resubmitHook != nil {
+ w.resubmitHook(minRecommit, recommit)
+ }
+
+ case adjust := <-w.resubmitAdjustCh:
+ // Adjust resubmit interval by feedback.
+ if adjust.inc {
+ before := recommit
+ recalcRecommit(float64(recommit.Nanoseconds())/adjust.ratio, true)
+ log.Trace("Increase miner recommit interval", "from", before, "to", recommit)
+ } else {
+ before := recommit
+ recalcRecommit(float64(minRecommit.Nanoseconds()), false)
+ log.Trace("Decrease miner recommit interval", "from", before, "to", recommit)
+ }
+
+ if w.resubmitHook != nil {
+ w.resubmitHook(minRecommit, recommit)
+ }
+
+ case <-w.exitCh:
+ return
+ }
+ }
+}
+
+// mainLoop is a standalone goroutine to regenerate the sealing task based on the received event.
+func (w *worker) mainLoop() {
+ defer w.txsSub.Unsubscribe()
+ defer w.chainHeadSub.Unsubscribe()
+ defer w.chainSideSub.Unsubscribe()
+
+ for {
+ select {
+ case req := <-w.newWorkCh:
+ w.commitNewWork(req.interrupt, req.noempty, req.timestamp)
+
+ case ev := <-w.chainSideCh:
+ // Short circuit for duplicate side blocks
+ if _, exist := w.localUncles[ev.Block.Hash()]; exist {
+ continue
+ }
+ if _, exist := w.remoteUncles[ev.Block.Hash()]; exist {
+ continue
+ }
+ // Add side block to possible uncle block set depending on the author.
+ if w.isLocalBlock != nil && w.isLocalBlock(ev.Block) {
+ w.localUncles[ev.Block.Hash()] = ev.Block
+ } else {
+ w.remoteUncles[ev.Block.Hash()] = ev.Block
+ }
+ // If our mining block contains less than 2 uncle blocks,
+ // add the new uncle block if valid and regenerate a mining block.
+ if w.isRunning() && w.current != nil && w.current.uncles.Cardinality() < 2 {
+ start := time.Now()
+ if err := w.commitUncle(w.current, ev.Block.Header()); err == nil {
+ var uncles []*types.Header
+ w.current.uncles.Each(func(item interface{}) bool {
+ hash, ok := item.(common.Hash)
+ if !ok {
+ return false
+ }
+ uncle, exist := w.localUncles[hash]
+ if !exist {
+ uncle, exist = w.remoteUncles[hash]
+ }
+ if !exist {
+ return false
+ }
+ uncles = append(uncles, uncle.Header())
+ return false
+ })
+ w.commit(uncles, nil, true, start)
+ }
+ }
+
+ case ev := <-w.txsCh:
+ // Apply transactions to the pending state if we're not mining.
+ //
+ // Note all transactions received may not be continuous with transactions
+ // already included in the current mining block. These transactions will
+ // be automatically eliminated.
+ if !w.isRunning() && w.current != nil {
+ // If block is already full, abort
+ if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas {
+ continue
+ }
+ w.mu.RLock()
+ coinbase := w.coinbase
+ w.mu.RUnlock()
+
+ txs := make(map[common.Address]types.Transactions)
+ for _, tx := range ev.Txs {
+ acc, _ := types.Sender(w.current.signer, tx)
+ txs[acc] = append(txs[acc], tx)
+ }
+ txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs)
+ tcount := w.current.tcount
+ w.commitTransactions(txset, coinbase, nil)
+ // Only update the snapshot if any new transactons were added
+ // to the pending block
+ if tcount != w.current.tcount {
+ w.updateSnapshot()
+ }
+ } else {
+ // If clique is running in dev mode(period is 0), disable
+ // advance sealing here.
+ if w.chainConfig.Clique != nil && w.chainConfig.Clique.Period == 0 {
+ w.commitNewWork(nil, true, time.Now().Unix())
+ }
+ }
+ atomic.AddInt32(&w.newTxs, int32(len(ev.Txs)))
+
+ // System stopped
+ case <-w.exitCh:
+ return
+ case <-w.txsSub.Err():
+ return
+ case <-w.chainHeadSub.Err():
+ return
+ case <-w.chainSideSub.Err():
+ return
+ }
+ }
+}
+
+// taskLoop is a standalone goroutine to fetch sealing task from the generator and
+// push them to consensus engine.
+func (w *worker) taskLoop() {
+ var (
+ stopCh chan struct{}
+ prev common.Hash
+ )
+
+ // interrupt aborts the in-flight sealing task.
+ interrupt := func() {
+ if stopCh != nil {
+ close(stopCh)
+ stopCh = nil
+ }
+ }
+ for {
+ select {
+ case task := <-w.taskCh:
+ if w.newTaskHook != nil {
+ w.newTaskHook(task)
+ }
+ // Reject duplicate sealing work due to resubmitting.
+ sealHash := w.engine.SealHash(task.block.Header())
+ if sealHash == prev {
+ continue
+ }
+ // Interrupt previous sealing operation
+ interrupt()
+ stopCh, prev = make(chan struct{}), sealHash
+
+ if w.skipSealHook != nil && w.skipSealHook(task) {
+ continue
+ }
+ w.pendingMu.Lock()
+ w.pendingTasks[w.engine.SealHash(task.block.Header())] = task
+ w.pendingMu.Unlock()
+
+ if err := w.engine.Seal(w.chain, task.block, w.resultCh, stopCh); err != nil {
+ log.Warn("Block sealing failed", "err", err)
+ }
+ case <-w.exitCh:
+ interrupt()
+ return
+ }
+ }
+}
+
+// resultLoop is a standalone goroutine to handle sealing result submitting
+// and flush relative data to the database.
+func (w *worker) resultLoop() {
+ for {
+ select {
+ case block := <-w.resultCh:
+ // Short circuit when receiving empty result.
+ if block == nil {
+ continue
+ }
+ // Short circuit when receiving duplicate result caused by resubmitting.
+ if w.chain.HasBlock(block.Hash(), block.NumberU64()) {
+ continue
+ }
+ var (
+ sealhash = w.engine.SealHash(block.Header())
+ hash = block.Hash()
+ )
+ w.pendingMu.RLock()
+ task, exist := w.pendingTasks[sealhash]
+ w.pendingMu.RUnlock()
+ if !exist {
+ log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash)
+ continue
+ }
+ // Different block could share same sealhash, deep copy here to prevent write-write conflict.
+ var (
+ receipts = make([]*types.Receipt, len(task.receipts))
+ logs []*types.Log
+ )
+ for i, receipt := range task.receipts {
+ // add block location fields
+ receipt.BlockHash = hash
+ receipt.BlockNumber = block.Number()
+ receipt.TransactionIndex = uint(i)
+
+ receipts[i] = new(types.Receipt)
+ *receipts[i] = *receipt
+ // Update the block hash in all logs since it is now available and not when the
+ // receipt/log of individual transactions were created.
+ for _, log := range receipt.Logs {
+ log.BlockHash = hash
+ }
+ logs = append(logs, receipt.Logs...)
+ }
+ // Commit block and state to database.
+ stat, err := w.chain.WriteBlockWithState(block, receipts, task.state)
+ if err != nil {
+ log.Error("Failed writing block to chain", "err", err)
+ continue
+ }
+ log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash,
+ "elapsed", common.PrettyDuration(time.Since(task.createdAt)))
+
+ // Broadcast the block and announce chain insertion event
+ w.mux.Post(core.NewMinedBlockEvent{Block: block})
+
+ var events []interface{}
+ switch stat {
+ case core.CanonStatTy:
+ events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
+ events = append(events, core.ChainHeadEvent{Block: block})
+ case core.SideStatTy:
+ events = append(events, core.ChainSideEvent{Block: block})
+ }
+ w.chain.PostChainEvents(events, logs)
+
+ // Insert the block into the set of pending ones to resultLoop for confirmations
+ w.unconfirmed.Insert(block.NumberU64(), block.Hash())
+
+ case <-w.exitCh:
+ return
+ }
+ }
+}
+
+// makeCurrent creates a new environment for the current cycle.
+func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
+ state, err := w.chain.StateAt(parent.Root())
+ if err != nil {
+ return err
+ }
+ env := &environment{
+ signer: types.NewEIP155Signer(w.chainConfig.ChainID),
+ state: state,
+ ancestors: mapset.NewSet(),
+ family: mapset.NewSet(),
+ uncles: mapset.NewSet(),
+ header: header,
+ }
+
+ // when 08 is processed ancestors contain 07 (quick block)
+ for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) {
+ for _, uncle := range ancestor.Uncles() {
+ env.family.Add(uncle.Hash())
+ }
+ env.family.Add(ancestor.Hash())
+ env.ancestors.Add(ancestor.Hash())
+ }
+
+ // Keep track of transactions which return errors so they can be removed
+ env.tcount = 0
+ w.current = env
+ return nil
+}
+
+// commitUncle adds the given block to uncle block set, returns error if failed to add.
+func (w *worker) commitUncle(env *environment, uncle *types.Header) error {
+ hash := uncle.Hash()
+ if env.uncles.Contains(hash) {
+ return errors.New("uncle not unique")
+ }
+ if env.header.ParentHash == uncle.ParentHash {
+ return errors.New("uncle is sibling")
+ }
+ if !env.ancestors.Contains(uncle.ParentHash) {
+ return errors.New("uncle's parent unknown")
+ }
+ if env.family.Contains(hash) {
+ return errors.New("uncle already included")
+ }
+ env.uncles.Add(uncle.Hash())
+ return nil
+}
+
+// updateSnapshot updates pending snapshot block and state.
+// Note this function assumes the current variable is thread safe.
+func (w *worker) updateSnapshot() {
+ w.snapshotMu.Lock()
+ defer w.snapshotMu.Unlock()
+
+ var uncles []*types.Header
+ w.current.uncles.Each(func(item interface{}) bool {
+ hash, ok := item.(common.Hash)
+ if !ok {
+ return false
+ }
+ uncle, exist := w.localUncles[hash]
+ if !exist {
+ uncle, exist = w.remoteUncles[hash]
+ }
+ if !exist {
+ return false
+ }
+ uncles = append(uncles, uncle.Header())
+ return false
+ })
+
+ w.snapshotBlock = types.NewBlock(
+ w.current.header,
+ w.current.txs,
+ uncles,
+ w.current.receipts,
+ )
+
+ w.snapshotState = w.current.state.Copy()
+}
+
+func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
+ snap := w.current.state.Snapshot()
+
+ receipt, _, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig())
+ if err != nil {
+ w.current.state.RevertToSnapshot(snap)
+ return nil, err
+ }
+ w.current.txs = append(w.current.txs, tx)
+ w.current.receipts = append(w.current.receipts, receipt)
+
+ return receipt.Logs, nil
+}
+
+func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool {
+ // Short circuit if current is nil
+ if w.current == nil {
+ return true
+ }
+
+ if w.current.gasPool == nil {
+ w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit)
+ }
+
+ var coalescedLogs []*types.Log
+
+ for {
+ // In the following three cases, we will interrupt the execution of the transaction.
+ // (1) new head block event arrival, the interrupt signal is 1
+ // (2) worker start or restart, the interrupt signal is 1
+ // (3) worker recreate the mining block with any newly arrived transactions, the interrupt signal is 2.
+ // For the first two cases, the semi-finished work will be discarded.
+ // For the third case, the semi-finished work will be submitted to the consensus engine.
+ if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone {
+ // Notify resubmit loop to increase resubmitting interval due to too frequent commits.
+ if atomic.LoadInt32(interrupt) == commitInterruptResubmit {
+ ratio := float64(w.current.header.GasLimit-w.current.gasPool.Gas()) / float64(w.current.header.GasLimit)
+ if ratio < 0.1 {
+ ratio = 0.1
+ }
+ w.resubmitAdjustCh <- &intervalAdjust{
+ ratio: ratio,
+ inc: true,
+ }
+ }
+ return atomic.LoadInt32(interrupt) == commitInterruptNewHead
+ }
+ // If we don't have enough gas for any further transactions then we're done
+ if w.current.gasPool.Gas() < params.TxGas {
+ log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas)
+ break
+ }
+ // Retrieve the next transaction and abort if all done
+ tx := txs.Peek()
+ if tx == nil {
+ break
+ }
+ // Error may be ignored here. The error has already been checked
+ // during transaction acceptance is the transaction pool.
+ //
+ // We use the eip155 signer regardless of the current hf.
+ from, _ := types.Sender(w.current.signer, tx)
+ // Check whether the tx is replay protected. If we're not in the EIP155 hf
+ // phase, start ignoring the sender until we do.
+ if tx.Protected() && !w.chainConfig.IsEIP155(w.current.header.Number) {
+ log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
+
+ txs.Pop()
+ continue
+ }
+ // Start executing the transaction
+ w.current.state.Prepare(tx.Hash(), common.Hash{}, w.current.tcount)
+
+ logs, err := w.commitTransaction(tx, coinbase)
+ switch err {
+ case core.ErrGasLimitReached:
+ // Pop the current out-of-gas transaction without shifting in the next from the account
+ log.Trace("Gas limit exceeded for current block", "sender", from)
+ txs.Pop()
+
+ case core.ErrNonceTooLow:
+ // New head notification data race between the transaction pool and miner, shift
+ log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
+ txs.Shift()
+
+ case core.ErrNonceTooHigh:
+ // Reorg notification data race between the transaction pool and miner, skip account =
+ log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
+ txs.Pop()
+
+ case nil:
+ // Everything ok, collect the logs and shift in the next transaction from the same account
+ coalescedLogs = append(coalescedLogs, logs...)
+ w.current.tcount++
+ txs.Shift()
+
+ default:
+ // Strange error, discard the transaction and get the next in line (note, the
+ // nonce-too-high clause will prevent us from executing in vain).
+ log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
+ txs.Shift()
+ }
+ }
+
+ if !w.isRunning() && len(coalescedLogs) > 0 {
+ // We don't push the pendingLogsEvent while we are mining. The reason is that
+ // when we are mining, the worker will regenerate a mining block every 3 seconds.
+ // In order to avoid pushing the repeated pendingLog, we disable the pending log pushing.
+
+ // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined
+ // logs by filling in the block hash when the block was mined by the local miner. This can
+ // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed.
+ cpy := make([]*types.Log, len(coalescedLogs))
+ for i, l := range coalescedLogs {
+ cpy[i] = new(types.Log)
+ *cpy[i] = *l
+ }
+ go w.mux.Post(core.PendingLogsEvent{Logs: cpy})
+ }
+ // Notify resubmit loop to decrease resubmitting interval if current interval is larger
+ // than the user-specified one.
+ if interrupt != nil {
+ w.resubmitAdjustCh <- &intervalAdjust{inc: false}
+ }
+ return false
+}
+
+// commitNewWork generates several new sealing tasks based on the parent block.
+func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) {
+ w.mu.RLock()
+ defer w.mu.RUnlock()
+
+ tstart := time.Now()
+ parent := w.chain.CurrentBlock()
+
+ if parent.Time() >= uint64(timestamp) {
+ timestamp = int64(parent.Time() + 1)
+ }
+ // this will ensure we're not going off too far in the future
+ if now := time.Now().Unix(); timestamp > now+1 {
+ wait := time.Duration(timestamp-now) * time.Second
+ log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait))
+ time.Sleep(wait)
+ }
+
+ num := parent.Number()
+ header := &types.Header{
+ ParentHash: parent.Hash(),
+ Number: num.Add(num, common.Big1),
+ GasLimit: core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil),
+ Extra: w.extra,
+ Time: uint64(timestamp),
+ }
+ // Only set the coinbase if our consensus engine is running (avoid spurious block rewards)
+ if w.isRunning() {
+ if w.coinbase == (common.Address{}) {
+ log.Error("Refusing to mine without etherbase")
+ return
+ }
+ header.Coinbase = w.coinbase
+ }
+ if err := w.engine.Prepare(w.chain, header); err != nil {
+ log.Error("Failed to prepare header for mining", "err", err)
+ return
+ }
+ // If we are care about TheDAO hard-fork check whether to override the extra-data or not
+ if daoBlock := w.chainConfig.DAOForkBlock; daoBlock != nil {
+ // Check whether the block is among the fork extra-override range
+ limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
+ if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 {
+ // Depending whether we support or oppose the fork, override differently
+ if w.chainConfig.DAOForkSupport {
+ header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
+ } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) {
+ header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data
+ }
+ }
+ }
+ // Could potentially happen if starting to mine in an odd state.
+ err := w.makeCurrent(parent, header)
+ if err != nil {
+ log.Error("Failed to create mining context", "err", err)
+ return
+ }
+ // Create the current work task and check any fork transitions needed
+ env := w.current
+ if w.chainConfig.DAOForkSupport && w.chainConfig.DAOForkBlock != nil && w.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 {
+ misc.ApplyDAOHardFork(env.state)
+ }
+ // Accumulate the uncles for the current block
+ uncles := make([]*types.Header, 0, 2)
+ commitUncles := func(blocks map[common.Hash]*types.Block) {
+ // Clean up stale uncle blocks first
+ for hash, uncle := range blocks {
+ if uncle.NumberU64()+staleThreshold <= header.Number.Uint64() {
+ delete(blocks, hash)
+ }
+ }
+ for hash, uncle := range blocks {
+ if len(uncles) == 2 {
+ break
+ }
+ if err := w.commitUncle(env, uncle.Header()); err != nil {
+ log.Trace("Possible uncle rejected", "hash", hash, "reason", err)
+ } else {
+ log.Debug("Committing new uncle to block", "hash", hash)
+ uncles = append(uncles, uncle.Header())
+ }
+ }
+ }
+ // Prefer to locally generated uncle
+ commitUncles(w.localUncles)
+ commitUncles(w.remoteUncles)
+
+ if !noempty {
+ // Create an empty block based on temporary copied state for sealing in advance without waiting block
+ // execution finished.
+ w.commit(uncles, nil, false, tstart)
+ }
+
+ // Fill the block with all available pending transactions.
+ pending, err := w.eth.TxPool().Pending()
+ if err != nil {
+ log.Error("Failed to fetch pending transactions", "err", err)
+ return
+ }
+ // Short circuit if there is no available pending transactions
+ if len(pending) == 0 {
+ w.updateSnapshot()
+ return
+ }
+ // Split the pending transactions into locals and remotes
+ localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending
+ for _, account := range w.eth.TxPool().Locals() {
+ if txs := remoteTxs[account]; len(txs) > 0 {
+ delete(remoteTxs, account)
+ localTxs[account] = txs
+ }
+ }
+ if len(localTxs) > 0 {
+ txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
+ if w.commitTransactions(txs, w.coinbase, interrupt) {
+ return
+ }
+ }
+ if len(remoteTxs) > 0 {
+ txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
+ if w.commitTransactions(txs, w.coinbase, interrupt) {
+ return
+ }
+ }
+ w.commit(uncles, w.fullTaskHook, true, tstart)
+}
+
+// commit runs any post-transaction state modifications, assembles the final block
+// and commits new work if consensus engine is running.
+func (w *worker) commit(uncles []*types.Header, interval func(), update bool, start time.Time) error {
+ // Deep copy receipts here to avoid interaction between different tasks.
+ receipts := make([]*types.Receipt, len(w.current.receipts))
+ for i, l := range w.current.receipts {
+ receipts[i] = new(types.Receipt)
+ *receipts[i] = *l
+ }
+ s := w.current.state.Copy()
+ block, err := w.engine.FinalizeAndAssemble(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts)
+ if err != nil {
+ return err
+ }
+ if w.isRunning() {
+ if interval != nil {
+ interval()
+ }
+ select {
+ case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}:
+ w.unconfirmed.Shift(block.NumberU64() - 1)
+
+ feesWei := new(big.Int)
+ for i, tx := range block.Transactions() {
+ feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice()))
+ }
+ feesEth := new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
+
+ log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
+ "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))
+
+ case <-w.exitCh:
+ log.Info("Worker has exited")
+ }
+ }
+ if update {
+ w.updateSnapshot()
+ }
+ return nil
+}
diff --git a/node/config.go b/node/config.go
new file mode 100644
index 0000000..1905ac7
--- /dev/null
+++ b/node/config.go
@@ -0,0 +1,545 @@
+// Copyright 2014 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package node
+
+import (
+ "crypto/ecdsa"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/external"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
+ "github.com/ethereum/go-ethereum/accounts/scwallet"
+ "github.com/ethereum/go-ethereum/accounts/usbwallet"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/p2p/enode"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+const (
+ datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key
+ datadirDefaultKeyStore = "keystore" // Path within the datadir to the keystore
+ datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list
+ datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list
+ datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos
+)
+
+// Config represents a small collection of configuration values to fine tune the
+// P2P network layer of a protocol stack. These values can be further extended by
+// all registered services.
+type Config struct {
+ // Name sets the instance name of the node. It must not contain the / character and is
+ // used in the devp2p node identifier. The instance name of geth is "geth". If no
+ // value is specified, the basename of the current executable is used.
+ Name string `toml:"-"`
+
+ // UserIdent, if set, is used as an additional component in the devp2p node identifier.
+ UserIdent string `toml:",omitempty"`
+
+ // Version should be set to the version number of the program. It is used
+ // in the devp2p node identifier.
+ Version string `toml:"-"`
+
+ // DataDir is the file system folder the node should use for any data storage
+ // requirements. The configured data directory will not be directly shared with
+ // registered services, instead those can use utility methods to create/access
+ // databases or flat files. This enables ephemeral nodes which can fully reside
+ // in memory.
+ DataDir string
+
+ // Configuration of peer-to-peer networking.
+ P2P p2p.Config
+
+ // KeyStoreDir is the file system folder that contains private keys. The directory can
+ // be specified as a relative path, in which case it is resolved relative to the
+ // current directory.
+ //
+ // If KeyStoreDir is empty, the default location is the "keystore" subdirectory of
+ // DataDir. If DataDir is unspecified and KeyStoreDir is empty, an ephemeral directory
+ // is created by New and destroyed when the node is stopped.
+ KeyStoreDir string `toml:",omitempty"`
+
+ // ExternalSigner specifies an external URI for a clef-type signer
+ ExternalSigner string `toml:"omitempty"`
+
+ // UseLightweightKDF lowers the memory and CPU requirements of the key store
+ // scrypt KDF at the expense of security.
+ UseLightweightKDF bool `toml:",omitempty"`
+
+ // InsecureUnlockAllowed allows user to unlock accounts in unsafe http environment.
+ InsecureUnlockAllowed bool `toml:",omitempty"`
+
+ // NoUSB disables hardware wallet monitoring and connectivity.
+ NoUSB bool `toml:",omitempty"`
+
+ // SmartCardDaemonPath is the path to the smartcard daemon's socket
+ SmartCardDaemonPath string `toml:",omitempty"`
+
+ // IPCPath is the requested location to place the IPC endpoint. If the path is
+ // a simple file name, it is placed inside the data directory (or on the root
+ // pipe path on Windows), whereas if it's a resolvable path name (absolute or
+ // relative), then that specific path is enforced. An empty path disables IPC.
+ IPCPath string `toml:",omitempty"`
+
+ // HTTPHost is the host interface on which to start the HTTP RPC server. If this
+ // field is empty, no HTTP API endpoint will be started.
+ HTTPHost string `toml:",omitempty"`
+
+ // HTTPPort is the TCP port number on which to start the HTTP RPC server. The
+ // default zero value is/ valid and will pick a port number randomly (useful
+ // for ephemeral nodes).
+ HTTPPort int `toml:",omitempty"`
+
+ // HTTPCors is the Cross-Origin Resource Sharing header to send to requesting
+ // clients. Please be aware that CORS is a browser enforced security, it's fully
+ // useless for custom HTTP clients.
+ HTTPCors []string `toml:",omitempty"`
+
+ // HTTPVirtualHosts is the list of virtual hostnames which are allowed on incoming requests.
+ // This is by default {'localhost'}. Using this prevents attacks like
+ // DNS rebinding, which bypasses SOP by simply masquerading as being within the same
+ // origin. These attacks do not utilize CORS, since they are not cross-domain.
+ // By explicitly checking the Host-header, the server will not allow requests
+ // made against the server with a malicious host domain.
+ // Requests using ip address directly are not affected
+ HTTPVirtualHosts []string `toml:",omitempty"`
+
+ // HTTPModules is a list of API modules to expose via the HTTP RPC interface.
+ // If the module list is empty, all RPC API endpoints designated public will be
+ // exposed.
+ HTTPModules []string `toml:",omitempty"`
+
+ // HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC
+ // interface.
+ HTTPTimeouts rpc.HTTPTimeouts
+
+ // WSHost is the host interface on which to start the websocket RPC server. If
+ // this field is empty, no websocket API endpoint will be started.
+ WSHost string `toml:",omitempty"`
+
+ // WSPort is the TCP port number on which to start the websocket RPC server. The
+ // default zero value is/ valid and will pick a port number randomly (useful for
+ // ephemeral nodes).
+ WSPort int `toml:",omitempty"`
+
+ // WSOrigins is the list of domain to accept websocket requests from. Please be
+ // aware that the server can only act upon the HTTP request the client sends and
+ // cannot verify the validity of the request header.
+ WSOrigins []string `toml:",omitempty"`
+
+ // WSModules is a list of API modules to expose via the websocket RPC interface.
+ // If the module list is empty, all RPC API endpoints designated public will be
+ // exposed.
+ WSModules []string `toml:",omitempty"`
+
+ // WSExposeAll exposes all API modules via the WebSocket RPC interface rather
+ // than just the public ones.
+ //
+ // *WARNING* Only set this if the node is running in a trusted network, exposing
+ // private APIs to untrusted users is a major security risk.
+ WSExposeAll bool `toml:",omitempty"`
+
+ // GraphQLHost is the host interface on which to start the GraphQL server. If this
+ // field is empty, no GraphQL API endpoint will be started.
+ GraphQLHost string `toml:",omitempty"`
+
+ // GraphQLPort is the TCP port number on which to start the GraphQL server. The
+ // default zero value is/ valid and will pick a port number randomly (useful
+ // for ephemeral nodes).
+ GraphQLPort int `toml:",omitempty"`
+
+ // GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting
+ // clients. Please be aware that CORS is a browser enforced security, it's fully
+ // useless for custom HTTP clients.
+ GraphQLCors []string `toml:",omitempty"`
+
+ // GraphQLVirtualHosts is the list of virtual hostnames which are allowed on incoming requests.
+ // This is by default {'localhost'}. Using this prevents attacks like
+ // DNS rebinding, which bypasses SOP by simply masquerading as being within the same
+ // origin. These attacks do not utilize CORS, since they are not cross-domain.
+ // By explicitly checking the Host-header, the server will not allow requests
+ // made against the server with a malicious host domain.
+ // Requests using ip address directly are not affected
+ GraphQLVirtualHosts []string `toml:",omitempty"`
+
+ // Logger is a custom logger to use with the p2p.Server.
+ Logger log.Logger `toml:",omitempty"`
+
+ staticNodesWarning bool
+ trustedNodesWarning bool
+ oldGethResourceWarning bool
+}
+
+// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
+// account the set data folders as well as the designated platform we're currently
+// running on.
+func (c *Config) IPCEndpoint() string {
+ // Short circuit if IPC has not been enabled
+ if c.IPCPath == "" {
+ return ""
+ }
+ // On windows we can only use plain top-level pipes
+ if runtime.GOOS == "windows" {
+ if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) {
+ return c.IPCPath
+ }
+ return `\\.\pipe\` + c.IPCPath
+ }
+ // Resolve names into the data directory full paths otherwise
+ if filepath.Base(c.IPCPath) == c.IPCPath {
+ if c.DataDir == "" {
+ return filepath.Join(os.TempDir(), c.IPCPath)
+ }
+ return filepath.Join(c.DataDir, c.IPCPath)
+ }
+ return c.IPCPath
+}
+
+// NodeDB returns the path to the discovery node database.
+func (c *Config) NodeDB() string {
+ if c.DataDir == "" {
+ return "" // ephemeral
+ }
+ return c.ResolvePath(datadirNodeDatabase)
+}
+
+// DefaultIPCEndpoint returns the IPC path used by default.
+func DefaultIPCEndpoint(clientIdentifier string) string {
+ if clientIdentifier == "" {
+ clientIdentifier = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
+ if clientIdentifier == "" {
+ panic("empty executable name")
+ }
+ }
+ config := &Config{DataDir: DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"}
+ return config.IPCEndpoint()
+}
+
+// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface
+// and port parameters.
+func (c *Config) HTTPEndpoint() string {
+ if c.HTTPHost == "" {
+ return ""
+ }
+ return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort)
+}
+
+// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface
+// and port parameters.
+func (c *Config) GraphQLEndpoint() string {
+ if c.GraphQLHost == "" {
+ return ""
+ }
+ return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort)
+}
+
+// DefaultHTTPEndpoint returns the HTTP endpoint used by default.
+func DefaultHTTPEndpoint() string {
+ config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort}
+ return config.HTTPEndpoint()
+}
+
+// WSEndpoint resolves a websocket endpoint based on the configured host interface
+// and port parameters.
+func (c *Config) WSEndpoint() string {
+ if c.WSHost == "" {
+ return ""
+ }
+ return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort)
+}
+
+// DefaultWSEndpoint returns the websocket endpoint used by default.
+func DefaultWSEndpoint() string {
+ config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort}
+ return config.WSEndpoint()
+}
+
+// ExtRPCEnabled returns the indicator whether node enables the external
+// RPC(http, ws or graphql).
+func (c *Config) ExtRPCEnabled() bool {
+ return c.HTTPHost != "" || c.WSHost != "" || c.GraphQLHost != ""
+}
+
+// NodeName returns the devp2p node identifier.
+func (c *Config) NodeName() string {
+ name := c.name()
+ // Backwards compatibility: previous versions used title-cased "Geth", keep that.
+ if name == "geth" || name == "geth-testnet" {
+ name = "Geth"
+ }
+ if c.UserIdent != "" {
+ name += "/" + c.UserIdent
+ }
+ if c.Version != "" {
+ name += "/v" + c.Version
+ }
+ name += "/" + runtime.GOOS + "-" + runtime.GOARCH
+ name += "/" + runtime.Version()
+ return name
+}
+
+func (c *Config) name() string {
+ if c.Name == "" {
+ progname := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
+ if progname == "" {
+ panic("empty executable name, set Config.Name")
+ }
+ return progname
+ }
+ return c.Name
+}
+
+// These resources are resolved differently for "geth" instances.
+var isOldGethResource = map[string]bool{
+ "chaindata": true,
+ "nodes": true,
+ "nodekey": true,
+ "static-nodes.json": false, // no warning for these because they have their
+ "trusted-nodes.json": false, // own separate warning.
+}
+
+// ResolvePath resolves path in the instance directory.
+func (c *Config) ResolvePath(path string) string {
+ if filepath.IsAbs(path) {
+ return path
+ }
+ if c.DataDir == "" {
+ return ""
+ }
+ // Backwards-compatibility: ensure that data directory files created
+ // by geth 1.4 are used if they exist.
+ if warn, isOld := isOldGethResource[path]; isOld {
+ oldpath := ""
+ if c.name() == "geth" {
+ oldpath = filepath.Join(c.DataDir, path)
+ }
+ if oldpath != "" && common.FileExist(oldpath) {
+ if warn {
+ c.warnOnce(&c.oldGethResourceWarning, "Using deprecated resource file %s, please move this file to the 'geth' subdirectory of datadir.", oldpath)
+ }
+ return oldpath
+ }
+ }
+ return filepath.Join(c.instanceDir(), path)
+}
+
+func (c *Config) instanceDir() string {
+ if c.DataDir == "" {
+ return ""
+ }
+ return filepath.Join(c.DataDir, c.name())
+}
+
+// NodeKey retrieves the currently configured private key of the node, checking
+// first any manually set key, falling back to the one found in the configured
+// data folder. If no key can be found, a new one is generated.
+func (c *Config) NodeKey() *ecdsa.PrivateKey {
+ // Use any specifically configured key.
+ if c.P2P.PrivateKey != nil {
+ return c.P2P.PrivateKey
+ }
+ // Generate ephemeral key if no datadir is being used.
+ if c.DataDir == "" {
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ log.Crit(fmt.Sprintf("Failed to generate ephemeral node key: %v", err))
+ }
+ return key
+ }
+
+ keyfile := c.ResolvePath(datadirPrivateKey)
+ if key, err := crypto.LoadECDSA(keyfile); err == nil {
+ return key
+ }
+ // No persistent key found, generate and store a new one.
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ log.Crit(fmt.Sprintf("Failed to generate node key: %v", err))
+ }
+ instanceDir := filepath.Join(c.DataDir, c.name())
+ if err := os.MkdirAll(instanceDir, 0700); err != nil {
+ log.Error(fmt.Sprintf("Failed to persist node key: %v", err))
+ return key
+ }
+ keyfile = filepath.Join(instanceDir, datadirPrivateKey)
+ if err := crypto.SaveECDSA(keyfile, key); err != nil {
+ log.Error(fmt.Sprintf("Failed to persist node key: %v", err))
+ }
+ return key
+}
+
+// StaticNodes returns a list of node enode URLs configured as static nodes.
+func (c *Config) StaticNodes() []*enode.Node {
+ return c.parsePersistentNodes(&c.staticNodesWarning, c.ResolvePath(datadirStaticNodes))
+}
+
+// TrustedNodes returns a list of node enode URLs configured as trusted nodes.
+func (c *Config) TrustedNodes() []*enode.Node {
+ return c.parsePersistentNodes(&c.trustedNodesWarning, c.ResolvePath(datadirTrustedNodes))
+}
+
+// parsePersistentNodes parses a list of discovery node URLs loaded from a .json
+// file from within the data directory.
+func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node {
+ // Short circuit if no node config is present
+ if c.DataDir == "" {
+ return nil
+ }
+ if _, err := os.Stat(path); err != nil {
+ return nil
+ }
+ c.warnOnce(w, "Found deprecated node list file %s, please use the TOML config file instead.", path)
+
+ // Load the nodes from the config file.
+ var nodelist []string
+ if err := common.LoadJSON(path, &nodelist); err != nil {
+ log.Error(fmt.Sprintf("Can't load node list file: %v", err))
+ return nil
+ }
+ // Interpret the list as a discovery node array
+ var nodes []*enode.Node
+ for _, url := range nodelist {
+ if url == "" {
+ continue
+ }
+ node, err := enode.Parse(enode.ValidSchemes, url)
+ if err != nil {
+ log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err))
+ continue
+ }
+ nodes = append(nodes, node)
+ }
+ return nodes
+}
+
+// AccountConfig determines the settings for scrypt and keydirectory
+func (c *Config) AccountConfig() (int, int, string, error) {
+ scryptN := keystore.StandardScryptN
+ scryptP := keystore.StandardScryptP
+ if c.UseLightweightKDF {
+ scryptN = keystore.LightScryptN
+ scryptP = keystore.LightScryptP
+ }
+
+ var (
+ keydir string
+ err error
+ )
+ switch {
+ case filepath.IsAbs(c.KeyStoreDir):
+ keydir = c.KeyStoreDir
+ case c.DataDir != "":
+ if c.KeyStoreDir == "" {
+ keydir = filepath.Join(c.DataDir, datadirDefaultKeyStore)
+ } else {
+ keydir, err = filepath.Abs(c.KeyStoreDir)
+ }
+ case c.KeyStoreDir != "":
+ keydir, err = filepath.Abs(c.KeyStoreDir)
+ }
+ return scryptN, scryptP, keydir, err
+}
+
+func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
+ scryptN, scryptP, keydir, err := conf.AccountConfig()
+ var ephemeral string
+ if keydir == "" {
+ // There is no datadir.
+ keydir, err = ioutil.TempDir("", "go-ethereum-keystore")
+ ephemeral = keydir
+ }
+
+ if err != nil {
+ return nil, "", err
+ }
+ if err := os.MkdirAll(keydir, 0700); err != nil {
+ return nil, "", err
+ }
+ // Assemble the account manager and supported backends
+ var backends []accounts.Backend
+ if len(conf.ExternalSigner) > 0 {
+ log.Info("Using external signer", "url", conf.ExternalSigner)
+ if extapi, err := external.NewExternalBackend(conf.ExternalSigner); err == nil {
+ backends = append(backends, extapi)
+ } else {
+ return nil, "", fmt.Errorf("error connecting to external signer: %v", err)
+ }
+ }
+ if len(backends) == 0 {
+ // For now, we're using EITHER external signer OR local signers.
+ // If/when we implement some form of lockfile for USB and keystore wallets,
+ // we can have both, but it's very confusing for the user to see the same
+ // accounts in both externally and locally, plus very racey.
+ backends = append(backends, keystore.NewKeyStore(keydir, scryptN, scryptP))
+ if !conf.NoUSB {
+ // Start a USB hub for Ledger hardware wallets
+ if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
+ log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
+ } else {
+ backends = append(backends, ledgerhub)
+ }
+ // Start a USB hub for Trezor hardware wallets (HID version)
+ if trezorhub, err := usbwallet.NewTrezorHubWithHID(); err != nil {
+ log.Warn(fmt.Sprintf("Failed to start HID Trezor hub, disabling: %v", err))
+ } else {
+ backends = append(backends, trezorhub)
+ }
+ // Start a USB hub for Trezor hardware wallets (WebUSB version)
+ if trezorhub, err := usbwallet.NewTrezorHubWithWebUSB(); err != nil {
+ log.Warn(fmt.Sprintf("Failed to start WebUSB Trezor hub, disabling: %v", err))
+ } else {
+ backends = append(backends, trezorhub)
+ }
+ }
+ if len(conf.SmartCardDaemonPath) > 0 {
+ // Start a smart card hub
+ if schub, err := scwallet.NewHub(conf.SmartCardDaemonPath, scwallet.Scheme, keydir); err != nil {
+ log.Warn(fmt.Sprintf("Failed to start smart card hub, disabling: %v", err))
+ } else {
+ backends = append(backends, schub)
+ }
+ }
+ }
+
+ return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed}, backends...), ephemeral, nil
+}
+
+var warnLock sync.Mutex
+
+func (c *Config) warnOnce(w *bool, format string, args ...interface{}) {
+ warnLock.Lock()
+ defer warnLock.Unlock()
+
+ if *w {
+ return
+ }
+ l := c.Logger
+ if l == nil {
+ l = log.Root()
+ }
+ l.Warn(fmt.Sprintf(format, args...))
+ *w = true
+}
diff --git a/node/defaults.go b/node/defaults.go
new file mode 100644
index 0000000..f84a5d5
--- /dev/null
+++ b/node/defaults.go
@@ -0,0 +1,113 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package node
+
+import (
+ "os"
+ "os/user"
+ "path/filepath"
+ "runtime"
+
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/p2p/nat"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+const (
+ DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server
+ DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server
+ DefaultWSHost = "localhost" // Default host interface for the websocket RPC server
+ DefaultWSPort = 8546 // Default TCP port for the websocket RPC server
+ DefaultGraphQLHost = "localhost" // Default host interface for the GraphQL server
+ DefaultGraphQLPort = 8547 // Default TCP port for the GraphQL server
+)
+
+// DefaultConfig contains reasonable default settings.
+var DefaultConfig = Config{
+ DataDir: DefaultDataDir(),
+ HTTPPort: DefaultHTTPPort,
+ HTTPModules: []string{"net", "web3"},
+ HTTPVirtualHosts: []string{"localhost"},
+ HTTPTimeouts: rpc.DefaultHTTPTimeouts,
+ WSPort: DefaultWSPort,
+ WSModules: []string{"net", "web3"},
+ GraphQLPort: DefaultGraphQLPort,
+ GraphQLVirtualHosts: []string{"localhost"},
+ P2P: p2p.Config{
+ ListenAddr: ":30303",
+ MaxPeers: 50,
+ NAT: nat.Any(),
+ },
+}
+
+// DefaultDataDir is the default data directory to use for the databases and other
+// persistence requirements.
+func DefaultDataDir() string {
+ // Try to place the data folder in the user's home dir
+ home := homeDir()
+ if home != "" {
+ switch runtime.GOOS {
+ case "darwin":
+ return filepath.Join(home, "Library", "Ethereum")
+ case "windows":
+ // We used to put everything in %HOME%\AppData\Roaming, but this caused
+ // problems with non-typical setups. If this fallback location exists and
+ // is non-empty, use it, otherwise DTRT and check %LOCALAPPDATA%.
+ fallback := filepath.Join(home, "AppData", "Roaming", "Ethereum")
+ appdata := windowsAppData()
+ if appdata == "" || isNonEmptyDir(fallback) {
+ return fallback
+ }
+ return filepath.Join(appdata, "Ethereum")
+ default:
+ return filepath.Join(home, ".ethereum")
+ }
+ }
+ // As we cannot guess a stable location, return empty and handle later
+ return ""
+}
+
+func windowsAppData() string {
+ v := os.Getenv("LOCALAPPDATA")
+ if v == "" {
+ // Windows XP and below don't have LocalAppData. Crash here because
+ // we don't support Windows XP and undefining the variable will cause
+ // other issues.
+ panic("environment variable LocalAppData is undefined")
+ }
+ return v
+}
+
+func isNonEmptyDir(dir string) bool {
+ f, err := os.Open(dir)
+ if err != nil {
+ return false
+ }
+ names, _ := f.Readdir(1)
+ f.Close()
+ return len(names) > 0
+}
+
+func homeDir() string {
+ if home := os.Getenv("HOME"); home != "" {
+ return home
+ }
+ if usr, err := user.Current(); err == nil {
+ return usr.HomeDir
+ }
+ return ""
+}
diff --git a/node/errors.go b/node/errors.go
new file mode 100644
index 0000000..2e0dadc
--- /dev/null
+++ b/node/errors.go
@@ -0,0 +1,63 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package node
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "syscall"
+)
+
+var (
+ ErrDatadirUsed = errors.New("datadir already used by another process")
+ ErrNodeStopped = errors.New("node not started")
+ ErrNodeRunning = errors.New("node already running")
+ ErrServiceUnknown = errors.New("unknown service")
+
+ datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
+)
+
+func convertFileLockError(err error) error {
+ if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] {
+ return ErrDatadirUsed
+ }
+ return err
+}
+
+// DuplicateServiceError is returned during Node startup if a registered service
+// constructor returns a service of the same type that was already started.
+type DuplicateServiceError struct {
+ Kind reflect.Type
+}
+
+// Error generates a textual representation of the duplicate service error.
+func (e *DuplicateServiceError) Error() string {
+ return fmt.Sprintf("duplicate service: %v", e.Kind)
+}
+
+// StopError is returned if a Node fails to stop either any of its registered
+// services or itself.
+type StopError struct {
+ Server error
+ Services map[reflect.Type]error
+}
+
+// Error generates a textual representation of the stop error.
+func (e *StopError) Error() string {
+ return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services)
+}
diff --git a/node/service.go b/node/service.go
new file mode 100644
index 0000000..fd03a57
--- /dev/null
+++ b/node/service.go
@@ -0,0 +1,131 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package node
+
+import (
+ "path/filepath"
+ "reflect"
+
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+// ServiceContext is a collection of service independent options inherited from
+// the protocol stack, that is passed to all constructors to be optionally used;
+// as well as utility methods to operate on the service environment.
+type ServiceContext struct {
+ config *Config
+ services map[reflect.Type]Service // Index of the already constructed services
+ EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
+ AccountManager *accounts.Manager // Account manager created by the node.
+}
+
+// OpenDatabase opens an existing database with the given name (or creates one
+// if no previous can be found) from within the node's data directory. If the
+// node is an ephemeral one, a memory database is returned.
+func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, namespace string) (ethdb.Database, error) {
+ if ctx.config.DataDir == "" {
+ return rawdb.NewMemoryDatabase(), nil
+ }
+ return rawdb.NewLevelDBDatabase(ctx.config.ResolvePath(name), cache, handles, namespace)
+}
+
+// OpenDatabaseWithFreezer opens an existing database with the given name (or
+// creates one if no previous can be found) from within the node's data directory,
+// also attaching a chain freezer to it that moves ancient chain data from the
+// database to immutable append-only files. If the node is an ephemeral one, a
+// memory database is returned.
+func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) {
+ if ctx.config.DataDir == "" {
+ return rawdb.NewMemoryDatabase(), nil
+ }
+ root := ctx.config.ResolvePath(name)
+
+ switch {
+ case freezer == "":
+ freezer = filepath.Join(root, "ancient")
+ case !filepath.IsAbs(freezer):
+ freezer = ctx.config.ResolvePath(freezer)
+ }
+ return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace)
+}
+
+// ResolvePath resolves a user path into the data directory if that was relative
+// and if the user actually uses persistent storage. It will return an empty string
+// for emphemeral storage and the user's own input for absolute paths.
+func (ctx *ServiceContext) ResolvePath(path string) string {
+ return ctx.config.ResolvePath(path)
+}
+
+// Service retrieves a currently running service registered of a specific type.
+func (ctx *ServiceContext) Service(service interface{}) error {
+ element := reflect.ValueOf(service).Elem()
+ if running, ok := ctx.services[element.Type()]; ok {
+ element.Set(reflect.ValueOf(running))
+ return nil
+ }
+ return ErrServiceUnknown
+}
+
+// ExtRPCEnabled returns the indicator whether node enables the external
+// RPC(http, ws or graphql).
+func (ctx *ServiceContext) ExtRPCEnabled() bool {
+ return ctx.config.ExtRPCEnabled()
+}
+
+func NewServiceContext(mux *event.TypeMux) ServiceContext {
+ return ServiceContext {
+ config: nil,
+ services: make(map[reflect.Type]Service),
+ EventMux: mux,
+ AccountManager: nil,
+ }
+}
+
+// ServiceConstructor is the function signature of the constructors needed to be
+// registered for service instantiation.
+type ServiceConstructor func(ctx *ServiceContext) (Service, error)
+
+// Service is an individual protocol that can be registered into a node.
+//
+// Notes:
+//
+// • Service life-cycle management is delegated to the node. The service is allowed to
+// initialize itself upon creation, but no goroutines should be spun up outside of the
+// Start method.
+//
+// • Restart logic is not required as the node will create a fresh instance
+// every time a service is started.
+type Service interface {
+ // Protocols retrieves the P2P protocols the service wishes to start.
+ Protocols() []p2p.Protocol
+
+ // APIs retrieves the list of RPC descriptors the service provides
+ APIs() []rpc.API
+
+ // Start is called after all services have been constructed and the networking
+ // layer was also initialized to spawn any goroutines required by the service.
+ Start(server *p2p.Server) error
+
+ // Stop terminates all goroutines belonging to the service, blocking until they
+ // are all terminated.
+ Stop() error
+}