mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
all: implement simple checkpoint syncing
cmd, les, node: remove callback mechanism cmd, node: remove callback definition les: simplify the registrar les: expose checkpoint rpc services in the light client les, light: don't store untrusted receipt
This commit is contained in:
parent
7c91038bff
commit
8d1972d793
43 changed files with 2811 additions and 344 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -317,14 +318,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
|
||||
// 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)
|
||||
}
|
||||
stateReader := ethclient.NewClient(rpcClient)
|
||||
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 {
|
||||
|
|
@ -348,7 +368,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())
|
||||
|
|
@ -377,7 +397,6 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
|||
"age", common.PrettyAge(timestamp))
|
||||
stack.Stop()
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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? (advisable at most %d)\n", len(signers))
|
||||
threshold = w.readDefaultBigInt(big.NewInt(0))
|
||||
if threshold.Int64() <= 0 || threshold.Int64() > int64(len(signers)) {
|
||||
fmt.Printf("Invalid minimal 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")
|
||||
|
||||
|
|
|
|||
130
cmd/registrar/common.go
Normal file
130
cmd/registrar/common.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"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/rpc"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// setupClient creates a client with specified remote URL.
|
||||
func setupClient(ctx *cli.Context) *ethclient.Client {
|
||||
url := ctx.GlobalString(clientURLFlag.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
|
||||
}
|
||||
|
||||
// setupDialContext creates a rpc client with specified node URL.
|
||||
func setupDialContext(ctx *cli.Context) *rpc.Client {
|
||||
url := ctx.GlobalString(clientURLFlag.Name)
|
||||
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
|
||||
}
|
||||
|
||||
// setupContract creates a registrar contract instance with specified
|
||||
// contract address or the default contracts for mainnet or testnet.
|
||||
func setupContract(client *rpc.Client) *registrar.Registrar {
|
||||
var addr string
|
||||
err := client.Call(&addr, "les_getCheckpointContractAddress")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to fetch checkpoint contract address, err %v", err)
|
||||
}
|
||||
contractAddr := common.HexToAddress(addr)
|
||||
if contractAddr == (common.Address{}) {
|
||||
utils.Fatalf("No specified registrar contract address")
|
||||
}
|
||||
contract, err := registrar.NewRegistrar(contractAddr, ethclient.NewClient(client))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to setup registrar contract %s: %v", contractAddr, err)
|
||||
}
|
||||
log.Info("Setup registrar contract", "address", contractAddr)
|
||||
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)
|
||||
}
|
||||
|
||||
// getPrivateKey retrieves the user key through specified key file.
|
||||
func getPrivateKey(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
|
||||
}
|
||||
176
cmd/registrar/exec.go
Normal file
176
cmd/registrar/exec.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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/contracts/registrar/contract"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var commandDeployContract = cli.Command{
|
||||
Name: "deploy",
|
||||
Usage: "deploy a registrar contract with specified trusted signers.",
|
||||
Description: "",
|
||||
Flags: []cli.Flag{
|
||||
trustedSignerFlag,
|
||||
sigThresholdFlag,
|
||||
clientURLFlag,
|
||||
keyFileFlag,
|
||||
utils.PasswordFileFlag,
|
||||
},
|
||||
Action: utils.MigrateFlags(deployContract),
|
||||
}
|
||||
|
||||
var commandRegisterCheckpoint = cli.Command{
|
||||
Name: "register",
|
||||
Usage: "register specified local checkpoint into contract",
|
||||
Description: `
|
||||
Register the specified checkpoint which generated by connected node with a
|
||||
authorised private key. It can help to fasten the light client checkpoint
|
||||
syncing.
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
clientURLFlag,
|
||||
checkpointIndexFlag,
|
||||
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 signerAddress []common.Address
|
||||
signers := strings.Split(ctx.GlobalString(trustedSignerFlag.Name), ",")
|
||||
for _, account := range signers {
|
||||
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
|
||||
utils.Fatalf("Invalid account in --signer: %s", trimmed)
|
||||
} else {
|
||||
signerAddress = append(signerAddress, common.HexToAddress(account))
|
||||
}
|
||||
}
|
||||
threshold := ctx.GlobalInt64(sigThresholdFlag.Name)
|
||||
if threshold == 0 || int(threshold) > len(signers) {
|
||||
utils.Fatalf("Invalid signature threshold %d", threshold)
|
||||
}
|
||||
addr, tx, _, err := contract.DeployContract(bind.NewKeyedTransactor(getPrivateKey(ctx).PrivateKey), setupClient(ctx), signerAddress, big.NewInt(int64(params.CheckpointFrequency)),
|
||||
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(threshold))
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to deploy registrar contract %v", err)
|
||||
}
|
||||
log.Info("Deploy registrar contract successfully", "address", addr, "tx", tx.Hash())
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerCheckpoint registers the specified checkpoint which generated by connected
|
||||
// node with a authorised private key.
|
||||
func registerCheckpoint(ctx *cli.Context) error {
|
||||
var (
|
||||
checkpoint *params.TrustedCheckpoint
|
||||
rpcClient = setupDialContext(ctx)
|
||||
)
|
||||
// Fetch specified checkpoint from connected node.
|
||||
if ctx.GlobalIsSet(checkpointIndexFlag.Name) {
|
||||
var result [3]string
|
||||
index := ctx.GlobalInt64(checkpointIndexFlag.Name)
|
||||
if err := rpcClient.Call(&result, "les_getCheckpoint", index); err != nil {
|
||||
utils.Fatalf("Failed to get local checkpoint %v", err)
|
||||
}
|
||||
checkpoint = ¶ms.TrustedCheckpoint{
|
||||
SectionIndex: uint64(index),
|
||||
SectionHead: common.HexToHash(result[0]),
|
||||
CHTRoot: common.HexToHash(result[1]),
|
||||
BloomRoot: common.HexToHash(result[2]),
|
||||
}
|
||||
log.Info("Retrieve local checkpoint", "index", checkpoint.SectionIndex, "sectionhead", checkpoint.SectionHead,
|
||||
"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot, "hash", checkpoint.Hash())
|
||||
} else {
|
||||
var result [4]string
|
||||
err := rpcClient.Call(&result, "les_latestCheckpoint")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to get local checkpoint %v", 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]),
|
||||
}
|
||||
log.Info("Retrieve local checkpoint", "index", checkpoint.SectionIndex, "sectionhead", checkpoint.SectionHead,
|
||||
"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot, "hash", checkpoint.Hash())
|
||||
}
|
||||
contract := setupContract(rpcClient)
|
||||
// Filter out stale checkpoint announcement
|
||||
latest, _, h, err := contract.Contract().GetLatestCheckpoint(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
head, err := ethclient.NewClient(rpcClient).HeaderByNumber(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headNumber := head.Number.Uint64()
|
||||
if headNumber < ((checkpoint.SectionIndex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) {
|
||||
utils.Fatalf("Checkpoint is not stable enough")
|
||||
}
|
||||
if headNumber >= ((checkpoint.SectionIndex + 2) * params.CheckpointFrequency) {
|
||||
utils.Fatalf("Checkpoint is too old")
|
||||
}
|
||||
if checkpoint.SectionIndex == latest.Uint64() && (latest.Uint64() != 0 || h.Uint64() != 0) {
|
||||
utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest.Uint64(), checkpoint.SectionIndex)
|
||||
}
|
||||
// Ensure the invoker is a trusted signer.
|
||||
signers, err := contract.Contract().GetAllAdmin(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := getPrivateKey(ctx)
|
||||
isTrusted := false
|
||||
for _, signer := range signers {
|
||||
if signer == key.Address {
|
||||
isTrusted = true
|
||||
}
|
||||
}
|
||||
if !isTrusted {
|
||||
utils.Fatalf("Address %s is not a trusted signer", key.Address)
|
||||
}
|
||||
// Register the checkpoint
|
||||
tx, err := contract.SetCheckpoint(key.PrivateKey, big.NewInt(int64(checkpoint.SectionIndex)), checkpoint.Hash().Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Set checkpoint successfully", "tx", tx.Hash())
|
||||
return nil
|
||||
}
|
||||
102
cmd/registrar/main.go
Normal file
102
cmd/registrar/main.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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}}`
|
||||
)
|
||||
|
||||
// Git SHA1 commit hash of the release (set via linker flags)
|
||||
var gitCommit = ""
|
||||
|
||||
var app *cli.App
|
||||
|
||||
func init() {
|
||||
app = utils.NewApp(gitCommit, "ethereum checkpoint helper tool")
|
||||
app.Commands = []cli.Command{
|
||||
commandQueryAdmin,
|
||||
commandQueryCheckpoint,
|
||||
commandPendingProposal,
|
||||
commandDeployContract,
|
||||
commandRegisterCheckpoint,
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
checkpointIndexFlag,
|
||||
sigThresholdFlag,
|
||||
keyFileFlag,
|
||||
clientURLFlag,
|
||||
trustedSignerFlag,
|
||||
utils.PasswordFileFlag,
|
||||
}
|
||||
cli.CommandHelpTemplate = commandHelperTemplate
|
||||
}
|
||||
|
||||
// Commonly used command line flags.
|
||||
var (
|
||||
checkpointIndexFlag = cli.Int64Flag{
|
||||
Name: "index",
|
||||
Usage: "The index of checkpoint, use the latest index if not specified",
|
||||
}
|
||||
sigThresholdFlag = cli.Int64Flag{
|
||||
Name: "threshold",
|
||||
Usage: "The minimal signature required to approve a checkpoint",
|
||||
}
|
||||
keyFileFlag = cli.StringFlag{
|
||||
Name: "keyfile",
|
||||
Usage: "The private key file",
|
||||
}
|
||||
clientURLFlag = cli.StringFlag{
|
||||
Name: "rpc",
|
||||
Value: "http://localhost:8545",
|
||||
Usage: "The rpc endpoint of a local or remote geth node",
|
||||
}
|
||||
trustedSignerFlag = cli.StringFlag{
|
||||
Name: "signer",
|
||||
Usage: "Comma separated accounts to treat as trusted checkpoint signer",
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
120
cmd/registrar/query.go
Normal file
120
cmd/registrar/query.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"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",
|
||||
Description: `
|
||||
Fetch the admin list of the specified registrar contract which are regarded
|
||||
as trusted signers to register checkpoint.
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
clientURLFlag,
|
||||
},
|
||||
Action: utils.MigrateFlags(queryAdmin),
|
||||
}
|
||||
|
||||
var commandQueryCheckpoint = cli.Command{
|
||||
Name: "querycheckpoint",
|
||||
Usage: "Fetch the specified checkpoint in the registrar contract",
|
||||
Description: `
|
||||
Fetch the registered checkpoint with the specified index.
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
checkpointIndexFlag,
|
||||
clientURLFlag,
|
||||
},
|
||||
Action: utils.MigrateFlags(queryCheckpoint),
|
||||
}
|
||||
|
||||
var commandPendingProposal = cli.Command{
|
||||
Name: "queryproposal",
|
||||
Usage: "Fetch the detail of the inflight new checkpoint proposal",
|
||||
Description: `
|
||||
Get detailed data of the new checkpoint proposal currently in progress,
|
||||
including the trusted signer address who has been approved and the corresponding
|
||||
checkpoint hash
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
clientURLFlag,
|
||||
},
|
||||
Action: utils.MigrateFlags(queryProposal),
|
||||
}
|
||||
|
||||
// queryAdmin fetches the admin list of specified registrar contract.
|
||||
func queryAdmin(ctx *cli.Context) error {
|
||||
contract := setupContract(setupDialContext(ctx))
|
||||
adminList, err := contract.Contract().GetAllAdmin(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Total admin number", len(adminList))
|
||||
for i, admin := range adminList {
|
||||
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 := setupContract(setupDialContext(ctx))
|
||||
if ctx.GlobalIsSet(checkpointIndexFlag.Name) {
|
||||
index := ctx.GlobalInt64(checkpointIndexFlag.Name)
|
||||
checkpoint, height, err := contract.Contract().GetCheckpoint(nil, big.NewInt(index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Checkpoint(registered at height #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex())
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
// queryProposal fetches the detail of inflight new checkpoint proposal
|
||||
// with specified contract address.
|
||||
func queryProposal(ctx *cli.Context) error {
|
||||
contract := setupContract(setupDialContext(ctx))
|
||||
index, addr, hashes, err := contract.Contract().GetPending(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(addr) != len(hashes) {
|
||||
return errors.New("trusted signer number is not match with corresponding hash")
|
||||
}
|
||||
fmt.Printf("Pending checkpoint proposal(index #%d)\n", index)
|
||||
for i, a := range addr {
|
||||
fmt.Printf("Signer(%s) => checkpoint hash(%s)\n", a.Hex(), common.Hash(hashes[i]).Hex())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -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)},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
475
contracts/registrar/contract/registrar.go
Normal file
475
contracts/registrar/contract/registrar.go
Normal file
File diff suppressed because one or more lines are too long
272
contracts/registrar/contract/registrar.sol
Normal file
272
contracts/registrar/contract/registrar.sol
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
pragma solidity ^0.5.2;
|
||||
|
||||
/**
|
||||
* @title Registrar
|
||||
* @author Gary Rong<garyrong@ethereum.org>
|
||||
* @dev Implementation of the blockchain checkpoint registrar.
|
||||
*/
|
||||
contract Registrar {
|
||||
/*
|
||||
Definitions
|
||||
*/
|
||||
|
||||
// Vote represents a checkpoint announcement from a trusted signer.
|
||||
struct Vote {
|
||||
address addr;
|
||||
bytes sig;
|
||||
}
|
||||
|
||||
// PendingProposal represents a tally for current pending new checkpoint
|
||||
// proposal.
|
||||
struct PendingProposal {
|
||||
uint index; // Checkpoint section index
|
||||
uint count; // Number of signers who have submitted checkpoint announcement
|
||||
mapping(address => bytes32) usermap; // map between signer address and advertised checkpoint hash
|
||||
mapping(bytes32 => Vote[]) votemap; // map between checkpoint hash and relative signer announcements.
|
||||
}
|
||||
|
||||
/*
|
||||
Modifiers
|
||||
*/
|
||||
|
||||
/**
|
||||
* @dev Check whether the message sender is authorized.
|
||||
*/
|
||||
modifier OnlyAuthorized() {
|
||||
require(admins[msg.sender]);
|
||||
_;
|
||||
}
|
||||
|
||||
/*
|
||||
Events
|
||||
*/
|
||||
|
||||
// NewCheckpointEvent is emitted when a new checkpoint proposal receive enough approvals.
|
||||
// We use checkpoint hash instead of the full checkpoint to make the transaction cheaper.
|
||||
event NewCheckpointEvent(uint indexed index, bytes32 checkpointHash, bytes signature);
|
||||
|
||||
/*
|
||||
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(uint, bytes32, uint) {
|
||||
(bytes32 hash, uint height) = GetCheckpoint(latest);
|
||||
return (latest, hash, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get a stable checkpoint information with specified section index.
|
||||
* @param _sectionIndex section index
|
||||
* @return checkpoint hash
|
||||
* @return the associated register block height
|
||||
*/
|
||||
function GetCheckpoint(uint _sectionIndex)
|
||||
view
|
||||
public
|
||||
returns(bytes32, uint)
|
||||
{
|
||||
return (checkpoints[_sectionIndex], register_height[_sectionIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Submit a new checkpoint announcement
|
||||
* Checkpoint represents a set of post-processed trie roots (CHT and BloomTrie)
|
||||
* associated with the appropriate section head hash.
|
||||
*
|
||||
* It is used to start light syncing from this checkpoint and avoid downloading
|
||||
* the entire header chain while still being able to securely access old headers/logs.
|
||||
*
|
||||
* @param _sectionIndex section index
|
||||
* @param _hash checkpoint hash calculated in the client side
|
||||
* @param _sig admin's signature for checkpoint hash
|
||||
* `checkpoint_hash = Hash(index, sectionHead, chtRoot, bloomRoot)`
|
||||
* `_sig = Sign(privateKey, checkpoint_hash)`
|
||||
* @return indicator whether set checkpoint successfully
|
||||
*/
|
||||
function SetCheckpoint(
|
||||
uint _sectionIndex,
|
||||
bytes32 _hash,
|
||||
bytes memory _sig
|
||||
)
|
||||
OnlyAuthorized
|
||||
public
|
||||
returns(bool)
|
||||
{
|
||||
// Checkpoint register/modification time window: [(secIndex+1)*size + confirms, (secIndex+2)*size)
|
||||
if (block.number < (_sectionIndex+1)*sectionSize+processConfirms || block.number >= (_sectionIndex+2)*sectionSize) {
|
||||
return false;
|
||||
}
|
||||
// Filter out stale announcement
|
||||
if (_sectionIndex == latest && (latest != 0 || register_height[0] != 0)) {
|
||||
return false;
|
||||
}
|
||||
// Filter out invalid announcement
|
||||
if (_hash == "" || _sig.length == 0) {
|
||||
return false;
|
||||
}
|
||||
// Delete stale pending proposal silently
|
||||
if (pending_proposal.index != _sectionIndex) {
|
||||
deletePending();
|
||||
}
|
||||
bytes32 old = pending_proposal.usermap[msg.sender];
|
||||
// Filter out duplicate announcement
|
||||
if (old == _hash) {
|
||||
return false;
|
||||
}
|
||||
bool isNew = (old == "");
|
||||
pending_proposal.usermap[msg.sender] = _hash;
|
||||
|
||||
if (!isNew) {
|
||||
// Checkpoint modification
|
||||
Vote[] storage votes = pending_proposal.votemap[old];
|
||||
for (uint i = 0; i < votes.length - 1; i++) {
|
||||
if (votes[i].addr == msg.sender) {
|
||||
votes[i] = votes[votes.length - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete votes[votes.length-1];
|
||||
votes.length -= 1;
|
||||
pending_proposal.votemap[_hash].push(Vote({
|
||||
addr: msg.sender,
|
||||
sig: _sig
|
||||
}));
|
||||
} else {
|
||||
// New checkpoint announcement
|
||||
pending_proposal.count += 1;
|
||||
pending_proposal.index = _sectionIndex;
|
||||
pending_proposal.votemap[_hash].push(Vote({
|
||||
addr: msg.sender,
|
||||
sig: _sig
|
||||
}));
|
||||
}
|
||||
if (pending_proposal.votemap[_hash].length < threshold) {
|
||||
return true;
|
||||
}
|
||||
checkpoints[_sectionIndex] = _hash;
|
||||
register_height[_sectionIndex] = block.number;
|
||||
latest = _sectionIndex;
|
||||
|
||||
bytes memory sigs;
|
||||
for (uint idx = 0; idx < threshold; idx++) {
|
||||
sigs = abi.encodePacked(sigs, pending_proposal.votemap[_hash][idx].sig);
|
||||
}
|
||||
emit NewCheckpointEvent(_sectionIndex, _hash, sigs);
|
||||
deletePending();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get the detail of pending proposal
|
||||
* @return checkpoint index
|
||||
* @return signers who have submitted checkpoint announcement
|
||||
* @return hashes corresponding checkpoint hash
|
||||
*/
|
||||
function GetPending()
|
||||
public
|
||||
view
|
||||
returns(uint, address[] memory, bytes32[] memory)
|
||||
{
|
||||
uint idx = 0;
|
||||
address[] memory addr = new address[](pending_proposal.count);
|
||||
bytes32[] memory hashes = new bytes32[](pending_proposal.count);
|
||||
for (uint i = 0; i < adminList.length; i++) {
|
||||
bytes32 h = pending_proposal.usermap[adminList[i]];
|
||||
if (h != "") {
|
||||
addr[idx] = adminList[i];
|
||||
hashes[idx] = h;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
return (pending_proposal.index, addr, hashes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Clear pending proposal
|
||||
*/
|
||||
function deletePending()
|
||||
private
|
||||
{
|
||||
for (uint i = 0; i < adminList.length; i++) {
|
||||
bytes32 h = pending_proposal.usermap[adminList[i]];
|
||||
if (h != "") {
|
||||
delete pending_proposal.votemap[h];
|
||||
delete pending_proposal.usermap[adminList[i]];
|
||||
}
|
||||
}
|
||||
delete pending_proposal;
|
||||
}
|
||||
|
||||
/*
|
||||
Fields
|
||||
*/
|
||||
// Inflight new stable checkpoint proposal.
|
||||
PendingProposal pending_proposal;
|
||||
|
||||
// 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;
|
||||
|
||||
// Registered checkpoint information
|
||||
mapping(uint => bytes32) checkpoints;
|
||||
|
||||
// Latest stored section id
|
||||
// Note all registered checkpoint information should continuous with previous one.
|
||||
uint latest;
|
||||
|
||||
// The block height associated with latest registered checkpoint.
|
||||
mapping(uint => uint) register_height;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
77
contracts/registrar/registrar.go
Normal file
77
contracts/registrar/registrar.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package registrar
|
||||
|
||||
//go:generate abigen --sol contract/registrar.sol --pkg contract --out contract/registrar.go
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"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"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
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.ContractNewCheckpointEvent {
|
||||
var result []*contract.ContractNewCheckpointEvent
|
||||
|
||||
for _, logs := range blockLogs {
|
||||
for _, log := range logs {
|
||||
event, err := registrar.contract.ParseNewCheckpointEvent(*log)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if event.Index.Uint64() == section && common.Hash(event.CheckpointHash) == hash {
|
||||
result = append(result, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SetCheckpoint creates a signature for given checkpoint with specified private key and registers into contract.
|
||||
func (registrar *Registrar) SetCheckpoint(key *ecdsa.PrivateKey, sectionIndex *big.Int, hash []byte) (*types.Transaction, error) {
|
||||
sig, err := crypto.Sign(hash, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var h [32]byte
|
||||
copy(h[:], hash)
|
||||
return registrar.contract.SetCheckpoint(bind.NewKeyedTransactor(key), sectionIndex, h, sig)
|
||||
}
|
||||
341
contracts/registrar/registrar_test.go
Normal file
341
contracts/registrar/registrar_test.go
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package registrar
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"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/light"
|
||||
)
|
||||
|
||||
const signatureLen = 65
|
||||
|
||||
var (
|
||||
emptyHash = [32]byte{}
|
||||
|
||||
checkpoint0 = light.TrustedCheckpoint{
|
||||
SectionIndex: 0,
|
||||
SectionHead: common.HexToHash("0x7fa3c32f996c2bfb41a1a65b3d8ea3e0a33a1674cde43678ad6f4235e764d17d"),
|
||||
CHTRoot: common.HexToHash("0x98fc5d3de23a0fecebad236f6655533c157d26a1aedcd0852a514dc1169e6350"),
|
||||
BloomRoot: common.HexToHash("0x99b5adb52b337fe25e74c1c6d3835b896bd638611b3aebddb2317cce27a3f9fa"),
|
||||
}
|
||||
checkpoint1 = light.TrustedCheckpoint{
|
||||
SectionIndex: 1,
|
||||
SectionHead: common.HexToHash("0x2d4dee68102125e59b0cc61b176bd89f0d12b3b91cfaf52ef8c2c82fb920c2d2"),
|
||||
CHTRoot: common.HexToHash("0x7d428008ece3b4c4ef5439f071930aad0bb75108d381308df73beadcd01ded95"),
|
||||
BloomRoot: common.HexToHash("0x652571f7736de17e7bbb427ac881474da684c6988a88bf51b10cca9a2ee148f4"),
|
||||
}
|
||||
checkpoint2 = light.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.ContractNewCheckpointEvent) error, opName string) {
|
||||
// Watch all events and deliver them to assert function
|
||||
var (
|
||||
sink = make(chan *contract.ContractNewCheckpointEvent)
|
||||
sub, _ = c.WatchNewCheckpointEvent(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(privateKey *ecdsa.PrivateKey, hash common.Hash) []byte {
|
||||
sig, _ := crypto.Sign(hash.Bytes(), privateKey)
|
||||
return sig
|
||||
}
|
||||
|
||||
// assertSignature verifies whether the recovered signers are equal with expected.
|
||||
func assertSignature(hash [32]byte, sigs []byte, expectSigners []common.Address) bool {
|
||||
if len(sigs) != signatureLen*len(expectSigners) {
|
||||
return false
|
||||
}
|
||||
|
||||
signerMap := make(map[common.Address]struct{})
|
||||
for i := 0; i < len(expectSigners); i += 1 {
|
||||
pubkey, err := crypto.Ecrecover(hash[:], sigs[i*signatureLen:(i+1)*signatureLen])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
signerMap[signer] = struct{}{}
|
||||
}
|
||||
for i := 0; i < len(expectSigners); i += 1 {
|
||||
if _, exist := signerMap[expectSigners[i]]; !exist {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Tests checkpoint managements.
|
||||
func TestCheckpointRegister(t *testing.T) {
|
||||
// Initialize test accounts
|
||||
type Account struct {
|
||||
key *ecdsa.PrivateKey
|
||||
addr common.Address
|
||||
}
|
||||
var accounts []Account
|
||||
for i := 0; i < 3; i++ {
|
||||
key, _ := crypto.GenerateKey()
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
accounts = append(accounts, Account{key: key, addr: addr})
|
||||
}
|
||||
|
||||
// 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
|
||||
_, _, 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("deploy registrar contract failed", err)
|
||||
}
|
||||
contractBackend.Commit()
|
||||
|
||||
// Register unstable checkpoint
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[0].key, checkpoint0.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
hash, _, err := c.GetCheckpoint(nil, big.NewInt(0))
|
||||
if err != nil {
|
||||
return errors.New("get checkpoint failed")
|
||||
}
|
||||
if hash != emptyHash {
|
||||
return errors.New("unstable checkpoint should be rejected")
|
||||
}
|
||||
return nil
|
||||
}, "register unstable checkpoint")
|
||||
|
||||
contractBackend.InsertEmptyBlocks(int(sectionSize.Uint64() + processConfirms.Uint64()))
|
||||
|
||||
// Register by unauthorized user
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
u, _ := crypto.GenerateKey()
|
||||
unauthorized := bind.NewKeyedTransactor(u)
|
||||
c.SetCheckpoint(unauthorized, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(u, checkpoint0.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
hash, _, err := c.GetCheckpoint(nil, big.NewInt(0))
|
||||
if err != nil {
|
||||
return errors.New("get checkpoint failed")
|
||||
}
|
||||
if hash != emptyHash {
|
||||
return errors.New("checkpoint from unauthorized user should be rejected")
|
||||
}
|
||||
return nil
|
||||
}, "register by unauthorized user")
|
||||
|
||||
// Submit a new checkpoint announcement
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[0].key, checkpoint0.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{checkpoint0.Hash()})
|
||||
}, "single checkpoint announcement")
|
||||
|
||||
// Submit a duplicate checkpoint announcement
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[0].key, checkpoint0.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{checkpoint0.Hash()})
|
||||
}, "duplicate checkpoint announcement")
|
||||
|
||||
// Modification
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(0), common.HexToHash("deadbeef"), signCheckpoint(accounts[0].key, common.HexToHash("deadbeef")))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{common.HexToHash("deadbeef")})
|
||||
}, "checkpoint modification")
|
||||
|
||||
// Modification
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(0), common.HexToHash("deadbeef2"), signCheckpoint(accounts[0].key, common.HexToHash("deadbeef2")))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
return assert(c, 0, []common.Address{accounts[0].addr}, []common.Hash{common.HexToHash("deadbeef2")})
|
||||
}, "checkpoint modification")
|
||||
|
||||
// Another correct checkpoint announcement
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[1].key), big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[1].key, checkpoint0.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
return assert(c, 0, []common.Address{accounts[0].addr, accounts[1].addr}, []common.Hash{common.HexToHash("deadbeef2"), checkpoint0.Hash()})
|
||||
}, "another checkpoint announcement")
|
||||
|
||||
// enough checkpoint announcement
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[2].key), big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[2].key, checkpoint0.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
if valid, recv := validateEvents(1, events); !valid {
|
||||
return errors.New("receive incorrect number of events")
|
||||
} else {
|
||||
event := recv[0].Interface().(*contract.ContractNewCheckpointEvent)
|
||||
if !assertSignature(event.CheckpointHash, event.Signature, []common.Address{accounts[1].addr, accounts[2].addr}) {
|
||||
return errors.New("recover signer failed")
|
||||
}
|
||||
}
|
||||
index, hash, height, err := c.GetLatestCheckpoint(nil)
|
||||
if err != nil || index.Uint64() != 0 || hash != checkpoint0.Hash() ||
|
||||
height.Uint64() != contractBackend.Blockchain().CurrentHeader().Number.Uint64() {
|
||||
return errors.New("stable checkpoint mismatch")
|
||||
}
|
||||
return assert(c, 0, nil, nil)
|
||||
}, "enough checkpoint announcement")
|
||||
|
||||
// submit a stale checkpoint announcement
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpoint0.Hash(), signCheckpoint(accounts[0].key, checkpoint0.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
return assert(c, 0, nil, nil)
|
||||
}, "submit stale checkpoint announcement")
|
||||
|
||||
// submit a future checkpoint announcement
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(1), checkpoint1.Hash(), signCheckpoint(accounts[0].key, checkpoint1.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
return assert(c, 0, nil, nil)
|
||||
}, "submit future checkpoint announcement")
|
||||
|
||||
// submit uncontinuous checkpoint announcement
|
||||
distance := 3*sectionSize.Uint64() + processConfirms.Uint64() - contractBackend.Blockchain().CurrentHeader().Number.Uint64()
|
||||
contractBackend.InsertEmptyBlocks(int(distance))
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(2), checkpoint2.Hash(), signCheckpoint(accounts[0].key, checkpoint2.Hash()))
|
||||
c.SetCheckpoint(bind.NewKeyedTransactor(accounts[1].key), big.NewInt(2), checkpoint2.Hash(), signCheckpoint(accounts[1].key, checkpoint2.Hash()))
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent) error {
|
||||
if valid, recv := validateEvents(1, events); !valid {
|
||||
return errors.New("receive incorrect number of events")
|
||||
} else {
|
||||
event := recv[0].Interface().(*contract.ContractNewCheckpointEvent)
|
||||
if !assertSignature(event.CheckpointHash, event.Signature, []common.Address{accounts[0].addr, accounts[1].addr}) {
|
||||
return errors.New("recover signer failed")
|
||||
}
|
||||
}
|
||||
index, hash, height, err := c.GetLatestCheckpoint(nil)
|
||||
if err != nil || index.Uint64() != 2 || hash != checkpoint2.Hash() ||
|
||||
height.Uint64() != contractBackend.Blockchain().CurrentHeader().Number.Uint64() {
|
||||
return errors.New("stable checkpoint mismatch")
|
||||
}
|
||||
return assert(c, 0, nil, nil)
|
||||
}, "uncontinuous checkpoint announcement")
|
||||
}
|
||||
|
||||
func assert(c *contract.Contract, index uint64, signers []common.Address, hashes []common.Hash) error {
|
||||
pi, pSigners, pHashes, err := c.GetPending(nil)
|
||||
if err != nil {
|
||||
return errors.New("get pending proposal failed")
|
||||
}
|
||||
if pi.Uint64() != index {
|
||||
return fmt.Errorf("pending checkpoint index mismatch, want=%d, got=%d", index, pi.Uint64())
|
||||
}
|
||||
// Assert signers
|
||||
if len(pSigners) != len(signers) {
|
||||
return fmt.Errorf("pending checkpoint signers number mismatch, want=%d, got=%d", len(signers), len(pSigners))
|
||||
}
|
||||
for _, a := range signers {
|
||||
found := false
|
||||
for _, b := range pSigners {
|
||||
if a == b {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("signer %s not found", a.Hex())
|
||||
}
|
||||
}
|
||||
// Assert hashes
|
||||
if len(hashes) != len(pHashes) {
|
||||
return fmt.Errorf("pending checkpoint hash number mismatch, want=%d, got=%d", len(hashes), len(pHashes))
|
||||
}
|
||||
for _, a := range hashes {
|
||||
found := false
|
||||
for _, b := range pHashes {
|
||||
if a == b {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("hash %s not found", a.Hex())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -267,6 +277,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{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ var Modules = map[string]string{
|
|||
"shh": ShhJs,
|
||||
"swarmfs": SwarmfsJs,
|
||||
"txpool": TxpoolJs,
|
||||
"les": LESJs,
|
||||
}
|
||||
|
||||
const ChequebookJs = `
|
||||
|
|
@ -760,3 +761,28 @@ web3._extend({
|
|||
]
|
||||
});
|
||||
`
|
||||
|
||||
const LESJs = `
|
||||
web3._extend({
|
||||
property: 'les',
|
||||
methods:
|
||||
[
|
||||
new web3._extend.Method({
|
||||
name: 'getCheckpoint',
|
||||
call: 'les_getCheckpoint',
|
||||
params: 1
|
||||
}),
|
||||
],
|
||||
properties:
|
||||
[
|
||||
new web3._extend.Property({
|
||||
name: 'latestCheckpoint',
|
||||
getter: 'les_latestCheckpoint'
|
||||
}),
|
||||
new web3._extend.Property({
|
||||
name: 'checkpointContractAddress',
|
||||
getter: 'les_getCheckpointContractAddress'
|
||||
}),
|
||||
]
|
||||
});
|
||||
`
|
||||
|
|
|
|||
58
les/api.go
58
les/api.go
|
|
@ -32,6 +32,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
|
||||
)
|
||||
|
|
@ -452,3 +454,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -114,7 +114,6 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
|||
if leth.config.ULC != nil {
|
||||
trustedNodes = leth.config.ULC.TrustedServers
|
||||
}
|
||||
leth.relay = NewLesTxRelay(peers, leth.reqDist)
|
||||
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg, trustedNodes)
|
||||
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
|
||||
|
||||
|
|
@ -140,32 +139,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
|||
rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
|
||||
}
|
||||
|
||||
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); 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.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, newLesTxRelay(peers, leth.reqDist))
|
||||
leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil}
|
||||
|
||||
gpoParams := config.GPO
|
||||
|
|
@ -173,6 +147,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); 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
|
||||
}
|
||||
|
||||
|
|
@ -233,6 +216,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,
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
|
@ -286,3 +274,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: params.TrustedCheckpoint(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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -89,7 +88,6 @@ type txPool interface {
|
|||
type ProtocolManager struct {
|
||||
lightSync bool
|
||||
txpool txPool
|
||||
txrelay *LesTxRelay
|
||||
networkId uint64
|
||||
chainConfig *params.ChainConfig
|
||||
iConfig *light.IndexerConfig
|
||||
|
|
@ -102,6 +100,7 @@ type ProtocolManager struct {
|
|||
reqDist *requestDistributor
|
||||
retriever *retrieveManager
|
||||
servingQueue *servingQueue
|
||||
reg *checkpointRegistrar // If reg == nil, it means the checkpoint registrar is not activated
|
||||
|
||||
downloader *downloader.Downloader
|
||||
fetcher *lightFetcher
|
||||
|
|
@ -123,23 +122,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,
|
||||
lightSync 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) (*ProtocolManager, error) {
|
||||
func NewProtocolManager(chainConfig *params.ChainConfig, indexerConfig *light.IndexerConfig, ulcConfig *eth.ULCConfig, lightSync 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) (*ProtocolManager, error) {
|
||||
// Create the protocol manager with the base fields
|
||||
manager := &ProtocolManager{
|
||||
lightSync: lightSync,
|
||||
|
|
@ -151,8 +134,8 @@ func NewProtocolManager(
|
|||
odr: odr,
|
||||
networkId: networkId,
|
||||
txpool: txpool,
|
||||
txrelay: txrelay,
|
||||
serverPool: serverPool,
|
||||
reg: registrar,
|
||||
peers: peers,
|
||||
newPeerCh: make(chan *peer),
|
||||
quitSync: quitSync,
|
||||
|
|
|
|||
|
|
@ -257,7 +257,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{
|
||||
|
|
@ -314,11 +313,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{
|
||||
|
|
@ -431,28 +429,27 @@ 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)
|
||||
defer peer.close()
|
||||
server.pm.txpool = txpool
|
||||
|
||||
var reqID uint64
|
||||
|
||||
test := func(tx *types.Transaction, send bool, expStatus 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, []txStatus{expStatus}); err != nil {
|
||||
if err := expectResponse(server.tPeer.app, TxStatusMsg, reqID, testBufLimit, []txStatus{expStatus}); err != nil {
|
||||
t.Errorf("transaction status mismatch")
|
||||
}
|
||||
}
|
||||
|
|
@ -460,16 +457,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, 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, txStatus{Status: core.TxStatusUnknown}) // query before sending, should be unknown
|
||||
test(tx1, true, txStatus{Status: core.TxStatusPending}) // send valid processable tx, should return pending
|
||||
test(tx1, true, 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, txStatus{Status: core.TxStatusQueued})
|
||||
test(tx2, true, txStatus{Status: core.TxStatusPending})
|
||||
|
|
@ -477,7 +474,7 @@ func TestTransactionStatusLes2(t *testing.T) {
|
|||
test(tx3, false, 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)
|
||||
})
|
||||
|
|
@ -496,12 +493,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, txStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}})
|
||||
test(tx2, false, 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,9 +63,20 @@ var (
|
|||
testContractDeployed = uint64(2)
|
||||
|
||||
testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029")
|
||||
testEventEmitterAddr common.Address
|
||||
|
||||
testBufLimit = uint64(100)
|
||||
|
||||
// Checkpoint registrar relative
|
||||
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)
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -80,100 +94,137 @@ 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:
|
||||
// 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)
|
||||
// deploy checkpoint contract
|
||||
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:
|
||||
// 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)
|
||||
bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
|
||||
userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1)
|
||||
|
||||
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)
|
||||
// bankUser transfers more ether to user1
|
||||
tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1000), params.TxGas, nil, nil), signer, bankKey)
|
||||
backend.SendTransaction(ctx, tx1)
|
||||
|
||||
// user1 relays ether to user2
|
||||
tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1000), params.TxGas, nil, nil), signer, userKey1)
|
||||
backend.SendTransaction(ctx, tx2)
|
||||
|
||||
// user1 deploys a test contract
|
||||
tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, userKey1)
|
||||
backend.SendTransaction(ctx, tx3)
|
||||
testContractAddr = crypto.CreateAddress(userAddr1, userNonce1+1)
|
||||
|
||||
// user1 deploys a event contract
|
||||
tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, userKey1)
|
||||
backend.SendTransaction(ctx, tx4)
|
||||
case 2:
|
||||
// Block 3 is empty but was mined by account #2.
|
||||
block.SetCoinbase(acc2Addr)
|
||||
block.SetExtra([]byte("yeehaw"))
|
||||
// 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")
|
||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey)
|
||||
block.AddTx(tx)
|
||||
tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey)
|
||||
backend.SendTransaction(ctx, tx2)
|
||||
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)
|
||||
// invoke test contract
|
||||
bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
|
||||
data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
|
||||
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey)
|
||||
block.AddTx(tx)
|
||||
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) (*ProtocolManager, error) {
|
||||
func newTestProtocolManager(lightSync bool, blocks int, odr *LesOdr, indexers []*core.ChainIndexer, peers *peerSet, db ethdb.Database, ulcConfig *eth.ULCConfig) (*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
|
||||
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
|
||||
chain = 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, nil, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup), ulcConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
config := ¶ms.CheckpointContractConfig{
|
||||
Name: "test",
|
||||
ContractAddr: crypto.CreateAddress(bankAddr, 0),
|
||||
Signers: []common.Address{signerAddr},
|
||||
}
|
||||
var reg *checkpointRegistrar
|
||||
if indexers != nil {
|
||||
getLocal := func(index uint64) light.TrustedCheckpoint {
|
||||
chtIndexer := indexers[0]
|
||||
sectionHead := chtIndexer.SectionHead(index)
|
||||
return light.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, nil, db, odr, nil, reg, exitCh, new(sync.WaitGroup))
|
||||
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.setThreads(4)
|
||||
|
||||
|
|
@ -185,19 +236,19 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
|
|||
srv.fcManager = flowcontrol.NewClientManager(nil, &mclock.System{})
|
||||
}
|
||||
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)
|
||||
// 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)
|
||||
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,6 +375,8 @@ type TestEntity struct {
|
|||
tPeer *testPeer
|
||||
peers *peerSet
|
||||
pm *ProtocolManager
|
||||
backend *backends.SimulatedBackend
|
||||
|
||||
// Indexers
|
||||
chtIndexer *core.ChainIndexer
|
||||
bloomIndexer *core.ChainIndexer
|
||||
|
|
@ -333,11 +386,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)
|
||||
|
||||
cIndexer, bIndexer, btIndexer := indexers[0], indexers[1], indexers[2]
|
||||
cIndexer.Start(pm.blockchain.(*core.BlockChain))
|
||||
bIndexer.Start(pm.blockchain.(*core.BlockChain))
|
||||
|
||||
|
|
@ -350,6 +404,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,
|
||||
|
|
@ -371,12 +426,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 {
|
||||
|
|
@ -416,6 +475,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,
|
||||
|
|
@ -424,6 +484,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,
|
||||
|
|
|
|||
|
|
@ -164,11 +164,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
|
||||
|
|
@ -321,8 +323,12 @@ func (r *ChtRequest) CanSend(peer *peer) bool {
|
|||
peer.lock.RLock()
|
||||
defer peer.lock.RUnlock()
|
||||
|
||||
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)
|
||||
func (r *ChtRequest) Request(reqID uint64, peer *peer) error {
|
||||
|
|
@ -362,6 +368,10 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
|
|||
}
|
||||
|
||||
// Verify the CHT
|
||||
// Note: For untrusted CHT request, there is no proof response but
|
||||
// header data.
|
||||
var node light.ChtNode
|
||||
if !r.Untrusted {
|
||||
var encNumber [8]byte
|
||||
binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
|
||||
|
||||
|
|
@ -374,7 +384,6 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
|
|||
return errUselessNodes
|
||||
}
|
||||
|
||||
var node light.ChtNode
|
||||
if err := rlp.DecodeBytes(value, &node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -384,10 +393,12 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, 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)
|
||||
|
|
|
|||
44
les/peer.go
44
les/peer.go
|
|
@ -30,6 +30,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"
|
||||
)
|
||||
|
||||
|
|
@ -68,6 +69,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
|
||||
|
|
@ -481,6 +487,14 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
|
|||
send = send.add("flowControl/MRC", costList)
|
||||
p.fcCosts = costList.decode()
|
||||
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
|
||||
|
|
@ -558,20 +572,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()
|
||||
|
||||
// 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 {
|
||||
|
|
|
|||
144
les/registrar.go
Normal file
144
les/registrar.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package les implements the Light Ethereum Subprotocol.
|
||||
package les
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"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"
|
||||
)
|
||||
|
||||
const signatureLen = 65
|
||||
|
||||
// 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("Bind registrar contract 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, _, err := reg.contract.Contract().GetLatestCheckpoint(nil)
|
||||
if err != nil || latest.Uint64() == 0 && hash == [32]byte{} {
|
||||
return nil, 0
|
||||
}
|
||||
index := latest.Uint64()
|
||||
for {
|
||||
local := reg.getLocal(index)
|
||||
hash, height, err := reg.contract.Contract().GetCheckpoint(nil, big.NewInt(int64(index)))
|
||||
if err == nil && local.HashEqual(common.Hash(hash)) {
|
||||
return &local, height.Uint64()
|
||||
}
|
||||
if index == 0 {
|
||||
break
|
||||
}
|
||||
index -= 1
|
||||
}
|
||||
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(checkpointHash [32]byte, signature []byte) (bool, []common.Address) {
|
||||
if len(signature)%signatureLen != 0 {
|
||||
log.Warn("Invalid aggregation signature", "length", len(signature))
|
||||
return false, nil
|
||||
}
|
||||
var (
|
||||
signers []common.Address
|
||||
number = len(signature) / signatureLen
|
||||
signerMap = make(map[common.Address]struct{})
|
||||
)
|
||||
for i := 0; i < number; i += 1 {
|
||||
pubkey, err := crypto.Ecrecover(checkpointHash[:], signature[i*signatureLen:(i+1)*signatureLen])
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
if _, exist := signerMap[signer]; exist {
|
||||
continue
|
||||
}
|
||||
for _, s := range reg.config.Signers {
|
||||
if s == signer {
|
||||
signers = append(signers, signer)
|
||||
signerMap[signer] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
threshold := reg.config.Threshold
|
||||
if uint64(len(signers)) < threshold {
|
||||
log.Warn("Checkpoint approval is not enough", "given", len(signers), "want", threshold)
|
||||
return false, nil
|
||||
}
|
||||
return true, signers
|
||||
}
|
||||
|
|
@ -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[:])
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"sync"
|
||||
|
||||
"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"
|
||||
|
|
@ -56,51 +57,28 @@ 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) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
costTracker: newCostTracker(eth.ChainDb(), config),
|
||||
costTracker: newCostTracker(e.ChainDb(), config),
|
||||
quitSync: quitSync,
|
||||
lesTopics: lesTopics,
|
||||
onlyAnnounce: config.OnlyAnnounce,
|
||||
}
|
||||
|
||||
logger := log.New()
|
||||
pm.server = srv
|
||||
srv.thcNormal = config.LightServ * 4 / 100
|
||||
if srv.thcNormal < 4 {
|
||||
srv.thcNormal = 4
|
||||
|
|
@ -108,22 +86,27 @@ 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())
|
||||
|
||||
chainConfig, _, genesisErr := core.SetupGenesisBlockWithOverride(e.ChainDb(), config.Genesis, config.ConstantinopleOverride)
|
||||
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
|
||||
return nil, genesisErr
|
||||
}
|
||||
|
||||
registrar := newCheckpointRegistrar(chainConfig.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))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv.protocolManager = pm
|
||||
pm.server = srv
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +118,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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,6 +220,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.chtIndexer.Close()
|
||||
|
|
|
|||
128
les/sync.go
128
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,115 @@ 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 {
|
||||
// Fetch the block header corresponding to the checkpoint registration.
|
||||
cp := peer.advertisedCheckpoint
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
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())
|
||||
for _, event := range events {
|
||||
valid, signers := pm.reg.verifySigner(event.CheckpointHash, event.Signature)
|
||||
if valid {
|
||||
log.Debug("Verify advertised checkpoint successfully", "peer", peer.id, "signernum", len(signers))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errInvalidCheckpoint
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// 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()
|
||||
pm.blockchain.(*light.LightChain).SyncCht(ctx)
|
||||
pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync)
|
||||
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)))
|
||||
}
|
||||
|
|
|
|||
104
les/sync_test.go
Normal file
104
les/sync_test.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package les
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
)
|
||||
|
||||
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()
|
||||
if cs >= 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
// Generate 512+4 blocks (totally 8 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
|
||||
bts, _, head := server.bloomTrieIndexer.Sections()
|
||||
chtRoot := light.GetChtRoot(server.db, 7, head)
|
||||
btRoot := light.GetBloomTrieRoot(server.db, bts-1, head)
|
||||
|
||||
cp := &light.TrustedCheckpoint{
|
||||
SectionIndex: 0,
|
||||
SectionHead: head,
|
||||
CHTRoot: chtRoot,
|
||||
BloomRoot: btRoot,
|
||||
}
|
||||
if _, err := server.pm.reg.contract.SetCheckpoint(signerKey, big.NewInt(int64(cp.SectionIndex)), cp.Hash().Bytes()); 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().GetCheckpoint(nil, big.NewInt(0))
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,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
|
||||
|
|
@ -40,8 +40,8 @@ type LesTxRelay struct {
|
|||
reqDist *requestDistributor
|
||||
}
|
||||
|
||||
func NewLesTxRelay(ps *peerSet, reqDist *requestDistributor) *LesTxRelay {
|
||||
r := &LesTxRelay{
|
||||
func newLesTxRelay(ps *peerSet, reqDist *requestDistributor) *lesTxRelay {
|
||||
r := &lesTxRelay{
|
||||
txSent: make(map[common.Hash]*ltrInfo),
|
||||
txPending: make(map[common.Hash]struct{}),
|
||||
ps: ps,
|
||||
|
|
@ -51,14 +51,14 @@ func NewLesTxRelay(ps *peerSet, reqDist *requestDistributor) *LesTxRelay {
|
|||
return r
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
|
|
@ -67,7 +67,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
|
||||
|
|
@ -136,14 +136,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()
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ func (self *LesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback
|
|||
}
|
||||
}
|
||||
|
||||
func (self *LesTxRelay) Discard(hashes []common.Hash) {
|
||||
func (self *lesTxRelay) Discard(hashes []common.Hash) {
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,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"
|
||||
|
|
@ -20,7 +19,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()},
|
||||
|
|
@ -47,7 +46,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()},
|
||||
|
|
@ -84,8 +83,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{}{}
|
||||
|
|
@ -115,9 +114,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,
|
||||
|
|
@ -195,10 +194,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",
|
||||
|
|
@ -222,7 +221,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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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) {
|
||||
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,10 +153,12 @@ type ChtRequest struct {
|
|||
func (req *ChtRequest) StoreResult(db ethdb.Database) {
|
||||
hash, num := req.Header.Hash(), req.Header.Number.Uint64()
|
||||
|
||||
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
|
||||
type BloomRequest struct {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,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{}) {
|
||||
|
|
@ -168,6 +178,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 (
|
||||
|
|
|
|||
|
|
@ -247,7 +247,6 @@ func (n *Node) Start() error {
|
|||
n.services = services
|
||||
n.server = running
|
||||
n.stop = make(chan struct{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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("0xb708d2ca0ead46f7f254a13a10c4fc7b86ea34e"),
|
||||
Signers: []common.Address{
|
||||
common.HexToAddress("0x779ca2fc4a31a48dc338bc71959c1f1942a9098b"),
|
||||
common.HexToAddress("0xacf370223f3ff062a997b3d138dddf1ad4d02a54"),
|
||||
common.HexToAddress("0x937e8a232121eed45ce654f895e662b913526cfe"),
|
||||
common.HexToAddress("0xdab27fd1a2ec8a899f87a5fa05f2c82870c036c7"),
|
||||
},
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -46,4 +46,10 @@ const (
|
|||
// HelperTrieProcessConfirmations is the number of confirmations before a HelperTrie
|
||||
// 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
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue