Browse Source

all: on-chain oracle checkpoint syncing (#19543)

* all: implement simple checkpoint syncing

cmd, les, node: remove callback mechanism

cmd, node: remove callback definition

les: simplify the registrar

les: expose checkpoint rpc services in the light client

les, light: don't store untrusted receipt

cmd, contracts, les: discard stale checkpoint

cmd, contracts/registrar: loose restriction of registeration

cmd, contracts: add replay-protection

all: off-chain multi-signature contract

params: deploy checkpoint contract for rinkeby

cmd/registrar: add raw signing mode for registrar

cmd/registrar, contracts/registrar, les: fixed messages

* cmd/registrar, contracts/registrar: fix lints

* accounts/abi/bind, les: address comments

* cmd, contracts, les, light, params: minor checkpoint sync cleanups

* cmd, eth, les, light: move checkpoint config to config file

* cmd, eth, les, params: address comments

* eth, les, params: address comments

* cmd: polish up the checkpoint admin CLI

* cmd, contracts, params: deploy new version contract

* cmd/checkpoint-admin: add another flag for clef mode signing

* cmd, contracts, les: rename and regen checkpoint oracle with abigen
gary rong 6 năm trước cách đây
mục cha
commit
f7cdea2bdc
49 tập tin đã thay đổi với 2477 bổ sung381 xóa
  1. 18 6
      accounts/abi/bind/backends/simulated.go
  2. 2 1
      accounts/abi/bind/bind_test.go
  3. 12 0
      accounts/abi/bind/template.go
  4. 2 1
      accounts/abi/bind/util_test.go
  5. 166 0
      cmd/checkpoint-admin/common.go
  6. 335 0
      cmd/checkpoint-admin/exec.go
  7. 124 0
      cmd/checkpoint-admin/main.go
  8. 61 0
      cmd/checkpoint-admin/status.go
  9. 27 8
      cmd/geth/main.go
  10. 9 8
      cmd/puppeth/wizard_genesis.go
  11. 33 0
      contracts/checkpointoracle/contract/oracle.go
  12. 174 0
      contracts/checkpointoracle/contract/oracle.sol
  13. 91 0
      contracts/checkpointoracle/oracle.go
  14. 333 0
      contracts/checkpointoracle/oracle_test.go
  15. 4 3
      core/chain_indexer.go
  16. 20 1
      eth/backend.go
  17. 6 0
      eth/config.go
  18. 19 0
      eth/gen_config.go
  19. 2 2
      eth/handler.go
  20. 7 8
      eth/handler_test.go
  21. 1 1
      eth/helper_test.go
  22. 26 0
      internal/web3ext/web3ext.go
  23. 58 0
      les/api.go
  24. 36 30
      les/backend.go
  25. 158 0
      les/checkpointoracle.go
  26. 33 19
      les/commons.go
  27. 10 24
      les/handler.go
  28. 23 24
      les/handler_test.go
  29. 156 93
      les/helper_test.go
  30. 35 24
      les/odr_requests.go
  31. 4 4
      les/odr_test.go
  32. 22 5
      les/peer.go
  33. 1 1
      les/request_test.go
  34. 44 51
      les/server.go
  35. 145 12
      les/sync.go
  36. 133 0
      les/sync_test.go
  37. 0 0
      les/transactions.rlp
  38. 10 10
      les/txrelay.go
  39. 10 11
      les/ulc_test.go
  40. 15 15
      light/lightchain.go
  41. 3 3
      light/lightchain_test.go
  42. 15 7
      light/odr.go
  43. 1 1
      light/odr_test.go
  44. 34 0
      light/odr_util.go
  45. 1 1
      light/txpool_test.go
  46. 0 1
      node/node.go
  47. 1 1
      p2p/message.go
  48. 51 5
      params/config.go
  49. 6 0
      params/network_params.go

+ 18 - 6
accounts/abi/bind/backends/simulated.go

@@ -45,8 +45,10 @@ import (
 // This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
 var _ bind.ContractBackend = (*SimulatedBackend)(nil)
 
-var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block")
-var errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
+var (
+	errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
+	errGasEstimationFailed    = errors.New("gas required exceeds allowance or always failing transaction")
+)
 
 // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
 // the background. Its main purpose is to allow easily testing contract bindings.
@@ -63,10 +65,9 @@ type SimulatedBackend struct {
 	config *params.ChainConfig
 }
 
-// NewSimulatedBackend creates a new binding backend using a simulated blockchain
-// for testing purposes.
-func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
-	database := rawdb.NewMemoryDatabase()
+// NewSimulatedBackendWithDatabase creates a new binding backend based on the given database
+// and uses a simulated blockchain for testing purposes.
+func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
 	genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc}
 	genesis.MustCommit(database)
 	blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil)
@@ -81,6 +82,12 @@ func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBac
 	return backend
 }
 
+// NewSimulatedBackend creates a new binding backend using a simulated blockchain
+// for testing purposes.
+func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
+	return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit)
+}
+
 // Commit imports all the pending transactions as a single block and starts a
 // fresh new state.
 func (b *SimulatedBackend) Commit() {
@@ -424,6 +431,11 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
 	return nil
 }
 
+// Blockchain returns the underlying blockchain.
+func (b *SimulatedBackend) Blockchain() *core.BlockChain {
+	return b.blockchain
+}
+
 // callmsg implements core.Message to allow passing it as a transaction simulator.
 type callmsg struct {
 	ethereum.CallMsg

+ 2 - 1
accounts/abi/bind/bind_test.go

@@ -475,11 +475,12 @@ var bindTests = []struct {
 		`
 			"github.com/ethereum/go-ethereum/accounts/abi/bind"
 			"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
+			"github.com/ethereum/go-ethereum/core"
 			"github.com/ethereum/go-ethereum/common"
 		`,
 		`
 			// Create a simulator and wrap a non-deployed contract
-			sim := backends.NewSimulatedBackend(nil, uint64(10000000000))
+			sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000))
 
 			nonexistent, err := NewNonExistent(common.Address{}, sim)
 			if err != nil {

+ 12 - 0
accounts/abi/bind/template.go

@@ -439,6 +439,18 @@ var (
 				}
 			}), nil
 		}
+
+		// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.Id}}.
+		//
+		// Solidity: {{.Original.String}}
+		func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
+			event := new({{$contract.Type}}{{.Normalized.Name}})
+			if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
+				return nil, err
+			}
+			return event, nil
+		}
+
  	{{end}}
 {{end}}
 `

+ 2 - 1
accounts/abi/bind/util_test.go

@@ -56,7 +56,8 @@ func TestWaitDeployed(t *testing.T) {
 		backend := backends.NewSimulatedBackend(
 			core.GenesisAlloc{
 				crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
-			}, 10000000,
+			},
+			10000000,
 		)
 
 		// Create the transaction.

+ 166 - 0
cmd/checkpoint-admin/common.go

@@ -0,0 +1,166 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	"io/ioutil"
+	"strconv"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/accounts/keystore"
+	"github.com/ethereum/go-ethereum/cmd/utils"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/console"
+	"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
+	"github.com/ethereum/go-ethereum/ethclient"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rpc"
+	"gopkg.in/urfave/cli.v1"
+)
+
+// newClient creates a client with specified remote URL.
+func newClient(ctx *cli.Context) *ethclient.Client {
+	client, err := ethclient.Dial(ctx.GlobalString(nodeURLFlag.Name))
+	if err != nil {
+		utils.Fatalf("Failed to connect to Ethereum node: %v", err)
+	}
+	return client
+}
+
+// newRPCClient creates a rpc client with specified node URL.
+func newRPCClient(url string) *rpc.Client {
+	client, err := rpc.Dial(url)
+	if err != nil {
+		utils.Fatalf("Failed to connect to Ethereum node: %v", err)
+	}
+	return client
+}
+
+// getContractAddr retrieves the register contract address through
+// rpc request.
+func getContractAddr(client *rpc.Client) common.Address {
+	var addr string
+	if err := client.Call(&addr, "les_getCheckpointContractAddress"); err != nil {
+		utils.Fatalf("Failed to fetch checkpoint oracle address: %v", err)
+	}
+	return common.HexToAddress(addr)
+}
+
+// getCheckpoint retrieves the specified checkpoint or the latest one
+// through rpc request.
+func getCheckpoint(ctx *cli.Context, client *rpc.Client) *params.TrustedCheckpoint {
+	var checkpoint *params.TrustedCheckpoint
+
+	if ctx.GlobalIsSet(indexFlag.Name) {
+		var result [3]string
+		index := uint64(ctx.GlobalInt64(indexFlag.Name))
+		if err := client.Call(&result, "les_getCheckpoint", index); err != nil {
+			utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err)
+		}
+		checkpoint = &params.TrustedCheckpoint{
+			SectionIndex: index,
+			SectionHead:  common.HexToHash(result[0]),
+			CHTRoot:      common.HexToHash(result[1]),
+			BloomRoot:    common.HexToHash(result[2]),
+		}
+	} else {
+		var result [4]string
+		err := client.Call(&result, "les_latestCheckpoint")
+		if err != nil {
+			utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err)
+		}
+		index, err := strconv.ParseUint(result[0], 0, 64)
+		if err != nil {
+			utils.Fatalf("Failed to parse checkpoint index %v", err)
+		}
+		checkpoint = &params.TrustedCheckpoint{
+			SectionIndex: index,
+			SectionHead:  common.HexToHash(result[1]),
+			CHTRoot:      common.HexToHash(result[2]),
+			BloomRoot:    common.HexToHash(result[3]),
+		}
+	}
+	return checkpoint
+}
+
+// newContract creates a registrar contract instance with specified
+// contract address or the default contracts for mainnet or testnet.
+func newContract(client *rpc.Client) (common.Address, *checkpointoracle.CheckpointOracle) {
+	addr := getContractAddr(client)
+	if addr == (common.Address{}) {
+		utils.Fatalf("No specified registrar contract address")
+	}
+	contract, err := checkpointoracle.NewCheckpointOracle(addr, ethclient.NewClient(client))
+	if err != nil {
+		utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err)
+	}
+	return addr, contract
+}
+
+// promptPassphrase prompts the user for a passphrase.
+// Set confirmation to true to require the user to confirm the passphrase.
+func promptPassphrase(confirmation bool) string {
+	passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
+	if err != nil {
+		utils.Fatalf("Failed to read passphrase: %v", err)
+	}
+
+	if confirmation {
+		confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
+		if err != nil {
+			utils.Fatalf("Failed to read passphrase confirmation: %v", err)
+		}
+		if passphrase != confirm {
+			utils.Fatalf("Passphrases do not match")
+		}
+	}
+	return passphrase
+}
+
+// getPassphrase obtains a passphrase given by the user. It first checks the
+// --password command line flag and ultimately prompts the user for a
+// passphrase.
+func getPassphrase(ctx *cli.Context) string {
+	passphraseFile := ctx.String(utils.PasswordFileFlag.Name)
+	if passphraseFile != "" {
+		content, err := ioutil.ReadFile(passphraseFile)
+		if err != nil {
+			utils.Fatalf("Failed to read passphrase file '%s': %v",
+				passphraseFile, err)
+		}
+		return strings.TrimRight(string(content), "\r\n")
+	}
+	// Otherwise prompt the user for the passphrase.
+	return promptPassphrase(false)
+}
+
+// getKey retrieves the user key through specified key file.
+func getKey(ctx *cli.Context) *keystore.Key {
+	// Read key from file.
+	keyFile := ctx.GlobalString(keyFileFlag.Name)
+	keyJson, err := ioutil.ReadFile(keyFile)
+	if err != nil {
+		utils.Fatalf("Failed to read the keyfile at '%s': %v", keyFile, err)
+	}
+	// Decrypt key with passphrase.
+	passphrase := getPassphrase(ctx)
+	key, err := keystore.DecryptKey(keyJson, passphrase)
+	if err != nil {
+		utils.Fatalf("Failed to decrypt user key '%s': %v", keyFile, err)
+	}
+	return key
+}

+ 335 - 0
cmd/checkpoint-admin/exec.go

@@ -0,0 +1,335 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	"bytes"
+	"context"
+	"encoding/binary"
+	"fmt"
+	"math/big"
+	"strings"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/cmd/utils"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
+	"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethclient"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rpc"
+	"gopkg.in/urfave/cli.v1"
+)
+
+var commandDeploy = cli.Command{
+	Name:  "deploy",
+	Usage: "Deploy a new checkpoint oracle contract",
+	Flags: []cli.Flag{
+		nodeURLFlag,
+		clefURLFlag,
+		signersFlag,
+		thresholdFlag,
+		keyFileFlag,
+		utils.PasswordFileFlag,
+	},
+	Action: utils.MigrateFlags(deploy),
+}
+
+var commandSign = cli.Command{
+	Name:  "sign",
+	Usage: "Sign the checkpoint with the specified key",
+	Flags: []cli.Flag{
+		nodeURLFlag,
+		clefURLFlag,
+		indexFlag,
+		hashFlag,
+		oracleFlag,
+		keyFileFlag,
+		signerFlag,
+		utils.PasswordFileFlag,
+	},
+	Action: utils.MigrateFlags(sign),
+}
+
+var commandPublish = cli.Command{
+	Name:  "publish",
+	Usage: "Publish a checkpoint into the oracle",
+	Flags: []cli.Flag{
+		nodeURLFlag,
+		indexFlag,
+		signaturesFlag,
+		keyFileFlag,
+		utils.PasswordFileFlag,
+	},
+	Action: utils.MigrateFlags(publish),
+}
+
+// deploy deploys the checkpoint registrar contract.
+//
+// Note the network where the contract is deployed depends on
+// the network where the connected node is located.
+func deploy(ctx *cli.Context) error {
+	// Gather all the addresses that should be permitted to sign
+	var addrs []common.Address
+	for _, account := range strings.Split(ctx.String(signersFlag.Name), ",") {
+		if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
+			utils.Fatalf("Invalid account in --signers: '%s'", trimmed)
+		}
+		addrs = append(addrs, common.HexToAddress(account))
+	}
+	// Retrieve and validate the signing threshold
+	needed := ctx.Int(thresholdFlag.Name)
+	if needed == 0 || needed > len(addrs) {
+		utils.Fatalf("Invalid signature threshold %d", needed)
+	}
+	// Print a summary to ensure the user understands what they're signing
+	fmt.Printf("Deploying new checkpoint oracle:\n\n")
+	for i, addr := range addrs {
+		fmt.Printf("Admin %d => %s\n", i+1, addr.Hex())
+	}
+	fmt.Printf("\nSignatures needed to publish: %d\n", needed)
+
+	// Retrieve the private key, create an abigen transactor and an RPC client
+	transactor := bind.NewKeyedTransactor(getKey(ctx).PrivateKey)
+	client := newClient(ctx)
+
+	// Deploy the checkpoint oracle
+	oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)),
+		big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed)))
+	if err != nil {
+		utils.Fatalf("Failed to deploy checkpoint oracle %v", err)
+	}
+	log.Info("Deployed checkpoint oracle", "address", oracle, "tx", tx.Hash().Hex())
+
+	return nil
+}
+
+// sign creates the signature for specific checkpoint
+// with local key. Only contract admins have the permission to
+// sign checkpoint.
+func sign(ctx *cli.Context) error {
+	var (
+		offline bool // The indicator whether we sign checkpoint by offline.
+		chash   common.Hash
+		cindex  uint64
+		address common.Address
+
+		node   *rpc.Client
+		oracle *checkpointoracle.CheckpointOracle
+	)
+	if !ctx.GlobalIsSet(nodeURLFlag.Name) {
+		// Offline mode signing
+		offline = true
+		if !ctx.IsSet(hashFlag.Name) {
+			utils.Fatalf("Please specify the checkpoint hash (--hash) to sign in offline mode")
+		}
+		chash = common.HexToHash(ctx.String(hashFlag.Name))
+
+		if !ctx.IsSet(indexFlag.Name) {
+			utils.Fatalf("Please specify checkpoint index (--index) to sign in offline mode")
+		}
+		cindex = ctx.Uint64(indexFlag.Name)
+
+		if !ctx.IsSet(oracleFlag.Name) {
+			utils.Fatalf("Please specify oracle address (--oracle) to sign in offline mode")
+		}
+		address = common.HexToAddress(ctx.String(oracleFlag.Name))
+	} else {
+		// Interactive mode signing, retrieve the data from the remote node
+		node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
+
+		checkpoint := getCheckpoint(ctx, node)
+		chash = checkpoint.Hash()
+		cindex = checkpoint.SectionIndex
+		address = getContractAddr(node)
+
+		// Check the validity of checkpoint
+		reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
+		defer cancelFn()
+
+		head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil)
+		if err != nil {
+			return err
+		}
+		num := head.Number.Uint64()
+		if num < ((cindex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) {
+			utils.Fatalf("Invalid future checkpoint")
+		}
+		_, oracle = newContract(node)
+		latest, _, h, err := oracle.Contract().GetLatestCheckpoint(nil)
+		if err != nil {
+			return err
+		}
+		if cindex < latest {
+			utils.Fatalf("Checkpoint is too old")
+		}
+		if cindex == latest && (latest != 0 || h.Uint64() != 0) {
+			utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest, cindex)
+		}
+	}
+	var (
+		signature string
+		signer    string
+	)
+	// isAdmin checks whether the specified signer is admin.
+	isAdmin := func(addr common.Address) error {
+		signers, err := oracle.Contract().GetAllAdmin(nil)
+		if err != nil {
+			return err
+		}
+		for _, s := range signers {
+			if s == addr {
+				return nil
+			}
+		}
+		return fmt.Errorf("signer %v is not the admin", addr.Hex())
+	}
+	// Print to the user the data thy are about to sign
+	fmt.Printf("Oracle     => %s\n", address.Hex())
+	fmt.Printf("Index %4d => %s\n", cindex, chash.Hex())
+
+	switch {
+	case ctx.GlobalIsSet(clefURLFlag.Name):
+		// Sign checkpoint in clef mode.
+		signer = ctx.String(signerFlag.Name)
+
+		if !offline {
+			if err := isAdmin(common.HexToAddress(signer)); err != nil {
+				return err
+			}
+		}
+		clef := newRPCClient(ctx.GlobalString(clefURLFlag.Name))
+		p := make(map[string]string)
+		buf := make([]byte, 8)
+		binary.BigEndian.PutUint64(buf, cindex)
+		p["address"] = address.Hex()
+		p["message"] = hexutil.Encode(append(buf, chash.Bytes()...))
+		if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil {
+			utils.Fatalf("Failed to sign checkpoint, err %v", err)
+		}
+	case ctx.GlobalIsSet(keyFileFlag.Name):
+		// Sign checkpoint in raw private key file mode.
+		key := getKey(ctx)
+		signer = key.Address.Hex()
+
+		if !offline {
+			if err := isAdmin(key.Address); err != nil {
+				return err
+			}
+		}
+		sig, err := crypto.Sign(sighash(cindex, address, chash), key.PrivateKey)
+		if err != nil {
+			utils.Fatalf("Failed to sign checkpoint, err %v", err)
+		}
+		sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
+		signature = common.Bytes2Hex(sig)
+	default:
+		utils.Fatalf("Please specify clef URL or private key file path to sign checkpoint")
+	}
+	fmt.Printf("Signer     => %s\n", signer)
+	fmt.Printf("Signature  => %s\n", signature)
+	return nil
+}
+
+// sighash calculates the hash of the data to sign for the checkpoint oracle.
+func sighash(index uint64, oracle common.Address, hash common.Hash) []byte {
+	buf := make([]byte, 8)
+	binary.BigEndian.PutUint64(buf, index)
+
+	data := append([]byte{0x19, 0x00}, append(oracle[:], append(buf, hash[:]...)...)...)
+	return crypto.Keccak256(data)
+}
+
+// ecrecover calculates the sender address from a sighash and signature combo.
+func ecrecover(sighash []byte, sig []byte) common.Address {
+	sig[64] -= 27
+	defer func() { sig[64] += 27 }()
+
+	signer, err := crypto.SigToPub(sighash, sig)
+	if err != nil {
+		utils.Fatalf("Failed to recover sender from signature %x: %v", sig, err)
+	}
+	return crypto.PubkeyToAddress(*signer)
+}
+
+// publish registers the specified checkpoint which generated by connected node
+// with a authorised private key.
+func publish(ctx *cli.Context) error {
+	// Print the checkpoint oracle's current status to make sure we're interacting
+	// with the correct network and contract.
+	status(ctx)
+
+	// Gather the signatures from the CLI
+	var sigs [][]byte
+	for _, sig := range strings.Split(ctx.String(signaturesFlag.Name), ",") {
+		trimmed := strings.TrimPrefix(strings.TrimSpace(sig), "0x")
+		if len(trimmed) != 130 {
+			utils.Fatalf("Invalid signature in --signature: '%s'", trimmed)
+		} else {
+			sigs = append(sigs, common.Hex2Bytes(trimmed))
+		}
+	}
+	// Retrieve the checkpoint we want to sign to sort the signatures
+	var (
+		client       = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
+		addr, oracle = newContract(client)
+		checkpoint   = getCheckpoint(ctx, client)
+		sighash      = sighash(checkpoint.SectionIndex, addr, checkpoint.Hash())
+	)
+	for i := 0; i < len(sigs); i++ {
+		for j := i + 1; j < len(sigs); j++ {
+			signerA := ecrecover(sighash, sigs[i])
+			signerB := ecrecover(sighash, sigs[j])
+			if bytes.Compare(signerA.Bytes(), signerB.Bytes()) > 0 {
+				sigs[i], sigs[j] = sigs[j], sigs[i]
+			}
+		}
+	}
+	// Retrieve recent header info to protect replay attack
+	reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancelFn()
+
+	head, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, nil)
+	if err != nil {
+		return err
+	}
+	num := head.Number.Uint64()
+	recent, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, big.NewInt(int64(num-128)))
+	if err != nil {
+		return err
+	}
+	// Print a summary of the operation that's going to be performed
+	fmt.Printf("Publishing %d => %s:\n\n", checkpoint.SectionIndex, checkpoint.Hash().Hex())
+	for i, sig := range sigs {
+		fmt.Printf("Signer %d => %s\n", i+1, ecrecover(sighash, sig).Hex())
+	}
+	fmt.Println()
+	fmt.Printf("Sentry number => %d\nSentry hash   => %s\n", recent.Number, recent.Hash().Hex())
+
+	// Publish the checkpoint into the oracle
+	tx, err := oracle.RegisterCheckpoint(getKey(ctx).PrivateKey, checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs)
+	if err != nil {
+		utils.Fatalf("Register contract failed %v", err)
+	}
+	log.Info("Successfully registered checkpoint", "tx", tx.Hash().Hex())
+	return nil
+}

+ 124 - 0
cmd/checkpoint-admin/main.go

@@ -0,0 +1,124 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+// checkpoint-admin is a utility that can be used to query checkpoint information
+// and register stable checkpoints into an oracle contract.
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/ethereum/go-ethereum/cmd/utils"
+	"github.com/ethereum/go-ethereum/common/fdlimit"
+	"github.com/ethereum/go-ethereum/log"
+	"gopkg.in/urfave/cli.v1"
+)
+
+const (
+	commandHelperTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...]
+{{if .Description}}{{.Description}}
+{{end}}{{if .Subcommands}}
+SUBCOMMANDS:
+	{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
+	{{end}}{{end}}{{if .Flags}}
+OPTIONS:
+{{range $.Flags}}{{"\t"}}{{.}}
+{{end}}
+{{end}}`
+)
+
+var (
+	// Git SHA1 commit hash of the release (set via linker flags)
+	gitCommit = ""
+	gitDate   = ""
+)
+
+var app *cli.App
+
+func init() {
+	app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
+	app.Commands = []cli.Command{
+		commandStatus,
+		commandDeploy,
+		commandSign,
+		commandPublish,
+	}
+	app.Flags = []cli.Flag{
+		oracleFlag,
+		keyFileFlag,
+		nodeURLFlag,
+		clefURLFlag,
+		utils.PasswordFileFlag,
+	}
+	cli.CommandHelpTemplate = commandHelperTemplate
+}
+
+// Commonly used command line flags.
+var (
+	indexFlag = cli.Int64Flag{
+		Name:  "index",
+		Usage: "Checkpoint index (query latest from remote node if not specified)",
+	}
+	hashFlag = cli.StringFlag{
+		Name:  "hash",
+		Usage: "Checkpoint hash (query latest from remote node if not specified)",
+	}
+	oracleFlag = cli.StringFlag{
+		Name:  "oracle",
+		Usage: "Checkpoint oracle address (query from remote node if not specified)",
+	}
+	thresholdFlag = cli.Int64Flag{
+		Name:  "threshold",
+		Usage: "Minimal number of signatures required to approve a checkpoint",
+	}
+	keyFileFlag = cli.StringFlag{
+		Name:  "keyfile",
+		Usage: "The private key file (keyfile signature is not recommended)",
+	}
+	nodeURLFlag = cli.StringFlag{
+		Name:  "rpc",
+		Value: "http://localhost:8545",
+		Usage: "The rpc endpoint of a local or remote geth node",
+	}
+	clefURLFlag = cli.StringFlag{
+		Name:  "clef",
+		Value: "http://localhost:8550",
+		Usage: "The rpc endpoint of clef",
+	}
+	signerFlag = cli.StringFlag{
+		Name:  "signer",
+		Usage: "Signer address for clef mode signing",
+	}
+	signersFlag = cli.StringFlag{
+		Name:  "signers",
+		Usage: "Comma separated accounts of trusted checkpoint signers",
+	}
+	signaturesFlag = cli.StringFlag{
+		Name:  "signatures",
+		Usage: "Comma separated checkpoint signatures to submit",
+	}
+)
+
+func main() {
+	log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+	fdlimit.Raise(2048)
+
+	if err := app.Run(os.Args); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}

+ 61 - 0
cmd/checkpoint-admin/status.go

@@ -0,0 +1,61 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	"fmt"
+
+	"github.com/ethereum/go-ethereum/cmd/utils"
+	"github.com/ethereum/go-ethereum/common"
+	"gopkg.in/urfave/cli.v1"
+)
+
+var commandStatus = cli.Command{
+	Name:  "status",
+	Usage: "Fetches the signers and checkpoint status of the oracle contract",
+	Flags: []cli.Flag{
+		nodeURLFlag,
+	},
+	Action: utils.MigrateFlags(status),
+}
+
+// status fetches the admin list of specified registrar contract.
+func status(ctx *cli.Context) error {
+	// Create a wrapper around the checkpoint oracle contract
+	addr, oracle := newContract(newRPCClient(ctx.GlobalString(nodeURLFlag.Name)))
+	fmt.Printf("Oracle => %s\n", addr.Hex())
+	fmt.Println()
+
+	// Retrieve the list of authorized signers (admins)
+	admins, err := oracle.Contract().GetAllAdmin(nil)
+	if err != nil {
+		return err
+	}
+	for i, admin := range admins {
+		fmt.Printf("Admin %d => %s\n", i+1, admin.Hex())
+	}
+	fmt.Println()
+
+	// Retrieve the latest checkpoint
+	index, checkpoint, height, err := oracle.Contract().GetLatestCheckpoint(nil)
+	if err != nil {
+		return err
+	}
+	fmt.Printf("Checkpoint (published at #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex())
+
+	return nil
+}

+ 27 - 8
cmd/geth/main.go

@@ -37,6 +37,7 @@ import (
 	"github.com/ethereum/go-ethereum/eth/downloader"
 	"github.com/ethereum/go-ethereum/ethclient"
 	"github.com/ethereum/go-ethereum/internal/debug"
+	"github.com/ethereum/go-ethereum/les"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
 	"github.com/ethereum/go-ethereum/node"
@@ -323,14 +324,33 @@ func startNode(ctx *cli.Context, stack *node.Node) {
 	events := make(chan accounts.WalletEvent, 16)
 	stack.AccountManager().Subscribe(events)
 
-	go func() {
-		// Create a chain state reader for self-derivation
-		rpcClient, err := stack.Attach()
-		if err != nil {
-			utils.Fatalf("Failed to attach to self: %v", err)
+	// Create a client to interact with local geth node.
+	rpcClient, err := stack.Attach()
+	if err != nil {
+		utils.Fatalf("Failed to attach to self: %v", err)
+	}
+	ethClient := ethclient.NewClient(rpcClient)
+
+	// Set contract backend for ethereum service if local node
+	// is serving LES requests.
+	if ctx.GlobalInt(utils.LightServFlag.Name) > 0 {
+		var ethService *eth.Ethereum
+		if err := stack.Service(&ethService); err != nil {
+			utils.Fatalf("Failed to retrieve ethereum service: %v", err)
+		}
+		ethService.SetContractBackend(ethClient)
+	}
+	// Set contract backend for les service if local node is
+	// running as a light client.
+	if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
+		var lesService *les.LightEthereum
+		if err := stack.Service(&lesService); err != nil {
+			utils.Fatalf("Failed to retrieve light ethereum service: %v", err)
 		}
-		stateReader := ethclient.NewClient(rpcClient)
+		lesService.SetContractBackend(ethClient)
+	}
 
+	go func() {
 		// Open any wallets already attached
 		for _, wallet := range stack.AccountManager().Wallets() {
 			if err := wallet.Open(""); err != nil {
@@ -354,7 +374,7 @@ func startNode(ctx *cli.Context, stack *node.Node) {
 				}
 				derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)
 
-				event.Wallet.SelfDerive(derivationPaths, stateReader)
+				event.Wallet.SelfDerive(derivationPaths, ethClient)
 
 			case accounts.WalletDropped:
 				log.Info("Old wallet dropped", "url", event.Wallet.URL())
@@ -383,7 +403,6 @@ func startNode(ctx *cli.Context, stack *node.Node) {
 						"age", common.PrettyAge(timestamp))
 					stack.Stop()
 				}
-
 			}
 		}()
 	}

+ 9 - 8
cmd/puppeth/wizard_genesis.go

@@ -44,12 +44,13 @@ func (w *wizard) makeGenesis() {
 		Difficulty: big.NewInt(524288),
 		Alloc:      make(core.GenesisAlloc),
 		Config: &params.ChainConfig{
-			HomesteadBlock:      big.NewInt(1),
-			EIP150Block:         big.NewInt(2),
-			EIP155Block:         big.NewInt(3),
-			EIP158Block:         big.NewInt(3),
-			ByzantiumBlock:      big.NewInt(4),
-			ConstantinopleBlock: big.NewInt(5),
+			HomesteadBlock:      big.NewInt(0),
+			EIP150Block:         big.NewInt(0),
+			EIP155Block:         big.NewInt(0),
+			EIP158Block:         big.NewInt(0),
+			ByzantiumBlock:      big.NewInt(0),
+			ConstantinopleBlock: big.NewInt(0),
+			PetersburgBlock:     big.NewInt(0),
 		},
 	}
 	// Figure out which consensus engine to choose
@@ -191,7 +192,7 @@ func (w *wizard) importGenesis() {
 func (w *wizard) manageGenesis() {
 	// Figure out whether to modify or export the genesis
 	fmt.Println()
-	fmt.Println(" 1. Modify existing fork rules")
+	fmt.Println(" 1. Modify existing configurations")
 	fmt.Println(" 2. Export genesis configurations")
 	fmt.Println(" 3. Remove genesis configuration")
 
@@ -226,7 +227,7 @@ func (w *wizard) manageGenesis() {
 			w.conf.Genesis.Config.PetersburgBlock = w.conf.Genesis.Config.ConstantinopleBlock
 		}
 		fmt.Println()
-		fmt.Printf("Which block should Constantinople-Fix (remove EIP-1283) come into effect? (default = %v)\n", w.conf.Genesis.Config.PetersburgBlock)
+		fmt.Printf("Which block should Petersburg come into effect? (default = %v)\n", w.conf.Genesis.Config.PetersburgBlock)
 		w.conf.Genesis.Config.PetersburgBlock = w.readDefaultBigInt(w.conf.Genesis.Config.PetersburgBlock)
 
 		out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", "  ")

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 33 - 0
contracts/checkpointoracle/contract/oracle.go


+ 174 - 0
contracts/checkpointoracle/contract/oracle.sol

@@ -0,0 +1,174 @@
+pragma solidity ^0.5.10;
+
+/**
+ * @title CheckpointOracle
+ * @author Gary Rong<garyrong@ethereum.org>, Martin Swende <martin.swende@ethereum.org>
+ * @dev Implementation of the blockchain checkpoint registrar.
+ */
+contract CheckpointOracle {
+    /*
+        Events
+    */
+
+    // NewCheckpointVote is emitted when a new checkpoint proposal receives a vote.
+    event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s);
+
+    /*
+        Public Functions
+    */
+    constructor(address[] memory _adminlist, uint _sectionSize, uint _processConfirms, uint _threshold) public {
+        for (uint i = 0; i < _adminlist.length; i++) {
+            admins[_adminlist[i]] = true;
+            adminList.push(_adminlist[i]);
+        }
+        sectionSize = _sectionSize;
+        processConfirms = _processConfirms;
+        threshold = _threshold;
+    }
+
+    /**
+     * @dev Get latest stable checkpoint information.
+     * @return section index
+     * @return checkpoint hash
+     * @return block height associated with checkpoint
+     */
+    function GetLatestCheckpoint()
+    view
+    public
+    returns(uint64, bytes32, uint) {
+        return (sectionIndex, hash, height);
+    }
+
+    // SetCheckpoint sets  a new checkpoint. It accepts a list of signatures
+    // @_recentNumber: a recent blocknumber, for replay protection
+    // @_recentHash : the hash of `_recentNumber`
+    // @_hash : the hash to set at _sectionIndex
+    // @_sectionIndex : the section index to set
+    // @v : the list of v-values
+    // @r : the list or r-values
+    // @s : the list of s-values
+    function SetCheckpoint(
+        uint _recentNumber,
+        bytes32 _recentHash,
+        bytes32 _hash,
+        uint64 _sectionIndex,
+        uint8[] memory v,
+        bytes32[] memory r,
+        bytes32[] memory s)
+        public
+        returns (bool)
+    {
+        // Ensure the sender is authorized.
+        require(admins[msg.sender]);
+
+        // These checks replay protection, so it cannot be replayed on forks,
+        // accidentally or intentionally
+        require(blockhash(_recentNumber) == _recentHash);
+
+        // Ensure the batch of signatures are valid.
+        require(v.length == r.length);
+        require(v.length == s.length);
+
+        // Filter out "future" checkpoint.
+        if (block.number < (_sectionIndex+1)*sectionSize+processConfirms) {
+            return false;
+        }
+        // Filter out "old" announcement
+        if (_sectionIndex < sectionIndex) {
+            return false;
+        }
+        // Filter out "stale" announcement
+        if (_sectionIndex == sectionIndex && (_sectionIndex != 0 || height != 0)) {
+            return false;
+        }
+        // Filter out "invalid" announcement
+        if (_hash == ""){
+            return false;
+        }
+
+        // EIP 191 style signatures
+        //
+        // Arguments when calculating hash to validate
+        // 1: byte(0x19) - the initial 0x19 byte
+        // 2: byte(0) - the version byte (data with intended validator)
+        // 3: this - the validator address
+        // --  Application specific data
+        // 4 : checkpoint section_index(uint64)
+        // 5 : checkpoint hash (bytes32)
+        //     hash = keccak256(checkpoint_index, section_head, cht_root, bloom_root)
+        bytes32 signedHash = keccak256(abi.encodePacked(byte(0x19), byte(0), this, _sectionIndex, _hash));
+
+        address lastVoter = address(0);
+
+        // In order for us not to have to maintain a mapping of who has already
+        // voted, and we don't want to count a vote twice, the signatures must
+        // be submitted in strict ordering.
+        for (uint idx = 0; idx < v.length; idx++){
+            address signer = ecrecover(signedHash, v[idx], r[idx], s[idx]);
+            require(admins[signer]);
+            require(uint256(signer) > uint256(lastVoter));
+            lastVoter = signer;
+            emit NewCheckpointVote(_sectionIndex, _hash, v[idx], r[idx], s[idx]);
+
+            // Sufficient signatures present, update latest checkpoint.
+            if (idx+1 >= threshold){
+                hash = _hash;
+                height = block.number;
+                sectionIndex = _sectionIndex;
+                return true;
+            }
+        }
+        // We shouldn't wind up here, reverting un-emits the events
+        revert();
+    }
+
+    /**
+     * @dev Get all admin addresses
+     * @return address list
+     */
+    function GetAllAdmin()
+    public
+    view
+    returns(address[] memory)
+    {
+        address[] memory ret = new address[](adminList.length);
+        for (uint i = 0; i < adminList.length; i++) {
+            ret[i] = adminList[i];
+        }
+        return ret;
+    }
+
+    /*
+        Fields
+    */
+    // A map of admin users who have the permission to update CHT and bloom Trie root
+    mapping(address => bool) admins;
+
+    // A list of admin users so that we can obtain all admin users.
+    address[] adminList;
+
+    // Latest stored section id
+    uint64 sectionIndex;
+
+    // The block height associated with latest registered checkpoint.
+    uint height;
+
+    // The hash of latest registered checkpoint.
+    bytes32 hash;
+
+    // The frequency for creating a checkpoint
+    //
+    // The default value should be the same as the checkpoint size(32768) in the ethereum.
+    uint sectionSize;
+
+    // The number of confirmations needed before a checkpoint can be registered.
+    // We have to make sure the checkpoint registered will not be invalid due to
+    // chain reorg.
+    //
+    // The default value should be the same as the checkpoint process confirmations(256)
+    // in the ethereum.
+    uint processConfirms;
+
+    // The required signatures to finalize a stable checkpoint.
+    uint threshold;
+}

+ 91 - 0
contracts/checkpointoracle/oracle.go

@@ -0,0 +1,91 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Package checkpointoracle is a an on-chain light client checkpoint oracle.
+package checkpointoracle
+
+//go:generate abigen --sol contract/oracle.sol --pkg contract --out contract/oracle.go
+
+import (
+	"crypto/ecdsa"
+	"errors"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract"
+	"github.com/ethereum/go-ethereum/core/types"
+)
+
+// CheckpointOracle is a Go wrapper around an on-chain light client checkpoint oracle.
+type CheckpointOracle struct {
+	contract *contract.CheckpointOracle
+}
+
+// NewCheckpointOracle binds checkpoint contract and returns a registrar instance.
+func NewCheckpointOracle(contractAddr common.Address, backend bind.ContractBackend) (*CheckpointOracle, error) {
+	c, err := contract.NewCheckpointOracle(contractAddr, backend)
+	if err != nil {
+		return nil, err
+	}
+	return &CheckpointOracle{contract: c}, nil
+}
+
+// Contract returns the underlying contract instance.
+func (oracle *CheckpointOracle) Contract() *contract.CheckpointOracle {
+	return oracle.contract
+}
+
+// LookupCheckpointEvents searches checkpoint event for specific section in the
+// given log batches.
+func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log, section uint64, hash common.Hash) []*contract.CheckpointOracleNewCheckpointVote {
+	var votes []*contract.CheckpointOracleNewCheckpointVote
+
+	for _, logs := range blockLogs {
+		for _, log := range logs {
+			event, err := oracle.contract.ParseNewCheckpointVote(*log)
+			if err != nil {
+				continue
+			}
+			if event.Index == section && common.Hash(event.CheckpointHash) == hash {
+				votes = append(votes, event)
+			}
+		}
+	}
+	return votes
+}
+
+// RegisterCheckpoint registers the checkpoint with a batch of associated signatures
+// that are collected off-chain and sorted by lexicographical order.
+//
+// Notably all signatures given should be transformed to "ethereum style" which transforms
+// v from 0/1 to 27/28 according to the yellow paper.
+func (oracle *CheckpointOracle) RegisterCheckpoint(key *ecdsa.PrivateKey, index uint64, hash []byte, rnum *big.Int, rhash [32]byte, sigs [][]byte) (*types.Transaction, error) {
+	var (
+		r [][32]byte
+		s [][32]byte
+		v []uint8
+	)
+	for i := 0; i < len(sigs); i++ {
+		if len(sigs[i]) != 65 {
+			return nil, errors.New("invalid signature")
+		}
+		r = append(r, common.BytesToHash(sigs[i][:32]))
+		s = append(s, common.BytesToHash(sigs[i][32:64]))
+		v = append(v, sigs[i][64])
+	}
+	return oracle.contract.SetCheckpoint(bind.NewKeyedTransactor(key), rnum, rhash, common.BytesToHash(hash), index, v, r, s)
+}

+ 333 - 0
contracts/checkpointoracle/oracle_test.go

@@ -0,0 +1,333 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package checkpointoracle
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"encoding/binary"
+	"errors"
+	"math/big"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+var (
+	emptyHash = [32]byte{}
+
+	checkpoint0 = params.TrustedCheckpoint{
+		SectionIndex: 0,
+		SectionHead:  common.HexToHash("0x7fa3c32f996c2bfb41a1a65b3d8ea3e0a33a1674cde43678ad6f4235e764d17d"),
+		CHTRoot:      common.HexToHash("0x98fc5d3de23a0fecebad236f6655533c157d26a1aedcd0852a514dc1169e6350"),
+		BloomRoot:    common.HexToHash("0x99b5adb52b337fe25e74c1c6d3835b896bd638611b3aebddb2317cce27a3f9fa"),
+	}
+	checkpoint1 = params.TrustedCheckpoint{
+		SectionIndex: 1,
+		SectionHead:  common.HexToHash("0x2d4dee68102125e59b0cc61b176bd89f0d12b3b91cfaf52ef8c2c82fb920c2d2"),
+		CHTRoot:      common.HexToHash("0x7d428008ece3b4c4ef5439f071930aad0bb75108d381308df73beadcd01ded95"),
+		BloomRoot:    common.HexToHash("0x652571f7736de17e7bbb427ac881474da684c6988a88bf51b10cca9a2ee148f4"),
+	}
+	checkpoint2 = params.TrustedCheckpoint{
+		SectionIndex: 2,
+		SectionHead:  common.HexToHash("0x61c0de578c0115b1dff8ef39aa600588c7c6ecb8a2f102003d7cf4c4146e9291"),
+		CHTRoot:      common.HexToHash("0x407a08a407a2bc3838b74ca3eb206903c9c8a186ccf5ef14af07794efff1970b"),
+		BloomRoot:    common.HexToHash("0x058b4161f558ce295a92925efc57f34f9210d5a30088d7475c183e0d3e58f5ac"),
+	}
+)
+
+var (
+	// The block frequency for creating checkpoint(only used in test)
+	sectionSize = big.NewInt(512)
+
+	// The number of confirmations needed to generate a checkpoint(only used in test).
+	processConfirms = big.NewInt(4)
+)
+
+// validateOperation executes the operation, watches and delivers all events fired by the backend and ensures the
+// correctness by assert function.
+func validateOperation(t *testing.T, c *contract.CheckpointOracle, backend *backends.SimulatedBackend, operation func(),
+	assert func(<-chan *contract.CheckpointOracleNewCheckpointVote) error, opName string) {
+	// Watch all events and deliver them to assert function
+	var (
+		sink   = make(chan *contract.CheckpointOracleNewCheckpointVote)
+		sub, _ = c.WatchNewCheckpointVote(nil, sink, nil)
+	)
+	defer func() {
+		// Close all subscribers
+		sub.Unsubscribe()
+	}()
+	operation()
+
+	// flush pending block
+	backend.Commit()
+	if err := assert(sink); err != nil {
+		t.Errorf("operation {%s} failed, err %s", opName, err)
+	}
+}
+
+// validateEvents checks that the correct number of contract events
+// fired by contract backend.
+func validateEvents(target int, sink interface{}) (bool, []reflect.Value) {
+	chanval := reflect.ValueOf(sink)
+	chantyp := chanval.Type()
+	if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.RecvDir == 0 {
+		return false, nil
+	}
+	count := 0
+	var recv []reflect.Value
+	timeout := time.After(1 * time.Second)
+	cases := []reflect.SelectCase{{Chan: chanval, Dir: reflect.SelectRecv}, {Chan: reflect.ValueOf(timeout), Dir: reflect.SelectRecv}}
+	for {
+		chose, v, _ := reflect.Select(cases)
+		if chose == 1 {
+			// Not enough event received
+			return false, nil
+		}
+		count += 1
+		recv = append(recv, v)
+		if count == target {
+			break
+		}
+	}
+	done := time.After(50 * time.Millisecond)
+	cases = cases[:1]
+	cases = append(cases, reflect.SelectCase{Chan: reflect.ValueOf(done), Dir: reflect.SelectRecv})
+	chose, _, _ := reflect.Select(cases)
+	// If chose equal 0, it means receiving redundant events.
+	return chose == 1, recv
+}
+
+func signCheckpoint(addr common.Address, privateKey *ecdsa.PrivateKey, index uint64, hash common.Hash) []byte {
+	// EIP 191 style signatures
+	//
+	// Arguments when calculating hash to validate
+	// 1: byte(0x19) - the initial 0x19 byte
+	// 2: byte(0) - the version byte (data with intended validator)
+	// 3: this - the validator address
+	// --  Application specific data
+	// 4 : checkpoint section_index(uint64)
+	// 5 : checkpoint hash (bytes32)
+	//     hash = keccak256(checkpoint_index, section_head, cht_root, bloom_root)
+	buf := make([]byte, 8)
+	binary.BigEndian.PutUint64(buf, index)
+	data := append([]byte{0x19, 0x00}, append(addr.Bytes(), append(buf, hash.Bytes()...)...)...)
+	sig, _ := crypto.Sign(crypto.Keccak256(data), privateKey)
+	sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
+	return sig
+}
+
+// assertSignature verifies whether the recovered signers are equal with expected.
+func assertSignature(addr common.Address, index uint64, hash [32]byte, r, s [32]byte, v uint8, expect common.Address) bool {
+	buf := make([]byte, 8)
+	binary.BigEndian.PutUint64(buf, index)
+	data := append([]byte{0x19, 0x00}, append(addr.Bytes(), append(buf, hash[:]...)...)...)
+	pubkey, err := crypto.Ecrecover(crypto.Keccak256(data), append(r[:], append(s[:], v-27)...))
+	if err != nil {
+		return false
+	}
+	var signer common.Address
+	copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
+	return bytes.Equal(signer.Bytes(), expect.Bytes())
+}
+
+type Account struct {
+	key  *ecdsa.PrivateKey
+	addr common.Address
+}
+type Accounts []Account
+
+func (a Accounts) Len() int           { return len(a) }
+func (a Accounts) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 }
+
+func TestCheckpointRegister(t *testing.T) {
+	// Initialize test accounts
+	var accounts Accounts
+	for i := 0; i < 3; i++ {
+		key, _ := crypto.GenerateKey()
+		addr := crypto.PubkeyToAddress(key.PublicKey)
+		accounts = append(accounts, Account{key: key, addr: addr})
+	}
+	sort.Sort(accounts)
+
+	// Deploy registrar contract
+	transactOpts := bind.NewKeyedTransactor(accounts[0].key)
+	contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{accounts[0].addr: {Balance: big.NewInt(1000000000)}, accounts[1].addr: {Balance: big.NewInt(1000000000)}, accounts[2].addr: {Balance: big.NewInt(1000000000)}}, 10000000)
+	// 3 trusted signers, threshold 2
+	contractAddr, _, c, err := contract.DeployCheckpointOracle(transactOpts, contractBackend, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr}, sectionSize, processConfirms, big.NewInt(2))
+	if err != nil {
+		t.Error("Failed to deploy registrar contract", err)
+	}
+	contractBackend.Commit()
+
+	// getRecent returns block height and hash of the head parent.
+	getRecent := func() (*big.Int, common.Hash) {
+		parentNumber := new(big.Int).Sub(contractBackend.Blockchain().CurrentHeader().Number, big.NewInt(1))
+		parentHash := contractBackend.Blockchain().CurrentHeader().ParentHash
+		return parentNumber, parentHash
+	}
+	// collectSig generates specified number signatures.
+	collectSig := func(index uint64, hash common.Hash, n int, unauthorized *ecdsa.PrivateKey) (v []uint8, r [][32]byte, s [][32]byte) {
+		for i := 0; i < n; i++ {
+			sig := signCheckpoint(contractAddr, accounts[i].key, index, hash)
+			if unauthorized != nil {
+				sig = signCheckpoint(contractAddr, unauthorized, index, hash)
+			}
+			r = append(r, common.BytesToHash(sig[:32]))
+			s = append(s, common.BytesToHash(sig[32:64]))
+			v = append(v, sig[64])
+		}
+		return v, r, s
+	}
+	// insertEmptyBlocks inserts a batch of empty blocks to blockchain.
+	insertEmptyBlocks := func(number int) {
+		for i := 0; i < number; i++ {
+			contractBackend.Commit()
+		}
+	}
+	// assert checks whether the current contract status is same with
+	// the expected.
+	assert := func(index uint64, hash [32]byte, height *big.Int) error {
+		lindex, lhash, lheight, err := c.GetLatestCheckpoint(nil)
+		if err != nil {
+			return err
+		}
+		if lindex != index {
+			return errors.New("latest checkpoint index mismatch")
+		}
+		if !bytes.Equal(lhash[:], hash[:]) {
+			return errors.New("latest checkpoint hash mismatch")
+		}
+		if lheight.Cmp(height) != 0 {
+			return errors.New("latest checkpoint height mismatch")
+		}
+		return nil
+	}
+
+	// Test future checkpoint registration
+	validateOperation(t, c, contractBackend, func() {
+		number, hash := getRecent()
+		v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil)
+		c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s)
+	}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error {
+		return assert(0, emptyHash, big.NewInt(0))
+	}, "test future checkpoint registration")
+
+	insertEmptyBlocks(int(sectionSize.Uint64() + processConfirms.Uint64()))
+
+	// Test transaction replay protection
+	validateOperation(t, c, contractBackend, func() {
+		number, hash := getRecent()
+		v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil)
+		hash = common.HexToHash("deadbeef")
+		c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s)
+	}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error {
+		return assert(0, emptyHash, big.NewInt(0))
+	}, "test transaction replay protection")
+
+	// Test unauthorized signature checking
+	validateOperation(t, c, contractBackend, func() {
+		number, hash := getRecent()
+		u, _ := crypto.GenerateKey()
+		v, r, s := collectSig(0, checkpoint0.Hash(), 2, u)
+		c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s)
+	}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error {
+		return assert(0, emptyHash, big.NewInt(0))
+	}, "test unauthorized signature checking")
+
+	// Test un-multi-signature checkpoint registration
+	validateOperation(t, c, contractBackend, func() {
+		number, hash := getRecent()
+		v, r, s := collectSig(0, checkpoint0.Hash(), 1, nil)
+		c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s)
+	}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error {
+		return assert(0, emptyHash, big.NewInt(0))
+	}, "test un-multi-signature checkpoint registration")
+
+	// Test valid checkpoint registration
+	validateOperation(t, c, contractBackend, func() {
+		number, hash := getRecent()
+		v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil)
+		c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s)
+	}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error {
+		if valid, recv := validateEvents(2, events); !valid {
+			return errors.New("receive incorrect number of events")
+		} else {
+			for i := 0; i < len(recv); i++ {
+				event := recv[i].Interface().(*contract.CheckpointOracleNewCheckpointVote)
+				if !assertSignature(contractAddr, event.Index, event.CheckpointHash, event.R, event.S, event.V, accounts[i].addr) {
+					return errors.New("recover signer failed")
+				}
+			}
+		}
+		number, _ := getRecent()
+		return assert(0, checkpoint0.Hash(), number.Add(number, big.NewInt(1)))
+	}, "test valid checkpoint registration")
+
+	distance := 3*sectionSize.Uint64() + processConfirms.Uint64() - contractBackend.Blockchain().CurrentHeader().Number.Uint64()
+	insertEmptyBlocks(int(distance))
+
+	// Test uncontinuous checkpoint registration
+	validateOperation(t, c, contractBackend, func() {
+		number, hash := getRecent()
+		v, r, s := collectSig(2, checkpoint2.Hash(), 2, nil)
+		c.SetCheckpoint(transactOpts, number, hash, checkpoint2.Hash(), 2, v, r, s)
+	}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error {
+		if valid, recv := validateEvents(2, events); !valid {
+			return errors.New("receive incorrect number of events")
+		} else {
+			for i := 0; i < len(recv); i++ {
+				event := recv[i].Interface().(*contract.CheckpointOracleNewCheckpointVote)
+				if !assertSignature(contractAddr, event.Index, event.CheckpointHash, event.R, event.S, event.V, accounts[i].addr) {
+					return errors.New("recover signer failed")
+				}
+			}
+		}
+		number, _ := getRecent()
+		return assert(2, checkpoint2.Hash(), number.Add(number, big.NewInt(1)))
+	}, "test uncontinuous checkpoint registration")
+
+	// Test old checkpoint registration
+	validateOperation(t, c, contractBackend, func() {
+		number, hash := getRecent()
+		v, r, s := collectSig(1, checkpoint1.Hash(), 2, nil)
+		c.SetCheckpoint(transactOpts, number, hash, checkpoint1.Hash(), 1, v, r, s)
+	}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error {
+		number, _ := getRecent()
+		return assert(2, checkpoint2.Hash(), number)
+	}, "test uncontinuous checkpoint registration")
+
+	// Test stale checkpoint registration
+	validateOperation(t, c, contractBackend, func() {
+		number, hash := getRecent()
+		v, r, s := collectSig(2, checkpoint2.Hash(), 2, nil)
+		c.SetCheckpoint(transactOpts, number, hash, checkpoint2.Hash(), 2, v, r, s)
+	}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error {
+		number, _ := getRecent()
+		return assert(2, checkpoint2.Hash(), number.Sub(number, big.NewInt(1)))
+	}, "test stale checkpoint registration")
+}

+ 4 - 3
core/chain_indexer.go

@@ -128,12 +128,13 @@ func (c *ChainIndexer) AddCheckpoint(section uint64, shead common.Hash) {
 	c.lock.Lock()
 	defer c.lock.Unlock()
 
+	// Short circuit if the given checkpoint is below than local's.
+	if c.checkpointSections >= section+1 || section < c.storedSections {
+		return
+	}
 	c.checkpointSections = section + 1
 	c.checkpointHead = shead
 
-	if section < c.storedSections {
-		return
-	}
 	c.setSectionHead(section, shead)
 	c.setValidSections(section + 1)
 }

+ 20 - 1
eth/backend.go

@@ -26,6 +26,7 @@ import (
 	"sync/atomic"
 
 	"github.com/ethereum/go-ethereum/accounts"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/consensus"
@@ -57,6 +58,7 @@ type LesServer interface {
 	APIs() []rpc.API
 	Protocols() []p2p.Protocol
 	SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
+	SetContractBackend(bind.ContractBackend)
 }
 
 // Ethereum implements the Ethereum full node service.
@@ -99,6 +101,14 @@ func (s *Ethereum) AddLesServer(ls LesServer) {
 	ls.SetBloomBitsIndexer(s.bloomIndexer)
 }
 
+// SetClient sets a rpc client which connecting to our local node.
+func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) {
+	// Pass the rpc client to les server if it is enabled.
+	if s.lesServer != nil {
+		s.lesServer.SetContractBackend(backend)
+	}
+}
+
 // New creates a new Ethereum object (including the
 // initialisation of the common Ethereum object)
 func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
@@ -192,7 +202,11 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
 
 	// Permit the downloader to use the trie cache allowance during fast sync
 	cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit
-	if eth.protocolManager, err = NewProtocolManager(chainConfig, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil {
+	checkpoint := config.Checkpoint
+	if checkpoint == nil {
+		checkpoint = params.TrustedCheckpoints[genesisHash]
+	}
+	if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil {
 		return nil, err
 	}
 	eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
@@ -268,6 +282,11 @@ func (s *Ethereum) APIs() []rpc.API {
 	// Append any APIs exposed explicitly by the consensus engine
 	apis = append(apis, s.engine.APIs(s.BlockChain())...)
 
+	// Append any APIs exposed explicitly by the les server
+	if s.lesServer != nil {
+		apis = append(apis, s.lesServer.APIs()...)
+	}
+
 	// Append all the local APIs and return
 	return append(apis, []rpc.API{
 		{

+ 6 - 0
eth/config.go

@@ -149,4 +149,10 @@ type Config struct {
 
 	// RPCGasCap is the global gas cap for eth-call variants.
 	RPCGasCap *big.Int `toml:",omitempty"`
+
+	// Checkpoint is a hardcoded checkpoint which can be nil.
+	Checkpoint *params.TrustedCheckpoint
+
+	// CheckpointOracle is the configuration for checkpoint oracle.
+	CheckpointOracle *params.CheckpointOracleConfig
 }

+ 19 - 0
eth/gen_config.go

@@ -12,6 +12,7 @@ import (
 	"github.com/ethereum/go-ethereum/eth/downloader"
 	"github.com/ethereum/go-ethereum/eth/gasprice"
 	"github.com/ethereum/go-ethereum/miner"
+	"github.com/ethereum/go-ethereum/params"
 )
 
 // MarshalTOML marshals as TOML.
@@ -32,6 +33,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
 		SkipBcVersionCheck      bool       `toml:"-"`
 		DatabaseHandles         int        `toml:"-"`
 		DatabaseCache           int
+		DatabaseFreezer         string
 		TrieCleanCache          int
 		TrieDirtyCache          int
 		TrieTimeout             time.Duration
@@ -45,6 +47,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
 		EVMInterpreter          string
 		ConstantinopleOverride  *big.Int
 		RPCGasCap               *big.Int `toml:",omitempty"`
+		Checkpoint              *params.TrustedCheckpoint
+		CheckpointOracle        *params.CheckpointOracleConfig
 	}
 	var enc Config
 	enc.Genesis = c.Genesis
@@ -62,6 +66,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
 	enc.SkipBcVersionCheck = c.SkipBcVersionCheck
 	enc.DatabaseHandles = c.DatabaseHandles
 	enc.DatabaseCache = c.DatabaseCache
+	enc.DatabaseFreezer = c.DatabaseFreezer
 	enc.TrieCleanCache = c.TrieCleanCache
 	enc.TrieDirtyCache = c.TrieDirtyCache
 	enc.TrieTimeout = c.TrieTimeout
@@ -75,6 +80,8 @@ func (c Config) MarshalTOML() (interface{}, error) {
 	enc.EVMInterpreter = c.EVMInterpreter
 	enc.ConstantinopleOverride = c.ConstantinopleOverride
 	enc.RPCGasCap = c.RPCGasCap
+	enc.Checkpoint = c.Checkpoint
+	enc.CheckpointOracle = c.CheckpointOracle
 	return &enc, nil
 }
 
@@ -96,6 +103,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 		SkipBcVersionCheck      *bool      `toml:"-"`
 		DatabaseHandles         *int       `toml:"-"`
 		DatabaseCache           *int
+		DatabaseFreezer         *string
 		TrieCleanCache          *int
 		TrieDirtyCache          *int
 		TrieTimeout             *time.Duration
@@ -109,6 +117,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 		EVMInterpreter          *string
 		ConstantinopleOverride  *big.Int
 		RPCGasCap               *big.Int `toml:",omitempty"`
+		Checkpoint              *params.TrustedCheckpoint
+		CheckpointOracle        *params.CheckpointOracleConfig
 	}
 	var dec Config
 	if err := unmarshal(&dec); err != nil {
@@ -159,6 +169,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 	if dec.DatabaseCache != nil {
 		c.DatabaseCache = *dec.DatabaseCache
 	}
+	if dec.DatabaseFreezer != nil {
+		c.DatabaseFreezer = *dec.DatabaseFreezer
+	}
 	if dec.TrieCleanCache != nil {
 		c.TrieCleanCache = *dec.TrieCleanCache
 	}
@@ -198,5 +211,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 	if dec.RPCGasCap != nil {
 		c.RPCGasCap = dec.RPCGasCap
 	}
+	if dec.Checkpoint != nil {
+		c.Checkpoint = dec.Checkpoint
+	}
+	if dec.CheckpointOracle != nil {
+		c.CheckpointOracle = dec.CheckpointOracle
+	}
 	return nil
 }

+ 2 - 2
eth/handler.go

@@ -106,7 +106,7 @@ type ProtocolManager struct {
 
 // NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
 // with the Ethereum network.
-func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash) (*ProtocolManager, error) {
+func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash) (*ProtocolManager, error) {
 	// Create the protocol manager with the base fields
 	manager := &ProtocolManager{
 		networkID:   networkID,
@@ -145,7 +145,7 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
 		}
 	}
 	// If we have trusted checkpoints, enforce them on the chain
-	if checkpoint, ok := params.TrustedCheckpoints[blockchain.Genesis().Hash()]; ok {
+	if checkpoint != nil {
 		manager.checkpointNumber = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1
 		manager.checkpointHash = checkpoint.SectionHead
 	}

+ 7 - 8
eth/handler_test.go

@@ -499,31 +499,30 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
 
 	// Initialize a chain and generate a fake CHT if checkpointing is enabled
 	var (
-		db      = rawdb.NewMemoryDatabase()
-		config  = new(params.ChainConfig)
-		genesis = (&core.Genesis{Config: config}).MustCommit(db)
+		db     = rawdb.NewMemoryDatabase()
+		config = new(params.ChainConfig)
 	)
+	(&core.Genesis{Config: config}).MustCommit(db) // Commit genesis block
 	// If checkpointing is enabled, create and inject a fake CHT and the corresponding
 	// chllenge response.
 	var response *types.Header
+	var cht *params.TrustedCheckpoint
 	if checkpoint {
 		index := uint64(rand.Intn(500))
 		number := (index+1)*params.CHTFrequency - 1
 		response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
 
-		cht := &params.TrustedCheckpoint{
+		cht = &params.TrustedCheckpoint{
 			SectionIndex: index,
 			SectionHead:  response.Hash(),
 		}
-		params.TrustedCheckpoints[genesis.Hash()] = cht
-		defer delete(params.TrustedCheckpoints, genesis.Hash())
 	}
 	// Create a checkpoint aware protocol manager
 	blockchain, err := core.NewBlockChain(db, nil, config, ethash.NewFaker(), vm.Config{}, nil)
 	if err != nil {
 		t.Fatalf("failed to create new blockchain: %v", err)
 	}
-	pm, err := NewProtocolManager(config, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), ethash.NewFaker(), blockchain, db, 1, nil)
+	pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), ethash.NewFaker(), blockchain, db, 1, nil)
 	if err != nil {
 		t.Fatalf("failed to start test protocol manager: %v", err)
 	}
@@ -610,7 +609,7 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
 	if err != nil {
 		t.Fatalf("failed to create new blockchain: %v", err)
 	}
-	pm, err := NewProtocolManager(config, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db, 1, nil)
+	pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, new(testTxPool), pow, blockchain, db, 1, nil)
 	if err != nil {
 		t.Fatalf("failed to start test protocol manager: %v", err)
 	}

+ 1 - 1
eth/helper_test.go

@@ -66,7 +66,7 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func
 	if _, err := blockchain.InsertChain(chain); err != nil {
 		panic(err)
 	}
-	pm, err := NewProtocolManager(gspec.Config, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db, 1, nil)
+	pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx}, engine, blockchain, db, 1, nil)
 	if err != nil {
 		return nil, nil, err
 	}

+ 26 - 0
internal/web3ext/web3ext.go

@@ -32,6 +32,7 @@ var Modules = map[string]string{
 	"shh":        ShhJs,
 	"swarmfs":    SwarmfsJs,
 	"txpool":     TxpoolJs,
+	"les":        LESJs,
 }
 
 const ChequebookJs = `
@@ -760,3 +761,28 @@ web3._extend({
 	]
 });
 `
+
+const LESJs = `
+web3._extend({
+	property: 'les',
+	methods: 
+	[
+		new web3._extend.Method({
+			name: 'getCheckpoint',
+			call: 'les_getCheckpoint',
+			params: 1
+		}),
+	],
+	properties: 
+	[
+		new web3._extend.Property({
+			name: 'latestCheckpoint',
+			getter: 'les_latestCheckpoint'
+		}),
+		new web3._extend.Property({
+			name: 'checkpointContractAddress',
+			getter: 'les_getCheckpointContractAddress'
+		}),
+	]
+});
+`

+ 58 - 0
les/api.go

@@ -34,6 +34,8 @@ var (
 	ErrMinCap               = errors.New("capacity too small")
 	ErrTotalCap             = errors.New("total capacity exceeded")
 	ErrUnknownBenchmarkType = errors.New("unknown benchmark type")
+	ErrNoCheckpoint         = errors.New("no local checkpoint provided")
+	ErrNotActivated         = errors.New("checkpoint registrar is not activated")
 
 	dropCapacityDelay = time.Second // delay applied to decreasing capacity changes
 )
@@ -470,3 +472,59 @@ func (api *PrivateLightServerAPI) Benchmark(setups []map[string]interface{}, pas
 	}
 	return result, nil
 }
+
+// PrivateLightAPI provides an API to access the LES light server or light client.
+type PrivateLightAPI struct {
+	backend *lesCommons
+	reg     *checkpointOracle
+}
+
+// NewPrivateLightAPI creates a new LES service API.
+func NewPrivateLightAPI(backend *lesCommons, reg *checkpointOracle) *PrivateLightAPI {
+	return &PrivateLightAPI{
+		backend: backend,
+		reg:     reg,
+	}
+}
+
+// LatestCheckpoint returns the latest local checkpoint package.
+//
+// The checkpoint package consists of 4 strings:
+//   result[0], hex encoded latest section index
+//   result[1], 32 bytes hex encoded latest section head hash
+//   result[2], 32 bytes hex encoded latest section canonical hash trie root hash
+//   result[3], 32 bytes hex encoded latest section bloom trie root hash
+func (api *PrivateLightAPI) LatestCheckpoint() ([4]string, error) {
+	var res [4]string
+	cp := api.backend.latestLocalCheckpoint()
+	if cp.Empty() {
+		return res, ErrNoCheckpoint
+	}
+	res[0] = hexutil.EncodeUint64(cp.SectionIndex)
+	res[1], res[2], res[3] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex()
+	return res, nil
+}
+
+// GetLocalCheckpoint returns the specific local checkpoint package.
+//
+// The checkpoint package consists of 3 strings:
+//   result[0], 32 bytes hex encoded latest section head hash
+//   result[1], 32 bytes hex encoded latest section canonical hash trie root hash
+//   result[2], 32 bytes hex encoded latest section bloom trie root hash
+func (api *PrivateLightAPI) GetCheckpoint(index uint64) ([3]string, error) {
+	var res [3]string
+	cp := api.backend.getLocalCheckpoint(index)
+	if cp.Empty() {
+		return res, ErrNoCheckpoint
+	}
+	res[0], res[1], res[2] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex()
+	return res, nil
+}
+
+// GetCheckpointContractAddress returns the contract contract address in hex format.
+func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) {
+	if api.reg == nil {
+		return "", ErrNotActivated
+	}
+	return api.reg.config.Address.Hex(), nil
+}

+ 36 - 30
les/backend.go

@@ -23,6 +23,7 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/accounts"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/mclock"
@@ -43,14 +44,13 @@ import (
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/p2p/discv5"
 	"github.com/ethereum/go-ethereum/params"
-	rpc "github.com/ethereum/go-ethereum/rpc"
+	"github.com/ethereum/go-ethereum/rpc"
 )
 
 type LightEthereum struct {
 	lesCommons
 
 	odr         *LesOdr
-	relay       *LesTxRelay
 	chainConfig *params.ChainConfig
 	// Channel for shutting down the service
 	shutdownChan chan bool
@@ -62,6 +62,7 @@ type LightEthereum struct {
 	serverPool *serverPool
 	reqDist    *requestDistributor
 	retriever  *retrieveManager
+	relay      *lesTxRelay
 
 	bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
 	bloomIndexer  *core.ChainIndexer
@@ -116,16 +117,20 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
 	}
 	leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg, trustedNodes)
 	leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
-	leth.relay = NewLesTxRelay(peers, leth.retriever)
+	leth.relay = newLesTxRelay(peers, leth.retriever)
 
 	leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever)
 	leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations)
 	leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency)
 	leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer)
 
+	checkpoint := config.Checkpoint
+	if checkpoint == nil {
+		checkpoint = params.TrustedCheckpoints[genesisHash]
+	}
 	// Note: NewLightChain adds the trusted checkpoint so it needs an ODR with
 	// indexers already set but not started yet
-	if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {
+	if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine, checkpoint); err != nil {
 		return nil, err
 	}
 	// Note: AddChildIndexer starts the update process for the child
@@ -141,32 +146,6 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
 	}
 
 	leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
-
-	if leth.protocolManager, err = NewProtocolManager(
-		leth.chainConfig,
-		light.DefaultClientIndexerConfig,
-		true,
-		config.NetworkId,
-		leth.eventMux,
-		leth.engine,
-		leth.peers,
-		leth.blockchain,
-		nil,
-		chainDb,
-		leth.odr,
-		leth.relay,
-		leth.serverPool,
-		quitSync,
-		&leth.wg,
-		config.ULC,
-		nil); err != nil {
-		return nil, err
-	}
-
-	if leth.protocolManager.isULCEnabled() {
-		log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.protocolManager.ulc.trustedKeys), "minTrustedFraction", leth.protocolManager.ulc.minTrustedFraction)
-		leth.blockchain.DisableCheckFreq()
-	}
 	leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil}
 
 	gpoParams := config.GPO
@@ -174,6 +153,19 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
 		gpoParams.Default = config.Miner.GasPrice
 	}
 	leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
+
+	oracle := config.CheckpointOracle
+	if oracle == nil {
+		oracle = params.CheckpointOracles[genesisHash]
+	}
+	registrar := newCheckpointOracle(oracle, leth.getLocalCheckpoint)
+	if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, checkpoint, light.DefaultClientIndexerConfig, config.ULC, true, config.NetworkId, leth.eventMux, leth.peers, leth.blockchain, nil, chainDb, leth.odr, leth.serverPool, registrar, quitSync, &leth.wg, nil); err != nil {
+		return nil, err
+	}
+	if leth.protocolManager.isULCEnabled() {
+		log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.protocolManager.ulc.trustedKeys), "minTrustedFraction", leth.protocolManager.ulc.minTrustedFraction)
+		leth.blockchain.DisableCheckFreq()
+	}
 	return leth, nil
 }
 
@@ -234,6 +226,11 @@ func (s *LightEthereum) APIs() []rpc.API {
 			Version:   "1.0",
 			Service:   s.netRPCService,
 			Public:    true,
+		}, {
+			Namespace: "les",
+			Version:   "1.0",
+			Service:   NewPrivateLightAPI(&s.lesCommons, s.protocolManager.reg),
+			Public:    false,
 		},
 	}...)
 }
@@ -288,3 +285,12 @@ func (s *LightEthereum) Stop() error {
 
 	return nil
 }
+
+// SetClient sets the rpc client and binds the registrar contract.
+func (s *LightEthereum) SetContractBackend(backend bind.ContractBackend) {
+	// Short circuit if registrar is nil
+	if s.protocolManager.reg == nil {
+		return
+	}
+	s.protocolManager.reg.start(backend)
+}

+ 158 - 0
les/checkpointoracle.go

@@ -0,0 +1,158 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package les
+
+import (
+	"encoding/binary"
+	"sync/atomic"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/contracts/checkpointoracle"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+// checkpointOracle is responsible for offering the latest stable checkpoint
+// generated and announced by the contract admins on-chain. The checkpoint is
+// verified by clients locally during the checkpoint syncing.
+type checkpointOracle struct {
+	config   *params.CheckpointOracleConfig
+	contract *checkpointoracle.CheckpointOracle
+
+	// Whether the contract backend is set.
+	running int32
+
+	getLocal     func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint
+	syncDoneHook func()                                // Function used to notify that light syncing has completed.
+}
+
+// newCheckpointOracle returns a checkpoint registrar handler.
+func newCheckpointOracle(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *checkpointOracle {
+	if config == nil {
+		log.Info("Checkpoint registrar is not enabled")
+		return nil
+	}
+	if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold {
+		log.Warn("Invalid checkpoint registrar config")
+		return nil
+	}
+	log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold)
+
+	return &checkpointOracle{
+		config:   config,
+		getLocal: getLocal,
+	}
+}
+
+// start binds the registrar contract and start listening to the
+// newCheckpointEvent for the server side.
+func (reg *checkpointOracle) start(backend bind.ContractBackend) {
+	contract, err := checkpointoracle.NewCheckpointOracle(reg.config.Address, backend)
+	if err != nil {
+		log.Error("Oracle contract binding failed", "err", err)
+		return
+	}
+	if !atomic.CompareAndSwapInt32(&reg.running, 0, 1) {
+		log.Error("Already bound and listening to registrar")
+		return
+	}
+	reg.contract = contract
+}
+
+// isRunning returns an indicator whether the registrar is running.
+func (reg *checkpointOracle) isRunning() bool {
+	return atomic.LoadInt32(&reg.running) == 1
+}
+
+// stableCheckpoint returns the stable checkpoint which was generated by local
+// indexers and announced by trusted signers.
+func (reg *checkpointOracle) stableCheckpoint() (*params.TrustedCheckpoint, uint64) {
+	// Retrieve the latest checkpoint from the contract, abort if empty
+	latest, hash, height, err := reg.contract.Contract().GetLatestCheckpoint(nil)
+	if err != nil || (latest == 0 && hash == [32]byte{}) {
+		return nil, 0
+	}
+	local := reg.getLocal(latest)
+
+	// The following scenarios may occur:
+	//
+	// * local node is out of sync so that it doesn't have the
+	//   checkpoint which registered in the contract.
+	// * local checkpoint doesn't match with the registered one.
+	//
+	// In both cases, server won't send the **stable** checkpoint
+	// to the client(no worry, client can use hardcoded one instead).
+	if local.HashEqual(common.Hash(hash)) {
+		return &local, height.Uint64()
+	}
+	return nil, 0
+}
+
+// verifySigners recovers the signer addresses according to the signature and
+// checks whether there are enough approvals to finalize the checkpoint.
+func (reg *checkpointOracle) verifySigners(index uint64, hash [32]byte, signatures [][]byte) (bool, []common.Address) {
+	// Short circuit if the given signatures doesn't reach the threshold.
+	if len(signatures) < int(reg.config.Threshold) {
+		return false, nil
+	}
+	var (
+		signers []common.Address
+		checked = make(map[common.Address]struct{})
+	)
+	for i := 0; i < len(signatures); i++ {
+		if len(signatures[i]) != 65 {
+			continue
+		}
+		// EIP 191 style signatures
+		//
+		// Arguments when calculating hash to validate
+		// 1: byte(0x19) - the initial 0x19 byte
+		// 2: byte(0) - the version byte (data with intended validator)
+		// 3: this - the validator address
+		// --  Application specific data
+		// 4 : checkpoint section_index (uint64)
+		// 5 : checkpoint hash (bytes32)
+		//     hash = keccak256(checkpoint_index, section_head, cht_root, bloom_root)
+		buf := make([]byte, 8)
+		binary.BigEndian.PutUint64(buf, index)
+		data := append([]byte{0x19, 0x00}, append(reg.config.Address.Bytes(), append(buf, hash[:]...)...)...)
+		signatures[i][64] -= 27 // Transform V from 27/28 to 0/1 according to the yellow paper for verification.
+		pubkey, err := crypto.Ecrecover(crypto.Keccak256(data), signatures[i])
+		if err != nil {
+			return false, nil
+		}
+		var signer common.Address
+		copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
+		if _, exist := checked[signer]; exist {
+			continue
+		}
+		for _, s := range reg.config.Signers {
+			if s == signer {
+				signers = append(signers, signer)
+				checked[signer] = struct{}{}
+			}
+		}
+	}
+	threshold := reg.config.Threshold
+	if uint64(len(signers)) < threshold {
+		log.Warn("Not enough signers to approve checkpoint", "signers", len(signers), "threshold", threshold)
+		return false, nil
+	}
+	return true, signers
+}

+ 33 - 19
les/commons.go

@@ -76,24 +76,6 @@ func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol {
 
 // nodeInfo retrieves some protocol metadata about the running host node.
 func (c *lesCommons) nodeInfo() interface{} {
-	var cht params.TrustedCheckpoint
-	sections, _, _ := c.chtIndexer.Sections()
-	sections2, _, _ := c.bloomTrieIndexer.Sections()
-
-	if sections2 < sections {
-		sections = sections2
-	}
-	if sections > 0 {
-		sectionIndex := sections - 1
-		sectionHead := c.bloomTrieIndexer.SectionHead(sectionIndex)
-		cht = params.TrustedCheckpoint{
-			SectionIndex: sectionIndex,
-			SectionHead:  sectionHead,
-			CHTRoot:      light.GetChtRoot(c.chainDb, sectionIndex, sectionHead),
-			BloomRoot:    light.GetBloomTrieRoot(c.chainDb, sectionIndex, sectionHead),
-		}
-	}
-
 	chain := c.protocolManager.blockchain
 	head := chain.CurrentHeader()
 	hash := head.Hash()
@@ -103,6 +85,38 @@ func (c *lesCommons) nodeInfo() interface{} {
 		Genesis:    chain.Genesis().Hash(),
 		Config:     chain.Config(),
 		Head:       chain.CurrentHeader().Hash(),
-		CHT:        cht,
+		CHT:        c.latestLocalCheckpoint(),
+	}
+}
+
+// latestLocalCheckpoint finds the common stored section index and returns a set of
+// post-processed trie roots (CHT and BloomTrie) associated with
+// the appropriate section index and head hash as a local checkpoint package.
+func (c *lesCommons) latestLocalCheckpoint() params.TrustedCheckpoint {
+	sections, _, _ := c.chtIndexer.Sections()
+	sections2, _, _ := c.bloomTrieIndexer.Sections()
+	// Cap the section index if the two sections are not consistent.
+	if sections > sections2 {
+		sections = sections2
+	}
+	if sections == 0 {
+		// No checkpoint information can be provided.
+		return params.TrustedCheckpoint{}
+	}
+	return c.getLocalCheckpoint(sections - 1)
+}
+
+// getLocalCheckpoint returns a set of post-processed trie roots (CHT and BloomTrie)
+// associated with the appropriate head hash by specific section index.
+//
+// The returned checkpoint is only the checkpoint generated by the local indexers,
+// not the stable checkpoint registered in the registrar contract.
+func (c *lesCommons) getLocalCheckpoint(index uint64) params.TrustedCheckpoint {
+	sectionHead := c.chtIndexer.SectionHead(index)
+	return params.TrustedCheckpoint{
+		SectionIndex: index,
+		SectionHead:  sectionHead,
+		CHTRoot:      light.GetChtRoot(c.chainDb, index, sectionHead),
+		BloomRoot:    light.GetBloomTrieRoot(c.chainDb, index, sectionHead),
 	}
 }

+ 10 - 24
les/handler.go

@@ -27,7 +27,6 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state"
@@ -101,7 +100,7 @@ type ProtocolManager struct {
 	networkId uint64 // The identity of network.
 
 	txpool       txPool
-	txrelay      *LesTxRelay
+	txrelay      *lesTxRelay
 	blockchain   BlockChain
 	chainDb      ethdb.Database
 	odr          *LesOdr
@@ -115,6 +114,8 @@ type ProtocolManager struct {
 	fetcher      *lightFetcher
 	ulc          *ulc
 	peers        *peerSet
+	checkpoint   *params.TrustedCheckpoint
+	reg          *checkpointOracle // If reg == nil, it means the checkpoint registrar is not activated
 
 	// channels for fetcher, syncer, txsyncLoop
 	newPeerCh   chan *peer
@@ -131,23 +132,7 @@ type ProtocolManager struct {
 
 // NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
 // with the ethereum network.
-func NewProtocolManager(
-	chainConfig *params.ChainConfig,
-	indexerConfig *light.IndexerConfig,
-	client bool,
-	networkId uint64,
-	mux *event.TypeMux,
-	engine consensus.Engine,
-	peers *peerSet,
-	blockchain BlockChain,
-	txpool txPool,
-	chainDb ethdb.Database,
-	odr *LesOdr,
-	txrelay *LesTxRelay,
-	serverPool *serverPool,
-	quitSync chan struct{},
-	wg *sync.WaitGroup,
-	ulcConfig *eth.ULCConfig, synced func() bool) (*ProtocolManager, error) {
+func NewProtocolManager(chainConfig *params.ChainConfig, checkpoint *params.TrustedCheckpoint, indexerConfig *light.IndexerConfig, ulcConfig *eth.ULCConfig, client bool, networkId uint64, mux *event.TypeMux, peers *peerSet, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, serverPool *serverPool, registrar *checkpointOracle, quitSync chan struct{}, wg *sync.WaitGroup, synced func() bool) (*ProtocolManager, error) {
 	// Create the protocol manager with the base fields
 	manager := &ProtocolManager{
 		client:      client,
@@ -159,13 +144,14 @@ func NewProtocolManager(
 		odr:         odr,
 		networkId:   networkId,
 		txpool:      txpool,
-		txrelay:     txrelay,
 		serverPool:  serverPool,
+		reg:         registrar,
 		peers:       peers,
 		newPeerCh:   make(chan *peer),
 		quitSync:    quitSync,
 		wg:          wg,
 		noMorePeers: make(chan struct{}),
+		checkpoint:  checkpoint,
 		synced:      synced,
 	}
 	if odr != nil {
@@ -182,11 +168,11 @@ func NewProtocolManager(
 		removePeer = func(id string) {}
 	}
 	if client {
-		var checkpoint uint64
-		if cht, ok := params.TrustedCheckpoints[blockchain.Genesis().Hash()]; ok {
-			checkpoint = (cht.SectionIndex+1)*params.CHTFrequency - 1
+		var checkpointNumber uint64
+		if checkpoint != nil {
+			checkpointNumber = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1
 		}
-		manager.downloader = downloader.New(checkpoint, chainDb, nil, manager.eventMux, nil, blockchain, removePeer)
+		manager.downloader = downloader.New(checkpointNumber, chainDb, nil, manager.eventMux, nil, blockchain, removePeer)
 		manager.peers.notify((*downloaderPeerNotify)(manager))
 		manager.fetcher = newLightFetcher(manager)
 	}

+ 23 - 24
les/handler_test.go

@@ -259,7 +259,6 @@ func testGetCode(t *testing.T, protocol int) {
 
 	var codereqs []*CodeReq
 	var codes [][]byte
-
 	for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
 		header := bc.GetHeaderByNumber(i)
 		req := &CodeReq{
@@ -342,11 +341,10 @@ func testGetProofs(t *testing.T, protocol int) {
 	var proofreqs []ProofReq
 	proofsV2 := light.NewNodeSet()
 
-	accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr, {}}
+	accounts := []common.Address{bankAddr, userAddr1, userAddr2, {}}
 	for i := uint64(0); i <= bc.CurrentBlock().NumberU64(); i++ {
 		header := bc.GetHeaderByNumber(i)
-		root := header.Root
-		trie, _ := trie.New(root, trie.NewDatabase(server.db))
+		trie, _ := trie.New(header.Root, trie.NewDatabase(server.db))
 
 		for _, acc := range accounts {
 			req := ProofReq{
@@ -377,7 +375,7 @@ func testGetStaleProof(t *testing.T, protocol int) {
 	check := func(number uint64, wantOK bool) {
 		var (
 			header  = bc.GetHeaderByNumber(number)
-			account = crypto.Keccak256(testBankAddress.Bytes())
+			account = crypto.Keccak256(userAddr1.Bytes())
 		)
 		req := &ProofReq{
 			BHash: header.Hash(),
@@ -390,7 +388,7 @@ func testGetStaleProof(t *testing.T, protocol int) {
 		if wantOK {
 			proofsV2 := light.NewNodeSet()
 			t, _ := trie.New(header.Root, trie.NewDatabase(server.db))
-			t.Prove(crypto.Keccak256(account), 0, proofsV2)
+			t.Prove(account, 0, proofsV2)
 			expected = proofsV2.NodeList()
 		}
 		if err := expectResponse(server.tPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil {
@@ -496,14 +494,15 @@ func TestGetBloombitsProofs(t *testing.T) {
 }
 
 func TestTransactionStatusLes2(t *testing.T) {
-	db := rawdb.NewMemoryDatabase()
-	pm := newTestProtocolManagerMust(t, false, 0, nil, nil, nil, db, nil)
-	chain := pm.blockchain.(*core.BlockChain)
+	server, tearDown := newServerEnv(t, 0, 2, nil)
+	defer tearDown()
+
+	chain := server.pm.blockchain.(*core.BlockChain)
 	config := core.DefaultTxPoolConfig
 	config.Journal = ""
 	txpool := core.NewTxPool(config, params.TestChainConfig, chain)
-	pm.txpool = txpool
-	peer, _ := newTestPeer(t, "peer", 2, pm, true, 0)
+	server.pm.txpool = txpool
+	peer, _ := newTestPeer(t, "peer", 2, server.pm, true, 0)
 	defer peer.close()
 
 	var reqID uint64
@@ -511,13 +510,13 @@ func TestTransactionStatusLes2(t *testing.T) {
 	test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) {
 		reqID++
 		if send {
-			cost := peer.GetRequestCost(SendTxV2Msg, 1)
-			sendRequest(peer.app, SendTxV2Msg, reqID, cost, types.Transactions{tx})
+			cost := server.tPeer.GetRequestCost(SendTxV2Msg, 1)
+			sendRequest(server.tPeer.app, SendTxV2Msg, reqID, cost, types.Transactions{tx})
 		} else {
-			cost := peer.GetRequestCost(GetTxStatusMsg, 1)
-			sendRequest(peer.app, GetTxStatusMsg, reqID, cost, []common.Hash{tx.Hash()})
+			cost := server.tPeer.GetRequestCost(GetTxStatusMsg, 1)
+			sendRequest(server.tPeer.app, GetTxStatusMsg, reqID, cost, []common.Hash{tx.Hash()})
 		}
-		if err := expectResponse(peer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil {
+		if err := expectResponse(server.tPeer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil {
 			t.Errorf("transaction status mismatch")
 		}
 	}
@@ -525,16 +524,16 @@ func TestTransactionStatusLes2(t *testing.T) {
 	signer := types.HomesteadSigner{}
 
 	// test error status by sending an underpriced transaction
-	tx0, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey)
+	tx0, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey)
 	test(tx0, true, light.TxStatus{Status: core.TxStatusUnknown, Error: core.ErrUnderpriced.Error()})
 
-	tx1, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey)
+	tx1, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey)
 	test(tx1, false, light.TxStatus{Status: core.TxStatusUnknown}) // query before sending, should be unknown
 	test(tx1, true, light.TxStatus{Status: core.TxStatusPending})  // send valid processable tx, should return pending
 	test(tx1, true, light.TxStatus{Status: core.TxStatusPending})  // adding it again should not return an error
 
-	tx2, _ := types.SignTx(types.NewTransaction(1, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey)
-	tx3, _ := types.SignTx(types.NewTransaction(2, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey)
+	tx2, _ := types.SignTx(types.NewTransaction(1, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey)
+	tx3, _ := types.SignTx(types.NewTransaction(2, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey)
 	// send transactions in the wrong order, tx3 should be queued
 	test(tx3, true, light.TxStatus{Status: core.TxStatusQueued})
 	test(tx2, true, light.TxStatus{Status: core.TxStatusPending})
@@ -542,7 +541,7 @@ func TestTransactionStatusLes2(t *testing.T) {
 	test(tx3, false, light.TxStatus{Status: core.TxStatusPending})
 
 	// generate and add a block with tx1 and tx2 included
-	gchain, _ := core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), db, 1, func(i int, block *core.BlockGen) {
+	gchain, _ := core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), server.db, 1, func(i int, block *core.BlockGen) {
 		block.AddTx(tx1)
 		block.AddTx(tx2)
 	})
@@ -561,12 +560,12 @@ func TestTransactionStatusLes2(t *testing.T) {
 	}
 
 	// check if their status is included now
-	block1hash := rawdb.ReadCanonicalHash(db, 1)
+	block1hash := rawdb.ReadCanonicalHash(server.db, 1)
 	test(tx1, false, light.TxStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}})
 	test(tx2, false, light.TxStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}})
 
 	// create a reorg that rolls them back
-	gchain, _ = core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), db, 2, func(i int, block *core.BlockGen) {})
+	gchain, _ = core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), server.db, 2, func(i int, block *core.BlockGen) {})
 	if _, err := chain.InsertChain(gchain); err != nil {
 		panic(err)
 	}
@@ -589,7 +588,7 @@ func TestStopResumeLes3(t *testing.T) {
 	db := rawdb.NewMemoryDatabase()
 	clock := &mclock.Simulated{}
 	testCost := testBufLimit / 10
-	pm, err := newTestProtocolManager(false, 0, nil, nil, nil, db, nil, testCost, clock)
+	pm, _, err := newTestProtocolManager(false, 0, nil, nil, nil, db, nil, testCost, clock)
 	if err != nil {
 		t.Fatalf("Failed to create protocol manager: %v", err)
 	}

+ 156 - 93
les/helper_test.go

@@ -20,19 +20,22 @@
 package les
 
 import (
+	"context"
 	"crypto/rand"
 	"math/big"
 	"sync"
 	"testing"
 	"time"
 
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/ethdb"
@@ -45,14 +48,14 @@ import (
 )
 
 var (
-	testBankKey, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-	testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
-	testBankFunds   = big.NewInt(1000000000000000000)
+	bankKey, _ = crypto.GenerateKey()
+	bankAddr   = crypto.PubkeyToAddress(bankKey.PublicKey)
+	bankFunds  = big.NewInt(1000000000000000000)
 
-	acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
-	acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
-	acc1Addr   = crypto.PubkeyToAddress(acc1Key.PublicKey)
-	acc2Addr   = crypto.PubkeyToAddress(acc2Key.PublicKey)
+	userKey1, _ = crypto.GenerateKey()
+	userKey2, _ = crypto.GenerateKey()
+	userAddr1   = crypto.PubkeyToAddress(userKey1.PublicKey)
+	userAddr2   = crypto.PubkeyToAddress(userKey2.PublicKey)
 
 	testContractCode         = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
 	testContractAddr         common.Address
@@ -60,8 +63,21 @@ var (
 	testContractDeployed     = uint64(2)
 
 	testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029")
-	testEventEmitterAddr common.Address
 
+	// Checkpoint registrar relative
+	registrarAddr common.Address
+	signerKey, _  = crypto.GenerateKey()
+	signerAddr    = crypto.PubkeyToAddress(signerKey.PublicKey)
+)
+
+var (
+	// The block frequency for creating checkpoint(only used in test)
+	sectionSize = big.NewInt(512)
+
+	// The number of confirmations needed to generate a checkpoint(only used in test).
+	processConfirms = big.NewInt(4)
+
+	//
 	testBufLimit    = uint64(1000000)
 	testBufRecharge = uint64(1000)
 )
@@ -81,102 +97,139 @@ contract test {
 }
 */
 
-func testChainGen(i int, block *core.BlockGen) {
-	signer := types.HomesteadSigner{}
-
-	switch i {
-	case 0:
-		// In block 1, the test bank sends account #1 some ether.
-		tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey)
-		block.AddTx(tx)
-	case 1:
-		// In block 2, the test bank sends some more ether to account #1.
-		// acc1Addr passes it on to account #2.
-		// acc1Addr creates a test contract.
-		// acc1Addr creates a test event.
-		nonce := block.TxNonce(acc1Addr)
-
-		tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey)
-		tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
-		tx3, _ := types.SignTx(types.NewContractCreation(nonce+1, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, acc1Key)
-		testContractAddr = crypto.CreateAddress(acc1Addr, nonce+1)
-		tx4, _ := types.SignTx(types.NewContractCreation(nonce+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, acc1Key)
-		testEventEmitterAddr = crypto.CreateAddress(acc1Addr, nonce+2)
-		block.AddTx(tx1)
-		block.AddTx(tx2)
-		block.AddTx(tx3)
-		block.AddTx(tx4)
-	case 2:
-		// Block 3 is empty but was mined by account #2.
-		block.SetCoinbase(acc2Addr)
-		block.SetExtra([]byte("yeehaw"))
-		data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001")
-		tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey)
-		block.AddTx(tx)
-	case 3:
-		// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
-		b2 := block.PrevBlock(1).Header()
-		b2.Extra = []byte("foo")
-		block.AddUncle(b2)
-		b3 := block.PrevBlock(2).Header()
-		b3.Extra = []byte("foo")
-		block.AddUncle(b3)
-		data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
-		tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey)
-		block.AddTx(tx)
+// prepareTestchain pre-commits specified number customized blocks into chain.
+func prepareTestchain(n int, backend *backends.SimulatedBackend) {
+	var (
+		ctx    = context.Background()
+		signer = types.HomesteadSigner{}
+	)
+	for i := 0; i < n; i++ {
+		switch i {
+		case 0:
+			// deploy checkpoint contract
+			registrarAddr, _, _, _ = contract.DeployCheckpointOracle(bind.NewKeyedTransactor(bankKey), backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1))
+			// bankUser transfers some ether to user1
+			nonce, _ := backend.PendingNonceAt(ctx, bankAddr)
+			tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey)
+			backend.SendTransaction(ctx, tx)
+		case 1:
+			bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
+			userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1)
+
+			// bankUser transfers more ether to user1
+			tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1000), params.TxGas, nil, nil), signer, bankKey)
+			backend.SendTransaction(ctx, tx1)
+
+			// user1 relays ether to user2
+			tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1000), params.TxGas, nil, nil), signer, userKey1)
+			backend.SendTransaction(ctx, tx2)
+
+			// user1 deploys a test contract
+			tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, userKey1)
+			backend.SendTransaction(ctx, tx3)
+			testContractAddr = crypto.CreateAddress(userAddr1, userNonce1+1)
+
+			// user1 deploys a event contract
+			tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, userKey1)
+			backend.SendTransaction(ctx, tx4)
+		case 2:
+			// bankUser transfer some ether to signer
+			bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
+			tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, nil, nil), signer, bankKey)
+			backend.SendTransaction(ctx, tx1)
+
+			// invoke test contract
+			data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001")
+			tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey)
+			backend.SendTransaction(ctx, tx2)
+		case 3:
+			// invoke test contract
+			bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
+			data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
+			tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey)
+			backend.SendTransaction(ctx, tx)
+		}
+		backend.Commit()
 	}
 }
 
 // testIndexers creates a set of indexers with specified params for testing purpose.
-func testIndexers(db ethdb.Database, odr light.OdrBackend, iConfig *light.IndexerConfig) (*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer) {
-	chtIndexer := light.NewChtIndexer(db, odr, iConfig.ChtSize, iConfig.ChtConfirms)
-	bloomIndexer := eth.NewBloomIndexer(db, iConfig.BloomSize, iConfig.BloomConfirms)
-	bloomTrieIndexer := light.NewBloomTrieIndexer(db, odr, iConfig.BloomSize, iConfig.BloomTrieSize)
-	bloomIndexer.AddChildIndexer(bloomTrieIndexer)
-	return chtIndexer, bloomIndexer, bloomTrieIndexer
+func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig) []*core.ChainIndexer {
+	var indexers [3]*core.ChainIndexer
+	indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms)
+	indexers[1] = eth.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms)
+	indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize)
+	// make bloomTrieIndexer as a child indexer of bloom indexer.
+	indexers[1].AddChildIndexer(indexers[2])
+	return indexers[:]
 }
 
 // newTestProtocolManager creates a new protocol manager for testing purposes,
 // with the given number of blocks already known, potential notification
 // channels for different events and relative chain indexers array.
-func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *core.BlockGen), odr *LesOdr, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig, testCost uint64, clock mclock.Clock) (*ProtocolManager, error) {
+func newTestProtocolManager(lightSync bool, blocks int, odr *LesOdr, indexers []*core.ChainIndexer, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig, testCost uint64, clock mclock.Clock) (*ProtocolManager, *backends.SimulatedBackend, error) {
 	var (
 		evmux  = new(event.TypeMux)
 		engine = ethash.NewFaker()
 		gspec  = core.Genesis{
-			Config: params.TestChainConfig,
-			Alloc:  core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
+			Config: params.AllEthashProtocolChanges,
+			Alloc:  core.GenesisAlloc{bankAddr: {Balance: bankFunds}},
 		}
-		genesis = gspec.MustCommit(db)
-		chain   BlockChain
-		pool    txPool
+		pool   txPool
+		chain  BlockChain
+		exitCh = make(chan struct{})
 	)
+	gspec.MustCommit(db)
 	if peers == nil {
 		peers = newPeerSet()
 	}
+	// create a simulation backend and pre-commit several customized block to the database.
+	simulation := backends.NewSimulatedBackendWithDatabase(db, gspec.Alloc, 100000000)
+	prepareTestchain(blocks, simulation)
 
+	// initialize empty chain for light client or pre-committed chain for server.
 	if lightSync {
-		chain, _ = light.NewLightChain(odr, gspec.Config, engine)
+		chain, _ = light.NewLightChain(odr, gspec.Config, engine, nil)
 	} else {
-		blockchain, _ := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil)
-		gchain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, generator)
-		if _, err := blockchain.InsertChain(gchain); err != nil {
-			panic(err)
-		}
-		chain = blockchain
-		pool = core.NewTxPool(core.DefaultTxPoolConfig, gspec.Config, blockchain)
+		chain = simulation.Blockchain()
+		pool = core.NewTxPool(core.DefaultTxPoolConfig, gspec.Config, simulation.Blockchain())
 	}
 
+	// Create contract registrar
 	indexConfig := light.TestServerIndexerConfig
 	if lightSync {
 		indexConfig = light.TestClientIndexerConfig
 	}
-	pm, err := NewProtocolManager(gspec.Config, indexConfig, lightSync, NetworkId, evmux, engine, peers, chain, pool, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup), ulcConfig, func() bool { return true })
+	config := &params.CheckpointOracleConfig{
+		Address:   crypto.CreateAddress(bankAddr, 0),
+		Signers:   []common.Address{signerAddr},
+		Threshold: 1,
+	}
+	var reg *checkpointOracle
+	if indexers != nil {
+		getLocal := func(index uint64) params.TrustedCheckpoint {
+			chtIndexer := indexers[0]
+			sectionHead := chtIndexer.SectionHead(index)
+			return params.TrustedCheckpoint{
+				SectionIndex: index,
+				SectionHead:  sectionHead,
+				CHTRoot:      light.GetChtRoot(db, index, sectionHead),
+				BloomRoot:    light.GetBloomTrieRoot(db, index, sectionHead),
+			}
+		}
+		reg = newCheckpointOracle(config, getLocal)
+	}
+	pm, err := NewProtocolManager(gspec.Config, nil, indexConfig, ulcConfig, lightSync, NetworkId, evmux, peers, chain, pool, db, odr, nil, reg, exitCh, new(sync.WaitGroup), func() bool { return true })
 	if err != nil {
-		return nil, err
+		return nil, nil, err
+	}
+	// Registrar initialization could failed if checkpoint contract is not specified.
+	if pm.reg != nil {
+		pm.reg.start(simulation)
 	}
+	// Set up les server stuff.
 	if !lightSync {
-		srv := &LesServer{lesCommons: lesCommons{protocolManager: pm}}
+		srv := &LesServer{lesCommons: lesCommons{protocolManager: pm, chainDb: db}}
 		pm.server = srv
 		pm.servingQueue = newServingQueue(int64(time.Millisecond*10), 1, nil)
 		pm.servingQueue.setThreads(4)
@@ -189,19 +242,19 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
 		srv.fcManager = flowcontrol.NewClientManager(nil, clock)
 	}
 	pm.Start(1000)
-	return pm, nil
+	return pm, simulation, nil
 }
 
 // newTestProtocolManagerMust creates a new protocol manager for testing purposes,
-// with the given number of blocks already known, potential notification
-// channels for different events and relative chain indexers array. In case of an error, the constructor force-
-// fails the test.
-func newTestProtocolManagerMust(t *testing.T, lightSync bool, blocks int, generator func(int, *core.BlockGen), odr *LesOdr, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig) *ProtocolManager {
-	pm, err := newTestProtocolManager(lightSync, blocks, generator, odr, peers, db, ulcConfig, 0, &mclock.System{})
+// with the given number of blocks already known, potential notification channels
+// for different events and relative chain indexers array. In case of an error, the
+// constructor force-fails the test.
+func newTestProtocolManagerMust(t *testing.T, lightSync bool, blocks int, odr *LesOdr, indexers []*core.ChainIndexer, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig) (*ProtocolManager, *backends.SimulatedBackend) {
+	pm, backend, err := newTestProtocolManager(lightSync, blocks, odr, indexers, peers, db, ulcConfig, 0, &mclock.System{})
 	if err != nil {
 		t.Fatalf("Failed to create protocol manager: %v", err)
 	}
-	return pm
+	return pm, backend
 }
 
 // testPeer is a simulated peer to allow testing direct network calls.
@@ -324,11 +377,13 @@ func (p *testPeer) close() {
 
 // TestEntity represents a network entity for testing with necessary auxiliary fields.
 type TestEntity struct {
-	db    ethdb.Database
-	rPeer *peer
-	tPeer *testPeer
-	peers *peerSet
-	pm    *ProtocolManager
+	db      ethdb.Database
+	rPeer   *peer
+	tPeer   *testPeer
+	peers   *peerSet
+	pm      *ProtocolManager
+	backend *backends.SimulatedBackend
+
 	// Indexers
 	chtIndexer       *core.ChainIndexer
 	bloomIndexer     *core.ChainIndexer
@@ -338,11 +393,12 @@ type TestEntity struct {
 // newServerEnv creates a server testing environment with a connected test peer for testing purpose.
 func newServerEnv(t *testing.T, blocks int, protocol int, waitIndexers func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer)) (*TestEntity, func()) {
 	db := rawdb.NewMemoryDatabase()
-	cIndexer, bIndexer, btIndexer := testIndexers(db, nil, light.TestServerIndexerConfig)
+	indexers := testIndexers(db, nil, light.TestServerIndexerConfig)
 
-	pm := newTestProtocolManagerMust(t, false, blocks, testChainGen, nil, nil, db, nil)
+	pm, b := newTestProtocolManagerMust(t, false, blocks, nil, indexers, nil, db, nil)
 	peer, _ := newTestPeer(t, "peer", protocol, pm, true, 0)
 
+	cIndexer, bIndexer, btIndexer := indexers[0], indexers[1], indexers[2]
 	cIndexer.Start(pm.blockchain.(*core.BlockChain))
 	bIndexer.Start(pm.blockchain.(*core.BlockChain))
 
@@ -355,6 +411,7 @@ func newServerEnv(t *testing.T, blocks int, protocol int, waitIndexers func(*cor
 			db:               db,
 			tPeer:            peer,
 			pm:               pm,
+			backend:          b,
 			chtIndexer:       cIndexer,
 			bloomIndexer:     bIndexer,
 			bloomTrieIndexer: btIndexer,
@@ -376,12 +433,16 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, waitIndexers fun
 	rm := newRetrieveManager(lPeers, dist, nil)
 	odr := NewLesOdr(ldb, light.TestClientIndexerConfig, rm)
 
-	cIndexer, bIndexer, btIndexer := testIndexers(db, nil, light.TestServerIndexerConfig)
-	lcIndexer, lbIndexer, lbtIndexer := testIndexers(ldb, odr, light.TestClientIndexerConfig)
+	indexers := testIndexers(db, nil, light.TestServerIndexerConfig)
+	lIndexers := testIndexers(ldb, odr, light.TestClientIndexerConfig)
+
+	cIndexer, bIndexer, btIndexer := indexers[0], indexers[1], indexers[2]
+	lcIndexer, lbIndexer, lbtIndexer := lIndexers[0], lIndexers[1], lIndexers[2]
+
 	odr.SetIndexers(lcIndexer, lbtIndexer, lbIndexer)
 
-	pm := newTestProtocolManagerMust(t, false, blocks, testChainGen, nil, peers, db, nil)
-	lpm := newTestProtocolManagerMust(t, true, 0, nil, odr, lPeers, ldb, nil)
+	pm, b := newTestProtocolManagerMust(t, false, blocks, nil, indexers, peers, db, nil)
+	lpm, lb := newTestProtocolManagerMust(t, true, 0, odr, lIndexers, lPeers, ldb, nil)
 
 	startIndexers := func(clientMode bool, pm *ProtocolManager) {
 		if clientMode {
@@ -421,6 +482,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, waitIndexers fun
 			pm:               pm,
 			rPeer:            peer,
 			peers:            peers,
+			backend:          b,
 			chtIndexer:       cIndexer,
 			bloomIndexer:     bIndexer,
 			bloomTrieIndexer: btIndexer,
@@ -429,6 +491,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, waitIndexers fun
 			pm:               lpm,
 			rPeer:            lPeer,
 			peers:            lPeers,
+			backend:          lb,
 			chtIndexer:       lcIndexer,
 			bloomIndexer:     lbIndexer,
 			bloomTrieIndexer: lbtIndexer,

+ 35 - 24
les/odr_requests.go

@@ -166,11 +166,13 @@ func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error {
 	receipt := receipts[0]
 
 	// Retrieve our stored header and validate receipt content against it
-	header := rawdb.ReadHeader(db, r.Hash, r.Number)
-	if header == nil {
+	if r.Header == nil {
+		r.Header = rawdb.ReadHeader(db, r.Hash, r.Number)
+	}
+	if r.Header == nil {
 		return errHeaderUnavailable
 	}
-	if header.ReceiptHash != types.DeriveSha(receipt) {
+	if r.Header.ReceiptHash != types.DeriveSha(receipt) {
 		return errReceiptHashMismatch
 	}
 	// Validations passed, store and return
@@ -323,7 +325,11 @@ func (r *ChtRequest) CanSend(peer *peer) bool {
 	peer.lock.RLock()
 	defer peer.lock.RUnlock()
 
-	return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize
+	if r.Untrusted {
+		return peer.headInfo.Number >= r.BlockNum && peer.id == r.PeerId
+	} else {
+		return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize
+	}
 }
 
 // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
@@ -364,32 +370,37 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
 	}
 
 	// Verify the CHT
-	var encNumber [8]byte
-	binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
+	// Note: For untrusted CHT request, there is no proof response but
+	// header data.
+	var node light.ChtNode
+	if !r.Untrusted {
+		var encNumber [8]byte
+		binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
 
-	reads := &readTraceDB{db: nodeSet}
-	value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
-	if err != nil {
-		return fmt.Errorf("merkle proof verification failed: %v", err)
-	}
-	if len(reads.reads) != nodeSet.KeyCount() {
-		return errUselessNodes
-	}
+		reads := &readTraceDB{db: nodeSet}
+		value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
+		if err != nil {
+			return fmt.Errorf("merkle proof verification failed: %v", err)
+		}
+		if len(reads.reads) != nodeSet.KeyCount() {
+			return errUselessNodes
+		}
 
-	var node light.ChtNode
-	if err := rlp.DecodeBytes(value, &node); err != nil {
-		return err
-	}
-	if node.Hash != header.Hash() {
-		return errCHTHashMismatch
-	}
-	if r.BlockNum != header.Number.Uint64() {
-		return errCHTNumberMismatch
+		if err := rlp.DecodeBytes(value, &node); err != nil {
+			return err
+		}
+		if node.Hash != header.Hash() {
+			return errCHTHashMismatch
+		}
+		if r.BlockNum != header.Number.Uint64() {
+			return errCHTNumberMismatch
+		}
 	}
 	// Verifications passed, store and return
 	r.Header = header
 	r.Proof = nodeSet
-	r.Td = node.Td
+	r.Td = node.Td // For untrusted request, td here is nil, todo improve the les/2 protocol
+
 	return nil
 }
 

+ 4 - 4
les/odr_test.go

@@ -78,7 +78,7 @@ func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrAccounts) }
 
 func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
 	dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
-	acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr}
+	acc := []common.Address{bankAddr, userAddr1, userAddr2, dummyAddr}
 
 	var (
 		res []byte
@@ -121,7 +121,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
 			statedb, err := state.New(header.Root, state.NewDatabase(db))
 
 			if err == nil {
-				from := statedb.GetOrNewStateObject(testBankAddress)
+				from := statedb.GetOrNewStateObject(bankAddr)
 				from.SetBalance(math.MaxBig256)
 
 				msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)}
@@ -137,8 +137,8 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
 		} else {
 			header := lc.GetHeaderByHash(bhash)
 			state := light.NewState(ctx, header, lc.Odr())
-			state.SetBalance(testBankAddress, math.MaxBig256)
-			msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)}
+			state.SetBalance(bankAddr, math.MaxBig256)
+			msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)}
 			context := core.NewEVMContext(msg, header, lc, nil)
 			vmenv := vm.NewEVM(context, state, config, vm.Config{})
 			gp := new(core.GasPool).AddGas(math.MaxUint64)

+ 22 - 5
les/peer.go

@@ -33,6 +33,7 @@ import (
 	"github.com/ethereum/go-ethereum/les/flowcontrol"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
@@ -79,6 +80,10 @@ type peer struct {
 
 	announceType uint64
 
+	// Checkpoint relative fields
+	checkpoint       params.TrustedCheckpoint
+	checkpointNumber uint64
+
 	id string
 
 	headInfo *announceData
@@ -575,6 +580,14 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
 		send = send.add("flowControl/MRC", costList)
 		p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)])
 		p.fcParams = server.defParams
+
+		if server.protocolManager != nil && server.protocolManager.reg != nil && server.protocolManager.reg.isRunning() {
+			cp, height := server.protocolManager.reg.stableCheckpoint()
+			if cp != nil {
+				send = send.add("checkpoint/value", cp)
+				send = send.add("checkpoint/registerHeight", height)
+			}
+		}
 	} else {
 		//on client node
 		p.announceType = announceTypeSimple
@@ -658,20 +671,24 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
 			return errResp(ErrUselessPeer, "peer cannot serve requests")
 		}
 
-		var params flowcontrol.ServerParams
-		if err := recv.get("flowControl/BL", &params.BufLimit); err != nil {
+		var sParams flowcontrol.ServerParams
+		if err := recv.get("flowControl/BL", &sParams.BufLimit); err != nil {
 			return err
 		}
-		if err := recv.get("flowControl/MRR", &params.MinRecharge); err != nil {
+		if err := recv.get("flowControl/MRR", &sParams.MinRecharge); err != nil {
 			return err
 		}
 		var MRC RequestCostList
 		if err := recv.get("flowControl/MRC", &MRC); err != nil {
 			return err
 		}
-		p.fcParams = params
-		p.fcServer = flowcontrol.NewServerNode(params, &mclock.System{})
+		p.fcParams = sParams
+		p.fcServer = flowcontrol.NewServerNode(sParams, &mclock.System{})
 		p.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)])
+
+		recv.get("checkpoint/value", &p.checkpoint)
+		recv.get("checkpoint/registerHeight", &p.checkpointNumber)
+
 		if !p.isOnlyAnnounce {
 			for msgCode := range reqAvgTimeCost {
 				if p.fcCosts[msgCode] == nil {

+ 1 - 1
les/request_test.go

@@ -28,7 +28,7 @@ import (
 	"github.com/ethereum/go-ethereum/light"
 )
 
-var testBankSecureTrieKey = secAddr(testBankAddress)
+var testBankSecureTrieKey = secAddr(bankAddr)
 
 func secAddr(addr common.Address) []byte {
 	return crypto.Keccak256(addr[:])

+ 44 - 51
les/server.go

@@ -21,6 +21,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/core"
@@ -72,68 +73,38 @@ type LesServer struct {
 	priorityClientPool         *priorityClientPool
 }
 
-func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
+func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
 	var csvLogger *csvlogger.Logger
 	if logFileName != "" {
 		csvLogger = csvlogger.NewLogger(logFileName, time.Second*10, "event, peerId")
 	}
-
-	quitSync := make(chan struct{})
-	pm, err := NewProtocolManager(
-		eth.BlockChain().Config(),
-		light.DefaultServerIndexerConfig,
-		false,
-		config.NetworkId,
-		eth.EventMux(),
-		eth.Engine(),
-		newPeerSet(),
-		eth.BlockChain(),
-		eth.TxPool(),
-		eth.ChainDb(),
-		nil,
-		nil,
-		nil,
-		quitSync,
-		new(sync.WaitGroup),
-		config.ULC,
-		eth.Synced)
-	if err != nil {
-		return nil, err
-	}
-	if logProtocolHandler {
-		pm.logger = csvLogger
-	}
 	requestLogger := csvLogger
 	if !logRequestServing {
 		requestLogger = nil
 	}
-	pm.servingQueue = newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100, requestLogger)
-
 	lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
 	for i, pv := range AdvertiseProtocolVersions {
-		lesTopics[i] = lesTopic(eth.BlockChain().Genesis().Hash(), pv)
+		lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv)
 	}
-
+	quitSync := make(chan struct{})
 	srv := &LesServer{
 		lesCommons: lesCommons{
 			config:           config,
-			chainDb:          eth.ChainDb(),
 			iConfig:          light.DefaultServerIndexerConfig,
-			chtIndexer:       light.NewChtIndexer(eth.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations),
-			bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency),
-			protocolManager:  pm,
+			chainDb:          e.ChainDb(),
+			chtIndexer:       light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations),
+			bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency),
 		},
-		archiveMode:  eth.ArchiveMode(),
+		archiveMode:  e.ArchiveMode(),
 		quitSync:     quitSync,
 		lesTopics:    lesTopics,
 		onlyAnnounce: config.OnlyAnnounce,
 		csvLogger:    csvLogger,
 		logTotalCap:  requestLogger.NewChannel("totalCapacity", 0.01),
 	}
-	srv.costTracker, srv.minCapacity = newCostTracker(eth.ChainDb(), config, requestLogger)
+	srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config, requestLogger)
 
 	logger := log.New()
-	pm.server = srv
 	srv.thcNormal = config.LightServ * 4 / 100
 	if srv.thcNormal < 4 {
 		srv.thcNormal = 4
@@ -141,22 +112,31 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
 	srv.thcBlockProcessing = config.LightServ/100 + 1
 	srv.fcManager = flowcontrol.NewClientManager(nil, &mclock.System{})
 
-	chtSectionCount, _, _ := srv.chtIndexer.Sections()
-	if chtSectionCount != 0 {
-		chtLastSection := chtSectionCount - 1
-		chtSectionHead := srv.chtIndexer.SectionHead(chtLastSection)
-		chtRoot := light.GetChtRoot(pm.chainDb, chtLastSection, chtSectionHead)
-		logger.Info("Loaded CHT", "section", chtLastSection, "head", chtSectionHead, "root", chtRoot)
+	checkpoint := srv.latestLocalCheckpoint()
+	if !checkpoint.Empty() {
+		logger.Info("Loaded latest checkpoint", "section", checkpoint.SectionIndex, "head", checkpoint.SectionHead,
+			"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot)
 	}
-	bloomTrieSectionCount, _, _ := srv.bloomTrieIndexer.Sections()
-	if bloomTrieSectionCount != 0 {
-		bloomTrieLastSection := bloomTrieSectionCount - 1
-		bloomTrieSectionHead := srv.bloomTrieIndexer.SectionHead(bloomTrieLastSection)
-		bloomTrieRoot := light.GetBloomTrieRoot(pm.chainDb, bloomTrieLastSection, bloomTrieSectionHead)
-		logger.Info("Loaded bloom trie", "section", bloomTrieLastSection, "head", bloomTrieSectionHead, "root", bloomTrieRoot)
+
+	srv.chtIndexer.Start(e.BlockChain())
+
+	oracle := config.CheckpointOracle
+	if oracle == nil {
+		oracle = params.CheckpointOracles[e.BlockChain().Genesis().Hash()]
 	}
+	registrar := newCheckpointOracle(oracle, srv.getLocalCheckpoint)
+	// TODO(rjl493456442) Checkpoint is useless for les server, separate handler for client and server.
+	pm, err := NewProtocolManager(e.BlockChain().Config(), nil, light.DefaultServerIndexerConfig, config.ULC, false, config.NetworkId, e.EventMux(), newPeerSet(), e.BlockChain(), e.TxPool(), e.ChainDb(), nil, nil, registrar, quitSync, new(sync.WaitGroup), e.Synced)
+	if err != nil {
+		return nil, err
+	}
+	srv.protocolManager = pm
+	if logProtocolHandler {
+		pm.logger = csvLogger
+	}
+	pm.servingQueue = newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100, requestLogger)
+	pm.server = srv
 
-	srv.chtIndexer.Start(eth.BlockChain())
 	return srv, nil
 }
 
@@ -168,6 +148,12 @@ func (s *LesServer) APIs() []rpc.API {
 			Service:   NewPrivateLightServerAPI(s),
 			Public:    false,
 		},
+		{
+			Namespace: "les",
+			Version:   "1.0",
+			Service:   NewPrivateLightAPI(&s.lesCommons, s.protocolManager.reg),
+			Public:    false,
+		},
 	}
 }
 
@@ -292,6 +278,13 @@ func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) {
 	bloomIndexer.AddChildIndexer(s.bloomTrieIndexer)
 }
 
+// SetClient sets the rpc client and starts running checkpoint contract if it is not yet watched.
+func (s *LesServer) SetContractBackend(backend bind.ContractBackend) {
+	if s.protocolManager.reg != nil {
+		s.protocolManager.reg.start(backend)
+	}
+}
+
 // Stop stops the LES service
 func (s *LesServer) Stop() {
 	s.fcManager.Stop()

+ 145 - 12
les/sync.go

@@ -18,11 +18,29 @@ package les
 
 import (
 	"context"
+	"errors"
 	"time"
 
+	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/eth/downloader"
 	"github.com/ethereum/go-ethereum/light"
+	"github.com/ethereum/go-ethereum/log"
+)
+
+var errInvalidCheckpoint = errors.New("invalid advertised checkpoint")
+
+const (
+	// lightSync starts syncing from the current highest block.
+	// If the chain is empty, syncing the entire header chain.
+	lightSync = iota
+
+	// legacyCheckpointSync starts syncing from a hardcoded checkpoint.
+	legacyCheckpointSync
+
+	// checkpointSync starts syncing from a checkpoint signed by trusted
+	// signer or hardcoded checkpoint for compatibility.
+	checkpointSync
 )
 
 // syncer is responsible for periodically synchronising with the network, both
@@ -54,26 +72,141 @@ func (pm *ProtocolManager) syncer() {
 	}
 }
 
-func (pm *ProtocolManager) needToSync(peerHead blockInfo) bool {
-	head := pm.blockchain.CurrentHeader()
-	currentTd := rawdb.ReadTd(pm.chainDb, head.Hash(), head.Number.Uint64())
-	return currentTd != nil && peerHead.Td.Cmp(currentTd) > 0
+// validateCheckpoint verifies the advertised checkpoint by peer is valid or not.
+//
+// Each network has several hard-coded checkpoint signer addresses. Only the
+// checkpoint issued by the specified signer is considered valid.
+//
+// In addition to the checkpoint registered in the registrar contract, there are
+// several legacy hardcoded checkpoints in our codebase. These checkpoints are
+// also considered as valid.
+func (pm *ProtocolManager) validateCheckpoint(peer *peer) error {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
+	defer cancel()
+
+	// Fetch the block header corresponding to the checkpoint registration.
+	cp := peer.checkpoint
+	header, err := light.GetUntrustedHeaderByNumber(ctx, pm.odr, peer.checkpointNumber, peer.id)
+	if err != nil {
+		return err
+	}
+	// Fetch block logs associated with the block header.
+	logs, err := light.GetUntrustedBlockLogs(ctx, pm.odr, header)
+	if err != nil {
+		return err
+	}
+	events := pm.reg.contract.LookupCheckpointEvents(logs, cp.SectionIndex, cp.Hash())
+	if len(events) == 0 {
+		return errInvalidCheckpoint
+	}
+	var (
+		index      = events[0].Index
+		hash       = events[0].CheckpointHash
+		signatures [][]byte
+	)
+	for _, event := range events {
+		signatures = append(signatures, append(event.R[:], append(event.S[:], event.V)...))
+	}
+	valid, signers := pm.reg.verifySigners(index, hash, signatures)
+	if !valid {
+		return errInvalidCheckpoint
+	}
+	log.Warn("Verified advertised checkpoint", "peer", peer.id, "signers", len(signers))
+	return nil
 }
 
-// synchronise tries to sync up our local block chain with a remote peer.
+// synchronise tries to sync up our local chain with a remote peer.
 func (pm *ProtocolManager) synchronise(peer *peer) {
-	// Short circuit if no peers are available
+	// Short circuit if the peer is nil.
 	if peer == nil {
 		return
 	}
-
 	// Make sure the peer's TD is higher than our own.
-	if !pm.needToSync(peer.headBlockInfo()) {
+	latest := pm.blockchain.CurrentHeader()
+	currentTd := rawdb.ReadTd(pm.chainDb, latest.Hash(), latest.Number.Uint64())
+	if currentTd != nil && peer.headBlockInfo().Td.Cmp(currentTd) < 0 {
 		return
 	}
+	// Recap the checkpoint.
+	//
+	// The light client may be connected to several different versions of the server.
+	// (1) Old version server which can not provide stable checkpoint in the handshake packet.
+	//     => Use hardcoded checkpoint or empty checkpoint
+	// (2) New version server but simple checkpoint syncing is not enabled(e.g. mainnet, new testnet or private network)
+	//     => Use hardcoded checkpoint or empty checkpoint
+	// (3) New version server but the provided stable checkpoint is even lower than the hardcoded one.
+	//     => Use hardcoded checkpoint
+	// (4) New version server with valid and higher stable checkpoint
+	//     => Use provided checkpoint
+	var checkpoint = &peer.checkpoint
+	var hardcoded bool
+	if pm.checkpoint != nil && pm.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex {
+		checkpoint = pm.checkpoint // Use the hardcoded one.
+		hardcoded = true
+	}
+	// Determine whether we should run checkpoint syncing or normal light syncing.
+	//
+	// Here has four situations that we will disable the checkpoint syncing:
+	//
+	// 1. The checkpoint is empty
+	// 2. The latest head block of the local chain is above the checkpoint.
+	// 3. The checkpoint is hardcoded(recap with local hardcoded checkpoint)
+	// 4. For some networks the checkpoint syncing is not activated.
+	mode := checkpointSync
+	switch {
+	case checkpoint.Empty():
+		mode = lightSync
+		log.Debug("Disable checkpoint syncing", "reason", "empty checkpoint")
+	case latest.Number.Uint64() >= (checkpoint.SectionIndex+1)*pm.iConfig.ChtSize-1:
+		mode = lightSync
+		log.Debug("Disable checkpoint syncing", "reason", "local chain beyond the checkpoint")
+	case hardcoded:
+		mode = legacyCheckpointSync
+		log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded")
+	case pm.reg == nil || !pm.reg.isRunning():
+		mode = legacyCheckpointSync
+		log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated")
+	}
+	// Notify testing framework if syncing has completed(for testing purpose).
+	defer func() {
+		if pm.reg != nil && pm.reg.syncDoneHook != nil {
+			pm.reg.syncDoneHook()
+		}
+	}()
+	start := time.Now()
+	if mode == checkpointSync || mode == legacyCheckpointSync {
+		// Validate the advertised checkpoint
+		if mode == legacyCheckpointSync {
+			checkpoint = pm.checkpoint
+		} else if mode == checkpointSync {
+			if err := pm.validateCheckpoint(peer); err != nil {
+				log.Debug("Failed to validate checkpoint", "reason", err)
+				pm.removePeer(peer.id)
+				return
+			}
+			pm.blockchain.(*light.LightChain).AddTrustedCheckpoint(checkpoint)
+		}
+		log.Debug("Checkpoint syncing start", "peer", peer.id, "checkpoint", checkpoint.SectionIndex)
 
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	defer cancel()
-	pm.blockchain.(*light.LightChain).SyncCht(ctx)
-	pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync)
+		// Fetch the start point block header.
+		//
+		// For the ethash consensus engine, the start header is the block header
+		// of the checkpoint.
+		//
+		// For the clique consensus engine, the start header is the block header
+		// of the latest epoch covered by checkpoint.
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
+		defer cancel()
+		if !checkpoint.Empty() && !pm.blockchain.(*light.LightChain).SyncCheckpoint(ctx, checkpoint) {
+			log.Debug("Sync checkpoint failed")
+			pm.removePeer(peer.id)
+			return
+		}
+	}
+	// Fetch the remaining block headers based on the current chain header.
+	if err := pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil {
+		log.Debug("Synchronise failed", "reason", err)
+		return
+	}
+	log.Debug("Synchronise finished", "elapsed", common.PrettyDuration(time.Since(start)))
 }

+ 133 - 0
les/sync_test.go

@@ -0,0 +1,133 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package les
+
+import (
+	"fmt"
+	"math/big"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/light"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+// Test light syncing which will download all headers from genesis.
+func TestLightSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2, 0) }
+func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 0) }
+
+// Test legacy checkpoint syncing which will download tail headers
+// based on a hardcoded checkpoint.
+func TestLegacyCheckpointSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2, 1) }
+func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 1) }
+
+// Test checkpoint syncing which will download tail headers based
+// on a verified checkpoint.
+func TestCheckpointSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2, 2) }
+func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 2) }
+
+func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
+	config := light.TestServerIndexerConfig
+
+	waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
+		for {
+			cs, _, _ := cIndexer.Sections()
+			bts, _, _ := btIndexer.Sections()
+			if cs >= 1 && bts >= 1 {
+				break
+			}
+			time.Sleep(10 * time.Millisecond)
+		}
+	}
+	// Generate 512+4 blocks (totally 1 CHT sections)
+	server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false)
+	defer tearDown()
+
+	expected := config.ChtSize + config.ChtConfirms
+
+	// Checkpoint syncing or legacy checkpoint syncing.
+	if syncMode == 1 || syncMode == 2 {
+		// Assemble checkpoint 0
+		s, _, head := server.chtIndexer.Sections()
+		cp := &params.TrustedCheckpoint{
+			SectionIndex: 0,
+			SectionHead:  head,
+			CHTRoot:      light.GetChtRoot(server.db, s-1, head),
+			BloomRoot:    light.GetBloomTrieRoot(server.db, s-1, head),
+		}
+		if syncMode == 1 {
+			// Register the assembled checkpoint as hardcoded one.
+			client.pm.checkpoint = cp
+			client.pm.blockchain.(*light.LightChain).AddTrustedCheckpoint(cp)
+		} else {
+			// Register the assembled checkpoint into oracle.
+			header := server.backend.Blockchain().CurrentHeader()
+
+			data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
+			sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
+			sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
+			if _, err := server.pm.reg.contract.RegisterCheckpoint(signerKey, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
+				t.Error("register checkpoint failed", err)
+			}
+			server.backend.Commit()
+
+			// Wait for the checkpoint registration
+			for {
+				_, hash, _, err := server.pm.reg.contract.Contract().GetLatestCheckpoint(nil)
+				if err != nil || hash == [32]byte{} {
+					time.Sleep(100 * time.Millisecond)
+					continue
+				}
+				break
+			}
+			expected += 1
+		}
+	}
+
+	done := make(chan error)
+	client.pm.reg.syncDoneHook = func() {
+		header := client.pm.blockchain.CurrentHeader()
+		if header.Number.Uint64() == expected {
+			done <- nil
+		} else {
+			done <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expected, header.Number)
+		}
+	}
+
+	// Create connected peer pair.
+	peer, err1, lPeer, err2 := newTestPeerPair("peer", protocol, server.pm, client.pm)
+	select {
+	case <-time.After(time.Millisecond * 100):
+	case err := <-err1:
+		t.Fatalf("peer 1 handshake error: %v", err)
+	case err := <-err2:
+		t.Fatalf("peer 2 handshake error: %v", err)
+	}
+	server.rPeer, client.rPeer = peer, lPeer
+
+	select {
+	case err := <-done:
+		if err != nil {
+			t.Error("sync failed", err)
+		}
+		return
+	case <-time.NewTimer(10 * time.Second).C:
+		t.Error("checkpoint syncing timeout")
+	}
+}

+ 0 - 0
les/transactions.rlp


+ 10 - 10
les/txrelay.go

@@ -30,7 +30,7 @@ type ltrInfo struct {
 	sentTo map[*peer]struct{}
 }
 
-type LesTxRelay struct {
+type lesTxRelay struct {
 	txSent       map[common.Hash]*ltrInfo
 	txPending    map[common.Hash]struct{}
 	ps           *peerSet
@@ -42,8 +42,8 @@ type LesTxRelay struct {
 	retriever *retrieveManager
 }
 
-func NewLesTxRelay(ps *peerSet, retriever *retrieveManager) *LesTxRelay {
-	r := &LesTxRelay{
+func newLesTxRelay(ps *peerSet, retriever *retrieveManager) *lesTxRelay {
+	r := &lesTxRelay{
 		txSent:    make(map[common.Hash]*ltrInfo),
 		txPending: make(map[common.Hash]struct{}),
 		ps:        ps,
@@ -54,18 +54,18 @@ func NewLesTxRelay(ps *peerSet, retriever *retrieveManager) *LesTxRelay {
 	return r
 }
 
-func (self *LesTxRelay) Stop() {
+func (self *lesTxRelay) Stop() {
 	close(self.stop)
 }
 
-func (self *LesTxRelay) registerPeer(p *peer) {
+func (self *lesTxRelay) registerPeer(p *peer) {
 	self.lock.Lock()
 	defer self.lock.Unlock()
 
 	self.peerList = self.ps.AllPeers()
 }
 
-func (self *LesTxRelay) unregisterPeer(p *peer) {
+func (self *lesTxRelay) unregisterPeer(p *peer) {
 	self.lock.Lock()
 	defer self.lock.Unlock()
 
@@ -74,7 +74,7 @@ func (self *LesTxRelay) unregisterPeer(p *peer) {
 
 // send sends a list of transactions to at most a given number of peers at
 // once, never resending any particular transaction to the same peer twice
-func (self *LesTxRelay) send(txs types.Transactions, count int) {
+func (self *lesTxRelay) send(txs types.Transactions, count int) {
 	sendTo := make(map[*peer]types.Transactions)
 
 	self.peerStartPos++ // rotate the starting position of the peer list
@@ -143,14 +143,14 @@ func (self *LesTxRelay) send(txs types.Transactions, count int) {
 	}
 }
 
-func (self *LesTxRelay) Send(txs types.Transactions) {
+func (self *lesTxRelay) Send(txs types.Transactions) {
 	self.lock.Lock()
 	defer self.lock.Unlock()
 
 	self.send(txs, 3)
 }
 
-func (self *LesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) {
+func (self *lesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) {
 	self.lock.Lock()
 	defer self.lock.Unlock()
 
@@ -173,7 +173,7 @@ func (self *LesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback
 	}
 }
 
-func (self *LesTxRelay) Discard(hashes []common.Hash) {
+func (self *lesTxRelay) Discard(hashes []common.Hash) {
 	self.lock.Lock()
 	defer self.lock.Unlock()
 

+ 10 - 11
les/ulc_test.go

@@ -26,7 +26,6 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/eth"
@@ -36,7 +35,7 @@ import (
 )
 
 func TestULCSyncWithOnePeer(t *testing.T) {
-	f := newFullPeerPair(t, 1, 4, testChainGen)
+	f := newFullPeerPair(t, 1, 4)
 	ulcConfig := &eth.ULCConfig{
 		MinTrustedFraction: 100,
 		TrustedServers:     []string{f.Node.String()},
@@ -63,7 +62,7 @@ func TestULCSyncWithOnePeer(t *testing.T) {
 }
 
 func TestULCReceiveAnnounce(t *testing.T) {
-	f := newFullPeerPair(t, 1, 4, testChainGen)
+	f := newFullPeerPair(t, 1, 4)
 	ulcConfig := &eth.ULCConfig{
 		MinTrustedFraction: 100,
 		TrustedServers:     []string{f.Node.String()},
@@ -100,8 +99,8 @@ func TestULCReceiveAnnounce(t *testing.T) {
 }
 
 func TestULCShouldNotSyncWithTwoPeersOneHaveEmptyChain(t *testing.T) {
-	f1 := newFullPeerPair(t, 1, 4, testChainGen)
-	f2 := newFullPeerPair(t, 2, 0, nil)
+	f1 := newFullPeerPair(t, 1, 4)
+	f2 := newFullPeerPair(t, 2, 0)
 	ulcConf := &ulc{minTrustedFraction: 100, trustedKeys: make(map[string]struct{})}
 	ulcConf.trustedKeys[f1.Node.ID().String()] = struct{}{}
 	ulcConf.trustedKeys[f2.Node.ID().String()] = struct{}{}
@@ -131,9 +130,9 @@ func TestULCShouldNotSyncWithTwoPeersOneHaveEmptyChain(t *testing.T) {
 }
 
 func TestULCShouldNotSyncWithThreePeersOneHaveEmptyChain(t *testing.T) {
-	f1 := newFullPeerPair(t, 1, 3, testChainGen)
-	f2 := newFullPeerPair(t, 2, 4, testChainGen)
-	f3 := newFullPeerPair(t, 3, 0, nil)
+	f1 := newFullPeerPair(t, 1, 3)
+	f2 := newFullPeerPair(t, 2, 4)
+	f3 := newFullPeerPair(t, 3, 0)
 
 	ulcConfig := &eth.ULCConfig{
 		MinTrustedFraction: 60,
@@ -211,10 +210,10 @@ func connectPeers(full, light pairPeer, version int) (*peer, *peer, error) {
 }
 
 // newFullPeerPair creates node with full sync mode
-func newFullPeerPair(t *testing.T, index int, numberOfblocks int, chainGen func(int, *core.BlockGen)) pairPeer {
+func newFullPeerPair(t *testing.T, index int, numberOfblocks int) pairPeer {
 	db := rawdb.NewMemoryDatabase()
 
-	pmFull := newTestProtocolManagerMust(t, false, numberOfblocks, chainGen, nil, nil, db, nil)
+	pmFull, _ := newTestProtocolManagerMust(t, false, numberOfblocks, nil, nil, nil, db, nil)
 
 	peerPairFull := pairPeer{
 		Name: "full node",
@@ -238,7 +237,7 @@ func newLightPeer(t *testing.T, ulcConfig *eth.ULCConfig) pairPeer {
 
 	odr := NewLesOdr(ldb, light.DefaultClientIndexerConfig, rm)
 
-	pmLight := newTestProtocolManagerMust(t, true, 0, nil, odr, peers, ldb, ulcConfig)
+	pmLight, _ := newTestProtocolManagerMust(t, true, 0, odr, nil, peers, ldb, ulcConfig)
 	peerPairLight := pairPeer{
 		Name: "ulc node",
 		PM:   pmLight,

+ 15 - 15
light/lightchain.go

@@ -77,7 +77,7 @@ type LightChain struct {
 // NewLightChain returns a fully initialised light chain using information
 // available in the database. It initialises the default Ethereum header
 // validator.
-func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine) (*LightChain, error) {
+func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine, checkpoint *params.TrustedCheckpoint) (*LightChain, error) {
 	bodyCache, _ := lru.New(bodyCacheLimit)
 	bodyRLPCache, _ := lru.New(bodyCacheLimit)
 	blockCache, _ := lru.New(blockCacheLimit)
@@ -101,8 +101,8 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
 	if bc.genesisBlock == nil {
 		return nil, core.ErrNoGenesis
 	}
-	if cp, ok := params.TrustedCheckpoints[bc.genesisBlock.Hash()]; ok {
-		bc.addTrustedCheckpoint(cp)
+	if checkpoint != nil {
+		bc.AddTrustedCheckpoint(checkpoint)
 	}
 	if err := bc.loadLastState(); err != nil {
 		return nil, err
@@ -118,8 +118,8 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
 	return bc, nil
 }
 
-// addTrustedCheckpoint adds a trusted checkpoint to the blockchain
-func (lc *LightChain) addTrustedCheckpoint(cp *params.TrustedCheckpoint) {
+// AddTrustedCheckpoint adds a trusted checkpoint to the blockchain
+func (lc *LightChain) AddTrustedCheckpoint(cp *params.TrustedCheckpoint) {
 	if lc.odr.ChtIndexer() != nil {
 		StoreChtRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.CHTRoot)
 		lc.odr.ChtIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead)
@@ -131,7 +131,7 @@ func (lc *LightChain) addTrustedCheckpoint(cp *params.TrustedCheckpoint) {
 	if lc.odr.BloomIndexer() != nil {
 		lc.odr.BloomIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead)
 	}
-	log.Info("Added trusted checkpoint", "chain", cp.Name, "block", (cp.SectionIndex+1)*lc.indexerConfig.ChtSize-1, "hash", cp.SectionHead)
+	log.Info("Added trusted checkpoint", "block", (cp.SectionIndex+1)*lc.indexerConfig.ChtSize-1, "hash", cp.SectionHead)
 }
 
 func (lc *LightChain) getProcInterrupt() bool {
@@ -462,21 +462,21 @@ func (lc *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64) (
 // Config retrieves the header chain's chain configuration.
 func (lc *LightChain) Config() *params.ChainConfig { return lc.hc.Config() }
 
-func (lc *LightChain) SyncCht(ctx context.Context) bool {
-	// If we don't have a CHT indexer, abort
-	if lc.odr.ChtIndexer() == nil {
-		return false
-	}
-	// Ensure the remote CHT head is ahead of us
+// SyncCheckpoint fetches the checkpoint point block header according to
+// the checkpoint provided by the remote peer.
+//
+// Note if we are running the clique, fetches the last epoch snapshot header
+// which covered by checkpoint.
+func (lc *LightChain) SyncCheckpoint(ctx context.Context, checkpoint *params.TrustedCheckpoint) bool {
+	// Ensure the remote checkpoint head is ahead of us
 	head := lc.CurrentHeader().Number.Uint64()
-	sections, _, _ := lc.odr.ChtIndexer().Sections()
 
-	latest := sections*lc.indexerConfig.ChtSize - 1
+	latest := (checkpoint.SectionIndex+1)*lc.indexerConfig.ChtSize - 1
 	if clique := lc.hc.Config().Clique; clique != nil {
 		latest -= latest % clique.Epoch // epoch snapshot for clique
 	}
 	if head >= latest {
-		return false
+		return true
 	}
 	// Retrieve the latest useful header and update to it
 	if header, err := GetHeaderByNumber(ctx, lc.odr, latest); header != nil && err == nil {

+ 3 - 3
light/lightchain_test.go

@@ -55,7 +55,7 @@ func newCanonical(n int) (ethdb.Database, *LightChain, error) {
 	db := rawdb.NewMemoryDatabase()
 	gspec := core.Genesis{Config: params.TestChainConfig}
 	genesis := gspec.MustCommit(db)
-	blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker())
+	blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker(), nil)
 
 	// Create and inject the requested chain
 	if n == 0 {
@@ -75,7 +75,7 @@ func newTestLightChain() *LightChain {
 		Config:     params.TestChainConfig,
 	}
 	gspec.MustCommit(db)
-	lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker())
+	lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker(), nil)
 	if err != nil {
 		panic(err)
 	}
@@ -344,7 +344,7 @@ func TestReorgBadHeaderHashes(t *testing.T) {
 	defer func() { delete(core.BadHashes, headers[3].Hash()) }()
 
 	// Create a new LightChain and check that it rolled back the state.
-	ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker())
+	ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker(), nil)
 	if err != nil {
 		t.Fatalf("failed to create new chain manager: %v", err)
 	}

+ 15 - 7
light/odr.go

@@ -122,19 +122,25 @@ func (req *BlockRequest) StoreResult(db ethdb.Database) {
 // ReceiptsRequest is the ODR request type for retrieving block bodies
 type ReceiptsRequest struct {
 	OdrRequest
-	Hash     common.Hash
-	Number   uint64
-	Receipts types.Receipts
+	Untrusted bool // Indicator whether the result retrieved is trusted or not
+	Hash      common.Hash
+	Number    uint64
+	Header    *types.Header
+	Receipts  types.Receipts
 }
 
 // StoreResult stores the retrieved data in local database
 func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
-	rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts)
+	if !req.Untrusted {
+		rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts)
+	}
 }
 
 // ChtRequest is the ODR request type for state/storage trie entries
 type ChtRequest struct {
 	OdrRequest
+	Untrusted        bool   // Indicator whether the result retrieved is trusted or not
+	PeerId           string // The specified peer id from which to retrieve data.
 	Config           *IndexerConfig
 	ChtNum, BlockNum uint64
 	ChtRoot          common.Hash
@@ -147,9 +153,11 @@ type ChtRequest struct {
 func (req *ChtRequest) StoreResult(db ethdb.Database) {
 	hash, num := req.Header.Hash(), req.Header.Number.Uint64()
 
-	rawdb.WriteHeader(db, req.Header)
-	rawdb.WriteTd(db, hash, num, req.Td)
-	rawdb.WriteCanonicalHash(db, hash, num)
+	if !req.Untrusted {
+		rawdb.WriteHeader(db, req.Header)
+		rawdb.WriteTd(db, hash, num, req.Td)
+		rawdb.WriteCanonicalHash(db, hash, num)
+	}
 }
 
 // BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure

+ 1 - 1
light/odr_test.go

@@ -264,7 +264,7 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) {
 	}
 
 	odr := &testOdr{sdb: sdb, ldb: ldb, indexerConfig: TestClientIndexerConfig}
-	lightchain, err := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker())
+	lightchain, err := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), nil)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 34 - 0
light/odr_util.go

@@ -69,6 +69,16 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ
 	return r.Header, nil
 }
 
+// GetUntrustedHeaderByNumber fetches specified block header without correctness checking.
+// Note this function should only be used in light client checkpoint syncing.
+func GetUntrustedHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64, peerId string) (*types.Header, error) {
+	r := &ChtRequest{BlockNum: number, ChtNum: number / odr.IndexerConfig().ChtSize, Untrusted: true, PeerId: peerId, Config: odr.IndexerConfig()}
+	if err := odr.Retrieve(ctx, r); err != nil {
+		return nil, err
+	}
+	return r.Header, nil
+}
+
 func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) {
 	hash := rawdb.ReadCanonicalHash(odr.Database(), number)
 	if (hash != common.Hash{}) {
@@ -169,6 +179,30 @@ func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number
 	return logs, nil
 }
 
+// GetUntrustedBlockLogs retrieves the logs generated by the transactions included in a
+// block. The retrieved logs are regarded as untrusted and will not be stored in the
+// database. This function should only be used in light client checkpoint syncing.
+func GetUntrustedBlockLogs(ctx context.Context, odr OdrBackend, header *types.Header) ([][]*types.Log, error) {
+	// Retrieve the potentially incomplete receipts from disk or network
+	hash, number := header.Hash(), header.Number.Uint64()
+	receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number)
+	if receipts == nil {
+		r := &ReceiptsRequest{Hash: hash, Number: number, Header: header, Untrusted: true}
+		if err := odr.Retrieve(ctx, r); err != nil {
+			return nil, err
+		}
+		receipts = r.Receipts
+		// Untrusted receipts won't be stored in the database. Therefore
+		// derived fields computation is unnecessary.
+	}
+	// Return the logs without deriving any computed fields on the receipts
+	logs := make([][]*types.Log, len(receipts))
+	for i, receipt := range receipts {
+		logs[i] = receipt.Logs
+	}
+	return logs, nil
+}
+
 // GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to the given bit index and section indexes
 func GetBloomBits(ctx context.Context, odr OdrBackend, bitIdx uint, sectionIdxList []uint64) ([][]byte, error) {
 	var (

+ 1 - 1
light/txpool_test.go

@@ -100,7 +100,7 @@ func TestTxPool(t *testing.T) {
 		discard: make(chan int, 1),
 		mined:   make(chan int, 1),
 	}
-	lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker())
+	lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), nil)
 	txPermanent = 50
 	pool := NewTxPool(params.TestChainConfig, lightchain, relay)
 	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)

+ 0 - 1
node/node.go

@@ -247,7 +247,6 @@ func (n *Node) Start() error {
 	n.services = services
 	n.server = running
 	n.stop = make(chan struct{})
-
 	return nil
 }
 

+ 1 - 1
p2p/message.go

@@ -169,7 +169,7 @@ type MsgPipeRW struct {
 	closed  *int32
 }
 
-// WriteMsg sends a messsage on the pipe.
+// WriteMsg sends a message on the pipe.
 // It blocks until the receiver has consumed the message payload.
 func (p *MsgPipeRW) WriteMsg(msg Msg) error {
 	if atomic.LoadInt32(p.closed) == 0 {

+ 51 - 5
params/config.go

@@ -17,10 +17,12 @@
 package params
 
 import (
+	"encoding/binary"
 	"fmt"
 	"math/big"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
 )
 
 // Genesis hashes to enforce below configs on.
@@ -40,6 +42,12 @@ var TrustedCheckpoints = map[common.Hash]*TrustedCheckpoint{
 	GoerliGenesisHash:  GoerliTrustedCheckpoint,
 }
 
+// CheckpointOracles associates each known checkpoint oracles with the genesis hash of
+// the chain it belongs to.
+var CheckpointOracles = map[common.Hash]*CheckpointOracleConfig{
+	RinkebyGenesisHash: RinkebyCheckpointOracle,
+}
+
 var (
 	// MainnetChainConfig is the chain parameters to run a node on the main network.
 	MainnetChainConfig = &ChainConfig{
@@ -59,7 +67,6 @@ var (
 
 	// MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network.
 	MainnetTrustedCheckpoint = &TrustedCheckpoint{
-		Name:         "mainnet",
 		SectionIndex: 227,
 		SectionHead:  common.HexToHash("0xa2e0b25d72c2fc6e35a7f853cdacb193b4b4f95c606accf7f8fa8415283582c7"),
 		CHTRoot:      common.HexToHash("0xf69bdd4053b95b61a27b106a0e86103d791edd8574950dc96aa351ab9b9f1aa0"),
@@ -84,7 +91,6 @@ var (
 
 	// TestnetTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network.
 	TestnetTrustedCheckpoint = &TrustedCheckpoint{
-		Name:         "testnet",
 		SectionIndex: 161,
 		SectionHead:  common.HexToHash("0x5378afa734e1feafb34bcca1534c4d96952b754579b96a4afb23d5301ecececc"),
 		CHTRoot:      common.HexToHash("0x1cf2b071e7443a62914362486b613ff30f60cea0d9c268ed8c545f876a3ee60c"),
@@ -112,13 +118,24 @@ var (
 
 	// RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network.
 	RinkebyTrustedCheckpoint = &TrustedCheckpoint{
-		Name:         "rinkeby",
 		SectionIndex: 125,
 		SectionHead:  common.HexToHash("0x8a738386f6bb34add15846f8f49c4c519a2f32519096e792b9f43bcb407c831c"),
 		CHTRoot:      common.HexToHash("0xa1e5720a9bad4dce794f129e4ac6744398197b652868011486a6f89c8ec84a75"),
 		BloomRoot:    common.HexToHash("0xa3048fe8b7e30f77f11bc755a88478363d7d3e71c2bdfe4e8ab9e269cd804ba2"),
 	}
 
+	// RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle.
+	RinkebyCheckpointOracle = &CheckpointOracleConfig{
+		Address: common.HexToAddress("0xebe8eFA441B9302A0d7eaECc277c09d20D684540"),
+		Signers: []common.Address{
+			common.HexToAddress("0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3"), // Peter
+			common.HexToAddress("0x78d1ad571a1a09d60d9bbf25894b44e4c8859595"), // Martin
+			common.HexToAddress("0x286834935f4A8Cfb4FF4C77D5770C2775aE2b0E7"), // Zsolt
+			common.HexToAddress("0xb86e2B0Ab5A4B1373e40c51A7C712c70Ba2f9f8E"), // Gary
+		},
+		Threshold: 2,
+	}
+
 	// GoerliChainConfig contains the chain parameters to run a node on the Görli test network.
 	GoerliChainConfig = &ChainConfig{
 		ChainID:             big.NewInt(5),
@@ -139,7 +156,6 @@ var (
 
 	// GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network.
 	GoerliTrustedCheckpoint = &TrustedCheckpoint{
-		Name:         "goerli",
 		SectionIndex: 9,
 		SectionHead:  common.HexToHash("0x8e223d827391eee53b07cb8ee057dbfa11c93e0b45352188c783affd7840a921"),
 		CHTRoot:      common.HexToHash("0xe0a817ac69b36c1e437c5b0cff9e764853f5115702b5f66d451b665d6afb7e78"),
@@ -169,13 +185,43 @@ var (
 // used to start light syncing from this checkpoint and avoid downloading the
 // entire header chain while still being able to securely access old headers/logs.
 type TrustedCheckpoint struct {
-	Name         string      `json:"-"`
 	SectionIndex uint64      `json:"sectionIndex"`
 	SectionHead  common.Hash `json:"sectionHead"`
 	CHTRoot      common.Hash `json:"chtRoot"`
 	BloomRoot    common.Hash `json:"bloomRoot"`
 }
 
+// HashEqual returns an indicator comparing the itself hash with given one.
+func (c *TrustedCheckpoint) HashEqual(hash common.Hash) bool {
+	if c.Empty() {
+		return hash == common.Hash{}
+	}
+	return c.Hash() == hash
+}
+
+// Hash returns the hash of checkpoint's four key fields(index, sectionHead, chtRoot and bloomTrieRoot).
+func (c *TrustedCheckpoint) Hash() common.Hash {
+	buf := make([]byte, 8+3*common.HashLength)
+	binary.BigEndian.PutUint64(buf, c.SectionIndex)
+	copy(buf[8:], c.SectionHead.Bytes())
+	copy(buf[8+common.HashLength:], c.CHTRoot.Bytes())
+	copy(buf[8+2*common.HashLength:], c.BloomRoot.Bytes())
+	return crypto.Keccak256Hash(buf)
+}
+
+// Empty returns an indicator whether the checkpoint is regarded as empty.
+func (c *TrustedCheckpoint) Empty() bool {
+	return c.SectionHead == (common.Hash{}) || c.CHTRoot == (common.Hash{}) || c.BloomRoot == (common.Hash{})
+}
+
+// CheckpointOracleConfig represents a set of checkpoint contract(which acts as an oracle)
+// config which used for light client checkpoint syncing.
+type CheckpointOracleConfig struct {
+	Address   common.Address   `json:"address"`
+	Signers   []common.Address `json:"signers"`
+	Threshold uint64           `json:"threshold"`
+}
+
 // ChainConfig is the core config which determines the blockchain settings.
 //
 // ChainConfig is stored in the database on a per block basis. This means

+ 6 - 0
params/network_params.go

@@ -47,6 +47,12 @@ const (
 	// is generated
 	HelperTrieProcessConfirmations = 256
 
+	// CheckpointFrequency is the block frequency for creating checkpoint
+	CheckpointFrequency = 32768
+
+	// CheckpointProcessConfirmations is the number before a checkpoint is generated
+	CheckpointProcessConfirmations = 256
+
 	// ImmutabilityThreshold is the number of blocks after which a chain segment is
 	// considered immutable (i.e. soft finality). It is used by the downloader as a
 	// hard limit against deep ancestors, by the blockchain against deep reorgs, by

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác