github.com/frankli-dev/go-ethereum@v1.1.1/eth/catalyst/api.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package catalyst implements the temporary eth1/eth2 RPC integration.
    18  package catalyst
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"math/big"
    24  	"time"
    25  
    26  	"github.com/frankli-dev/go-ethereum/common"
    27  	"github.com/frankli-dev/go-ethereum/core"
    28  	"github.com/frankli-dev/go-ethereum/core/state"
    29  	"github.com/frankli-dev/go-ethereum/core/types"
    30  	"github.com/frankli-dev/go-ethereum/eth"
    31  	"github.com/frankli-dev/go-ethereum/log"
    32  	"github.com/frankli-dev/go-ethereum/node"
    33  	chainParams "github.com/frankli-dev/go-ethereum/params"
    34  	"github.com/frankli-dev/go-ethereum/rpc"
    35  	"github.com/frankli-dev/go-ethereum/trie"
    36  )
    37  
    38  // Register adds catalyst APIs to the node.
    39  func Register(stack *node.Node, backend *eth.Ethereum) error {
    40  	chainconfig := backend.BlockChain().Config()
    41  	if chainconfig.CatalystBlock == nil {
    42  		return errors.New("catalystBlock is not set in genesis config")
    43  	} else if chainconfig.CatalystBlock.Sign() != 0 {
    44  		return errors.New("catalystBlock of genesis config must be zero")
    45  	}
    46  
    47  	log.Warn("Catalyst mode enabled")
    48  	stack.RegisterAPIs([]rpc.API{
    49  		{
    50  			Namespace: "consensus",
    51  			Version:   "1.0",
    52  			Service:   newConsensusAPI(backend),
    53  			Public:    true,
    54  		},
    55  	})
    56  	return nil
    57  }
    58  
    59  type consensusAPI struct {
    60  	eth *eth.Ethereum
    61  }
    62  
    63  func newConsensusAPI(eth *eth.Ethereum) *consensusAPI {
    64  	return &consensusAPI{eth: eth}
    65  }
    66  
    67  // blockExecutionEnv gathers all the data required to execute
    68  // a block, either when assembling it or when inserting it.
    69  type blockExecutionEnv struct {
    70  	chain   *core.BlockChain
    71  	state   *state.StateDB
    72  	tcount  int
    73  	gasPool *core.GasPool
    74  
    75  	header   *types.Header
    76  	txs      []*types.Transaction
    77  	receipts []*types.Receipt
    78  }
    79  
    80  func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error {
    81  	vmconfig := *env.chain.GetVMConfig()
    82  	receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	env.txs = append(env.txs, tx)
    87  	env.receipts = append(env.receipts, receipt)
    88  	return nil
    89  }
    90  
    91  func (api *consensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) {
    92  	state, err := api.eth.BlockChain().StateAt(parent.Root())
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	env := &blockExecutionEnv{
    97  		chain:   api.eth.BlockChain(),
    98  		state:   state,
    99  		header:  header,
   100  		gasPool: new(core.GasPool).AddGas(header.GasLimit),
   101  	}
   102  	return env, nil
   103  }
   104  
   105  // AssembleBlock creates a new block, inserts it into the chain, and returns the "execution
   106  // data" required for eth2 clients to process the new block.
   107  func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableData, error) {
   108  	log.Info("Producing block", "parentHash", params.ParentHash)
   109  
   110  	bc := api.eth.BlockChain()
   111  	parent := bc.GetBlockByHash(params.ParentHash)
   112  	if parent == nil {
   113  		log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", params.ParentHash)
   114  		return nil, fmt.Errorf("cannot assemble block with unknown parent %s", params.ParentHash)
   115  	}
   116  
   117  	pool := api.eth.TxPool()
   118  
   119  	if parent.Time() >= params.Timestamp {
   120  		return nil, fmt.Errorf("child timestamp lower than parent's: %d >= %d", parent.Time(), params.Timestamp)
   121  	}
   122  	if now := uint64(time.Now().Unix()); params.Timestamp > now+1 {
   123  		wait := time.Duration(params.Timestamp-now) * time.Second
   124  		log.Info("Producing block too far in the future", "wait", common.PrettyDuration(wait))
   125  		time.Sleep(wait)
   126  	}
   127  
   128  	pending, err := pool.Pending()
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	coinbase, err := api.eth.Etherbase()
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	num := parent.Number()
   138  	header := &types.Header{
   139  		ParentHash: parent.Hash(),
   140  		Number:     num.Add(num, common.Big1),
   141  		Coinbase:   coinbase,
   142  		GasLimit:   parent.GasLimit(), // Keep the gas limit constant in this prototype
   143  		Extra:      []byte{},
   144  		Time:       params.Timestamp,
   145  	}
   146  	err = api.eth.Engine().Prepare(bc, header)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	env, err := api.makeEnv(parent, header)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	var (
   157  		signer       = types.MakeSigner(bc.Config(), header.Number)
   158  		txHeap       = types.NewTransactionsByPriceAndNonce(signer, pending)
   159  		transactions []*types.Transaction
   160  	)
   161  	for {
   162  		if env.gasPool.Gas() < chainParams.TxGas {
   163  			log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", chainParams.TxGas)
   164  			break
   165  		}
   166  		tx := txHeap.Peek()
   167  		if tx == nil {
   168  			break
   169  		}
   170  
   171  		// The sender is only for logging purposes, and it doesn't really matter if it's correct.
   172  		from, _ := types.Sender(signer, tx)
   173  
   174  		// Execute the transaction
   175  		env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount)
   176  		err = env.commitTransaction(tx, coinbase)
   177  		switch err {
   178  		case core.ErrGasLimitReached:
   179  			// Pop the current out-of-gas transaction without shifting in the next from the account
   180  			log.Trace("Gas limit exceeded for current block", "sender", from)
   181  			txHeap.Pop()
   182  
   183  		case core.ErrNonceTooLow:
   184  			// New head notification data race between the transaction pool and miner, shift
   185  			log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
   186  			txHeap.Shift()
   187  
   188  		case core.ErrNonceTooHigh:
   189  			// Reorg notification data race between the transaction pool and miner, skip account =
   190  			log.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce())
   191  			txHeap.Pop()
   192  
   193  		case nil:
   194  			// Everything ok, collect the logs and shift in the next transaction from the same account
   195  			env.tcount++
   196  			txHeap.Shift()
   197  			transactions = append(transactions, tx)
   198  
   199  		default:
   200  			// Strange error, discard the transaction and get the next in line (note, the
   201  			// nonce-too-high clause will prevent us from executing in vain).
   202  			log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
   203  			txHeap.Shift()
   204  		}
   205  	}
   206  
   207  	// Create the block.
   208  	block, _, err := api.eth.Engine().FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	return &executableData{
   213  		BlockHash:    block.Hash(),
   214  		ParentHash:   block.ParentHash(),
   215  		Miner:        block.Coinbase(),
   216  		StateRoot:    block.Root(),
   217  		Number:       block.NumberU64(),
   218  		GasLimit:     block.GasLimit(),
   219  		GasUsed:      block.GasUsed(),
   220  		Timestamp:    block.Time(),
   221  		ReceiptRoot:  block.ReceiptHash(),
   222  		LogsBloom:    block.Bloom().Bytes(),
   223  		Transactions: encodeTransactions(block.Transactions()),
   224  	}, nil
   225  }
   226  
   227  func encodeTransactions(txs []*types.Transaction) [][]byte {
   228  	var enc = make([][]byte, len(txs))
   229  	for i, tx := range txs {
   230  		enc[i], _ = tx.MarshalBinary()
   231  	}
   232  	return enc
   233  }
   234  
   235  func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
   236  	var txs = make([]*types.Transaction, len(enc))
   237  	for i, encTx := range enc {
   238  		var tx types.Transaction
   239  		if err := tx.UnmarshalBinary(encTx); err != nil {
   240  			return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
   241  		}
   242  		txs[i] = &tx
   243  	}
   244  	return txs, nil
   245  }
   246  
   247  func insertBlockParamsToBlock(params executableData) (*types.Block, error) {
   248  	txs, err := decodeTransactions(params.Transactions)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	number := big.NewInt(0)
   254  	number.SetUint64(params.Number)
   255  	header := &types.Header{
   256  		ParentHash:  params.ParentHash,
   257  		UncleHash:   types.EmptyUncleHash,
   258  		Coinbase:    params.Miner,
   259  		Root:        params.StateRoot,
   260  		TxHash:      types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
   261  		ReceiptHash: params.ReceiptRoot,
   262  		Bloom:       types.BytesToBloom(params.LogsBloom),
   263  		Difficulty:  big.NewInt(1),
   264  		Number:      number,
   265  		GasLimit:    params.GasLimit,
   266  		GasUsed:     params.GasUsed,
   267  		Time:        params.Timestamp,
   268  	}
   269  	block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
   270  	return block, nil
   271  }
   272  
   273  // NewBlock creates an Eth1 block, inserts it in the chain, and either returns true,
   274  // or false + an error. This is a bit redundant for go, but simplifies things on the
   275  // eth2 side.
   276  func (api *consensusAPI) NewBlock(params executableData) (*newBlockResponse, error) {
   277  	parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash)
   278  	if parent == nil {
   279  		return &newBlockResponse{false}, fmt.Errorf("could not find parent %x", params.ParentHash)
   280  	}
   281  	block, err := insertBlockParamsToBlock(params)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	_, err = api.eth.BlockChain().InsertChainWithoutSealVerification(block)
   287  	return &newBlockResponse{err == nil}, err
   288  }
   289  
   290  // Used in tests to add a the list of transactions from a block to the tx pool.
   291  func (api *consensusAPI) addBlockTxs(block *types.Block) error {
   292  	for _, tx := range block.Transactions() {
   293  		api.eth.TxPool().AddLocal(tx)
   294  	}
   295  	return nil
   296  }
   297  
   298  // FinalizeBlock is called to mark a block as synchronized, so
   299  // that data that is no longer needed can be removed.
   300  func (api *consensusAPI) FinalizeBlock(blockHash common.Hash) (*genericResponse, error) {
   301  	return &genericResponse{true}, nil
   302  }
   303  
   304  // SetHead is called to perform a force choice.
   305  func (api *consensusAPI) SetHead(newHead common.Hash) (*genericResponse, error) {
   306  	return &genericResponse{true}, nil
   307  }