github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/eth/catalyst/api.go (about)

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