From f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9 Mon Sep 17 00:00:00 2001
From: gary rong <garyrong0905@gmail.com>
Date: Fri, 28 Jun 2019 15:34:02 +0800
Subject: [PATCH] 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
---
 accounts/abi/bind/backends/simulated.go       |  24 +-
 accounts/abi/bind/bind_test.go                |   3 +-
 accounts/abi/bind/template.go                 |  12 +
 accounts/abi/bind/util_test.go                |   3 +-
 cmd/checkpoint-admin/common.go                | 166 +++++++
 cmd/checkpoint-admin/exec.go                  | 335 ++++++++++++++
 cmd/checkpoint-admin/main.go                  | 124 ++++++
 cmd/checkpoint-admin/status.go                |  61 +++
 cmd/geth/main.go                              |  35 +-
 cmd/puppeth/wizard_genesis.go                 |  17 +-
 contracts/checkpointoracle/contract/oracle.go | 415 ++++++++++++++++++
 .../checkpointoracle/contract/oracle.sol      | 174 ++++++++
 contracts/checkpointoracle/oracle.go          |  91 ++++
 contracts/checkpointoracle/oracle_test.go     | 333 ++++++++++++++
 core/chain_indexer.go                         |   7 +-
 eth/backend.go                                |  21 +-
 eth/config.go                                 |   6 +
 eth/gen_config.go                             |  19 +
 eth/handler.go                                |   4 +-
 eth/handler_test.go                           |  15 +-
 eth/helper_test.go                            |   2 +-
 internal/web3ext/web3ext.go                   |  26 ++
 les/api.go                                    |  58 +++
 les/backend.go                                |  66 +--
 les/checkpointoracle.go                       | 158 +++++++
 les/commons.go                                |  52 ++-
 les/handler.go                                |  34 +-
 les/handler_test.go                           |  47 +-
 les/helper_test.go                            | 249 +++++++----
 les/odr_requests.go                           |  59 ++-
 les/odr_test.go                               |   8 +-
 les/peer.go                                   |  27 +-
 les/request_test.go                           |   2 +-
 les/server.go                                 |  95 ++--
 les/sync.go                                   | 157 ++++++-
 les/sync_test.go                              | 133 ++++++
 les/transactions.rlp                          |   0
 les/txrelay.go                                |  20 +-
 les/ulc_test.go                               |  21 +-
 light/lightchain.go                           |  30 +-
 light/lightchain_test.go                      |   6 +-
 light/odr.go                                  |  22 +-
 light/odr_test.go                             |   2 +-
 light/odr_util.go                             |  34 ++
 light/txpool_test.go                          |   2 +-
 node/node.go                                  |   1 -
 p2p/message.go                                |   2 +-
 params/config.go                              |  56 ++-
 params/network_params.go                      |   6 +
 49 files changed, 2859 insertions(+), 381 deletions(-)
 create mode 100644 cmd/checkpoint-admin/common.go
 create mode 100644 cmd/checkpoint-admin/exec.go
 create mode 100644 cmd/checkpoint-admin/main.go
 create mode 100644 cmd/checkpoint-admin/status.go
 create mode 100644 contracts/checkpointoracle/contract/oracle.go
 create mode 100644 contracts/checkpointoracle/contract/oracle.sol
 create mode 100644 contracts/checkpointoracle/oracle.go
 create mode 100644 contracts/checkpointoracle/oracle_test.go
 create mode 100644 les/checkpointoracle.go
 create mode 100644 les/sync_test.go
 delete mode 100755 les/transactions.rlp

diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 236c57564..6c59092b7 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/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
diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go
index c3760ed66..e32a7d743 100644
--- a/accounts/abi/bind/bind_test.go
+++ b/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 {
diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go
index 9ea503803..f5a0ca6ef 100644
--- a/accounts/abi/bind/template.go
+++ b/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}}
 `
diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go
index 8f4092971..87bc29822 100644
--- a/accounts/abi/bind/util_test.go
+++ b/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.
diff --git a/cmd/checkpoint-admin/common.go b/cmd/checkpoint-admin/common.go
new file mode 100644
index 000000000..1f4a34a41
--- /dev/null
+++ b/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
+}
diff --git a/cmd/checkpoint-admin/exec.go b/cmd/checkpoint-admin/exec.go
new file mode 100644
index 000000000..02c4f35cc
--- /dev/null
+++ b/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
+}
diff --git a/cmd/checkpoint-admin/main.go b/cmd/checkpoint-admin/main.go
new file mode 100644
index 000000000..39eae6878
--- /dev/null
+++ b/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)
+	}
+}
diff --git a/cmd/checkpoint-admin/status.go b/cmd/checkpoint-admin/status.go
new file mode 100644
index 000000000..c134ec090
--- /dev/null
+++ b/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
+}
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 00809e2e1..7e94da1f5 100644
--- a/cmd/geth/main.go
+++ b/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()
 				}
-
 			}
 		}()
 	}
diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go
index 6aed09f14..499f320f6 100644
--- a/cmd/puppeth/wizard_genesis.go
+++ b/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, "", "  ")
diff --git a/contracts/checkpointoracle/contract/oracle.go b/contracts/checkpointoracle/contract/oracle.go
new file mode 100644
index 000000000..3bb351792
--- /dev/null
+++ b/contracts/checkpointoracle/contract/oracle.go
@@ -0,0 +1,415 @@
+// Code generated - DO NOT EDIT.
+// This file is a generated binding and any manual changes will be lost.
+
+package contract
+
+import (
+	"math/big"
+	"strings"
+
+	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum/accounts/abi"
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/event"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var (
+	_ = big.NewInt
+	_ = strings.NewReader
+	_ = ethereum.NotFound
+	_ = abi.U256
+	_ = bind.Bind
+	_ = common.Big1
+	_ = types.BloomLookup
+	_ = event.NewSubscription
+)
+
+// CheckpointOracleABI is the input ABI used to generate the binding from.
+const CheckpointOracleABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint64\"},{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"name\":\"_hash\",\"type\":\"bytes32\"},{\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"name\":\"v\",\"type\":\"uint8[]\"},{\"name\":\"r\",\"type\":\"bytes32[]\"},{\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"name\":\"_threshold\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"}]"
+
+// CheckpointOracleBin is the compiled bytecode used for deploying new contracts.
+const CheckpointOracleBin = `0x608060405234801561001057600080fd5b506040516108153803806108158339818101604052608081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8201602081018481111561005e57600080fd5b815185602082028301116401000000008211171561007b57600080fd5b505060208201516040830151606090930151919450925060005b84518110156101415760016000808784815181106100af57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff02191690831515021790555060018582815181106100fc57fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b039093169290921790915501610095565b50600592909255600655600755506106b78061015e6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a661034f565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061036a945050505050565b604080519115158252519081900360200190f35b6060806001805490506040519080825280602002602001820160405280156102e2578160200160208202803883390190505b50905060005b60015481101561034957600181815481106102ff57fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061032957fe5b6001600160a01b03909216602092830291909101909101526001016102e8565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661038657600080fd5b8688401461039357600080fd5b82518451146103a157600080fd5b81518451146103af57600080fd5b6006546005548660010167ffffffffffffffff1602014310156103d457506000610677565b60025467ffffffffffffffff90811690861610156103f457506000610677565b60025467ffffffffffffffff8681169116148015610426575067ffffffffffffffff8516151580610426575060035415155b1561043357506000610677565b8561044057506000610677565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106715760006001848984815181106104b457fe5b60200260200101518985815181106104c857fe5b60200260200101518986815181106104dc57fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa15801561053b573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061057057600080fd5b826001600160a01b0316816001600160a01b03161161058e57600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105ca57fe5b60200260200101518a86815181106105de57fe5b60200260200101518a87815181106105f257fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a260075482600101106106685750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff86161790556001610677565b5060010161049a565b50600080fd5b97965050505050505056fea265627a7a723058207f6a191ce575596a2f1e907c8c0a01003d16b69fb2c4f432d10878e8c0a99a0264736f6c634300050a0032`
+
+// DeployCheckpointOracle deploys a new Ethereum contract, binding an instance of CheckpointOracle to it.
+func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBackend, _adminlist []common.Address, _sectionSize *big.Int, _processConfirms *big.Int, _threshold *big.Int) (common.Address, *types.Transaction, *CheckpointOracle, error) {
+	parsed, err := abi.JSON(strings.NewReader(CheckpointOracleABI))
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(CheckpointOracleBin), backend, _adminlist, _sectionSize, _processConfirms, _threshold)
+	if err != nil {
+		return common.Address{}, nil, nil, err
+	}
+	return address, tx, &CheckpointOracle{CheckpointOracleCaller: CheckpointOracleCaller{contract: contract}, CheckpointOracleTransactor: CheckpointOracleTransactor{contract: contract}, CheckpointOracleFilterer: CheckpointOracleFilterer{contract: contract}}, nil
+}
+
+// CheckpointOracle is an auto generated Go binding around an Ethereum contract.
+type CheckpointOracle struct {
+	CheckpointOracleCaller     // Read-only binding to the contract
+	CheckpointOracleTransactor // Write-only binding to the contract
+	CheckpointOracleFilterer   // Log filterer for contract events
+}
+
+// CheckpointOracleCaller is an auto generated read-only Go binding around an Ethereum contract.
+type CheckpointOracleCaller struct {
+	contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// CheckpointOracleTransactor is an auto generated write-only Go binding around an Ethereum contract.
+type CheckpointOracleTransactor struct {
+	contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// CheckpointOracleFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
+type CheckpointOracleFilterer struct {
+	contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// CheckpointOracleSession is an auto generated Go binding around an Ethereum contract,
+// with pre-set call and transact options.
+type CheckpointOracleSession struct {
+	Contract     *CheckpointOracle // Generic contract binding to set the session for
+	CallOpts     bind.CallOpts     // Call options to use throughout this session
+	TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// CheckpointOracleCallerSession is an auto generated read-only Go binding around an Ethereum contract,
+// with pre-set call options.
+type CheckpointOracleCallerSession struct {
+	Contract *CheckpointOracleCaller // Generic contract caller binding to set the session for
+	CallOpts bind.CallOpts           // Call options to use throughout this session
+}
+
+// CheckpointOracleTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
+// with pre-set transact options.
+type CheckpointOracleTransactorSession struct {
+	Contract     *CheckpointOracleTransactor // Generic contract transactor binding to set the session for
+	TransactOpts bind.TransactOpts           // Transaction auth options to use throughout this session
+}
+
+// CheckpointOracleRaw is an auto generated low-level Go binding around an Ethereum contract.
+type CheckpointOracleRaw struct {
+	Contract *CheckpointOracle // Generic contract binding to access the raw methods on
+}
+
+// CheckpointOracleCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
+type CheckpointOracleCallerRaw struct {
+	Contract *CheckpointOracleCaller // Generic read-only contract binding to access the raw methods on
+}
+
+// CheckpointOracleTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
+type CheckpointOracleTransactorRaw struct {
+	Contract *CheckpointOracleTransactor // Generic write-only contract binding to access the raw methods on
+}
+
+// NewCheckpointOracle creates a new instance of CheckpointOracle, bound to a specific deployed contract.
+func NewCheckpointOracle(address common.Address, backend bind.ContractBackend) (*CheckpointOracle, error) {
+	contract, err := bindCheckpointOracle(address, backend, backend, backend)
+	if err != nil {
+		return nil, err
+	}
+	return &CheckpointOracle{CheckpointOracleCaller: CheckpointOracleCaller{contract: contract}, CheckpointOracleTransactor: CheckpointOracleTransactor{contract: contract}, CheckpointOracleFilterer: CheckpointOracleFilterer{contract: contract}}, nil
+}
+
+// NewCheckpointOracleCaller creates a new read-only instance of CheckpointOracle, bound to a specific deployed contract.
+func NewCheckpointOracleCaller(address common.Address, caller bind.ContractCaller) (*CheckpointOracleCaller, error) {
+	contract, err := bindCheckpointOracle(address, caller, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &CheckpointOracleCaller{contract: contract}, nil
+}
+
+// NewCheckpointOracleTransactor creates a new write-only instance of CheckpointOracle, bound to a specific deployed contract.
+func NewCheckpointOracleTransactor(address common.Address, transactor bind.ContractTransactor) (*CheckpointOracleTransactor, error) {
+	contract, err := bindCheckpointOracle(address, nil, transactor, nil)
+	if err != nil {
+		return nil, err
+	}
+	return &CheckpointOracleTransactor{contract: contract}, nil
+}
+
+// NewCheckpointOracleFilterer creates a new log filterer instance of CheckpointOracle, bound to a specific deployed contract.
+func NewCheckpointOracleFilterer(address common.Address, filterer bind.ContractFilterer) (*CheckpointOracleFilterer, error) {
+	contract, err := bindCheckpointOracle(address, nil, nil, filterer)
+	if err != nil {
+		return nil, err
+	}
+	return &CheckpointOracleFilterer{contract: contract}, nil
+}
+
+// bindCheckpointOracle binds a generic wrapper to an already deployed contract.
+func bindCheckpointOracle(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+	parsed, err := abi.JSON(strings.NewReader(CheckpointOracleABI))
+	if err != nil {
+		return nil, err
+	}
+	return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_CheckpointOracle *CheckpointOracleRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
+	return _CheckpointOracle.Contract.CheckpointOracleCaller.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_CheckpointOracle *CheckpointOracleRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _CheckpointOracle.Contract.CheckpointOracleTransactor.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_CheckpointOracle *CheckpointOracleRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _CheckpointOracle.Contract.CheckpointOracleTransactor.contract.Transact(opts, method, params...)
+}
+
+// Call invokes the (constant) contract method with params as input values and
+// sets the output to result. The result type might be a single field for simple
+// returns, a slice of interfaces for anonymous returns and a struct for named
+// returns.
+func (_CheckpointOracle *CheckpointOracleCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
+	return _CheckpointOracle.Contract.contract.Call(opts, result, method, params...)
+}
+
+// Transfer initiates a plain transaction to move funds to the contract, calling
+// its default method if one is available.
+func (_CheckpointOracle *CheckpointOracleTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+	return _CheckpointOracle.Contract.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_CheckpointOracle *CheckpointOracleTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+	return _CheckpointOracle.Contract.contract.Transact(opts, method, params...)
+}
+
+// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
+//
+// Solidity: function GetAllAdmin() constant returns(address[])
+func (_CheckpointOracle *CheckpointOracleCaller) GetAllAdmin(opts *bind.CallOpts) ([]common.Address, error) {
+	var (
+		ret0 = new([]common.Address)
+	)
+	out := ret0
+	err := _CheckpointOracle.contract.Call(opts, out, "GetAllAdmin")
+	return *ret0, err
+}
+
+// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
+//
+// Solidity: function GetAllAdmin() constant returns(address[])
+func (_CheckpointOracle *CheckpointOracleSession) GetAllAdmin() ([]common.Address, error) {
+	return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts)
+}
+
+// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
+//
+// Solidity: function GetAllAdmin() constant returns(address[])
+func (_CheckpointOracle *CheckpointOracleCallerSession) GetAllAdmin() ([]common.Address, error) {
+	return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts)
+}
+
+// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
+//
+// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
+func (_CheckpointOracle *CheckpointOracleCaller) GetLatestCheckpoint(opts *bind.CallOpts) (uint64, [32]byte, *big.Int, error) {
+	var (
+		ret0 = new(uint64)
+		ret1 = new([32]byte)
+		ret2 = new(*big.Int)
+	)
+	out := &[]interface{}{
+		ret0,
+		ret1,
+		ret2,
+	}
+	err := _CheckpointOracle.contract.Call(opts, out, "GetLatestCheckpoint")
+	return *ret0, *ret1, *ret2, err
+}
+
+// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
+//
+// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
+func (_CheckpointOracle *CheckpointOracleSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) {
+	return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts)
+}
+
+// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
+//
+// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
+func (_CheckpointOracle *CheckpointOracleCallerSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) {
+	return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts)
+}
+
+// SetCheckpoint is a paid mutator transaction binding the contract method 0xd459fc46.
+//
+// Solidity: function SetCheckpoint(uint256 _recentNumber, bytes32 _recentHash, bytes32 _hash, uint64 _sectionIndex, uint8[] v, bytes32[] r, bytes32[] s) returns(bool)
+func (_CheckpointOracle *CheckpointOracleTransactor) SetCheckpoint(opts *bind.TransactOpts, _recentNumber *big.Int, _recentHash [32]byte, _hash [32]byte, _sectionIndex uint64, v []uint8, r [][32]byte, s [][32]byte) (*types.Transaction, error) {
+	return _CheckpointOracle.contract.Transact(opts, "SetCheckpoint", _recentNumber, _recentHash, _hash, _sectionIndex, v, r, s)
+}
+
+// SetCheckpoint is a paid mutator transaction binding the contract method 0xd459fc46.
+//
+// Solidity: function SetCheckpoint(uint256 _recentNumber, bytes32 _recentHash, bytes32 _hash, uint64 _sectionIndex, uint8[] v, bytes32[] r, bytes32[] s) returns(bool)
+func (_CheckpointOracle *CheckpointOracleSession) SetCheckpoint(_recentNumber *big.Int, _recentHash [32]byte, _hash [32]byte, _sectionIndex uint64, v []uint8, r [][32]byte, s [][32]byte) (*types.Transaction, error) {
+	return _CheckpointOracle.Contract.SetCheckpoint(&_CheckpointOracle.TransactOpts, _recentNumber, _recentHash, _hash, _sectionIndex, v, r, s)
+}
+
+// SetCheckpoint is a paid mutator transaction binding the contract method 0xd459fc46.
+//
+// Solidity: function SetCheckpoint(uint256 _recentNumber, bytes32 _recentHash, bytes32 _hash, uint64 _sectionIndex, uint8[] v, bytes32[] r, bytes32[] s) returns(bool)
+func (_CheckpointOracle *CheckpointOracleTransactorSession) SetCheckpoint(_recentNumber *big.Int, _recentHash [32]byte, _hash [32]byte, _sectionIndex uint64, v []uint8, r [][32]byte, s [][32]byte) (*types.Transaction, error) {
+	return _CheckpointOracle.Contract.SetCheckpoint(&_CheckpointOracle.TransactOpts, _recentNumber, _recentHash, _hash, _sectionIndex, v, r, s)
+}
+
+// CheckpointOracleNewCheckpointVoteIterator is returned from FilterNewCheckpointVote and is used to iterate over the raw logs and unpacked data for NewCheckpointVote events raised by the CheckpointOracle contract.
+type CheckpointOracleNewCheckpointVoteIterator struct {
+	Event *CheckpointOracleNewCheckpointVote // Event containing the contract specifics and raw log
+
+	contract *bind.BoundContract // Generic contract to use for unpacking event data
+	event    string              // Event name to use for unpacking event data
+
+	logs chan types.Log        // Log channel receiving the found contract events
+	sub  ethereum.Subscription // Subscription for errors, completion and termination
+	done bool                  // Whether the subscription completed delivering logs
+	fail error                 // Occurred error to stop iteration
+}
+
+// Next advances the iterator to the subsequent event, returning whether there
+// are any more events found. In case of a retrieval or parsing error, false is
+// returned and Error() can be queried for the exact failure.
+func (it *CheckpointOracleNewCheckpointVoteIterator) Next() bool {
+	// If the iterator failed, stop iterating
+	if it.fail != nil {
+		return false
+	}
+	// If the iterator completed, deliver directly whatever's available
+	if it.done {
+		select {
+		case log := <-it.logs:
+			it.Event = new(CheckpointOracleNewCheckpointVote)
+			if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+				it.fail = err
+				return false
+			}
+			it.Event.Raw = log
+			return true
+
+		default:
+			return false
+		}
+	}
+	// Iterator still in progress, wait for either a data or an error event
+	select {
+	case log := <-it.logs:
+		it.Event = new(CheckpointOracleNewCheckpointVote)
+		if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+			it.fail = err
+			return false
+		}
+		it.Event.Raw = log
+		return true
+
+	case err := <-it.sub.Err():
+		it.done = true
+		it.fail = err
+		return it.Next()
+	}
+}
+
+// Error returns any retrieval or parsing error occurred during filtering.
+func (it *CheckpointOracleNewCheckpointVoteIterator) Error() error {
+	return it.fail
+}
+
+// Close terminates the iteration process, releasing any pending underlying
+// resources.
+func (it *CheckpointOracleNewCheckpointVoteIterator) Close() error {
+	it.sub.Unsubscribe()
+	return nil
+}
+
+// CheckpointOracleNewCheckpointVote represents a NewCheckpointVote event raised by the CheckpointOracle contract.
+type CheckpointOracleNewCheckpointVote struct {
+	Index          uint64
+	CheckpointHash [32]byte
+	V              uint8
+	R              [32]byte
+	S              [32]byte
+	Raw            types.Log // Blockchain specific contextual infos
+}
+
+// FilterNewCheckpointVote is a free log retrieval operation binding the contract event 0xce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a41.
+//
+// Solidity: event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s)
+func (_CheckpointOracle *CheckpointOracleFilterer) FilterNewCheckpointVote(opts *bind.FilterOpts, index []uint64) (*CheckpointOracleNewCheckpointVoteIterator, error) {
+
+	var indexRule []interface{}
+	for _, indexItem := range index {
+		indexRule = append(indexRule, indexItem)
+	}
+
+	logs, sub, err := _CheckpointOracle.contract.FilterLogs(opts, "NewCheckpointVote", indexRule)
+	if err != nil {
+		return nil, err
+	}
+	return &CheckpointOracleNewCheckpointVoteIterator{contract: _CheckpointOracle.contract, event: "NewCheckpointVote", logs: logs, sub: sub}, nil
+}
+
+// WatchNewCheckpointVote is a free log subscription operation binding the contract event 0xce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a41.
+//
+// Solidity: event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s)
+func (_CheckpointOracle *CheckpointOracleFilterer) WatchNewCheckpointVote(opts *bind.WatchOpts, sink chan<- *CheckpointOracleNewCheckpointVote, index []uint64) (event.Subscription, error) {
+
+	var indexRule []interface{}
+	for _, indexItem := range index {
+		indexRule = append(indexRule, indexItem)
+	}
+
+	logs, sub, err := _CheckpointOracle.contract.WatchLogs(opts, "NewCheckpointVote", indexRule)
+	if err != nil {
+		return nil, err
+	}
+	return event.NewSubscription(func(quit <-chan struct{}) error {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case log := <-logs:
+				// New log arrived, parse the event and forward to the user
+				event := new(CheckpointOracleNewCheckpointVote)
+				if err := _CheckpointOracle.contract.UnpackLog(event, "NewCheckpointVote", log); err != nil {
+					return err
+				}
+				event.Raw = log
+
+				select {
+				case sink <- event:
+				case err := <-sub.Err():
+					return err
+				case <-quit:
+					return nil
+				}
+			case err := <-sub.Err():
+				return err
+			case <-quit:
+				return nil
+			}
+		}
+	}), nil
+}
+
+// ParseNewCheckpointVote is a log parse operation binding the contract event 0xce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a41.
+//
+// Solidity: event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s)
+func (_CheckpointOracle *CheckpointOracleFilterer) ParseNewCheckpointVote(log types.Log) (*CheckpointOracleNewCheckpointVote, error) {
+	event := new(CheckpointOracleNewCheckpointVote)
+	if err := _CheckpointOracle.contract.UnpackLog(event, "NewCheckpointVote", log); err != nil {
+		return nil, err
+	}
+	return event, nil
+}
diff --git a/contracts/checkpointoracle/contract/oracle.sol b/contracts/checkpointoracle/contract/oracle.sol
new file mode 100644
index 000000000..010644727
--- /dev/null
+++ b/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;
+}
diff --git a/contracts/checkpointoracle/oracle.go b/contracts/checkpointoracle/oracle.go
new file mode 100644
index 000000000..702e27d95
--- /dev/null
+++ b/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)
+}
diff --git a/contracts/checkpointoracle/oracle_test.go b/contracts/checkpointoracle/oracle_test.go
new file mode 100644
index 000000000..8c123a3b4
--- /dev/null
+++ b/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")
+}
diff --git a/core/chain_indexer.go b/core/chain_indexer.go
index 26538260c..80062714a 100644
--- a/core/chain_indexer.go
+++ b/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)
 }
diff --git a/eth/backend.go b/eth/backend.go
index 52ec40f5a..9f6b5a002 100644
--- a/eth/backend.go
+++ b/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{
 		{
diff --git a/eth/config.go b/eth/config.go
index ccd5674a7..eb61f1d5d 100644
--- a/eth/config.go
+++ b/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
 }
diff --git a/eth/gen_config.go b/eth/gen_config.go
index 178faf7cb..77b28d025 100644
--- a/eth/gen_config.go
+++ b/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
 }
diff --git a/eth/handler.go b/eth/handler.go
index 7d2000386..db6901a23 100644
--- a/eth/handler.go
+++ b/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
 	}
diff --git a/eth/handler_test.go b/eth/handler_test.go
index 8c2818421..445d52afc 100644
--- a/eth/handler_test.go
+++ b/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)
 	}
diff --git a/eth/helper_test.go b/eth/helper_test.go
index 27e7189ed..1482e99c4 100644
--- a/eth/helper_test.go
+++ b/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
 	}
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index 31c0c57ec..0abcd5a8a 100644
--- a/internal/web3ext/web3ext.go
+++ b/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'
+		}),
+	]
+});
+`
diff --git a/les/api.go b/les/api.go
index 3a8d49ca5..b53512196 100644
--- a/les/api.go
+++ b/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
+}
diff --git a/les/backend.go b/les/backend.go
index ed0f45057..69aa4e6e2 100644
--- a/les/backend.go
+++ b/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)
+}
diff --git a/les/checkpointoracle.go b/les/checkpointoracle.go
new file mode 100644
index 000000000..4695fbc16
--- /dev/null
+++ b/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
+}
diff --git a/les/commons.go b/les/commons.go
index d46479976..7eaf39c84 100644
--- a/les/commons.go
+++ b/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),
 	}
 }
diff --git a/les/handler.go b/les/handler.go
index c7bd23103..c902db65a 100644
--- a/les/handler.go
+++ b/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)
 	}
diff --git a/les/handler_test.go b/les/handler_test.go
index dd7f1dbc4..e48db216a 100644
--- a/les/handler_test.go
+++ b/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)
 	}
diff --git a/les/helper_test.go b/les/helper_test.go
index dbb081344..035865b08 100644
--- a/les/helper_test.go
+++ b/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,
diff --git a/les/odr_requests.go b/les/odr_requests.go
index 89c609177..3c4dd7090 100644
--- a/les/odr_requests.go
+++ b/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
 }
 
diff --git a/les/odr_test.go b/les/odr_test.go
index a1d547956..1e8a5f8b4 100644
--- a/les/odr_test.go
+++ b/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)
diff --git a/les/peer.go b/les/peer.go
index a615c9b73..76900410e 100644
--- a/les/peer.go
+++ b/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 {
diff --git a/les/request_test.go b/les/request_test.go
index e0d00d18c..42a63c351 100644
--- a/les/request_test.go
+++ b/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[:])
diff --git a/les/server.go b/les/server.go
index fbdf6cf1e..08d973416 100644
--- a/les/server.go
+++ b/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()
diff --git a/les/sync.go b/les/sync.go
index 1ac645585..54fd81c2c 100644
--- a/les/sync.go
+++ b/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)))
 }
diff --git a/les/sync_test.go b/les/sync_test.go
new file mode 100644
index 000000000..634be8e6d
--- /dev/null
+++ b/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")
+	}
+}
diff --git a/les/transactions.rlp b/les/transactions.rlp
deleted file mode 100755
index e69de29bb..000000000
diff --git a/les/txrelay.go b/les/txrelay.go
index 5ebef1c22..ffbe251fc 100644
--- a/les/txrelay.go
+++ b/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()
 
diff --git a/les/ulc_test.go b/les/ulc_test.go
index 38adeb95f..3a3281a3f 100644
--- a/les/ulc_test.go
+++ b/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,
diff --git a/light/lightchain.go b/light/lightchain.go
index f0beec47b..7f64d1c28 100644
--- a/light/lightchain.go
+++ b/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 {
diff --git a/light/lightchain_test.go b/light/lightchain_test.go
index 58ea93044..70d2e70c1 100644
--- a/light/lightchain_test.go
+++ b/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)
 	}
diff --git a/light/odr.go b/light/odr.go
index d1185e4e0..907712ede 100644
--- a/light/odr.go
+++ b/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
diff --git a/light/odr_test.go b/light/odr_test.go
index 912a0cbdd..debd5544c 100644
--- a/light/odr_test.go
+++ b/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)
 	}
diff --git a/light/odr_util.go b/light/odr_util.go
index 100bd5842..82e33bb78 100644
--- a/light/odr_util.go
+++ b/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 (
diff --git a/light/txpool_test.go b/light/txpool_test.go
index 4f446c6ca..0996bd7c9 100644
--- a/light/txpool_test.go
+++ b/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)
diff --git a/node/node.go b/node/node.go
index 08daeeee0..c9c27d826 100644
--- a/node/node.go
+++ b/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
 }
 
diff --git a/p2p/message.go b/p2p/message.go
index ac12666e4..9e387ba9b 100644
--- a/p2p/message.go
+++ b/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 {
diff --git a/params/config.go b/params/config.go
index c59c748ac..e79d4ffc9 100644
--- a/params/config.go
+++ b/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
diff --git a/params/network_params.go b/params/network_params.go
index a949b8457..bba24721c 100644
--- a/params/network_params.go
+++ b/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
-- 
GitLab