github.com/core-coin/go-core/v2@v2.1.9/les/checkpointoracle/oracle.go (about)

     1  // Copyright 2020 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-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 go-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 go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package checkpointoracle is a wrapper of checkpoint oracle contract with
    18  // additional rules defined. This package can be used both in LES client or
    19  // server side for offering oracle related APIs.
    20  package checkpointoracle
    21  
    22  import (
    23  	"encoding/binary"
    24  	"sync"
    25  	"sync/atomic"
    26  	"time"
    27  
    28  	"github.com/core-coin/go-core/v2/accounts/abi/bind"
    29  	"github.com/core-coin/go-core/v2/common"
    30  	"github.com/core-coin/go-core/v2/contracts/checkpointoracle"
    31  	"github.com/core-coin/go-core/v2/crypto"
    32  	"github.com/core-coin/go-core/v2/log"
    33  	"github.com/core-coin/go-core/v2/params"
    34  )
    35  
    36  // CheckpointOracle is responsible for offering the latest stable checkpoint
    37  // generated and announced by the contract admins on-chain. The checkpoint can
    38  // be verified by clients locally during the checkpoint syncing.
    39  type CheckpointOracle struct {
    40  	config   *params.CheckpointOracleConfig
    41  	contract *checkpointoracle.CheckpointOracle
    42  
    43  	running  int32                                 // Flag whether the contract backend is set or not
    44  	getLocal func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint
    45  
    46  	checkMu              sync.Mutex                // Mutex to sync access to the fields below
    47  	lastCheckTime        time.Time                 // Time we last checked the checkpoint
    48  	lastCheckPoint       *params.TrustedCheckpoint // The last stable checkpoint
    49  	lastCheckPointHeight uint64                    // The height of last stable checkpoint
    50  }
    51  
    52  // New creates a checkpoint oracle handler with given configs and callback.
    53  func New(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *CheckpointOracle {
    54  	return &CheckpointOracle{
    55  		config:   config,
    56  		getLocal: getLocal,
    57  	}
    58  }
    59  
    60  // Start binds the contract backend, initializes the oracle instance
    61  // and marks the status as available.
    62  func (oracle *CheckpointOracle) Start(backend bind.ContractBackend) {
    63  	contract, err := checkpointoracle.NewCheckpointOracle(oracle.config.Address, backend)
    64  	if err != nil {
    65  		log.Error("Oracle contract binding failed", "err", err)
    66  		return
    67  	}
    68  	if !atomic.CompareAndSwapInt32(&oracle.running, 0, 1) {
    69  		log.Error("Already bound and listening to registrar")
    70  		return
    71  	}
    72  	oracle.contract = contract
    73  }
    74  
    75  // IsRunning returns an indicator whether the oracle is running.
    76  func (oracle *CheckpointOracle) IsRunning() bool {
    77  	return atomic.LoadInt32(&oracle.running) == 1
    78  }
    79  
    80  // Contract returns the underlying raw checkpoint oracle contract.
    81  func (oracle *CheckpointOracle) Contract() *checkpointoracle.CheckpointOracle {
    82  	return oracle.contract
    83  }
    84  
    85  // StableCheckpoint returns the stable checkpoint which was generated by local
    86  // indexers and announced by trusted signers.
    87  func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, uint64) {
    88  	oracle.checkMu.Lock()
    89  	defer oracle.checkMu.Unlock()
    90  	if time.Since(oracle.lastCheckTime) < 1*time.Minute {
    91  		return oracle.lastCheckPoint, oracle.lastCheckPointHeight
    92  	}
    93  	// Look it up properly
    94  	// Retrieve the latest checkpoint from the contract, abort if empty
    95  	latest, hash, height, err := oracle.contract.Contract().GetLatestCheckpoint(nil)
    96  	oracle.lastCheckTime = time.Now()
    97  	if err != nil || (latest == 0 && hash == [32]byte{}) {
    98  		oracle.lastCheckPointHeight = 0
    99  		oracle.lastCheckPoint = nil
   100  		return oracle.lastCheckPoint, oracle.lastCheckPointHeight
   101  	}
   102  	local := oracle.getLocal(latest)
   103  
   104  	// The following scenarios may occur:
   105  	//
   106  	// * local node is out of sync so that it doesn't have the
   107  	//   checkpoint which registered in the contract.
   108  	// * local checkpoint doesn't match with the registered one.
   109  	//
   110  	// In both cases, no stable checkpoint will be returned.
   111  	if local.HashEqual(hash) {
   112  		oracle.lastCheckPointHeight = height.Uint64()
   113  		oracle.lastCheckPoint = &local
   114  		return oracle.lastCheckPoint, oracle.lastCheckPointHeight
   115  	}
   116  	return nil, 0
   117  }
   118  
   119  // VerifySigners recovers the signer addresses according to the signature and
   120  // checks whether there are enough approvals to finalize the checkpoint.
   121  func (oracle *CheckpointOracle) VerifySigners(index uint64, hash [32]byte, signatures [][]byte) (bool, []common.Address) {
   122  	// Short circuit if the given signatures doesn't reach the threshold.
   123  	if len(signatures) < int(oracle.config.Threshold) {
   124  		return false, nil
   125  	}
   126  	var (
   127  		signers []common.Address
   128  		checked = make(map[common.Address]struct{})
   129  	)
   130  	for i := 0; i < len(signatures); i++ {
   131  		if len(signatures[i]) != crypto.ExtendedSignatureLength {
   132  			continue
   133  		}
   134  		// CIP 191 style signatures
   135  		//
   136  		// Arguments when calculating hash to validate
   137  		// 1: byte(0x19) - the initial 0x19 byte
   138  		// 2: byte(0) - the version byte (data with intended validator)
   139  		// 3: this - the validator address
   140  		// --  Application specific data
   141  		// 4 : checkpoint section_index (uint64)
   142  		// 5 : checkpoint hash (bytes32)
   143  		//     hash = SHA3(checkpoint_index, section_head, cht_root, bloom_root)
   144  		buf := make([]byte, 8)
   145  		binary.BigEndian.PutUint64(buf, index)
   146  		data := append([]byte{0x19, 0x00}, append(oracle.config.Address.Bytes(), append(buf, hash[:]...)...)...)
   147  		signatures[i][64] -= 27 // Transform V from 27/28 to 0/1 according to the yellow paper for verification.
   148  		pubkey, err := crypto.Ecrecover(crypto.SHA3(data), signatures[i])
   149  		if err != nil {
   150  			return false, nil
   151  		}
   152  		var signer common.Address
   153  		copy(signer[:], crypto.SHA3(pubkey)[12:])
   154  		if _, exist := checked[signer]; exist {
   155  			continue
   156  		}
   157  		for _, s := range oracle.config.Signers {
   158  			if s == signer {
   159  				signers = append(signers, signer)
   160  				checked[signer] = struct{}{}
   161  			}
   162  		}
   163  	}
   164  	threshold := oracle.config.Threshold
   165  	if uint64(len(signers)) < threshold {
   166  		log.Warn("Not enough signers to approve checkpoint", "signers", len(signers), "threshold", threshold)
   167  		return false, nil
   168  	}
   169  	return true, signers
   170  }