This commit is contained in:
gary rong 2018-08-07 10:31:52 +00:00 committed by GitHub
commit 84e0b0304a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 2118 additions and 224 deletions

View file

@ -405,6 +405,28 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
return nil return nil
} }
// ShiftBlocks inserts a batch of empty blocks to blockchain.
func (b *SimulatedBackend) ShiftBlocks(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
}
// callmsg implements core.Message to allow passing it as a transaction simulator. // callmsg implements core.Message to allow passing it as a transaction simulator.
type callmsg struct { type callmsg struct {
ethereum.CallMsg ethereum.CallMsg

View file

@ -19,11 +19,13 @@ package utils
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -44,6 +46,7 @@ import (
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/ethstats"
"github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/les"
@ -1175,6 +1178,22 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
} }
return fullNode, err return fullNode, err
}) })
if err == nil {
err = stack.RegisterCallback(reflect.TypeOf(&eth.Ethereum{}), func(service node.Service) error {
if e, ok := service.(*eth.Ethereum); ok {
// The node lock will be held during the whole node setup procedure, so no extra
// lock operation is needed.
rpcClient, err := stack.AttachLocked()
if err != nil {
return err
}
e.SetClient(ethclient.NewClient(rpcClient))
return nil
} else {
return errors.New("the service given is not the required type")
}
})
}
} }
if err != nil { if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err) Fatalf("Failed to register the Ethereum service: %v", err)

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,217 @@
pragma solidity ^0.4.24;
/**
* @title Registrar
* @author Gary Rong<garyrong0905@gmail.com>
* @dev Implementation of the blockchain checkpoint information registrar.
*/
contract Registrar {
/*
Modifiers
*/
/**
* @dev Check whether the message sender is authorized.
*/
modifier OnlyAuthorized() {
require(admins[msg.sender] > 0);
_;
}
/*
Events
*/
// NewCheckpointEvent is emitted when new checkpoint is registered.
// Grantor indicates the people register the checkpoint.
// We use checkpoint hash instead of the full checkpoint to make the transaction cheaper.
event NewCheckpointEvent(uint indexed index, address grantor, bytes32 checkpointHash);
// AddAdminEvent is emitted when new address is accepted as admin.
// Grantor indicates who authorizes the add admin operation.
event AddAdminEvent(address addr, address grantor, string description);
// RemoveAdminEvent is emitted when an admin is removed.
// Grantor indicates who authorizes the remove admin operation.
event RemoveAdminEvent(address addr, address grantor, string reason);
/*
Public Functions
*/
constructor(address[] _adminlist) public {
// regard contract creator as a default admin.
admins[msg.sender] = 1;
adminList.push(msg.sender);
for (uint i = 0; i < _adminlist.length; i++) {
admins[_adminlist[i]] = 1;
adminList.push(_adminlist[i]);
}
}
/**
* @dev Get latest stable checkpoint information.
* @return section index
* @return checkpoint hash
*/
function GetLatestCheckpoint()
view
public
returns(uint, bytes32) {
bytes32 hash = GetCheckpoint(latest);
return (latest, hash);
}
/**
* @dev Get a stable checkpoint information with specified section index.
* @param _sectionIndex section index
* @return checkpoint hash
*/
function GetCheckpoint(uint _sectionIndex)
view
public
returns(bytes32)
{
return checkpoints[_sectionIndex];
}
/**
* @dev Set stable checkpoint information.
* 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.
*
* Note we trust the given information here provided by foundation,
* need a trust less version for future.
* @param _sectionIndex section index
* @param _hash checkpoint hash calculated in the client side
* @return indicator whether set checkpoint successfully
*/
function SetCheckpoint(
uint _sectionIndex,
bytes32 _hash
)
OnlyAuthorized
public
returns(bool)
{
// Ensure the checkpoint information provided is strictly continuous with previous one.
// But the latest checkpoint modification is allowed.
if (_sectionIndex != latest && _sectionIndex != latest + 1 && latest != 0) {
return false;
}
// Ensure the checkpoint is stable enough to be registered.
if (block.number < (_sectionIndex+1)*sectionSize+processConfirmations) {
return false;
}
// Ensure the modification for registered checkpoint within the allowed time range
if (latest != 0 && _sectionIndex == latest && block.number >= (_sectionIndex+1)*sectionSize+confirmations) {
return false;
}
checkpoints[_sectionIndex] = _hash;
latest = _sectionIndex;
emit NewCheckpointEvent(_sectionIndex, msg.sender, _hash);
}
/**
* @dev Add a new address to admin list
* @param _addr specified new admin address.
* @return indicator whether add new admin successfully
*/
function AddAdmin(address _addr, string _description)
OnlyAuthorized
public
returns(bool)
{
// Ensure the specified address is not admin yet.
if (admins[_addr] > 0) {
return false;
}
admins[_addr] = 1;
adminList.push(_addr);
emit AddAdminEvent(_addr, msg.sender, _description);
return true;
}
/**
* @dev Remove a admin from the list
* @param _addr specified admin address to remove.
* @return indicator whether remove admin successfully
*/
function RemoveAdmin(address _addr, string _reason)
OnlyAuthorized
public
returns(bool)
{
// Ensure the specified address is admin.
if (admins[_addr] == 0) {
return false;
}
delete admins[_addr];
for (uint i = 0; i < adminList.length; i++) {
if (adminList[i] == _addr) {
// Not leave a gap
for (uint idx = i; idx < adminList.length-1; idx++){
adminList[idx] = adminList[idx+1];
}
delete adminList[adminList.length-1];
adminList.length -= 1;
break;
}
}
emit RemoveAdminEvent(_addr, msg.sender, _reason);
return true;
}
/**
* @dev Get all admin addresses
* @return address list
*/
function GetAllAdmin()
public
view
returns(address[])
{
address[] memory ret = new address[](adminList.length);
for (uint i = 0; i < adminList.length; i++) {
ret[i] = adminList[i];
}
return ret;
}
/*
Fields
*/
// A map of admin users who have the permission to update CHT and bloom Trie root
mapping(address => uint) 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 frequency for creating a checkpoint
uint constant sectionSize = 32768;
// 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.
uint constant processConfirmations = 256;
// The number of confirmations when a registered checkpoint can not be modified.
uint constant confirmations = 8192;
}

View file

@ -0,0 +1,73 @@
// 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 (
"errors"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/contracts/registrar/contract"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
)
var (
// registrar contract address for mainnet and testnet.
RegistrarAddr = map[common.Hash]common.Address{
// params.MainnetGenesisHash: common.HexToAddress(""),
// params.TestnetGenesisHash: common.HexToAddress(""),
params.RinkebyGenesisHash: common.HexToAddress("0xc72f57e41e2498ad3dab92f665b0f21e2c4f4b79"),
}
)
var errEventNotFound = errors.New("contract event not found")
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) {
contract, err := contract.NewContract(contractAddr, backend)
if err != nil {
return nil, err
}
return &Registrar{
contract: contract,
}, nil
}
// WatchNewCheckpointEvent watches new fired NewCheckpointEvent and delivers all matching events by result channel.
func (registrar *Registrar) WatchNewCheckpointEvent(sink chan<- *contract.ContractNewCheckpointEvent) (event.Subscription, error) {
return registrar.contract.WatchNewCheckpointEvent(nil, sink, nil)
}
// FilterNewCheckpointEvent filters out NewCheckpointEvent for specific section number.
func (registrar *Registrar) FilterNewCheckpointEvent(head, section, sectionSize, processConfirm uint64) (*contract.ContractNewCheckpointEventIterator, error) {
start := (section+1)*sectionSize + processConfirm
if head < start {
return nil, errEventNotFound
}
opt := &bind.FilterOpts{
Start: start,
End: &head,
}
return registrar.contract.FilterNewCheckpointEvent(opt, []*big.Int{big.NewInt(int64(section))})
}

View file

@ -0,0 +1,297 @@
// 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 (
"errors"
"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"
)
var (
key, _ = crypto.GenerateKey()
addr = crypto.PubkeyToAddress(key.PublicKey)
emptyHash = [32]byte{}
checkpointHash0 = crypto.Keccak256Hash(common.FromHex("dead0"), common.FromHex("beef0"), common.FromHex("deadbeef0"))
checkpointHash1 = crypto.Keccak256Hash(common.FromHex("dead1"), common.FromHex("beef1"), common.FromHex("deadbeef1"))
)
// 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, <-chan *contract.ContractAddAdminEvent, <-chan *contract.ContractRemoveAdminEvent) error, opName string) {
// Watch all events and deliver them to assert function
var (
sink1 = make(chan *contract.ContractNewCheckpointEvent)
sink2 = make(chan *contract.ContractAddAdminEvent)
sink3 = make(chan *contract.ContractRemoveAdminEvent)
)
sub1, _ := c.WatchNewCheckpointEvent(nil, sink1, nil)
sub2, _ := c.WatchAddAdminEvent(nil, sink2)
sub3, _ := c.WatchRemoveAdminEvent(nil, sink3)
defer func() {
// Close all subscribers
sub1.Unsubscribe()
sub2.Unsubscribe()
sub3.Unsubscribe()
}()
operation()
// flush pending block
backend.Commit()
if err := assert(sink1, sink2, sink3); 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 {
chanval := reflect.ValueOf(sink)
chantyp := chanval.Type()
if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.RecvDir == 0 {
return false
}
count := 0
timeout := time.After(1 * time.Second)
cases := []reflect.SelectCase{{Chan: chanval, Dir: reflect.SelectRecv}, {Chan: reflect.ValueOf(timeout), Dir: reflect.SelectRecv}}
for {
chose, _, _ := reflect.Select(cases)
if chose == 1 {
// Not enough event received
return false
}
count += 1
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
}
// Tests contract administrator managements.
func TestAdminManagement(t *testing.T) {
var (
adminCandidate = common.HexToAddress("0xdead")
adminCandidate2 = common.HexToAddress("0xbeef")
)
// Deploy registrar contract
transactOpts := bind.NewKeyedTransactor(key)
contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}})
_, _, c, err := contract.DeployContract(transactOpts, contractBackend, nil)
if err != nil {
t.Error("deploy registrar contract failed", err)
}
contractBackend.Commit()
// Test AddAdmin function
validateOperation(t, c, contractBackend, func() {
for _, a := range []common.Address{addr, adminCandidate, adminCandidate2} {
c.AddAdmin(transactOpts, a, "")
}
}, func(sink1 <-chan *contract.ContractNewCheckpointEvent, sink2 <-chan *contract.ContractAddAdminEvent, sink3 <-chan *contract.ContractRemoveAdminEvent) error {
adminList, err := c.GetAllAdmin(nil)
if err != nil {
return errors.New("get admin list failed")
}
if !reflect.DeepEqual(adminList, []common.Address{addr, adminCandidate, adminCandidate2}) {
return errors.New("add admin failed")
}
if !validateEvents(2, sink2) {
return errors.New("receive incorrect number of events")
}
return nil
}, "add admin")
// Test Remove admin function
validateOperation(t, c, contractBackend, func() {
c.RemoveAdmin(transactOpts, adminCandidate, "")
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
adminList, err := c.GetAllAdmin(nil)
if err != nil {
return errors.New("get admin list failed")
}
if !reflect.DeepEqual(adminList, []common.Address{addr, adminCandidate2}) {
return errors.New("remove admin failed")
}
if !validateEvents(1, events3) {
return errors.New("receive incorrect number of events")
}
return nil
}, "remove admin at middle")
// Test RemoveAdmin function (remove at the head)
validateOperation(t, c, contractBackend, func() {
c.RemoveAdmin(transactOpts, addr, "")
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
adminList, err := c.GetAllAdmin(nil)
if err != nil {
return errors.New("get admin list failed")
}
if !reflect.DeepEqual(adminList, []common.Address{adminCandidate2}) {
return errors.New("remove admin failed")
}
if !validateEvents(1, events3) {
return errors.New("receive incorrect number of events")
}
return nil
}, "remove admin at head")
// Test unauthorized operation
validateOperation(t, c, contractBackend, func() {
c.AddAdmin(transactOpts, adminCandidate, "")
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
adminList, err := c.GetAllAdmin(nil)
if err != nil {
return errors.New("get admin list failed")
}
if !reflect.DeepEqual(adminList, []common.Address{adminCandidate2}) {
return errors.New("unauthorized operation should be banned")
}
return nil
}, "unauthorized operation")
}
// Tests checkpoint managements.
func TestCheckpointRegister(t *testing.T) {
// Deploy registrar contract
transactOpts := bind.NewKeyedTransactor(key)
contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}})
_, _, c, err := contract.DeployContract(transactOpts, contractBackend, []common.Address{addr})
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), checkpointHash0)
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) 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 banned")
}
return nil
}, "register unstable checkpoint")
contractBackend.ShiftBlocks(light.CheckpointFrequency + light.CheckpointProcessConfirmations)
// Register by unauthorized user
validateOperation(t, c, contractBackend, func() {
user2, _ := crypto.GenerateKey()
unauthorized := bind.NewKeyedTransactor(user2)
c.SetCheckpoint(unauthorized, big.NewInt(0), checkpointHash0)
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) 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 banned")
}
return nil
}, "register by unauthorized user")
// Register a stable checkpoint
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(0), checkpointHash0)
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
hash, err := c.GetCheckpoint(nil, big.NewInt(0))
if err != nil {
return errors.New("get checkpoint failed")
}
if hash != checkpointHash0 {
return errors.New("register stable checkpoint failed")
}
if !validateEvents(1, events) {
return errors.New("receive incorrect number of events")
}
return nil
}, "register stable checkpoint")
newHash := crypto.Keccak256Hash(common.FromHex("dead00"), common.FromHex("beef00"), common.FromHex("deadbeef00"))
// Modify the latest checkpoint
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(0), newHash)
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
hash, err := c.GetCheckpoint(nil, big.NewInt(0))
if err != nil {
return errors.New("get checkpoint failed")
}
if hash != newHash {
return errors.New("register stable checkpoint failed")
}
if !validateEvents(1, events) {
return errors.New("receive incorrect number of events")
}
return nil
}, "modify latest checkpoint")
contractBackend.ShiftBlocks(light.CheckpointFrequency)
// Register checkpoint 1
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(1), checkpointHash1)
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
hash, err := c.GetCheckpoint(nil, big.NewInt(1))
if err != nil {
return errors.New("get checkpoint failed")
}
if hash != checkpointHash1 {
return errors.New("register stable checkpoint failed")
}
if !validateEvents(1, events) {
return errors.New("receive incorrect number of events")
}
return nil
}, "register stable checkpoint 1")
contractBackend.ShiftBlocks(light.CheckpointConfirmations)
newHash1 := crypto.Keccak256Hash(common.FromHex("dead11"), common.FromHex("beef11"), common.FromHex("deadbeef11"))
// Modify the registered checkpoint after a very long time
validateOperation(t, c, contractBackend, func() {
c.SetCheckpoint(transactOpts, big.NewInt(1), newHash1)
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
hash, err := c.GetCheckpoint(nil, big.NewInt(1))
if err != nil {
return errors.New("get checkpoint failed")
}
if hash != checkpointHash1 {
return errors.New("checkpoint modified out of allowed time range")
}
return nil
}, "modify checkpoint out of allowed time range")
}

View file

@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
@ -54,8 +55,10 @@ import (
type LesServer interface { type LesServer interface {
Start(srvr *p2p.Server) Start(srvr *p2p.Server)
Stop() Stop()
APIs() []rpc.API
Protocols() []p2p.Protocol Protocols() []p2p.Protocol
SetBloomBitsIndexer(bbIndexer *core.ChainIndexer) SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
SetClient(*ethclient.Client)
} }
// Ethereum implements the Ethereum full node service. // Ethereum implements the Ethereum full node service.
@ -99,6 +102,14 @@ func (s *Ethereum) AddLesServer(ls LesServer) {
ls.SetBloomBitsIndexer(s.bloomIndexer) ls.SetBloomBitsIndexer(s.bloomIndexer)
} }
// SetClient sets a rpc client which connecting to our local node.
func (s *Ethereum) SetClient(client *ethclient.Client) {
// Pass the rpc client to les server if it is enabled.
if s.lesServer != nil {
s.lesServer.SetClient(client)
}
}
// New creates a new Ethereum object (including the // New creates a new Ethereum object (including the
// initialisation of the common Ethereum object) // initialisation of the common Ethereum object)
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
@ -248,6 +259,11 @@ func (s *Ethereum) APIs() []rpc.API {
// Append any APIs exposed explicitly by the consensus engine // Append any APIs exposed explicitly by the consensus engine
apis = append(apis, s.engine.APIs(s.BlockChain())...) 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 // Append all the local APIs and return
return append(apis, []rpc.API{ return append(apis, []rpc.API{
{ {

View file

@ -31,6 +31,7 @@ var Modules = map[string]string{
"shh": Shh_JS, "shh": Shh_JS,
"swarmfs": SWARMFS_JS, "swarmfs": SWARMFS_JS,
"txpool": TxPool_JS, "txpool": TxPool_JS,
"les": LES_JS,
} }
const Chequebook_JS = ` const Chequebook_JS = `
@ -670,3 +671,24 @@ web3._extend({
] ]
}); });
` `
const LES_JS = `
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'
}),
]
});
`

75
les/api.go Normal file
View file

@ -0,0 +1,75 @@
// 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"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var (
errNoCheckpoint = errors.New("no local checkpoint provided")
)
// PrivateLesServerAPI provides a private API to access the les server.
type PrivateLesServerAPI struct {
server *LesServer
}
// NewPrivateLesServerAPI creates a new les server API.
func NewPrivateLesServerAPI(server *LesServer) *PrivateLesServerAPI {
return &PrivateLesServerAPI{
server: server,
}
}
// Checkpoint 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 *PrivateLesServerAPI) LatestCheckpoint() ([4]string, error) {
var res [4]string
sectionIdx, sectionHead, chtRoot, bloomTrieRoot := api.server.latestCheckpoint()
if sectionHead == (common.Hash{}) || chtRoot == (common.Hash{}) || bloomTrieRoot == (common.Hash{}) {
return res, errNoCheckpoint
}
res[0] = hexutil.Encode(big.NewInt(int64(sectionIdx)).Bytes())
res[1], res[2], res[3] = sectionHead.Hex(), chtRoot.Hex(), bloomTrieRoot.Hex()
return res, nil
}
// GetCheckpoint 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 *PrivateLesServerAPI) GetCheckpoint(index uint64) ([3]string, error) {
var res [3]string
sectionHead, chtRoot, bloomTrieRoot := api.server.getCheckpoint(index)
if sectionHead == (common.Hash{}) || chtRoot == (common.Hash{}) || bloomTrieRoot == (common.Hash{}) {
return res, errNoCheckpoint
}
res[0], res[1], res[2] = sectionHead.Hex(), chtRoot.Hex(), bloomTrieRoot.Hex()
return res, nil
}

199
les/costtable.go Normal file
View file

@ -0,0 +1,199 @@
// 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
import (
"encoding/binary"
"math"
"sync"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
)
type requestCosts struct {
baseCost, reqCost uint64
}
type requestCostTable map[uint64]*requestCosts
type RequestCostList []struct {
MsgCode, BaseCost, ReqCost uint64
}
func (list RequestCostList) decode() requestCostTable {
table := make(requestCostTable)
for _, e := range list {
table[e.MsgCode] = &requestCosts{
baseCost: e.BaseCost,
reqCost: e.ReqCost,
}
}
return table
}
type linReg struct {
sumX, sumY, sumXX, sumXY float64
cnt uint64
}
const linRegMaxCnt = 100000
func (l *linReg) add(x, y float64) {
if l.cnt >= linRegMaxCnt {
sub := float64(l.cnt+1-linRegMaxCnt) / linRegMaxCnt
l.sumX -= l.sumX * sub
l.sumY -= l.sumY * sub
l.sumXX -= l.sumXX * sub
l.sumXY -= l.sumXY * sub
l.cnt = linRegMaxCnt - 1
}
l.cnt++
l.sumX += x
l.sumY += y
l.sumXX += x * x
l.sumXY += x * y
}
func (l *linReg) calc() (b, m float64) {
if l.cnt == 0 {
return 0, 0
}
cnt := float64(l.cnt)
d := cnt*l.sumXX - l.sumX*l.sumX
if d < 0.001 {
return l.sumY / cnt, 0
}
m = (cnt*l.sumXY - l.sumX*l.sumY) / d
b = (l.sumY / cnt) - (m * l.sumX / cnt)
return b, m
}
func (l *linReg) toBytes() []byte {
var arr [40]byte
binary.BigEndian.PutUint64(arr[0:8], math.Float64bits(l.sumX))
binary.BigEndian.PutUint64(arr[8:16], math.Float64bits(l.sumY))
binary.BigEndian.PutUint64(arr[16:24], math.Float64bits(l.sumXX))
binary.BigEndian.PutUint64(arr[24:32], math.Float64bits(l.sumXY))
binary.BigEndian.PutUint64(arr[32:40], l.cnt)
return arr[:]
}
func linRegFromBytes(data []byte) *linReg {
if len(data) != 40 {
return nil
}
l := &linReg{}
l.sumX = math.Float64frombits(binary.BigEndian.Uint64(data[0:8]))
l.sumY = math.Float64frombits(binary.BigEndian.Uint64(data[8:16]))
l.sumXX = math.Float64frombits(binary.BigEndian.Uint64(data[16:24]))
l.sumXY = math.Float64frombits(binary.BigEndian.Uint64(data[24:32]))
l.cnt = binary.BigEndian.Uint64(data[32:40])
return l
}
type requestCostStats struct {
lock sync.RWMutex
db ethdb.Database
stats map[uint64]*linReg
}
type requestCostStatsRlp []struct {
MsgCode uint64
Data []byte
}
var rcStatsKey = []byte("_requestCostStats")
func newCostStats(db ethdb.Database) *requestCostStats {
stats := make(map[uint64]*linReg)
for _, code := range reqList {
stats[code] = &linReg{cnt: 100}
}
if db != nil {
data, err := db.Get(rcStatsKey)
var statsRlp requestCostStatsRlp
if err == nil {
err = rlp.DecodeBytes(data, &statsRlp)
}
if err == nil {
for _, r := range statsRlp {
if stats[r.MsgCode] != nil {
if l := linRegFromBytes(r.Data); l != nil {
stats[r.MsgCode] = l
}
}
}
}
}
return &requestCostStats{
db: db,
stats: stats,
}
}
func (s *requestCostStats) store() {
s.lock.Lock()
defer s.lock.Unlock()
statsRlp := make(requestCostStatsRlp, len(reqList))
for i, code := range reqList {
statsRlp[i].MsgCode = code
statsRlp[i].Data = s.stats[code].toBytes()
}
if data, err := rlp.EncodeToBytes(statsRlp); err == nil {
s.db.Put(rcStatsKey, data)
}
}
func (s *requestCostStats) getCurrentList() RequestCostList {
s.lock.Lock()
defer s.lock.Unlock()
list := make(RequestCostList, len(reqList))
//fmt.Println("RequestCostList")
for idx, code := range reqList {
b, m := s.stats[code].calc()
//fmt.Println(code, s.stats[code].cnt, b/1000000, m/1000000)
if m < 0 {
b += m
m = 0
}
if b < 0 {
b = 0
}
list[idx].MsgCode = code
list[idx].BaseCost = uint64(b * 2)
list[idx].ReqCost = uint64(m * 2)
}
return list
}
func (s *requestCostStats) update(msgCode, reqCnt, cost uint64) {
s.lock.Lock()
defer s.lock.Unlock()
c, ok := s.stats[msgCode]
if !ok || reqCnt == 0 {
return
}
c.add(float64(reqCnt), float64(cost))
}

View file

@ -19,26 +19,35 @@ package les
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/binary" "errors"
"math"
"sync" "sync"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/contracts/registrar"
"github.com/ethereum/go-ethereum/contracts/registrar/contract"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/les/flowcontrol"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc"
) )
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
const SubscribeChainHeadEvent = 10
type LesServer struct { type LesServer struct {
config *eth.Config config *eth.Config
backend *eth.EthAPIBackend
chaindb ethdb.Database
protocolManager *ProtocolManager protocolManager *ProtocolManager
fcManager *flowcontrol.ClientManager // nil if our node is client only fcManager *flowcontrol.ClientManager // nil if our node is client only
fcCostStats *requestCostStats fcCostStats *requestCostStats
@ -47,28 +56,38 @@ type LesServer struct {
privateKey *ecdsa.PrivateKey privateKey *ecdsa.PrivateKey
quitSync chan struct{} quitSync chan struct{}
chtIndexer, bloomTrieIndexer *core.ChainIndexer // Checkpoint contract relative fields
genesis common.Hash // Genesis block hash for contract address detection
registrar *registrar.Registrar // Handler for checkpoint contract, initialized after server is started.
watching int32 // Indicator whether the checkpoint contract is being watched
// Indexers
chtIndexer *core.ChainIndexer // Indexers for creating cht root for each block section
bloomTrieIndexer *core.ChainIndexer // Indexers for creating bloom trie root for each block section
} }
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
quitSync := make(chan struct{}) quitSync := make(chan struct{})
pm, err := NewProtocolManager(eth.BlockChain().Config(), false, ServerProtocolVersions, config.NetworkId, eth.EventMux(), eth.Engine(), newPeerSet(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil, nil, quitSync, new(sync.WaitGroup)) pm, err := NewProtocolManager(e.BlockChain().Config(), false, ServerProtocolVersions, config.NetworkId, e.EventMux(), e.Engine(), newPeerSet(), e.BlockChain(), e.TxPool(), e.ChainDb(), nil, nil, nil, quitSync, new(sync.WaitGroup))
if err != nil { if err != nil {
return nil, err return nil, err
} }
lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
for i, pv := range AdvertiseProtocolVersions { for i, pv := range AdvertiseProtocolVersions {
lesTopics[i] = lesTopic(eth.BlockChain().Genesis().Hash(), pv) lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv)
} }
srv := &LesServer{ srv := &LesServer{
config: config, config: config,
backend: e.APIBackend,
chaindb: e.ChainDb(),
protocolManager: pm, protocolManager: pm,
quitSync: quitSync, quitSync: quitSync,
lesTopics: lesTopics, lesTopics: lesTopics,
chtIndexer: light.NewChtIndexer(eth.ChainDb(), false), chtIndexer: light.NewChtIndexer(e.ChainDb(), false),
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), false), bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), false),
genesis: e.BlockChain().Genesis().Hash(),
} }
logger := log.New() logger := log.New()
@ -80,18 +99,18 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
// convert last LES/2 section index back to LES/1 index for chtIndexer.SectionHead // convert last LES/2 section index back to LES/1 index for chtIndexer.SectionHead
chtLastSectionV1 := (chtLastSection+1)*(light.CHTFrequencyClient/light.CHTFrequencyServer) - 1 chtLastSectionV1 := (chtLastSection+1)*(light.CHTFrequencyClient/light.CHTFrequencyServer) - 1
chtSectionHead := srv.chtIndexer.SectionHead(chtLastSectionV1) chtSectionHead := srv.chtIndexer.SectionHead(chtLastSectionV1)
chtRoot := light.GetChtV2Root(pm.chainDb, chtLastSection, chtSectionHead) chtRoot := light.GetChtV2Root(srv.chaindb, chtLastSection, chtSectionHead)
logger.Info("Loaded CHT", "section", chtLastSection, "head", chtSectionHead, "root", chtRoot) logger.Info("Loaded CHT", "section", chtLastSection, "head", chtSectionHead, "root", chtRoot)
} }
bloomTrieSectionCount, _, _ := srv.bloomTrieIndexer.Sections() bloomTrieSectionCount, _, _ := srv.bloomTrieIndexer.Sections()
if bloomTrieSectionCount != 0 { if bloomTrieSectionCount != 0 {
bloomTrieLastSection := bloomTrieSectionCount - 1 bloomTrieLastSection := bloomTrieSectionCount - 1
bloomTrieSectionHead := srv.bloomTrieIndexer.SectionHead(bloomTrieLastSection) bloomTrieSectionHead := srv.bloomTrieIndexer.SectionHead(bloomTrieLastSection)
bloomTrieRoot := light.GetBloomTrieRoot(pm.chainDb, bloomTrieLastSection, bloomTrieSectionHead) bloomTrieRoot := light.GetBloomTrieRoot(srv.chaindb, bloomTrieLastSection, bloomTrieSectionHead)
logger.Info("Loaded bloom trie", "section", bloomTrieLastSection, "head", bloomTrieSectionHead, "root", bloomTrieRoot) logger.Info("Loaded bloom trie", "section", bloomTrieLastSection, "head", bloomTrieSectionHead, "root", bloomTrieRoot)
} }
srv.chtIndexer.Start(eth.BlockChain()) srv.chtIndexer.Start(e.BlockChain())
pm.server = srv pm.server = srv
srv.defParams = &flowcontrol.ServerParams{ srv.defParams = &flowcontrol.ServerParams{
@ -99,7 +118,7 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
MinRecharge: 50000, MinRecharge: 50000,
} }
srv.fcManager = flowcontrol.NewClientManager(uint64(config.LightServ), 10, 1000000000) srv.fcManager = flowcontrol.NewClientManager(uint64(config.LightServ), 10, 1000000000)
srv.fcCostStats = newCostStats(eth.ChainDb()) srv.fcCostStats = newCostStats(e.ChainDb())
return srv, nil return srv, nil
} }
@ -130,6 +149,26 @@ func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) {
bloomIndexer.AddChildIndexer(s.bloomTrieIndexer) bloomIndexer.AddChildIndexer(s.bloomTrieIndexer)
} }
// SetClient sets the rpc client and starts watching checkpoint contract if it is not yet watched.
func (s *LesServer) SetClient(client *ethclient.Client) {
addr, ok := registrar.RegistrarAddr[s.genesis]
if !ok {
log.Info("The registrar contract is not deployed")
return
}
registrar, err := registrar.NewRegistrar(addr, client)
if err != nil {
log.Info("Bind registrar contract failed", "err", err)
return
}
if !atomic.CompareAndSwapInt32(&s.watching, 0, 1) {
log.Info("Already bound and listening to registrar contract")
return
}
s.registrar = registrar
go s.checkpointLoop(s.recoverCheckpoint())
}
// Stop stops the LES service // Stop stops the LES service
func (s *LesServer) Stop() { func (s *LesServer) Stop() {
s.chtIndexer.Close() s.chtIndexer.Close()
@ -140,179 +179,187 @@ func (s *LesServer) Stop() {
<-s.protocolManager.noMorePeers <-s.protocolManager.noMorePeers
}() }()
s.protocolManager.Stop() s.protocolManager.Stop()
atomic.StoreInt32(&s.watching, 0)
} }
type requestCosts struct { // APIs implements LesServer, returns all API service provided by les server.
baseCost, reqCost uint64 func (s *LesServer) APIs() []rpc.API {
} return []rpc.API{
{
type requestCostTable map[uint64]*requestCosts Namespace: "les",
Version: "1.0",
type RequestCostList []struct { Service: NewPrivateLesServerAPI(s),
MsgCode, BaseCost, ReqCost uint64 Public: false,
} },
func (list RequestCostList) decode() requestCostTable {
table := make(requestCostTable)
for _, e := range list {
table[e.MsgCode] = &requestCosts{
baseCost: e.BaseCost,
reqCost: e.ReqCost,
}
}
return table
}
type linReg struct {
sumX, sumY, sumXX, sumXY float64
cnt uint64
}
const linRegMaxCnt = 100000
func (l *linReg) add(x, y float64) {
if l.cnt >= linRegMaxCnt {
sub := float64(l.cnt+1-linRegMaxCnt) / linRegMaxCnt
l.sumX -= l.sumX * sub
l.sumY -= l.sumY * sub
l.sumXX -= l.sumXX * sub
l.sumXY -= l.sumXY * sub
l.cnt = linRegMaxCnt - 1
}
l.cnt++
l.sumX += x
l.sumY += y
l.sumXX += x * x
l.sumXY += x * y
}
func (l *linReg) calc() (b, m float64) {
if l.cnt == 0 {
return 0, 0
}
cnt := float64(l.cnt)
d := cnt*l.sumXX - l.sumX*l.sumX
if d < 0.001 {
return l.sumY / cnt, 0
}
m = (cnt*l.sumXY - l.sumX*l.sumY) / d
b = (l.sumY / cnt) - (m * l.sumX / cnt)
return b, m
}
func (l *linReg) toBytes() []byte {
var arr [40]byte
binary.BigEndian.PutUint64(arr[0:8], math.Float64bits(l.sumX))
binary.BigEndian.PutUint64(arr[8:16], math.Float64bits(l.sumY))
binary.BigEndian.PutUint64(arr[16:24], math.Float64bits(l.sumXX))
binary.BigEndian.PutUint64(arr[24:32], math.Float64bits(l.sumXY))
binary.BigEndian.PutUint64(arr[32:40], l.cnt)
return arr[:]
}
func linRegFromBytes(data []byte) *linReg {
if len(data) != 40 {
return nil
}
l := &linReg{}
l.sumX = math.Float64frombits(binary.BigEndian.Uint64(data[0:8]))
l.sumY = math.Float64frombits(binary.BigEndian.Uint64(data[8:16]))
l.sumXX = math.Float64frombits(binary.BigEndian.Uint64(data[16:24]))
l.sumXY = math.Float64frombits(binary.BigEndian.Uint64(data[24:32]))
l.cnt = binary.BigEndian.Uint64(data[32:40])
return l
}
type requestCostStats struct {
lock sync.RWMutex
db ethdb.Database
stats map[uint64]*linReg
}
type requestCostStatsRlp []struct {
MsgCode uint64
Data []byte
}
var rcStatsKey = []byte("_requestCostStats")
func newCostStats(db ethdb.Database) *requestCostStats {
stats := make(map[uint64]*linReg)
for _, code := range reqList {
stats[code] = &linReg{cnt: 100}
}
if db != nil {
data, err := db.Get(rcStatsKey)
var statsRlp requestCostStatsRlp
if err == nil {
err = rlp.DecodeBytes(data, &statsRlp)
}
if err == nil {
for _, r := range statsRlp {
if stats[r.MsgCode] != nil {
if l := linRegFromBytes(r.Data); l != nil {
stats[r.MsgCode] = l
}
}
}
} }
} }
return &requestCostStats{ // latestCheckpoint finds the common stored section index and returns a set of
db: db, // post-processed trie roots (CHT and BloomTrie) associated with
stats: stats, // the appropriate section index and head hash as a local checkpoint package.
//
// Note for cht, the section size in LES1 is 4K, so indexer still uses LES/1
// 4k section size for backwards server compatibility. For bloomTrie, the size
// of the section used for indexer is 32K.
func (s *LesServer) latestCheckpoint() (uint64, common.Hash, common.Hash, common.Hash) {
chtCount, _, _ := s.chtIndexer.Sections()
bloomTrieCount, _, _ := s.bloomTrieIndexer.Sections()
count := chtCount / (light.CHTFrequencyClient / light.CHTFrequencyServer)
// Cap the section index if the two sections are not consistent.
if count > bloomTrieCount {
count = bloomTrieCount
} }
if count == 0 {
// No checkpoint information can be provided.
return 0, common.Hash{}, common.Hash{}, common.Hash{}
}
sectionHead, chtRoot, bloomTrieRoot := s.getCheckpoint(count - 1)
return count - 1, sectionHead, chtRoot, bloomTrieRoot
} }
func (s *requestCostStats) store() { // getCheckpoint returns a set of post-processed trie roots (CHT and BloomTrie)
s.lock.Lock() // associated with the appropriate head hash by specific section index.
defer s.lock.Unlock() //
// The returned checkpoint is only the checkpoint generated by the local indexers,
// not the stable checkpoint registered in the registrar contract.
func (s *LesServer) getCheckpoint(index uint64) (common.Hash, common.Hash, common.Hash) {
// convert last LES/2 section index back to LES/1 index for chtIndexer.SectionHead
latest := (index+1)*(light.CHTFrequencyClient/light.CHTFrequencyServer) - 1
statsRlp := make(requestCostStatsRlp, len(reqList)) sectionHead := s.chtIndexer.SectionHead(latest)
for i, code := range reqList { chtRoot := light.GetChtRoot(s.protocolManager.chainDb, latest, sectionHead)
statsRlp[i].MsgCode = code bloomTrieRoot := light.GetBloomTrieRoot(s.protocolManager.chainDb, index, sectionHead)
statsRlp[i].Data = s.stats[code].toBytes() return sectionHead, chtRoot, bloomTrieRoot
} }
if data, err := rlp.EncodeToBytes(statsRlp); err == nil { // checkpointLoop starts a standalone goroutine to watch new checkpoint events and updates local's stable checkpoint.
s.db.Put(rcStatsKey, data) func (s *LesServer) checkpointLoop(checkpoint *light.TrustedCheckpoint) (err error) {
var (
eventCh = make(chan *contract.ContractNewCheckpointEvent)
headCh = make(chan core.ChainHeadEvent, SubscribeChainHeadEvent)
announcement = make(map[uint64]common.Hash)
)
eventSub, err := s.registrar.WatchNewCheckpointEvent(eventCh)
if err != nil {
return err
} }
headSub := s.backend.SubscribeChainHeadEvent(headCh)
if headSub == nil {
eventSub.Unsubscribe()
return errors.New("subscribe head event failed")
} }
func (s *requestCostStats) getCurrentList() RequestCostList { ticker := time.NewTicker(5 * time.Minute)
s.lock.Lock() defer func() {
defer s.lock.Unlock() eventSub.Unsubscribe()
headSub.Unsubscribe()
ticker.Stop()
}()
list := make(RequestCostList, len(reqList)) for {
//fmt.Println("RequestCostList") select {
for idx, code := range reqList { case event := <-eventCh:
b, m := s.stats[code].calc() if event == nil {
//fmt.Println(code, s.stats[code].cnt, b/1000000, m/1000000) // This should never happen.
if m < 0 { log.Info("Ignore empty checkpoint event")
b += m continue
m = 0
} }
if b < 0 { // Note several events have same index may be received because of chain reorg and
b = 0 // the modification of the latest checkpoint.
if checkpoint == nil || event.Index.Uint64() > checkpoint.SectionIdx {
log.Info("Receive new checkpoint event", "section", event.Index, "hash", common.Hash(event.CheckpointHash).Hex(),
"grantor", event.Grantor.Hex())
announcement[event.Index.Uint64()] = common.Hash(event.CheckpointHash)
} }
case head := <-headCh:
list[idx].MsgCode = code number := head.Block.NumberU64()
list[idx].BaseCost = uint64(b * 2) if number < light.CheckpointConfirmations+light.CheckpointFrequency {
list[idx].ReqCost = uint64(m * 2) continue
} }
return list if checkpoint == nil {
checkpoint = s.recoverCheckpoint()
} }
idx := (number-light.CheckpointConfirmations)/light.CheckpointFrequency - 1
func (s *requestCostStats) update(msgCode, reqCnt, cost uint64) { if checkpoint == nil || idx > checkpoint.SectionIdx {
s.lock.Lock() hash, ok := announcement[idx]
defer s.lock.Unlock() if !ok {
continue
c, ok := s.stats[msgCode] }
if !ok || reqCnt == 0 { sectionHead := s.bloomTrieIndexer.SectionHead(idx)
c := &light.TrustedCheckpoint{
SectionIdx: idx,
SectionHead: sectionHead,
ChtRoot: light.GetChtV2Root(s.chaindb, idx, sectionHead),
BloomTrieRoot: light.GetBloomTrieRoot(s.chaindb, idx, sectionHead),
}
if c.HashEqual(common.Hash(hash)) {
light.WriteTrustedCheckpoint(s.chaindb, c)
checkpoint = c
delete(announcement, idx)
log.Info("Update stable checkpoint", "section", checkpoint.SectionIdx, "hash", checkpoint.Hash().Hex())
}
}
case <-ticker.C:
// Evict useless announcement every 5 minutes.
for idx := range announcement {
if checkpoint != nil && checkpoint.SectionIdx >= idx {
delete(announcement, idx)
}
}
case <-s.quitSync:
// Les server is closed.
return return
} }
c.add(float64(reqCnt), float64(cost)) }
}
// recoveryCheckpoint filters checkpoint announcement events and recovers stable checkpoint.
func (s *LesServer) recoverCheckpoint() *light.TrustedCheckpoint {
var (
sectionCnt, _, _ = s.bloomTrieIndexer.Sections()
stable = light.ReadTrustedCheckpoint(s.chaindb)
headHash = rawdb.ReadHeadHeaderHash(s.chaindb)
headNumber = rawdb.ReadHeaderNumber(s.chaindb, headHash)
)
// Short circuit if there is no local checkpoint generated.
if headNumber == nil || sectionCnt == 0 {
return nil
}
unstableIdx := sectionCnt - 1
for stable == nil || stable.SectionIdx < unstableIdx {
if (unstableIdx+1)*light.CheckpointFrequency+light.CheckpointConfirmations <= *headNumber {
iter, err := s.registrar.FilterNewCheckpointEvent(*headNumber, unstableIdx, light.CheckpointFrequency, light.CheckpointProcessConfirmations)
if err != nil {
continue
}
for iter.Next() {
sectionHead := s.bloomTrieIndexer.SectionHead(unstableIdx)
checkpoint := &light.TrustedCheckpoint{
SectionIdx: unstableIdx,
SectionHead: sectionHead,
ChtRoot: light.GetChtV2Root(s.chaindb, unstableIdx, sectionHead),
BloomTrieRoot: light.GetBloomTrieRoot(s.chaindb, unstableIdx, sectionHead),
}
if checkpoint.HashEqual(common.Hash(iter.Event.CheckpointHash)) {
light.WriteTrustedCheckpoint(s.chaindb, checkpoint)
iter.Close()
log.Info("Recover stable checkpoint", "index", checkpoint.SectionIdx, "hash", checkpoint.Hash().Hex())
return checkpoint
}
}
iter.Close()
}
if unstableIdx == 0 {
break
}
unstableIdx -= 1
}
if stable == nil {
log.Info("No stable checkpoint")
} else {
log.Info("Recover stable checkpoint", "index", stable.SectionIdx, "hash", stable.Hash().Hex())
}
return stable
} }
func (pm *ProtocolManager) blockLoop() { func (pm *ProtocolManager) blockLoop() {

144
light/checkpoint.go Normal file
View file

@ -0,0 +1,144 @@
// 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 light
import (
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
const (
// CheckpointFrequency is the block frequency for creating checkpoint
CheckpointFrequency = 32768
// CheckpointProcessConfirmations is the number before a checkpoint is generated
CheckpointProcessConfirmations = 256
// CheckpointConfirmations is the number of confirmations before a checkpoint is stable
CheckpointConfirmations = 8192
)
// checkpointKey tracks the latest stable checkpoint.
var checkpointKey = []byte("Checkpoint")
// TrustedCheckpoint represents a set of post-processed trie roots (CHT and BloomTrie) associated with
// the appropriate section index and 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.
type TrustedCheckpoint struct {
Name string // Indicator which chain the checkpoint belongs to
SectionIdx uint64 // Section index
SectionHead common.Hash // Block Hash for the last block in the section
ChtRoot common.Hash // CHT(Canonical Hash Trie) root associated to the section
BloomTrieRoot common.Hash // Bloom Trie root associated to the section
}
type trustCheckpointRLP struct {
SectionIdx uint64
SectionHead common.Hash
ChtRoot common.Hash
BloomTrieRoot common.Hash
}
// EncodeRLP implements rlp.Encoder, and flattens the necessary fields of a checkpoint
// into an RLP stream.
func (c *TrustedCheckpoint) EncodeRLP(w io.Writer) (err error) {
return rlp.Encode(w, &trustCheckpointRLP{c.SectionIdx, c.SectionHead, c.ChtRoot, c.BloomTrieRoot})
}
// DecodeRLP implements rlp.Decoder, and loads the necessary fields of a checkpoint
// from an RLP stream.
func (c *TrustedCheckpoint) DecodeRLP(s *rlp.Stream) error {
var dec trustCheckpointRLP
if err := s.Decode(&dec); err != nil {
return err
}
c.SectionIdx, c.SectionHead, c.ChtRoot, c.BloomTrieRoot = dec.SectionIdx, dec.SectionHead, dec.ChtRoot, dec.BloomTrieRoot
return nil
}
// HashEqual returns an indicator comparing the itself hash with given one.
// A nil argument is equivalent to an empty slice.
func (c *TrustedCheckpoint) HashEqual(hash common.Hash) bool {
if c.SectionHead == (common.Hash{}) && c.ChtRoot == (common.Hash{}) && c.BloomTrieRoot == (common.Hash{}) {
return hash == common.Hash{}
}
return c.Hash() == hash
}
// Hash returns the hash of checkpoint three key fields(sectionHead, chtRoot and bloomTrieRoot).
func (c *TrustedCheckpoint) Hash() common.Hash {
return crypto.Keccak256Hash(c.SectionHead.Bytes(), c.ChtRoot.Bytes(), c.BloomTrieRoot.Bytes())
}
var (
// Hardcode checkpoint for mainnet and testnet(ropsten). Will be deleted eventually once checkpoint contract
// works.
mainnetCheckpoint = TrustedCheckpoint{
Name: "mainnet",
SectionIdx: 179,
SectionHead: common.HexToHash("ae778e455492db1183e566fa0c67f954d256fdd08618f6d5a393b0e24576d0ea"),
ChtRoot: common.HexToHash("646b338f9ca74d936225338916be53710ec84020b89946004a8605f04c817f16"),
BloomTrieRoot: common.HexToHash("d0f978f5dbc86e5bf931d8dd5b2ecbebbda6dc78f8896af6a27b46a3ced0ac25"),
}
ropstenCheckpoint = TrustedCheckpoint{
Name: "ropsten",
SectionIdx: 107,
SectionHead: common.HexToHash("e1988f95399debf45b873e065e5cd61b416ef2e2e5deec5a6f87c3127086e1ce"),
ChtRoot: common.HexToHash("15cba18e4de0ab1e95e202625199ba30147aec8b0b70384b66ebea31ba6a18e0"),
BloomTrieRoot: common.HexToHash("e00fa6389b2e597d9df52172cd8e936879eed0fca4fa59db99e2c8ed682562f2"),
}
)
// TrustedCheckpoints associates each known checkpoint with the genesis hash of the chain it belongs to.
var TrustedCheckpoints = map[common.Hash]TrustedCheckpoint{
params.MainnetGenesisHash: mainnetCheckpoint,
params.TestnetGenesisHash: ropstenCheckpoint,
}
// ReadTrustedCheckpoint retrieves the checkpoint from the database.
func ReadTrustedCheckpoint(db ethdb.Database) *TrustedCheckpoint {
data, err := db.Get(checkpointKey)
if err != nil {
return nil
}
c := new(TrustedCheckpoint)
if err := rlp.DecodeBytes(data, c); err != nil {
log.Error("Invalid checkpoint RLP", "err", err)
return nil
}
return c
}
// WriteTrustedCheckpoint stores an RLP encoded checkpoint into the database.
func WriteTrustedCheckpoint(db ethdb.Putter, checkpoint *TrustedCheckpoint) {
data, err := rlp.EncodeToBytes(checkpoint)
if err != nil {
log.Crit("Failed to RLP encode checkpoint", err)
}
if err := db.Put(checkpointKey, data); err != nil {
log.Crit("Failed to store checkpoint", "err", err)
}
}

55
light/checkpoint_test.go Normal file
View file

@ -0,0 +1,55 @@
// 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 light
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
)
var testCheckpoint = &TrustedCheckpoint{
Name: "test",
SectionIdx: 100,
SectionHead: common.HexToHash("0xbeef"),
ChtRoot: common.HexToHash("0xdead"),
BloomTrieRoot: common.HexToHash("0xdeadbeef"),
}
func TestRWCheckpoint(t *testing.T) {
mdb := ethdb.NewMemDatabase()
WriteTrustedCheckpoint(mdb, testCheckpoint)
if !assertCheckpointEqual(testCheckpoint, ReadTrustedCheckpoint(mdb)) {
t.Error("the checkpoint retrieved from database is different")
}
}
func TestHashEqual(t *testing.T) {
if !testCheckpoint.HashEqual(common.HexToHash("0x6142a271d44a56107cd9de0be0a04211841593906b310f8c4d33be56b6e78959")) {
t.Error("checkpoint should hash equal to given one")
}
emptyCheckpoint := &TrustedCheckpoint{}
if !emptyCheckpoint.HashEqual(common.Hash{}) {
t.Error("empty checkpoint should equal to empty hash")
}
}
func assertCheckpointEqual(ckp1, ckp2 *TrustedCheckpoint) bool {
return ckp1.SectionIdx == ckp2.SectionIdx && ckp1.SectionHead == ckp2.SectionHead && ckp1.ChtRoot == ckp2.ChtRoot &&
ckp1.BloomTrieRoot == ckp2.BloomTrieRoot
}

View file

@ -98,7 +98,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
if bc.genesisBlock == nil { if bc.genesisBlock == nil {
return nil, core.ErrNoGenesis return nil, core.ErrNoGenesis
} }
if cp, ok := trustedCheckpoints[bc.genesisBlock.Hash()]; ok { if cp, ok := TrustedCheckpoints[bc.genesisBlock.Hash()]; ok {
bc.addTrustedCheckpoint(cp) bc.addTrustedCheckpoint(cp)
} }
if err := bc.loadLastState(); err != nil { if err := bc.loadLastState(); err != nil {
@ -116,19 +116,19 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
} }
// addTrustedCheckpoint adds a trusted checkpoint to the blockchain // addTrustedCheckpoint adds a trusted checkpoint to the blockchain
func (self *LightChain) addTrustedCheckpoint(cp trustedCheckpoint) { func (self *LightChain) addTrustedCheckpoint(cp TrustedCheckpoint) {
if self.odr.ChtIndexer() != nil { if self.odr.ChtIndexer() != nil {
StoreChtRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.chtRoot) StoreChtRoot(self.chainDb, cp.SectionIdx, cp.SectionHead, cp.ChtRoot)
self.odr.ChtIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead) self.odr.ChtIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead)
} }
if self.odr.BloomTrieIndexer() != nil { if self.odr.BloomTrieIndexer() != nil {
StoreBloomTrieRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.bloomTrieRoot) StoreBloomTrieRoot(self.chainDb, cp.SectionIdx, cp.SectionHead, cp.BloomTrieRoot)
self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead) self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead)
} }
if self.odr.BloomIndexer() != nil { if self.odr.BloomIndexer() != nil {
self.odr.BloomIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead) self.odr.BloomIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead)
} }
log.Info("Added trusted checkpoint", "chain", cp.name, "block", (cp.sectionIdx+1)*CHTFrequencyClient-1, "hash", cp.sectionHead) log.Info("Added trusted checkpoint", "chain", cp.Name, "block", (cp.SectionIdx+1)*CHTFrequencyClient-1, "hash", cp.SectionHead)
} }
func (self *LightChain) getProcInterrupt() bool { func (self *LightChain) getProcInterrupt() bool {

View file

@ -164,7 +164,7 @@ func (req *BloomRequest) StoreResult(db ethdb.Database) {
for i, sectionIdx := range req.SectionIdxList { for i, sectionIdx := range req.SectionIdxList {
sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1) sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1)
// if we don't have the canonical hash stored for this section head number, we'll still store it under // if we don't have the canonical hash stored for this section head number, we'll still store it under
// a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical // a key with a zero SectionHead. GetBloomBits will look there too if we still don't have the canonical
// hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the // hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the
// bit vector again from the network. // bit vector again from the network.
rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i]) rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i])

View file

@ -203,7 +203,7 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bitIdx uint, sectionIdxLi
for i, sectionIdx := range sectionIdxList { for i, sectionIdx := range sectionIdxList {
sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1) sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1)
// if we don't have the canonical hash stored for this section head number, we'll still look for // if we don't have the canonical hash stored for this section head number, we'll still look for
// an entry with a zero sectionHead (we store it with zero section head too if we don't know it // an entry with a zero SectionHead (we store it with zero section head too if we don't know it
// at the time of the retrieval) // at the time of the retrieval)
bloomBits, err := rawdb.ReadBloomBits(db, bitIdx, sectionIdx, sectionHead) bloomBits, err := rawdb.ReadBloomBits(db, bitIdx, sectionIdx, sectionHead)
if err == nil { if err == nil {

View file

@ -29,11 +29,18 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
var (
ErrNoTrustedCht = errors.New("No trusted canonical hash trie")
ErrNoTrustedBloomTrie = errors.New("No trusted bloom trie")
ErrNoHeader = errors.New("Header not found")
chtPrefix = []byte("chtRoot-") // chtPrefix + chtNum (uint64 big endian) -> trie root hash
ChtTablePrefix = "cht-"
)
const ( const (
// CHTFrequencyClient is the block frequency for creating CHTs on the client side. // CHTFrequencyClient is the block frequency for creating CHTs on the client side.
CHTFrequencyClient = 32768 CHTFrequencyClient = 32768
@ -47,47 +54,6 @@ const (
HelperTrieProcessConfirmations = 256 // number of confirmations before a HelperTrie is generated HelperTrieProcessConfirmations = 256 // number of confirmations before a HelperTrie is generated
) )
// trustedCheckpoint represents a set of post-processed trie roots (CHT and BloomTrie) associated with
// the appropriate section index and 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.
type trustedCheckpoint struct {
name string
sectionIdx uint64
sectionHead, chtRoot, bloomTrieRoot common.Hash
}
var (
mainnetCheckpoint = trustedCheckpoint{
name: "mainnet",
sectionIdx: 179,
sectionHead: common.HexToHash("ae778e455492db1183e566fa0c67f954d256fdd08618f6d5a393b0e24576d0ea"),
chtRoot: common.HexToHash("646b338f9ca74d936225338916be53710ec84020b89946004a8605f04c817f16"),
bloomTrieRoot: common.HexToHash("d0f978f5dbc86e5bf931d8dd5b2ecbebbda6dc78f8896af6a27b46a3ced0ac25"),
}
ropstenCheckpoint = trustedCheckpoint{
name: "ropsten",
sectionIdx: 107,
sectionHead: common.HexToHash("e1988f95399debf45b873e065e5cd61b416ef2e2e5deec5a6f87c3127086e1ce"),
chtRoot: common.HexToHash("15cba18e4de0ab1e95e202625199ba30147aec8b0b70384b66ebea31ba6a18e0"),
bloomTrieRoot: common.HexToHash("e00fa6389b2e597d9df52172cd8e936879eed0fca4fa59db99e2c8ed682562f2"),
}
)
// trustedCheckpoints associates each known checkpoint with the genesis hash of the chain it belongs to
var trustedCheckpoints = map[common.Hash]trustedCheckpoint{
params.MainnetGenesisHash: mainnetCheckpoint,
params.TestnetGenesisHash: ropstenCheckpoint,
}
var (
ErrNoTrustedCht = errors.New("No trusted canonical hash trie")
ErrNoTrustedBloomTrie = errors.New("No trusted bloom trie")
ErrNoHeader = errors.New("Header not found")
chtPrefix = []byte("chtRoot-") // chtPrefix + chtNum (uint64 big endian) -> trie root hash
ChtTablePrefix = "cht-"
)
// ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format // ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format
type ChtNode struct { type ChtNode struct {
Hash common.Hash Hash common.Hash
@ -95,7 +61,7 @@ type ChtNode struct {
} }
// GetChtRoot reads the CHT root assoctiated to the given section from the database // GetChtRoot reads the CHT root assoctiated to the given section from the database
// Note that sectionIdx is specified according to LES/1 CHT section size // Note that SectionIdx is specified according to LES/1 CHT section size
func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
var encNumber [8]byte var encNumber [8]byte
binary.BigEndian.PutUint64(encNumber[:], sectionIdx) binary.BigEndian.PutUint64(encNumber[:], sectionIdx)
@ -104,13 +70,13 @@ func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) c
} }
// GetChtV2Root reads the CHT root assoctiated to the given section from the database // GetChtV2Root reads the CHT root assoctiated to the given section from the database
// Note that sectionIdx is specified according to LES/2 CHT section size // Note that SectionIdx is specified according to LES/2 CHT section size
func GetChtV2Root(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { func GetChtV2Root(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash {
return GetChtRoot(db, (sectionIdx+1)*(CHTFrequencyClient/CHTFrequencyServer)-1, sectionHead) return GetChtRoot(db, (sectionIdx+1)*(CHTFrequencyClient/CHTFrequencyServer)-1, sectionHead)
} }
// StoreChtRoot writes the CHT root assoctiated to the given section into the database // StoreChtRoot writes the CHT root assoctiated to the given section into the database
// Note that sectionIdx is specified according to LES/1 CHT section size // Note that SectionIdx is specified according to LES/1 CHT section size
func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) {
var encNumber [8]byte var encNumber [8]byte
binary.BigEndian.PutUint64(encNumber[:], sectionIdx) binary.BigEndian.PutUint64(encNumber[:], sectionIdx)

View file

@ -49,6 +49,7 @@ type Node struct {
server *p2p.Server // Currently running P2P networking layer server *p2p.Server // Currently running P2P networking layer
serviceFuncs []ServiceConstructor // Service constructors (in dependency order) serviceFuncs []ServiceConstructor // Service constructors (in dependency order)
callbacks map[reflect.Type][]ServiceCallback // Service callback functions
services map[reflect.Type]Service // Currently running services services map[reflect.Type]Service // Currently running services
rpcAPIs []rpc.API // List of APIs currently provided by the node rpcAPIs []rpc.API // List of APIs currently provided by the node
@ -113,6 +114,7 @@ func New(conf *Config) (*Node, error) {
ephemeralKeystore: ephemeralKeystore, ephemeralKeystore: ephemeralKeystore,
config: conf, config: conf,
serviceFuncs: []ServiceConstructor{}, serviceFuncs: []ServiceConstructor{},
callbacks: make(map[reflect.Type][]ServiceCallback),
ipcEndpoint: conf.IPCEndpoint(), ipcEndpoint: conf.IPCEndpoint(),
httpEndpoint: conf.HTTPEndpoint(), httpEndpoint: conf.HTTPEndpoint(),
wsEndpoint: conf.WSEndpoint(), wsEndpoint: conf.WSEndpoint(),
@ -134,6 +136,18 @@ func (n *Node) Register(constructor ServiceConstructor) error {
return nil return nil
} }
// RegisterCallback injects a callback function associated with the specified service.
func (n *Node) RegisterCallback(typ reflect.Type, callback ServiceCallback) error {
n.lock.Lock()
defer n.lock.Unlock()
if n.server != nil {
return ErrNodeRunning
}
n.callbacks[typ] = append(n.callbacks[typ], callback)
return nil
}
// Start create a live P2P node and starts running it. // Start create a live P2P node and starts running it.
func (n *Node) Start() error { func (n *Node) Start() error {
n.lock.Lock() n.lock.Lock()
@ -211,7 +225,7 @@ func (n *Node) Start() error {
// Mark the service started for potential cleanup // Mark the service started for potential cleanup
started = append(started, kind) started = append(started, kind)
} }
// Lastly start the configured RPC interfaces // Start the configured RPC interfaces
if err := n.startRPC(services); err != nil { if err := n.startRPC(services); err != nil {
for _, service := range services { for _, service := range services {
service.Stop() service.Stop()
@ -223,7 +237,21 @@ func (n *Node) Start() error {
n.services = services n.services = services
n.server = running n.server = running
n.stop = make(chan struct{}) n.stop = make(chan struct{})
// Lastly invokes all registered services callbacks after server is started.
for typ, callbacks := range n.callbacks {
if service, ok := services[typ]; ok {
for _, callback := range callbacks {
if err := callback(service); err != nil {
for _, service := range services {
service.Stop()
}
running.Stop()
n.services, n.server, n.stop = nil, nil, nil
return err
}
}
}
}
return nil return nil
} }
@ -480,7 +508,12 @@ func (n *Node) Restart() error {
func (n *Node) Attach() (*rpc.Client, error) { func (n *Node) Attach() (*rpc.Client, error) {
n.lock.RLock() n.lock.RLock()
defer n.lock.RUnlock() defer n.lock.RUnlock()
return n.AttachLocked()
}
// AttachLocked creates an RPC client attached to an in-process API handler.
// Note, this function assumes the lock of node is held.
func (n *Node) AttachLocked() (*rpc.Client, error) {
if n.server == nil { if n.server == nil {
return nil, ErrNodeStopped return nil, ErrNodeStopped
} }

View file

@ -71,6 +71,10 @@ func (ctx *ServiceContext) Service(service interface{}) error {
// registered for service instantiation. // registered for service instantiation.
type ServiceConstructor func(ctx *ServiceContext) (Service, error) type ServiceConstructor func(ctx *ServiceContext) (Service, error)
// ServiceCallback is the function signature of the callbacks needed to be invoked
// after associated service is started.
type ServiceCallback func(service Service) error
// Service is an individual protocol that can be registered into a node. // Service is an individual protocol that can be registered into a node.
// //
// Notes: // Notes:

View file

@ -27,6 +27,7 @@ import (
var ( var (
MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
) )
var ( var (