github.com/codingfuture/orig-energi3@v0.8.4/energi/service/checkpoints.go (about)

     1  // Copyright 2019 The Energi Core Authors
     2  // This file is part of the Energi Core library.
     3  //
     4  // The Energi Core 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 Energi Core 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 Energi Core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package service
    18  
    19  import (
    20  	"context"
    21  	"math/big"
    22  
    23  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/core"
    26  	"github.com/ethereum/go-ethereum/eth"
    27  	"github.com/ethereum/go-ethereum/eth/downloader"
    28  	"github.com/ethereum/go-ethereum/log"
    29  	"github.com/ethereum/go-ethereum/node"
    30  	"github.com/ethereum/go-ethereum/p2p"
    31  	"github.com/ethereum/go-ethereum/rpc"
    32  
    33  	energi_abi "energi.world/core/gen3/energi/abi"
    34  	energi_common "energi.world/core/gen3/energi/common"
    35  	energi_params "energi.world/core/gen3/energi/params"
    36  )
    37  
    38  const (
    39  	cppChanBufferSize = 10
    40  )
    41  
    42  type CheckpointProposalEvent struct {
    43  	core.Checkpoint
    44  	Proposal common.Address
    45  }
    46  
    47  type CheckpointService struct {
    48  	server *p2p.Server
    49  	eth    *eth.Ethereum
    50  
    51  	cpRegistry *energi_abi.ICheckpointRegistry
    52  	callOpts   *bind.CallOpts
    53  }
    54  
    55  func NewCheckpointService(ethServ *eth.Ethereum) (node.Service, error) {
    56  	r := &CheckpointService{
    57  		eth:      ethServ,
    58  		callOpts: &bind.CallOpts{},
    59  	}
    60  	return r, nil
    61  }
    62  
    63  func (c *CheckpointService) Protocols() []p2p.Protocol {
    64  	return nil
    65  }
    66  
    67  func (c *CheckpointService) APIs() []rpc.API {
    68  	return nil
    69  }
    70  
    71  func (c *CheckpointService) Start(server *p2p.Server) (err error) {
    72  	c.cpRegistry, err = energi_abi.NewICheckpointRegistry(
    73  		energi_params.Energi_CheckpointRegistry, c.eth.APIBackend)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	c.server = server
    79  
    80  	//---
    81  	oldCheckpoints, err := c.cpRegistry.Checkpoints(c.callOpts)
    82  	if err != nil {
    83  		log.Error("Failed to get old checkpoints (startup)", "err", err)
    84  	} else if lc := len(oldCheckpoints); lc > 0 {
    85  		// NOTE: enable the latest checkpoint immediately for safety reasons
    86  		c.onCheckpoint(oldCheckpoints[lc-1], false)
    87  	}
    88  
    89  	//---
    90  	go c.loop()
    91  	return nil
    92  }
    93  
    94  func (c *CheckpointService) Stop() error {
    95  	return nil
    96  }
    97  
    98  func (c *CheckpointService) waitDownloader() bool {
    99  	events := c.eth.EventMux().Subscribe(
   100  		downloader.StartEvent{},
   101  		downloader.DoneEvent{},
   102  		downloader.FailedEvent{},
   103  	)
   104  	defer events.Unsubscribe()
   105  
   106  	for {
   107  		select {
   108  		case ev := <-events.Chan():
   109  			if ev == nil {
   110  				return false
   111  			}
   112  			switch ev.Data.(type) {
   113  			case downloader.StartEvent:
   114  				continue
   115  			case downloader.DoneEvent:
   116  				return true
   117  			case downloader.FailedEvent:
   118  				return c.eth.BlockChain().IsRunning()
   119  			}
   120  		}
   121  	}
   122  }
   123  
   124  func (c *CheckpointService) loop() {
   125  	if !c.waitDownloader() {
   126  		return
   127  	}
   128  
   129  	cpChan := make(chan *energi_abi.ICheckpointRegistryCheckpoint, cppChanBufferSize)
   130  
   131  	watchOpts := &bind.WatchOpts{
   132  		Context: context.WithValue(
   133  			context.Background(),
   134  			energi_params.GeneralProxyCtxKey,
   135  			energi_common.GeneralProxyHashGen(c.eth.BlockChain()),
   136  		),
   137  	}
   138  
   139  	subscribe, err := c.cpRegistry.WatchCheckpoint(watchOpts, cpChan, []*big.Int{})
   140  	if err != nil {
   141  		log.Error("Failed checkpoint subscription", "err", err)
   142  		return
   143  	}
   144  
   145  	defer subscribe.Unsubscribe()
   146  
   147  	oldCheckpoints, err := c.cpRegistry.Checkpoints(c.callOpts)
   148  	if err != nil {
   149  		log.Error("Failed to get old checkpoints", "err", err)
   150  	} else {
   151  		// NOTE: we should feed for recent first
   152  		for i := len(oldCheckpoints) - 1; i >= 0; i-- {
   153  			c.onCheckpoint(oldCheckpoints[i], false)
   154  		}
   155  	}
   156  
   157  	for {
   158  		select {
   159  		case err = <-subscribe.Err():
   160  			log.Debug("Checkpoint subscription error", "err", err)
   161  			return
   162  
   163  		case cpData := <-cpChan:
   164  			c.onCheckpoint(cpData.Checkpoint, true)
   165  		}
   166  	}
   167  }
   168  
   169  func (c *CheckpointService) onCheckpoint(cpAddr common.Address, live bool) {
   170  	backend := c.eth.APIBackend
   171  	cppSigner := backend.ChainConfig().Energi.CPPSigner
   172  
   173  	cp, err := energi_abi.NewICheckpointV2Caller(cpAddr, backend)
   174  	if err != nil {
   175  		log.Warn("Failed to create CP contract caller", "addr", cpAddr, "err", err)
   176  		return
   177  	}
   178  
   179  	info, err := cp.Info(c.callOpts)
   180  	if err != nil {
   181  		log.Warn("Failed to get CP info", "addr", cpAddr, "err", err)
   182  		return
   183  	}
   184  
   185  	cpp_sig, err := cp.Signature(c.callOpts, cppSigner)
   186  	if err != nil {
   187  		log.Debug("Skipping checkpoint with no CPP sig", "addr", cpAddr, "err", err)
   188  		return
   189  	}
   190  
   191  	if len(cpp_sig) >= 65 {
   192  		// Drop Ecrecover workaround
   193  		cpp_sig[64] -= 27
   194  	}
   195  
   196  	sigs := []core.CheckpointSignature{
   197  		core.CheckpointSignature(cpp_sig),
   198  	}
   199  
   200  	backend.AddDynamicCheckpoint(info.Since.Uint64(), info.Number.Uint64(), info.Hash, sigs)
   201  
   202  	if live {
   203  		log.Warn("Found new dynamic checkpoint", "num", info.Number, "hash", common.Hash(info.Hash).Hex())
   204  
   205  		c.eth.EventMux().Post(CheckpointProposalEvent{
   206  			core.Checkpoint{
   207  				Since:  info.Since.Uint64(),
   208  				Number: info.Number.Uint64(),
   209  				Hash:   info.Hash,
   210  			},
   211  			cpAddr,
   212  		})
   213  	}
   214  }