Skip to content

Commit

Permalink
Minor fixes in Eth2 JSON-RPC (#8)
Browse files Browse the repository at this point in the history
* Minor fixes in Eth2 JSON-RPC

* fix linter issues

* fix unit tests

* add a couple comments

* move eth2 code to its own file to simplify rebase

Co-authored-by: Guillaume Ballet <[email protected]>
  • Loading branch information
mkalinin and gballet authored Jan 13, 2021
1 parent 11218c3 commit 3ded09c
Show file tree
Hide file tree
Showing 4 changed files with 609 additions and 541 deletions.
272 changes: 0 additions & 272 deletions eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ import (
"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/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
chainParams "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
Expand Down Expand Up @@ -304,275 +301,6 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error
return stateDb.RawDump(false, false, true), nil
}

type Eth2API struct {
eth *Ethereum
env *eth2bpenv
head common.Hash
}

// NewEth2API creates a new API definition for the eth2 prototype.
func NewEth2API(eth *Ethereum) *Eth2API {
return &Eth2API{eth: eth}
}

type eth2bpenv struct {
state *state.StateDB
tcount int
gasPool *core.GasPool

header *types.Header
txs []*types.Transaction
receipts []*types.Receipt
}

func (api *Eth2API) commitTransaction(tx *types.Transaction, coinbase common.Address, bcParentRoots []common.Hash, randao common.Hash) error {
//snap := eth2rpc.current.state.Snapshot()

chain := api.eth.BlockChain()
receipt, err := core.ApplyTransaction(chain.Config(), chain, &coinbase, api.env.gasPool, api.env.state, api.env.header, tx, &api.env.header.GasUsed, *chain.GetVMConfig(), &vm.BeaconChainContext{bcParentRoots, randao})
if err != nil {
//w.current.state.RevertToSnapshot(snap)
return err
}
api.env.txs = append(api.env.txs, tx)
api.env.receipts = append(api.env.receipts, receipt)

return nil
}

func (api *Eth2API) makeEnv(parent *types.Block, header *types.Header) error {
state, err := api.eth.BlockChain().StateAt(parent.Root())
if err != nil {
return err
}
api.env = &eth2bpenv{
state: state,
header: header,
gasPool: new(core.GasPool).AddGas(header.GasLimit),
}
return nil
}

// Structure described at https://hackmd.io/T9x2mMA4S7us8tJwEB3FDQ
type ProduceBlockParams struct {
ParentRoot common.Hash `json:"parent_root"`
RandaoMix common.Hash `json:"randao_mix"`
Slot uint64 `json:"slot"`
Timestamp uint64 `json:"timestamp"`
RecentBeaconBlockRoots []common.Hash `json:"recent_beacon_block_roots"`
}

// Structure described at https://ethresear.ch/t/executable-beacon-chain/8271
type ExecutableData struct {
Coinbase common.Address `json:"coinbase"`
StateRoot common.Hash `json:"state_root"`
GasLimit uint64 `json:"gas_limit"`
GasUsed uint64 `json:"gas_used"`
Transactions []*types.Transaction `json:"transactions"`
ReceiptRoot common.Hash `json:"receipt_root"`
LogsBloom []byte `json:"logs_bloom"`
BlockHash common.Hash `json:"block_hash"`
Difficulty *big.Int `json:"difficulty"`
}

func (api *Eth2API) ProduceBlock(params ProduceBlockParams) (*ExecutableData, error) {
log.Info("Produce block", "parentHash", params.ParentRoot)

bc := api.eth.BlockChain()
parent := bc.GetBlockByHash(params.ParentRoot)
pool := api.eth.TxPool()

if parent.Time() >= params.Timestamp {
return nil, fmt.Errorf("child timestamp lower than parent's: %d >= %d", parent.Time(), params.Timestamp)
}
// this will ensure we're not going off too far in the future
if now := uint64(time.Now().Unix()); params.Timestamp > now+1 {
wait := time.Duration(params.Timestamp-now) * time.Second
log.Info("Producing block too far in the future", "wait", common.PrettyDuration(wait))
time.Sleep(wait)
}

pending, err := pool.Pending()
if err != nil {
return nil, err
}

coinbase, err := api.eth.Etherbase()
if err != nil {
return nil, err
}
num := parent.Number()
header := &types.Header{
ParentHash: parent.Hash(),
Number: num.Add(num, common.Big1),
Coinbase: coinbase,
GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype
Extra: []byte{},
Time: params.Timestamp,
}
err = api.eth.Engine().Prepare(bc, header)
if err != nil {
return nil, err
}

err = api.makeEnv(parent, header)
if err != nil {
return nil, err
}
signer := types.NewEIP155Signer(bc.Config().ChainID)
txs := types.NewTransactionsByPriceAndNonce(signer, pending)

var transactions []*types.Transaction

for {
if api.env.gasPool.Gas() < chainParams.TxGas {
log.Trace("Not enough gas for further transactions", "have", api.env.gasPool, "want", chainParams.TxGas)
break
}

tx := txs.Peek()
if tx == nil {
break
}

from, _ := types.Sender(signer, tx)
// XXX replay protection check is missing

// Execute the transaction
api.env.state.Prepare(tx.Hash(), common.Hash{}, api.env.tcount)
err := api.commitTransaction(tx, coinbase, params.RecentBeaconBlockRoots, params.RandaoMix)
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 high 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
api.env.tcount++
txs.Shift()
transactions = append(transactions, tx)

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()
}
}

block, err := api.eth.Engine().FinalizeAndAssemble(bc, header, api.env.state, transactions, nil /* uncles */, api.env.receipts)
if err != nil {
return nil, err
}

var logs []*types.Log
var receipts = make(types.Receipts, len(api.env.receipts))
hash := block.Hash()
for i, receipt := range api.env.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...)
}

block.Header().ReceiptHash = types.DeriveSha(receipts, new(trie.Trie))

return &ExecutableData{
Coinbase: block.Coinbase(),
StateRoot: block.Root(),
GasLimit: block.GasLimit(),
GasUsed: block.GasUsed(),
Transactions: []*types.Transaction(block.Transactions()),
ReceiptRoot: block.ReceiptHash(),
LogsBloom: block.Bloom().Bytes(),
BlockHash: block.Hash(),
Difficulty: block.Difficulty(),
}, nil
}

// Structure described at https://hackmd.io/T9x2mMA4S7us8tJwEB3FDQ
type InsertBlockParams struct {
ProduceBlockParams
BeaconBlockRoot common.Hash `json:"beacon_block_root"`
ExecutableData ExecutableData `json:"executable_data"`
}

var zeroNonce [8]byte

func insertBlockParamsToBlock(params InsertBlockParams) *types.Block {
header := &types.Header{
ParentHash: params.ProduceBlockParams.ParentRoot,
UncleHash: types.EmptyUncleHash,
Coinbase: params.ExecutableData.Coinbase,
Root: params.ExecutableData.StateRoot,
TxHash: types.DeriveSha(types.Transactions(params.ExecutableData.Transactions), trie.NewStackTrie(nil)),
ReceiptHash: params.ExecutableData.ReceiptRoot,
Bloom: types.BytesToBloom(params.ExecutableData.LogsBloom),
Difficulty: params.ExecutableData.Difficulty,
Number: big.NewInt(int64(params.ProduceBlockParams.Slot)),
GasLimit: params.ExecutableData.GasLimit,
GasUsed: params.ExecutableData.GasUsed,
Time: params.Timestamp,
Extra: nil,
MixDigest: common.Hash{},
Nonce: zeroNonce,
}
block := types.NewBlockWithHeader(header).WithBody(params.ExecutableData.Transactions, nil /* uncles */)

return block
}

func (api *Eth2API) InsertBlock(params InsertBlockParams) error {
block := insertBlockParamsToBlock(params)
_, err := api.eth.BlockChain().InsertChainWithoutSealVerification(types.Blocks([]*types.Block{block}))
return err
}

func (api *Eth2API) AddBlockTxs(block *types.Block) error {
for _, tx := range block.Transactions() {
api.eth.txPool.AddLocal(tx)
}

return nil
}

//func (api *Eth2API) SetHead(newHead common.Hash) error {
//oldBlock := api.eth.BlockChain().CurrentBlock()

//if oldBlock.Hash() == newHead {
//return nil
//}

//newBlock := api.eth.BlockChain().GetBlockByHash(newHead)

//err := api.eth.BlockChain().Reorg(oldBlock, newBlock)
//if err != nil {
//return err
//}
//api.head = newHead
//return nil
//}

// PrivateDebugAPI is the collection of Ethereum full node APIs exposed over
// the private debugging endpoint.
type PrivateDebugAPI struct {
Expand Down
Loading

0 comments on commit 3ded09c

Please sign in to comment.