diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index ac4602fe37..c2c1971dfd 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.
@@ -65,8 +67,11 @@ type SimulatedBackend struct {
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
// for testing purposes.
-func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
- database := rawdb.NewMemoryDatabase()
+func NewSimulatedBackend(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
+ // Don't panic for the lazy user.
+ if database == nil {
+ database = rawdb.NewMemoryDatabase()
+ }
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)
@@ -424,6 +429,33 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
return nil
}
+// InsertEmptyBlocks inserts a batch of empty blocks to blockchain.
+func (b *SimulatedBackend) InsertEmptyBlocks(number int) error {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+ // Insert a batch of empty blocks and commit to the database
+ blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, number, func(i int, block *core.BlockGen) {})
+ if _, err := b.blockchain.InsertChain(blocks); err != nil {
+ panic(err) // This cannot happen unless the simulator is wrong, fail in that case
+ }
+ // Apply all pending transactions to new pending blocks.
+ blocks, _ = core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
+ for _, tx := range b.pendingBlock.Transactions() {
+ block.AddTx(tx)
+ }
+ })
+
+ statedb, _ := b.blockchain.State()
+ b.pendingBlock = blocks[0]
+ b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
+ 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/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go
index bd75807d75..a92bb740e0 100644
--- a/accounts/abi/bind/backends/simulated_test.go
+++ b/accounts/abi/bind/backends/simulated_test.go
@@ -37,7 +37,7 @@ func TestSimulatedBackend(t *testing.T) {
genAlloc := make(core.GenesisAlloc)
genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)}
- sim := backends.NewSimulatedBackend(genAlloc, gasLimit)
+ sim := backends.NewSimulatedBackend(nil, genAlloc, gasLimit)
// should return an error if the tx is not found
txHash := common.HexToHash("2")
diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go
index eae12b3f4d..16f3c85340 100644
--- a/accounts/abi/bind/bind_test.go
+++ b/accounts/abi/bind/bind_test.go
@@ -258,7 +258,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy an interaction tester contract and call a transaction on it
_, _, interactor, err := DeployInteractor(auth, sim, "Deploy string")
@@ -307,7 +307,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a tuple tester contract and execute a structured call on it
_, _, getter, err := DeployGetter(auth, sim)
@@ -347,7 +347,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a tuple tester contract and execute a structured call on it
_, _, tupler, err := DeployTupler(auth, sim)
@@ -399,7 +399,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a slice tester contract and execute a n array call on it
_, _, slicer, err := DeploySlicer(auth, sim)
@@ -441,7 +441,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a default method invoker contract and execute its default method
_, _, defaulter, err := DeployDefaulter(auth, sim)
@@ -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(nil, core.GenesisAlloc{}, uint64(10000000000))
nonexistent, err := NewNonExistent(common.Address{}, sim)
if err != nil {
@@ -523,7 +524,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a funky gas pattern contract
_, _, limiter, err := DeployFunkyGasPattern(auth, sim)
@@ -567,7 +568,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a sender tester contract and execute a structured call on it
_, _, callfrom, err := DeployCallFrom(auth, sim)
@@ -636,7 +637,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy a underscorer tester contract and execute a structured call on it
_, _, underscorer, err := DeployUnderscorer(auth, sim)
@@ -724,7 +725,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
// Deploy an eventer contract
_, _, eventer, err := DeployEventer(auth, sim)
@@ -908,7 +909,7 @@ var bindTests = []struct {
// Generate a new random account and a funded simulator
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
- sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
+ sim := backends.NewSimulatedBackend(nil, core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000)
//deploy the test contract
_, _, testContract, err := DeployDeeplyNestedArray(auth, sim)
diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go
index 02d0258e0c..b245e2af4d 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 8f4092971f..49dc8c547c 100644
--- a/accounts/abi/bind/util_test.go
+++ b/accounts/abi/bind/util_test.go
@@ -54,9 +54,11 @@ var waitDeployedTests = map[string]struct {
func TestWaitDeployed(t *testing.T) {
for name, test := range waitDeployedTests {
backend := backends.NewSimulatedBackend(
+ nil,
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
- }, 10000000,
+ },
+ 10000000,
)
// Create the transaction.
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index d0a9bb08ae..df58d118ce 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"
@@ -321,14 +322,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)
- }
- stateReader := ethclient.NewClient(rpcClient)
+ // 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(ðService); 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)
+ }
+ lesService.SetContractBackend(ethClient)
+ }
+
+ go func() {
// Open any wallets already attached
for _, wallet := range stack.AccountManager().Wallets() {
if err := wallet.Open(""); err != nil {
@@ -352,7 +372,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())
@@ -381,7 +401,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 6aed09f141..10f015326d 100644
--- a/cmd/puppeth/wizard_genesis.go
+++ b/cmd/puppeth/wizard_genesis.go
@@ -27,10 +27,17 @@ import (
"net/http"
"os"
"path/filepath"
+ "strings"
"time"
+ "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/contracts/registrar/contract"
"github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/vm/runtime"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
@@ -132,6 +139,64 @@ func (w *wizard) makeGenesis() {
fmt.Println("Specify your chain/network ID if you want an explicit one (default = random)")
genesis.Config.ChainID = new(big.Int).SetUint64(uint64(w.readDefaultInt(rand.Intn(65536))))
+ // Query the user for checkpoint contract config
+ fmt.Println()
+ fmt.Println("Should checkpoint contract be deployed (default = no)")
+ if w.readDefaultYesNo(false) {
+ // Read the address of the trusted signers
+ fmt.Println("Which accounts should be the trusted signer? (advisable at least one)")
+ var (
+ signers []common.Address
+ threshold *big.Int
+ )
+ // Get trusted signer addresses
+ for {
+ if address := w.readAddress(); address != nil {
+ signers = append(signers, *address)
+ continue
+ }
+ break
+ }
+ // Get stable checkpoint signature minFraction
+ for {
+ fmt.Printf("What is the minimal approval threshold? (maximum %d)\n", len(signers))
+ threshold = w.readDefaultBigInt(big.NewInt(0))
+ if threshold.Int64() <= 0 || threshold.Int64() > int64(len(signers)) {
+ fmt.Printf("Invalid approval threshold, please enter in range [1, %d]\n", len(signers))
+ }
+ break
+ }
+ parsed, err := abi.JSON(strings.NewReader(contract.ContractABI))
+ if err != nil {
+ log.Crit("Parse contract ABI failed", "err", err)
+ }
+ input, err := parsed.Pack("", signers, big.NewInt(params.CheckpointFrequency), big.NewInt(params.CheckpointProcessConfirmations), threshold)
+ if err != nil {
+ log.Crit("Pack contract constructor arguments failed", "err", err)
+ }
+ config := &runtime.Config{GasLimit: math.MaxInt64}
+ config.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
+ code, address, _, err := runtime.Create(append(common.FromHex(contract.ContractBin), input...), config)
+ if err != nil {
+ log.Crit("Execute contract constructor failed", "err", err)
+ }
+ config.State.Commit(true)
+ genesis.Alloc[address] = core.GenesisAccount{Code: code, Storage: make(map[common.Hash]common.Hash), Balance: big.NewInt(1)}
+ err = config.State.ForEachStorage(address, func(key, value common.Hash) bool {
+ genesis.Alloc[address].Storage[key] = value
+ return true
+ })
+ if err != nil {
+ log.Crit("Failed to iterate contract storage", "err", err)
+ }
+ genesis.Config.CheckpointContract = ¶ms.CheckpointContractConfig{
+ Name: w.network,
+ ContractAddr: address,
+ Signers: signers,
+ Threshold: threshold.Uint64(),
+ }
+ }
+
// All done, store the genesis and flush to disk
log.Info("Configured new genesis block")
diff --git a/cmd/registrar/common.go b/cmd/registrar/common.go
new file mode 100644
index 0000000000..592ab5390c
--- /dev/null
+++ b/cmd/registrar/common.go
@@ -0,0 +1,172 @@
+// 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 .
+
+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/registrar"
+ "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"
+)
+
+// newClient creates a client with specified remote URL.
+func newClient(ctx *cli.Context) *ethclient.Client {
+ url := ctx.GlobalString(nodeURLFlag.Name)
+ client, err := ethclient.Dial(url)
+ if err != nil {
+ utils.Fatalf("Failed to setup ethereum client at url %s: %v", url, err)
+ }
+ log.Info("Setup ethereum client", "URL", url)
+ 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 setup rpc client at url %s: %v", url, err)
+ }
+ log.Info("Setup rpc client", "URL", url)
+ return client
+}
+
+// getContractAddr retrieves the register contract address through
+// rpc request.
+func getContractAddr(client *rpc.Client) common.Address {
+ var addr string
+ err := client.Call(&addr, "les_getCheckpointContractAddress")
+ if err != nil {
+ utils.Fatalf("Failed to fetch checkpoint contract address, err %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 = ¶ms.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 = ¶ms.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) *registrar.Registrar {
+ addr := getContractAddr(client)
+ if addr == (common.Address{}) {
+ utils.Fatalf("No specified registrar contract address")
+ }
+ contract, err := registrar.NewRegistrar(addr, ethclient.NewClient(client))
+ if err != nil {
+ utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err)
+ }
+ log.Info("Setup registrar contract", "address", addr)
+ return 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/registrar/exec.go b/cmd/registrar/exec.go
new file mode 100644
index 0000000000..27a846f31e
--- /dev/null
+++ b/cmd/registrar/exec.go
@@ -0,0 +1,312 @@
+// 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 .
+
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/binary"
+ "fmt"
+ "math/big"
+ "sort"
+ "strings"
+ "time"
+
+ "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/registrar"
+ "github.com/ethereum/go-ethereum/contracts/registrar/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 commandDeployContract = cli.Command{
+ Name: "deploy",
+ Usage: "Deploy a registrar contract with specified trusted signers.",
+ Flags: []cli.Flag{
+ signerFlag,
+ thresholdFlag,
+ nodeURLFlag,
+ keyFileFlag,
+ utils.PasswordFileFlag,
+ },
+ Action: utils.MigrateFlags(deployContract),
+}
+
+var commandSignCheckpoint = cli.Command{
+ Name: "sign",
+ Usage: "Sign the checkpoint with the specified key",
+ Flags: []cli.Flag{
+ nodeURLFlag,
+ clefURLFlag,
+ indexFlag,
+ hashFlag,
+ addressFlag,
+ keyFileFlag,
+ signerFlag,
+ utils.PasswordFileFlag,
+ },
+ Action: utils.MigrateFlags(signCheckpoint),
+}
+
+var commandRegisterCheckpoint = cli.Command{
+ Name: "register",
+ Usage: "Register specified checkpoint into contract",
+ Flags: []cli.Flag{
+ nodeURLFlag,
+ indexFlag,
+ signerFlag,
+ signatureFlag,
+ keyFileFlag,
+ utils.PasswordFileFlag,
+ },
+ Action: utils.MigrateFlags(registerCheckpoint),
+}
+
+// deployContract deploys the checkpoint registrar contract.
+//
+// Note the network where the contract is deployed depends on
+// the network where the connected node is located.
+func deployContract(ctx *cli.Context) error {
+ var addrs []common.Address
+ signers := strings.Split(ctx.GlobalString(signerFlag.Name), ",")
+ for _, account := range signers {
+ if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
+ utils.Fatalf("Invalid account in --signer: %s", trimmed)
+ } else {
+ addrs = append(addrs, common.HexToAddress(account))
+ }
+ }
+
+ t := ctx.GlobalInt64(thresholdFlag.Name)
+ if t == 0 || int(t) > len(signers) {
+ utils.Fatalf("Invalid signature threshold %d", t)
+ }
+ addr, tx, _, err := contract.DeployContract(bind.NewKeyedTransactor(getKey(ctx).PrivateKey), newClient(ctx), addrs, big.NewInt(int64(params.CheckpointFrequency)),
+ big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(t))
+ if err != nil {
+ utils.Fatalf("Failed to deploy registrar contract %v", err)
+ }
+ log.Info("Deployed registrar contract successfully", "address", addr, "tx", tx.Hash())
+ return nil
+}
+
+// signCheckpoint creates the signature for specific checkpoint
+// with local key. Only contract admins have the permission to
+// sign checkpoint.
+func signCheckpoint(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
+ c *registrar.Registrar
+ )
+ if !ctx.GlobalIsSet(nodeURLFlag.Name) {
+ // Offline mode signing
+ offline = true
+ if !ctx.GlobalIsSet(hashFlag.Name) {
+ utils.Fatalf("Please specify checkpoint hash for offline mode signing")
+ }
+ chash = common.HexToHash(ctx.GlobalString(hashFlag.Name))
+ if !ctx.GlobalIsSet(indexFlag.Name) {
+ utils.Fatalf("Please specify checkpoint index for offline mode signing")
+ }
+ cindex = ctx.GlobalUint64(indexFlag.Name)
+ if !ctx.GlobalIsSet(addressFlag.Name) {
+ utils.Fatalf("Please specify contract address for offline mode signing")
+ }
+ address = common.HexToAddress(ctx.GlobalString(addressFlag.Name))
+ } else {
+ // Interactive mode signing
+ node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
+ checkpoint := getCheckpoint(ctx, node)
+
+ chash = checkpoint.Hash()
+ cindex = checkpoint.SectionIndex
+ address = getContractAddr(node)
+
+ reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancelFn()
+
+ // Check the validity of checkpoint.
+ 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")
+ }
+ c = newContract(node)
+ latest, _, h, err := c.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 := c.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())
+ }
+ switch {
+ case ctx.GlobalIsSet(clefURLFlag.Name):
+ // Sign checkpoint in clef mode.
+ signer = ctx.GlobalString(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", "data/validator", 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
+ }
+ }
+ buf := make([]byte, 8)
+ binary.BigEndian.PutUint64(buf, cindex)
+ data := append([]byte{0x19, 0x00}, append(address[:], append(buf, chash.Bytes()...)...)...)
+ sig, err := crypto.Sign(crypto.Keccak256(data), 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")
+ }
+ log.Info("Successfully signed checkpoint", "index", cindex, "hash", chash.Hex(), "address", address.Hex(), "signer", signer, "signature", signature)
+ return nil
+}
+
+type Signer struct {
+ addr common.Address
+ sig []byte
+}
+type Signers []Signer
+
+func (s Signers) Len() int { return len(s) }
+func (s Signers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s Signers) Less(i, j int) bool { return bytes.Compare(s[i].addr.Bytes(), s[j].addr.Bytes()) < 0 }
+
+// registerCheckpoint registers the specified checkpoint which generated by connected
+// node with a authorised private key.
+func registerCheckpoint(ctx *cli.Context) error {
+ var (
+ addrs []common.Address
+ sigs [][]byte
+ signers Signers
+ )
+ signerStrs := strings.Split(ctx.GlobalString(signerFlag.Name), ",")
+ for _, account := range signerStrs {
+ if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
+ utils.Fatalf("Invalid account in --signer: %s", trimmed)
+ } else {
+ addrs = append(addrs, common.HexToAddress(account))
+ }
+ }
+ sigStrs := strings.Split(ctx.GlobalString(signatureFlag.Name), ",")
+ for _, sig := range sigStrs {
+ trimmed := strings.TrimSpace(sig)
+ if strings.HasPrefix(trimmed, "0x") {
+ trimmed = trimmed[2:]
+ }
+ if len(trimmed) != 130 {
+ utils.Fatalf("Invalid signature in --signature: %s", trimmed)
+ } else {
+ sigs = append(sigs, common.Hex2Bytes(trimmed))
+ }
+ }
+ if len(addrs) != len(sigs) {
+ utils.Fatalf("The length of signer and corresponding signature mismatch")
+ }
+ for i := 0; i < len(addrs); i++ {
+ signers = append(signers, Signer{addr: addrs[i], sig: sigs[i]})
+ }
+ sort.Sort(signers)
+ sigs = sigs[:0]
+ for i := 0; i < len(signers); i++ {
+ sigs = append(sigs, signers[i].sig)
+ }
+
+ reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancelFn()
+
+ // Retrieve recent header info to protect replay attack.
+ node := newRPCClient(ctx.GlobalString(nodeURLFlag.Name))
+ head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil)
+ if err != nil {
+ return err
+ }
+ num := head.Number.Uint64()
+ recent, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, big.NewInt(int64(num-128)))
+ if err != nil {
+ return err
+ }
+ var (
+ c = newContract(node)
+ key = getKey(ctx)
+ checkpoint = getCheckpoint(ctx, node)
+ )
+ tx, err := c.RegisterCheckpoint(key.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", "index", checkpoint.SectionIndex, "hash", checkpoint.Hash(), "signumber", len(signers), "txhash", tx.Hash())
+ return nil
+}
diff --git a/cmd/registrar/main.go b/cmd/registrar/main.go
new file mode 100644
index 0000000000..1b5a1166be
--- /dev/null
+++ b/cmd/registrar/main.go
@@ -0,0 +1,126 @@
+// 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 .
+
+// registrar is a utility that can be used to query checkpoint information
+// and register stable checkpoint into the 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{
+ commandQueryAdmin,
+ commandQueryCheckpoint,
+ commandDeployContract,
+ commandSignCheckpoint,
+ commandRegisterCheckpoint,
+ }
+ app.Flags = []cli.Flag{
+ indexFlag,
+ hashFlag,
+ addressFlag,
+ thresholdFlag,
+ keyFileFlag,
+ nodeURLFlag,
+ clefURLFlag,
+ signerFlag,
+ signatureFlag,
+ 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)",
+ }
+ addressFlag = cli.StringFlag{
+ Name: "address",
+ Usage: "Checkpoint contract 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: "Comma separated accounts of trusted checkpoint signers",
+ }
+ signatureFlag = cli.StringFlag{
+ Name: "signature",
+ 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/registrar/query.go b/cmd/registrar/query.go
new file mode 100644
index 0000000000..7f085d0852
--- /dev/null
+++ b/cmd/registrar/query.go
@@ -0,0 +1,69 @@
+// 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 .
+
+package main
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/cmd/utils"
+ "github.com/ethereum/go-ethereum/common"
+ "gopkg.in/urfave/cli.v1"
+)
+
+var commandQueryAdmin = cli.Command{
+ Name: "queryadmin",
+ Usage: "Fetch the admin list of specified registrar contract",
+ Flags: []cli.Flag{
+ nodeURLFlag,
+ },
+ Action: utils.MigrateFlags(queryAdmin),
+}
+
+var commandQueryCheckpoint = cli.Command{
+ Name: "querycheckpoint",
+ Usage: "Fetch the latest registered checkpoint in the registrar contract",
+ Flags: []cli.Flag{
+ nodeURLFlag,
+ },
+ Action: utils.MigrateFlags(queryCheckpoint),
+}
+
+// queryAdmin fetches the admin list of specified registrar contract.
+func queryAdmin(ctx *cli.Context) error {
+ contract := newContract(newRPCClient(ctx.GlobalString(nodeURLFlag.Name)))
+ admins, err := contract.Contract().GetAllAdmin(nil)
+ if err != nil {
+ return err
+ }
+ fmt.Println("Total admin number", len(admins))
+ for i, admin := range admins {
+ fmt.Printf("Admin %d => %s\n", i+1, admin.Hex())
+ }
+ return nil
+}
+
+// queryCheckpoint fetches the checkpoint hash with specified index from
+// registrar contract.
+func queryCheckpoint(ctx *cli.Context) error {
+ contract := newContract(newRPCClient(ctx.GlobalString(nodeURLFlag.Name)))
+ index, checkpoint, height, err := contract.Contract().GetLatestCheckpoint(nil)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("Latest checkpoint(registered at height #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex())
+ return nil
+}
diff --git a/contracts/chequebook/cheque_test.go b/contracts/chequebook/cheque_test.go
index 4bd2e176b1..8afcb00c14 100644
--- a/contracts/chequebook/cheque_test.go
+++ b/contracts/chequebook/cheque_test.go
@@ -42,7 +42,7 @@ var (
)
func newTestBackend() *backends.SimulatedBackend {
- return backends.NewSimulatedBackend(core.GenesisAlloc{
+ return backends.NewSimulatedBackend(nil, core.GenesisAlloc{
addr0: {Balance: big.NewInt(1000000000)},
addr1: {Balance: big.NewInt(1000000000)},
addr2: {Balance: big.NewInt(1000000000)},
diff --git a/contracts/ens/ens_test.go b/contracts/ens/ens_test.go
index c65df836ad..5d5f1655d9 100644
--- a/contracts/ens/ens_test.go
+++ b/contracts/ens/ens_test.go
@@ -39,7 +39,7 @@ var (
)
func TestENS(t *testing.T) {
- contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000)
+ contractBackend := backends.NewSimulatedBackend(nil, core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000)
transactOpts := bind.NewKeyedTransactor(key)
ensAddr, ens, err := DeployENS(transactOpts, contractBackend)
diff --git a/contracts/registrar/contract/registrar.go b/contracts/registrar/contract/registrar.go
new file mode 100644
index 0000000000..6b0af64da3
--- /dev/null
+++ b/contracts/registrar/contract/registrar.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
+)
+
+// ContractABI is the input ABI used to generate the binding from.
+const ContractABI = "[{\"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\"}]"
+
+// ContractBin is the compiled bytecode used for deploying new contracts.
+const ContractBin = `608060405234801561001057600080fd5b50604051610b8e380380610b8e8339810180604052608081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185602082028301116401000000008211171561007e57600080fd5b505092919060200180519060200190929190805190602001909291908051906020019092919050505060008090505b84518110156101ae57600160008087848151811015156100c957fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055506001858281518110151561013457fe5b9060200190602002015190806001815401808255809150509060018203906000526020600020016000909192909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505080806001019150506100ad565b50826005819055508160068190555080600781905550505050506109b7806101d76000396000f3fe608060405234801561001057600080fd5b506004361061005e576000357c01000000000000000000000000000000000000000000000000000000009004806345848dfc146100635780634d6a304c146100c2578063d459fc4614610102575b600080fd5b61006b61032c565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156100ae578082015181840152602081019050610093565b505050509050019250505060405180910390f35b6100ca610412565b604051808467ffffffffffffffff1667ffffffffffffffff168152602001838152602001828152602001935050505060405180910390f35b610312600480360360e081101561011857600080fd5b81019080803590602001909291908035906020019092919080359060200190929190803567ffffffffffffffff1690602001909291908035906020019064010000000081111561016757600080fd5b82018360208201111561017957600080fd5b8035906020019184602083028401116401000000008311171561019b57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156101fb57600080fd5b82018360208201111561020d57600080fd5b8035906020019184602083028401116401000000008311171561022f57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561028f57600080fd5b8201836020820111156102a157600080fd5b803590602001918460208302840111640100000000831117156102c357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050919291929050505061043f565b604051808215151515815260200191505060405180910390f35b6060806001805490506040519080825280602002602001820160405280156103635781602001602082028038833980820191505090505b50905060008090505b60018054905081101561040a5760018181548110151561038857fe5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1682828151811015156103c157fe5b9060200190602002019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050808060010191505061036c565b508091505090565b6000806000600260009054906101000a900467ffffffffffffffff16600454600354925092509250909192565b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151561049857600080fd5b8688401415156104a757600080fd5b825184511415156104b757600080fd5b815184511415156104c757600080fd5b6006546005546001870167ffffffffffffffff1602014310156104ed5760009050610980565b600260009054906101000a900467ffffffffffffffff1667ffffffffffffffff168567ffffffffffffffff1610156105285760009050610980565b600260009054906101000a900467ffffffffffffffff1667ffffffffffffffff168567ffffffffffffffff1614801561057a575060008567ffffffffffffffff161415806105795750600060035414155b5b156105885760009050610980565b600086141561059a5760009050610980565b600060197f01000000000000000000000000000000000000000000000000000000000000000260007f01000000000000000000000000000000000000000000000000000000000000000230888a60405160200180867effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018367ffffffffffffffff1667ffffffffffffffff16780100000000000000000000000000000000000000000000000002815260080182815260200195505050505050604051602081830303815290604052805190602001209050600080905060008090505b865181101561097a576000600184898481518110151561074457fe5b90602001906020020151898581518110151561075c57fe5b90602001906020020151898681518110151561077457fe5b9060200190602002015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156107d5573d6000803e3d6000fd5b5050506020604051035190506000808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151561083857600080fd5b8273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1611151561087257600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a858151811015156108b057fe5b906020019060200201518a868151811015156108c857fe5b906020019060200201518a878151811015156108e057fe5b90602001906020020151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a26007546001830110151561096c57896004819055504360038190555088600260006101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506001945050505050610980565b508080600101915050610728565b50600080fd5b97965050505050505056fea165627a7a723058204604a6548d846cceba68490999fe29b8caee393188ca39ed7e4e9d678d0216790029`
+
+// DeployContract deploys a new Ethereum contract, binding an instance of Contract to it.
+func DeployContract(auth *bind.TransactOpts, backend bind.ContractBackend, _adminlist []common.Address, _sectionSize *big.Int, _processConfirms *big.Int, _threshold *big.Int) (common.Address, *types.Transaction, *Contract, error) {
+ parsed, err := abi.JSON(strings.NewReader(ContractABI))
+ if err != nil {
+ return common.Address{}, nil, nil, err
+ }
+ address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ContractBin), backend, _adminlist, _sectionSize, _processConfirms, _threshold)
+ if err != nil {
+ return common.Address{}, nil, nil, err
+ }
+ return address, tx, &Contract{ContractCaller: ContractCaller{contract: contract}, ContractTransactor: ContractTransactor{contract: contract}, ContractFilterer: ContractFilterer{contract: contract}}, nil
+}
+
+// Contract is an auto generated Go binding around an Ethereum contract.
+type Contract struct {
+ ContractCaller // Read-only binding to the contract
+ ContractTransactor // Write-only binding to the contract
+ ContractFilterer // Log filterer for contract events
+}
+
+// ContractCaller is an auto generated read-only Go binding around an Ethereum contract.
+type ContractCaller struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// ContractTransactor is an auto generated write-only Go binding around an Ethereum contract.
+type ContractTransactor struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// ContractFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
+type ContractFilterer struct {
+ contract *bind.BoundContract // Generic contract wrapper for the low level calls
+}
+
+// ContractSession is an auto generated Go binding around an Ethereum contract,
+// with pre-set call and transact options.
+type ContractSession struct {
+ Contract *Contract // 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
+}
+
+// ContractCallerSession is an auto generated read-only Go binding around an Ethereum contract,
+// with pre-set call options.
+type ContractCallerSession struct {
+ Contract *ContractCaller // Generic contract caller binding to set the session for
+ CallOpts bind.CallOpts // Call options to use throughout this session
+}
+
+// ContractTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
+// with pre-set transact options.
+type ContractTransactorSession struct {
+ Contract *ContractTransactor // Generic contract transactor binding to set the session for
+ TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
+}
+
+// ContractRaw is an auto generated low-level Go binding around an Ethereum contract.
+type ContractRaw struct {
+ Contract *Contract // Generic contract binding to access the raw methods on
+}
+
+// ContractCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
+type ContractCallerRaw struct {
+ Contract *ContractCaller // Generic read-only contract binding to access the raw methods on
+}
+
+// ContractTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
+type ContractTransactorRaw struct {
+ Contract *ContractTransactor // Generic write-only contract binding to access the raw methods on
+}
+
+// NewContract creates a new instance of Contract, bound to a specific deployed contract.
+func NewContract(address common.Address, backend bind.ContractBackend) (*Contract, error) {
+ contract, err := bindContract(address, backend, backend, backend)
+ if err != nil {
+ return nil, err
+ }
+ return &Contract{ContractCaller: ContractCaller{contract: contract}, ContractTransactor: ContractTransactor{contract: contract}, ContractFilterer: ContractFilterer{contract: contract}}, nil
+}
+
+// NewContractCaller creates a new read-only instance of Contract, bound to a specific deployed contract.
+func NewContractCaller(address common.Address, caller bind.ContractCaller) (*ContractCaller, error) {
+ contract, err := bindContract(address, caller, nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &ContractCaller{contract: contract}, nil
+}
+
+// NewContractTransactor creates a new write-only instance of Contract, bound to a specific deployed contract.
+func NewContractTransactor(address common.Address, transactor bind.ContractTransactor) (*ContractTransactor, error) {
+ contract, err := bindContract(address, nil, transactor, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &ContractTransactor{contract: contract}, nil
+}
+
+// NewContractFilterer creates a new log filterer instance of Contract, bound to a specific deployed contract.
+func NewContractFilterer(address common.Address, filterer bind.ContractFilterer) (*ContractFilterer, error) {
+ contract, err := bindContract(address, nil, nil, filterer)
+ if err != nil {
+ return nil, err
+ }
+ return &ContractFilterer{contract: contract}, nil
+}
+
+// bindContract binds a generic wrapper to an already deployed contract.
+func bindContract(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
+ parsed, err := abi.JSON(strings.NewReader(ContractABI))
+ 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 (_Contract *ContractRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
+ return _Contract.Contract.ContractCaller.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 (_Contract *ContractRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _Contract.Contract.ContractTransactor.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_Contract *ContractRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _Contract.Contract.ContractTransactor.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 (_Contract *ContractCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
+ return _Contract.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 (_Contract *ContractTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
+ return _Contract.Contract.contract.Transfer(opts)
+}
+
+// Transact invokes the (paid) contract method with params as input values.
+func (_Contract *ContractTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
+ return _Contract.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 (_Contract *ContractCaller) GetAllAdmin(opts *bind.CallOpts) ([]common.Address, error) {
+ var (
+ ret0 = new([]common.Address)
+ )
+ out := ret0
+ err := _Contract.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 (_Contract *ContractSession) GetAllAdmin() ([]common.Address, error) {
+ return _Contract.Contract.GetAllAdmin(&_Contract.CallOpts)
+}
+
+// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
+//
+// Solidity: function GetAllAdmin() constant returns(address[])
+func (_Contract *ContractCallerSession) GetAllAdmin() ([]common.Address, error) {
+ return _Contract.Contract.GetAllAdmin(&_Contract.CallOpts)
+}
+
+// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
+//
+// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
+func (_Contract *ContractCaller) 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 := _Contract.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 (_Contract *ContractSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) {
+ return _Contract.Contract.GetLatestCheckpoint(&_Contract.CallOpts)
+}
+
+// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
+//
+// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
+func (_Contract *ContractCallerSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) {
+ return _Contract.Contract.GetLatestCheckpoint(&_Contract.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 (_Contract *ContractTransactor) 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 _Contract.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 (_Contract *ContractSession) SetCheckpoint(_recentNumber *big.Int, _recentHash [32]byte, _hash [32]byte, _sectionIndex uint64, v []uint8, r [][32]byte, s [][32]byte) (*types.Transaction, error) {
+ return _Contract.Contract.SetCheckpoint(&_Contract.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 (_Contract *ContractTransactorSession) SetCheckpoint(_recentNumber *big.Int, _recentHash [32]byte, _hash [32]byte, _sectionIndex uint64, v []uint8, r [][32]byte, s [][32]byte) (*types.Transaction, error) {
+ return _Contract.Contract.SetCheckpoint(&_Contract.TransactOpts, _recentNumber, _recentHash, _hash, _sectionIndex, v, r, s)
+}
+
+// ContractNewCheckpointVoteIterator is returned from FilterNewCheckpointVote and is used to iterate over the raw logs and unpacked data for NewCheckpointVote events raised by the Contract contract.
+type ContractNewCheckpointVoteIterator struct {
+ Event *ContractNewCheckpointVote // 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 *ContractNewCheckpointVoteIterator) 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(ContractNewCheckpointVote)
+ 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(ContractNewCheckpointVote)
+ 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 *ContractNewCheckpointVoteIterator) Error() error {
+ return it.fail
+}
+
+// Close terminates the iteration process, releasing any pending underlying
+// resources.
+func (it *ContractNewCheckpointVoteIterator) Close() error {
+ it.sub.Unsubscribe()
+ return nil
+}
+
+// ContractNewCheckpointVote represents a NewCheckpointVote event raised by the Contract contract.
+type ContractNewCheckpointVote 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 (_Contract *ContractFilterer) FilterNewCheckpointVote(opts *bind.FilterOpts, index []uint64) (*ContractNewCheckpointVoteIterator, error) {
+
+ var indexRule []interface{}
+ for _, indexItem := range index {
+ indexRule = append(indexRule, indexItem)
+ }
+
+ logs, sub, err := _Contract.contract.FilterLogs(opts, "NewCheckpointVote", indexRule)
+ if err != nil {
+ return nil, err
+ }
+ return &ContractNewCheckpointVoteIterator{contract: _Contract.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 (_Contract *ContractFilterer) WatchNewCheckpointVote(opts *bind.WatchOpts, sink chan<- *ContractNewCheckpointVote, index []uint64) (event.Subscription, error) {
+
+ var indexRule []interface{}
+ for _, indexItem := range index {
+ indexRule = append(indexRule, indexItem)
+ }
+
+ logs, sub, err := _Contract.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(ContractNewCheckpointVote)
+ if err := _Contract.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 (_Contract *ContractFilterer) ParseNewCheckpointVote(log types.Log) (*ContractNewCheckpointVote, error) {
+ event := new(ContractNewCheckpointVote)
+ if err := _Contract.contract.UnpackLog(event, "NewCheckpointVote", log); err != nil {
+ return nil, err
+ }
+ return event, nil
+}
diff --git a/contracts/registrar/contract/registrar.sol b/contracts/registrar/contract/registrar.sol
new file mode 100644
index 0000000000..434f4e3a77
--- /dev/null
+++ b/contracts/registrar/contract/registrar.sol
@@ -0,0 +1,175 @@
+pragma solidity ^0.5.2;
+
+/**
+ * @title Registrar
+ * @author Gary Rong, Martin Swende
+ * @dev Implementation of the blockchain checkpoint registrar.
+ */
+contract Registrar {
+ /*
+ 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/registrar/registrar.go b/contracts/registrar/registrar.go
new file mode 100644
index 0000000000..45d5b80b5c
--- /dev/null
+++ b/contracts/registrar/registrar.go
@@ -0,0 +1,89 @@
+// 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 .
+
+package registrar
+
+//go:generate abigen --sol contract/registrar.sol --pkg contract --out contract/registrar.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/registrar/contract"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+type Registrar struct {
+ contract *contract.Contract
+}
+
+// NewRegistrar binds checkpoint contract and returns a registrar instance.
+func NewRegistrar(contractAddr common.Address, backend bind.ContractBackend) (*Registrar, error) {
+ c, err := contract.NewContract(contractAddr, backend)
+ if err != nil {
+ return nil, err
+ }
+ return &Registrar{contract: c}, nil
+}
+
+// Contract returns the underlying contract instance.
+func (registrar *Registrar) Contract() *contract.Contract {
+ return registrar.contract
+}
+
+// LookupCheckpointEvent searches checkpoint event for specific section in the
+// given log batches.
+func (registrar *Registrar) LookupCheckpointEvent(blockLogs [][]*types.Log, section uint64, hash common.Hash) []*contract.ContractNewCheckpointVote {
+ var votes []*contract.ContractNewCheckpointVote
+
+ for _, logs := range blockLogs {
+ for _, log := range logs {
+ event, err := registrar.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 (registrar *Registrar) 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 registrar.contract.SetCheckpoint(bind.NewKeyedTransactor(key), rnum, rhash, common.BytesToHash(hash), index, v, r, s)
+}
diff --git a/contracts/registrar/registrar_test.go b/contracts/registrar/registrar_test.go
new file mode 100644
index 0000000000..a0e030853f
--- /dev/null
+++ b/contracts/registrar/registrar_test.go
@@ -0,0 +1,327 @@
+// 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 .
+
+package registrar
+
+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/registrar/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.Contract, backend *backends.SimulatedBackend, operation func(),
+ assert func(<-chan *contract.ContractNewCheckpointVote) error, opName string) {
+ // Watch all events and deliver them to assert function
+ var (
+ sink = make(chan *contract.ContractNewCheckpointVote)
+ 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.Compare(signer.Bytes(), expect.Bytes()) == 0
+}
+
+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(nil, 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.DeployContract(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
+ }
+ // 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.Compare(lhash[:], hash[:]) != 0 {
+ 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.ContractNewCheckpointVote) error {
+ return assert(0, emptyHash, big.NewInt(0))
+ }, "test future checkpoint registration")
+
+ contractBackend.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.ContractNewCheckpointVote) 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.ContractNewCheckpointVote) 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.ContractNewCheckpointVote) 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.ContractNewCheckpointVote) 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.ContractNewCheckpointVote)
+ 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()
+ contractBackend.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.ContractNewCheckpointVote) 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.ContractNewCheckpointVote)
+ 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.ContractNewCheckpointVote) 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.ContractNewCheckpointVote) 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 26538260cc..80062714a8 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 fbf4dd7bbc..9865e9ff75 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) {
@@ -268,6 +278,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/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index 31c0c57ec0..0abcd5a8a5 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 3a8d49ca5e..9ce6ed4800 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 *checkpointRegistrar
+}
+
+// NewPrivateLightAPI creates a new LES service API.
+func NewPrivateLightAPI(backend *lesCommons, reg *checkpointRegistrar) *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.ContractAddr.Hex(), nil
+}
diff --git a/les/backend.go b/les/backend.go
index ed0f45057f..71c6499145 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,7 +117,7 @@ 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)
@@ -141,32 +142,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 +149,15 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
gpoParams.Default = config.Miner.GasPrice
}
leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
+
+ registrar := newCheckpointRegistrar(chainConfig.CheckpointContract, leth.getLocalCheckpoint)
+ if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, 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 +218,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 +277,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/commons.go b/les/commons.go
index d46479976c..7eaf39c843 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 59bfd81cd7..c043755326 100644
--- a/les/handler.go
+++ b/les/handler.go
@@ -25,7 +25,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"
@@ -97,7 +96,7 @@ type ProtocolManager struct {
networkId uint64 // The identity of network.
txpool txPool
- txrelay *LesTxRelay
+ txrelay *lesTxRelay
blockchain BlockChain
chainDb ethdb.Database
odr *LesOdr
@@ -111,6 +110,7 @@ type ProtocolManager struct {
fetcher *lightFetcher
ulc *ulc
peers *peerSet
+ reg *checkpointRegistrar // If reg == nil, it means the checkpoint registrar is not activated
// channels for fetcher, syncer, txsyncLoop
newPeerCh chan *peer
@@ -127,23 +127,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, 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 *checkpointRegistrar, quitSync chan struct{}, wg *sync.WaitGroup, synced func() bool) (*ProtocolManager, error) {
// Create the protocol manager with the base fields
manager := &ProtocolManager{
client: client,
@@ -155,8 +139,8 @@ func NewProtocolManager(
odr: odr,
networkId: networkId,
txpool: txpool,
- txrelay: txrelay,
serverPool: serverPool,
+ reg: registrar,
peers: peers,
newPeerCh: make(chan *peer),
quitSync: quitSync,
diff --git a/les/handler_test.go b/les/handler_test.go
index 6e24165cf6..1f09ca2997 100644
--- a/les/handler_test.go
+++ b/les/handler_test.go
@@ -258,7 +258,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{
@@ -315,11 +314,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{
@@ -432,14 +430,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
@@ -447,13 +446,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")
}
}
@@ -461,16 +460,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})
@@ -478,7 +477,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)
})
@@ -497,12 +496,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)
}
@@ -525,7 +524,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 dbb0813444..e78765ca33 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/registrar/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,140 @@ contract test {
}
*/
-func testChainGen(i int, block *core.BlockGen) {
- signer := types.HomesteadSigner{}
+// 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.DeployContract(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)
- 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)
+ // 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)
- 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)
+ // 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.NewSimulatedBackend(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)
} 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 })
- if err != nil {
- return nil, err
+ config := ¶ms.CheckpointContractConfig{
+ Name: "test",
+ ContractAddr: crypto.CreateAddress(bankAddr, 0),
+ Signers: []common.Address{signerAddr},
+ Threshold: 1,
}
+ var reg *checkpointRegistrar
+ 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 = newCheckpointRegistrar(config, getLocal)
+ }
+ pm, err := NewProtocolManager(gspec.Config, 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, 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 +243,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 +378,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 +394,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 +412,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 +434,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 +483,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 +492,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 89c6091777..3c4dd7090f 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)
-
- 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
- }
-
+ // Note: For untrusted CHT request, there is no proof response but
+ // header data.
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 !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
+ }
+
+ 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 a1d5479563..1e8a5f8b46 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 6792d0611e..879e9f74c7 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"
)
@@ -77,6 +78,11 @@ type peer struct {
announceType uint64
+ // Checkpoint relative fields
+ advertisedCheckpoint params.TrustedCheckpoint
+ registeredHeight uint64
+ isHardcode bool // Indicator whether the checkpoint is hardcoded
+
id string
headInfo *announceData
@@ -565,6 +571,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
@@ -648,20 +662,40 @@ 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", ¶ms.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", ¶ms.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)])
+
+ // 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
+ hardcoded := params.TrustedCheckpoints[genesis]
+ if err := recv.get("checkpoint/value", &p.advertisedCheckpoint); hardcoded != nil &&
+ (err != nil || p.advertisedCheckpoint.SectionIndex < hardcoded.SectionIndex) {
+ p.advertisedCheckpoint = *hardcoded
+ p.isHardcode = true
+ }
+ recv.get("checkpoint/registerHeight", &p.registeredHeight)
+
if !p.isOnlyAnnounce {
for msgCode := range reqAvgTimeCost {
if p.fcCosts[msgCode] == nil {
diff --git a/les/registrar.go b/les/registrar.go
new file mode 100644
index 0000000000..a848ee1650
--- /dev/null
+++ b/les/registrar.go
@@ -0,0 +1,161 @@
+// Copyright 2016 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 .
+
+// Package les implements the Light Ethereum Subprotocol.
+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/registrar"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// checkpointRegistrar is responsible for offering the latest stable checkpoint
+// which generated by local and announced by contract admins in the server
+// side and verifying advertised checkpoint during the checkpoint syncing
+// in the client side.
+type checkpointRegistrar struct {
+ config *params.CheckpointContractConfig
+ contract *registrar.Registrar
+
+ // 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.
+}
+
+// newCheckpointRegistrar returns a checkpoint registrar handler.
+func newCheckpointRegistrar(config *params.CheckpointContractConfig, getLocal func(uint64) params.TrustedCheckpoint) *checkpointRegistrar {
+ if config == nil {
+ log.Info("Checkpoint registrar is not enabled")
+ return nil
+ }
+ if config.ContractAddr == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold {
+ log.Warn("Invalid checkpoint contract config")
+ return nil
+ }
+ log.Info("Setup checkpoint registrar", "contract", config.ContractAddr, "numsigner", len(config.Signers),
+ "threshold", config.Threshold)
+ return &checkpointRegistrar{
+ config: config,
+ getLocal: getLocal,
+ }
+}
+
+// start binds the registrar contract and start listening to the
+// newCheckpointEvent for the server side.
+func (reg *checkpointRegistrar) start(backend bind.ContractBackend) {
+ contract, err := registrar.NewRegistrar(reg.config.ContractAddr, backend)
+ if err != nil {
+ log.Info("Registrar contract binding failed", "err", err)
+ return
+ }
+ if !atomic.CompareAndSwapInt32(®.running, 0, 1) {
+ log.Info("Already bound and listening to registrar contract")
+ return
+ }
+ reg.contract = contract
+}
+
+// isRunning returns an indicator whether the registrar is running.
+func (reg *checkpointRegistrar) isRunning() bool {
+ return atomic.LoadInt32(®.running) == 1
+}
+
+// stableCheckpoint returns the stable checkpoint which generated by local indexers
+// and announced by trusted signers.
+func (reg *checkpointRegistrar) stableCheckpoint() (*params.TrustedCheckpoint, uint64) {
+ latest, hash, height, err := reg.contract.Contract().GetLatestCheckpoint(nil)
+
+ // Short circuit if the checkpoint contract is empty.
+ 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
+}
+
+// VerifySigner recovers the signer address according to the signature and
+// checks whether there are enough approves to finalize the checkpoint.
+func (reg *checkpointRegistrar) verifySigner(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 += 1 {
+ 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.ContractAddr.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 signatures to approve checkpoint", "given", len(signers), "want", threshold)
+ return false, nil
+ }
+ return true, signers
+}
diff --git a/les/request_test.go b/les/request_test.go
index e0d00d18c5..42a63c351e 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 836fa0d552..5b5c0cae84 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"
@@ -70,55 +71,27 @@ 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),
},
quitSync: quitSync,
lesTopics: lesTopics,
@@ -126,10 +99,9 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
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
@@ -137,22 +109,26 @@ 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)
- }
- 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)
+ checkpoint := srv.latestLocalCheckpoint()
+ if !checkpoint.Empty() {
+ logger.Info("Loaded latest checkpoint", "section", checkpoint.SectionIndex, "head", checkpoint.SectionHead,
+ "chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot)
}
- srv.chtIndexer.Start(eth.BlockChain())
+ srv.chtIndexer.Start(e.BlockChain())
+
+ registrar := newCheckpointRegistrar(e.BlockChain().Config().CheckpointContract, srv.getLocalCheckpoint)
+ pm, err := NewProtocolManager(e.BlockChain().Config(), 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
+
return srv, nil
}
@@ -164,6 +140,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,
+ },
}
}
@@ -288,6 +270,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 1ac6455852..53fb2d6567 100644
--- a/les/sync.go
+++ b/les/sync.go
@@ -18,11 +18,30 @@ 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"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+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 +73,125 @@ 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.advertisedCheckpoint
+ header, err := light.GetUntrustedHeaderByNumber(ctx, pm.odr, peer.registeredHeight, 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.LookupCheckpointEvent(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.verifySigner(index, hash, signatures)
+ if !valid {
+ return errInvalidCheckpoint
+ }
+ log.Debug("Verify advertised checkpoint successfully", "peer", peer.id, "signernum", 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
}
+ // 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.
+ cp := &peer.advertisedCheckpoint
+ mode := checkpointSync
+ switch {
+ case cp.Empty():
+ mode = lightSync
+ log.Debug("Disable checkpoint syncing", "reason", "empty checkpoint")
+ case latest.Number.Uint64() >= (cp.SectionIndex+1)*pm.iConfig.ChtSize-1:
+ mode = lightSync
+ log.Debug("Disable checkpoint syncing", "reason", "local chain beyonds the checkpoint")
+ case peer.isHardcode:
+ 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 {
+ cp = params.TrustedCheckpoints[pm.blockchain.Genesis().Hash()]
+ } 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(cp)
+ }
+ log.Debug("Checkpoint syncing start", "peer", peer.id, "checkpoint", cp.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 !cp.Empty() && !pm.blockchain.(*light.LightChain).SyncCheckpoint(ctx, cp) {
+ 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 0000000000..7dfef953c5
--- /dev/null
+++ b/les/sync_test.go
@@ -0,0 +1,112 @@
+// 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 .
+
+package les
+
+import (
+ "errors"
+ "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"
+)
+
+func TestCheckpointSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2) }
+
+func testCheckpointSyncing(t *testing.T, protocol 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()
+
+ // Register checkpoint 0 into the contract at block (512+4)+1
+ s, _, head := server.chtIndexer.Sections()
+ chtRoot := light.GetChtRoot(server.db, s-1, head)
+ btRoot := light.GetBloomTrieRoot(server.db, s-1, head)
+
+ cp := ¶ms.TrustedCheckpoint{
+ SectionIndex: 0,
+ SectionHead: head,
+ CHTRoot: chtRoot,
+ BloomRoot: btRoot,
+ }
+ 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()
+ server.backend.InsertEmptyBlocks(1)
+
+ // 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
+ }
+
+ done := make(chan error)
+ client.pm.reg.syncDoneHook = func() {
+ header := client.pm.blockchain.CurrentHeader()
+ if header.Number.Uint64() == config.ChtSize+config.ChtConfirms+2 {
+ done <- nil
+ } else {
+ done <- errors.New("blockchain mismatch")
+ }
+ }
+
+ // 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/txrelay.go b/les/txrelay.go
index 5ebef1c226..ffbe251fc4 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 38adeb95f1..3a3281a3f3 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 := ð.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 := ð.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 := ð.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 f0beec47bb..daaa4e1ad9 100644
--- a/light/lightchain.go
+++ b/light/lightchain.go
@@ -102,7 +102,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
return nil, core.ErrNoGenesis
}
if cp, ok := params.TrustedCheckpoints[bc.genesisBlock.Hash()]; ok {
- bc.addTrustedCheckpoint(cp)
+ bc.AddTrustedCheckpoint(cp)
}
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)
@@ -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/odr.go b/light/odr.go
index d1185e4e07..907712ede7 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_util.go b/light/odr_util.go
index 100bd58428..82e33bb78f 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/node/node.go b/node/node.go
index 08daeeee0f..c9c27d8262 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 ac12666e41..9e387ba9b6 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 c59c748ac2..2a4fae5605 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.
@@ -108,6 +110,17 @@ var (
Period: 15,
Epoch: 30000,
},
+ CheckpointContract: &CheckpointContractConfig{
+ Name: "rinkeby",
+ ContractAddr: common.HexToAddress("0x62652ed8e969ce7bd5e3dd13590efa1e569215f1"),
+ Signers: []common.Address{
+ common.HexToAddress("0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3"), // Peter
+ common.HexToAddress("0x78d1ad571a1a09d60d9bbf25894b44e4c8859595"), // Martin
+ common.HexToAddress("0x286834935f4A8Cfb4FF4C77D5770C2775aE2b0E7"), // Zsolt
+ common.HexToAddress("0xb86e2B0Ab5A4B1373e40c51A7C712c70Ba2f9f8E"), // Gary
+ },
+ Threshold: 2,
+ },
}
// RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network.
@@ -151,16 +164,16 @@ var (
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
- AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
+ AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus.
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
- AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
+ AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil}
- TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
+ TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil}
TestRules = TestChainConfig.Rules(new(big.Int))
)
@@ -176,6 +189,38 @@ type TrustedCheckpoint struct {
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{})
+}
+
+// CheckpointContractConfig represents a set of checkpoint contract config
+// which used for light client checkpoint syncing.
+type CheckpointContractConfig struct {
+ Name string `json:"-"`
+ ContractAddr common.Address `json:"contractAddr"`
+ 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
@@ -204,6 +249,9 @@ type ChainConfig struct {
// Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"`
Clique *CliqueConfig `json:"clique,omitempty"`
+
+ // Checkpoint contract configs
+ CheckpointContract *CheckpointContractConfig `json:"checkpointContract,omitempty"`
}
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
diff --git a/params/network_params.go b/params/network_params.go
index a949b84578..bba24721c1 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