github.com/Elemental-core/elementalcore@v0.0.0-20191206075037-63891242267a/contracts/release/release.go (about)

     1  // Copyright 2015 The elementalcore Authors
     2  // This file is part of the elementalcore library.
     3  //
     4  // The elementalcore 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 elementalcore 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 elementalcore library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package release contains the node service that tracks client releases.
    18  package release
    19  
    20  //go:generate abigen --sol ./contract.sol --pkg release --out ./contract.go
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/Elemental-core/elementalcore/accounts/abi/bind"
    29  	"github.com/Elemental-core/elementalcore/common"
    30  	"github.com/Elemental-core/elementalcore/eth"
    31  	"github.com/Elemental-core/elementalcore/internal/ethapi"
    32  	"github.com/Elemental-core/elementalcore/les"
    33  	"github.com/Elemental-core/elementalcore/log"
    34  	"github.com/Elemental-core/elementalcore/node"
    35  	"github.com/Elemental-core/elementalcore/p2p"
    36  	"github.com/Elemental-core/elementalcore/rpc"
    37  )
    38  
    39  // Interval to check for new releases
    40  const releaseRecheckInterval = time.Hour
    41  
    42  // Config contains the configurations of the release service.
    43  type Config struct {
    44  	Oracle common.Address // Ethereum address of the release oracle
    45  	Major  uint32         // Major version component of the release
    46  	Minor  uint32         // Minor version component of the release
    47  	Patch  uint32         // Patch version component of the release
    48  	Commit [20]byte       // Git SHA1 commit hash of the release
    49  }
    50  
    51  // ReleaseService is a node service that periodically checks the blockchain for
    52  // newly released versions of the client being run and issues a warning to the
    53  // user about it.
    54  type ReleaseService struct {
    55  	config Config          // Current version to check releases against
    56  	oracle *ReleaseOracle  // Native binding to the release oracle contract
    57  	quit   chan chan error // Quit channel to terminate the version checker
    58  }
    59  
    60  // NewReleaseService creates a new service to periodically check for new client
    61  // releases and notify the user of such.
    62  func NewReleaseService(ctx *node.ServiceContext, config Config) (node.Service, error) {
    63  	// Retrieve the Ethereum service dependency to access the blockchain
    64  	var apiBackend ethapi.Backend
    65  	var ethereum *eth.Ethereum
    66  	if err := ctx.Service(&ethereum); err == nil {
    67  		apiBackend = ethereum.ApiBackend
    68  	} else {
    69  		var ethereum *les.LightEthereum
    70  		if err := ctx.Service(&ethereum); err == nil {
    71  			apiBackend = ethereum.ApiBackend
    72  		} else {
    73  			return nil, err
    74  		}
    75  	}
    76  	// Construct the release service
    77  	contract, err := NewReleaseOracle(config.Oracle, eth.NewContractBackend(apiBackend))
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	return &ReleaseService{
    82  		config: config,
    83  		oracle: contract,
    84  		quit:   make(chan chan error),
    85  	}, nil
    86  }
    87  
    88  // Protocols returns an empty list of P2P protocols as the release service does
    89  // not have a networking component.
    90  func (r *ReleaseService) Protocols() []p2p.Protocol { return nil }
    91  
    92  // APIs returns an empty list of RPC descriptors as the release service does not
    93  // expose any functioanlity to the outside world.
    94  func (r *ReleaseService) APIs() []rpc.API { return nil }
    95  
    96  // Start spawns the periodic version checker goroutine
    97  func (r *ReleaseService) Start(server *p2p.Server) error {
    98  	go r.checker()
    99  	return nil
   100  }
   101  
   102  // Stop terminates all goroutines belonging to the service, blocking until they
   103  // are all terminated.
   104  func (r *ReleaseService) Stop() error {
   105  	errc := make(chan error)
   106  	r.quit <- errc
   107  	return <-errc
   108  }
   109  
   110  // checker runs indefinitely in the background, periodically checking for new
   111  // client releases.
   112  func (r *ReleaseService) checker() {
   113  	// Set up the timers to periodically check for releases
   114  	timer := time.NewTimer(0) // Immediately fire a version check
   115  	defer timer.Stop()
   116  
   117  	for {
   118  		select {
   119  		case <-timer.C:
   120  			// Rechedule the timer before continuing
   121  			timer.Reset(releaseRecheckInterval)
   122  			r.checkVersion()
   123  		case errc := <-r.quit:
   124  			errc <- nil
   125  			return
   126  		}
   127  	}
   128  }
   129  
   130  func (r *ReleaseService) checkVersion() {
   131  	// Retrieve the current version, and handle missing contracts gracefully
   132  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
   133  	opts := &bind.CallOpts{Context: ctx}
   134  	defer cancel()
   135  
   136  	version, err := r.oracle.CurrentVersion(opts)
   137  	if err != nil {
   138  		if err == bind.ErrNoCode {
   139  			log.Debug("Release oracle not found", "contract", r.config.Oracle)
   140  		} else {
   141  			log.Error("Failed to retrieve current release", "err", err)
   142  		}
   143  		return
   144  	}
   145  	// Version was successfully retrieved, notify if newer than ours
   146  	if version.Major > r.config.Major ||
   147  		(version.Major == r.config.Major && version.Minor > r.config.Minor) ||
   148  		(version.Major == r.config.Major && version.Minor == r.config.Minor && version.Patch > r.config.Patch) {
   149  
   150  		warning := fmt.Sprintf("Client v%d.%d.%d-%x seems older than the latest upstream release v%d.%d.%d-%x",
   151  			r.config.Major, r.config.Minor, r.config.Patch, r.config.Commit[:4], version.Major, version.Minor, version.Patch, version.Commit[:4])
   152  		howtofix := fmt.Sprintf("Please check https://github.com/Elemental-core/elementalcore/releases for new releases")
   153  		separator := strings.Repeat("-", len(warning))
   154  
   155  		log.Warn(separator)
   156  		log.Warn(warning)
   157  		log.Warn(howtofix)
   158  		log.Warn(separator)
   159  	} else {
   160  		log.Debug("Client seems up to date with upstream",
   161  			"local", fmt.Sprintf("v%d.%d.%d-%x", r.config.Major, r.config.Minor, r.config.Patch, r.config.Commit[:4]),
   162  			"upstream", fmt.Sprintf("v%d.%d.%d-%x", version.Major, version.Minor, version.Patch, version.Commit[:4]))
   163  	}
   164  }