github.com/theQRL/go-zond@v0.1.1/zond/catalyst/simulated_beacon.go (about)

     1  // Copyright 2023 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
    18  
    19  import (
    20  	"crypto/rand"
    21  	"errors"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/theQRL/go-zond/beacon/engine"
    26  	"github.com/theQRL/go-zond/common"
    27  	"github.com/theQRL/go-zond/core"
    28  	"github.com/theQRL/go-zond/core/types"
    29  	"github.com/theQRL/go-zond/log"
    30  	"github.com/theQRL/go-zond/node"
    31  	"github.com/theQRL/go-zond/rpc"
    32  	"github.com/theQRL/go-zond/zond"
    33  )
    34  
    35  const devEpochLength = 32
    36  
    37  // withdrawalQueue implements a FIFO queue which holds withdrawals that are
    38  // pending inclusion.
    39  type withdrawalQueue struct {
    40  	pending chan *types.Withdrawal
    41  }
    42  
    43  // add queues a withdrawal for future inclusion.
    44  func (w *withdrawalQueue) add(withdrawal *types.Withdrawal) error {
    45  	select {
    46  	case w.pending <- withdrawal:
    47  		break
    48  	default:
    49  		return errors.New("withdrawal queue full")
    50  	}
    51  	return nil
    52  }
    53  
    54  // gatherPending returns a number of queued withdrawals up to a maximum count.
    55  func (w *withdrawalQueue) gatherPending(maxCount int) []*types.Withdrawal {
    56  	withdrawals := []*types.Withdrawal{}
    57  	for {
    58  		select {
    59  		case withdrawal := <-w.pending:
    60  			withdrawals = append(withdrawals, withdrawal)
    61  			if len(withdrawals) == maxCount {
    62  				break
    63  			}
    64  		default:
    65  			return withdrawals
    66  		}
    67  	}
    68  }
    69  
    70  type SimulatedBeacon struct {
    71  	shutdownCh  chan struct{}
    72  	eth         *zond.Ethereum
    73  	period      uint64
    74  	withdrawals withdrawalQueue
    75  
    76  	feeRecipient     common.Address
    77  	feeRecipientLock sync.Mutex // lock gates concurrent access to the feeRecipient
    78  
    79  	engineAPI          *ConsensusAPI
    80  	curForkchoiceState engine.ForkchoiceStateV1
    81  	lastBlockTime      uint64
    82  }
    83  
    84  func NewSimulatedBeacon(period uint64, eth *zond.Ethereum) (*SimulatedBeacon, error) {
    85  	chainConfig := eth.APIBackend.ChainConfig()
    86  	if !chainConfig.IsDevMode {
    87  		return nil, errors.New("incompatible pre-existing chain configuration")
    88  	}
    89  	block := eth.BlockChain().CurrentBlock()
    90  	current := engine.ForkchoiceStateV1{
    91  		HeadBlockHash:      block.Hash(),
    92  		SafeBlockHash:      block.Hash(),
    93  		FinalizedBlockHash: block.Hash(),
    94  	}
    95  	engineAPI := newConsensusAPIWithoutHeartbeat(eth)
    96  
    97  	// if genesis block, send forkchoiceUpdated to trigger transition to PoS
    98  	if block.Number.Sign() == 0 {
    99  		if _, err := engineAPI.ForkchoiceUpdatedV2(current, nil); err != nil {
   100  			return nil, err
   101  		}
   102  	}
   103  	return &SimulatedBeacon{
   104  		eth:                eth,
   105  		period:             period,
   106  		shutdownCh:         make(chan struct{}),
   107  		engineAPI:          engineAPI,
   108  		lastBlockTime:      block.Time,
   109  		curForkchoiceState: current,
   110  		withdrawals:        withdrawalQueue{make(chan *types.Withdrawal, 20)},
   111  	}, nil
   112  }
   113  
   114  func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) {
   115  	c.feeRecipientLock.Lock()
   116  	c.feeRecipient = feeRecipient
   117  	c.feeRecipientLock.Unlock()
   118  }
   119  
   120  // Start invokes the SimulatedBeacon life-cycle function in a goroutine.
   121  func (c *SimulatedBeacon) Start() error {
   122  	if c.period == 0 {
   123  		go c.loopOnDemand()
   124  	} else {
   125  		go c.loop()
   126  	}
   127  	return nil
   128  }
   129  
   130  // Stop halts the SimulatedBeacon service.
   131  func (c *SimulatedBeacon) Stop() error {
   132  	close(c.shutdownCh)
   133  	return nil
   134  }
   135  
   136  // sealBlock initiates payload building for a new block and creates a new block
   137  // with the completed payload.
   138  func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
   139  	tstamp := uint64(time.Now().Unix())
   140  	if tstamp <= c.lastBlockTime {
   141  		tstamp = c.lastBlockTime + 1
   142  	}
   143  	c.feeRecipientLock.Lock()
   144  	feeRecipient := c.feeRecipient
   145  	c.feeRecipientLock.Unlock()
   146  
   147  	// Reset to CurrentBlock in case of the chain was rewound
   148  	if header := c.eth.BlockChain().CurrentBlock(); c.curForkchoiceState.HeadBlockHash != header.Hash() {
   149  		finalizedHash := c.finalizedBlockHash(header.Number.Uint64())
   150  		c.setCurrentState(header.Hash(), *finalizedHash)
   151  	}
   152  
   153  	var random [32]byte
   154  	rand.Read(random[:])
   155  	fcResponse, err := c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, &engine.PayloadAttributes{
   156  		Timestamp:             tstamp,
   157  		SuggestedFeeRecipient: feeRecipient,
   158  		Withdrawals:           withdrawals,
   159  		Random:                random,
   160  	})
   161  	if err != nil {
   162  		return err
   163  	}
   164  	if fcResponse == engine.STATUS_SYNCING {
   165  		return errors.New("chain rewind prevented invocation of payload creation")
   166  	}
   167  
   168  	envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	payload := envelope.ExecutionPayload
   173  
   174  	var finalizedHash common.Hash
   175  	if payload.Number%devEpochLength == 0 {
   176  		finalizedHash = payload.BlockHash
   177  	} else {
   178  		if fh := c.finalizedBlockHash(payload.Number); fh == nil {
   179  			return errors.New("chain rewind interrupted calculation of finalized block hash")
   180  		} else {
   181  			finalizedHash = *fh
   182  		}
   183  	}
   184  
   185  	// Mark the payload as canon
   186  	if _, err = c.engineAPI.NewPayloadV2(*payload); err != nil {
   187  		return err
   188  	}
   189  	c.setCurrentState(payload.BlockHash, finalizedHash)
   190  	// Mark the block containing the payload as canonical
   191  	if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil {
   192  		return err
   193  	}
   194  	c.lastBlockTime = payload.Timestamp
   195  	return nil
   196  }
   197  
   198  // loopOnDemand runs the block production loop for "on-demand" configuration (period = 0)
   199  func (c *SimulatedBeacon) loopOnDemand() {
   200  	var (
   201  		newTxs = make(chan core.NewTxsEvent)
   202  		sub    = c.eth.TxPool().SubscribeNewTxsEvent(newTxs)
   203  	)
   204  	defer sub.Unsubscribe()
   205  
   206  	for {
   207  		select {
   208  		case <-c.shutdownCh:
   209  			return
   210  		case w := <-c.withdrawals.pending:
   211  			withdrawals := append(c.withdrawals.gatherPending(9), w)
   212  			if err := c.sealBlock(withdrawals); err != nil {
   213  				log.Warn("Error performing sealing work", "err", err)
   214  			}
   215  		case <-newTxs:
   216  			withdrawals := c.withdrawals.gatherPending(10)
   217  			if err := c.sealBlock(withdrawals); err != nil {
   218  				log.Warn("Error performing sealing work", "err", err)
   219  			}
   220  		}
   221  	}
   222  }
   223  
   224  // loop runs the block production loop for non-zero period configuration
   225  func (c *SimulatedBeacon) loop() {
   226  	timer := time.NewTimer(0)
   227  	for {
   228  		select {
   229  		case <-c.shutdownCh:
   230  			return
   231  		case <-timer.C:
   232  			withdrawals := c.withdrawals.gatherPending(10)
   233  			if err := c.sealBlock(withdrawals); err != nil {
   234  				log.Warn("Error performing sealing work", "err", err)
   235  			} else {
   236  				timer.Reset(time.Second * time.Duration(c.period))
   237  			}
   238  		}
   239  	}
   240  }
   241  
   242  // finalizedBlockHash returns the block hash of the finalized block corresponding to the given number
   243  // or nil if doesn't exist in the chain.
   244  func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
   245  	var finalizedNumber uint64
   246  	if number%devEpochLength == 0 {
   247  		finalizedNumber = number
   248  	} else {
   249  		finalizedNumber = (number - 1) / devEpochLength * devEpochLength
   250  	}
   251  
   252  	if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil {
   253  		fh := finalizedBlock.Hash()
   254  		return &fh
   255  	}
   256  	return nil
   257  }
   258  
   259  // setCurrentState sets the current forkchoice state
   260  func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) {
   261  	c.curForkchoiceState = engine.ForkchoiceStateV1{
   262  		HeadBlockHash:      headHash,
   263  		SafeBlockHash:      headHash,
   264  		FinalizedBlockHash: finalizedHash,
   265  	}
   266  }
   267  
   268  func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
   269  	stack.RegisterAPIs([]rpc.API{
   270  		{
   271  			Namespace: "dev",
   272  			Service:   &api{sim},
   273  			Version:   "1.0",
   274  		},
   275  	})
   276  }