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