mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
Merge 90366b3bb7 into 042191338d
This commit is contained in:
commit
84e0b0304a
20 changed files with 2118 additions and 224 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(ð.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)
|
||||||
|
|
|
||||||
704
contracts/registrar/contract/registrar.go
Normal file
704
contracts/registrar/contract/registrar.go
Normal file
File diff suppressed because one or more lines are too long
217
contracts/registrar/contract/registrar.sol
Normal file
217
contracts/registrar/contract/registrar.sol
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
73
contracts/registrar/registrar.go
Normal file
73
contracts/registrar/registrar.go
Normal 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))})
|
||||||
|
}
|
||||||
297
contracts/registrar/registrar_test.go
Normal file
297
contracts/registrar/registrar_test.go
Normal 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")
|
||||||
|
}
|
||||||
|
|
@ -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{
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
75
les/api.go
Normal 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
199
les/costtable.go
Normal 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))
|
||||||
|
}
|
||||||
377
les/server.go
377
les/server.go
|
|
@ -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 {
|
// latestCheckpoint finds the common stored section index and returns a set of
|
||||||
sumX, sumY, sumXX, sumXY float64
|
// post-processed trie roots (CHT and BloomTrie) associated with
|
||||||
cnt uint64
|
// 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
|
||||||
const linRegMaxCnt = 100000
|
// 4k section size for backwards server compatibility. For bloomTrie, the size
|
||||||
|
// of the section used for indexer is 32K.
|
||||||
func (l *linReg) add(x, y float64) {
|
func (s *LesServer) latestCheckpoint() (uint64, common.Hash, common.Hash, common.Hash) {
|
||||||
if l.cnt >= linRegMaxCnt {
|
chtCount, _, _ := s.chtIndexer.Sections()
|
||||||
sub := float64(l.cnt+1-linRegMaxCnt) / linRegMaxCnt
|
bloomTrieCount, _, _ := s.bloomTrieIndexer.Sections()
|
||||||
l.sumX -= l.sumX * sub
|
count := chtCount / (light.CHTFrequencyClient / light.CHTFrequencyServer)
|
||||||
l.sumY -= l.sumY * sub
|
// Cap the section index if the two sections are not consistent.
|
||||||
l.sumXX -= l.sumXX * sub
|
if count > bloomTrieCount {
|
||||||
l.sumXY -= l.sumXY * sub
|
count = bloomTrieCount
|
||||||
l.cnt = linRegMaxCnt - 1
|
|
||||||
}
|
}
|
||||||
l.cnt++
|
if count == 0 {
|
||||||
l.sumX += x
|
// No checkpoint information can be provided.
|
||||||
l.sumY += y
|
return 0, common.Hash{}, common.Hash{}, common.Hash{}
|
||||||
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)
|
sectionHead, chtRoot, bloomTrieRoot := s.getCheckpoint(count - 1)
|
||||||
d := cnt*l.sumXX - l.sumX*l.sumX
|
return count - 1, sectionHead, chtRoot, bloomTrieRoot
|
||||||
if d < 0.001 {
|
}
|
||||||
return l.sumY / cnt, 0
|
|
||||||
|
// getCheckpoint 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 (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
|
||||||
|
|
||||||
|
sectionHead := s.chtIndexer.SectionHead(latest)
|
||||||
|
chtRoot := light.GetChtRoot(s.protocolManager.chainDb, latest, sectionHead)
|
||||||
|
bloomTrieRoot := light.GetBloomTrieRoot(s.protocolManager.chainDb, index, sectionHead)
|
||||||
|
return sectionHead, chtRoot, bloomTrieRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkpointLoop starts a standalone goroutine to watch new checkpoint events and updates local's stable checkpoint.
|
||||||
|
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
|
||||||
}
|
}
|
||||||
m = (cnt*l.sumXY - l.sumX*l.sumY) / d
|
headSub := s.backend.SubscribeChainHeadEvent(headCh)
|
||||||
b = (l.sumY / cnt) - (m * l.sumX / cnt)
|
if headSub == nil {
|
||||||
return b, m
|
eventSub.Unsubscribe()
|
||||||
}
|
return errors.New("subscribe head event failed")
|
||||||
|
|
||||||
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 {
|
ticker := time.NewTicker(5 * time.Minute)
|
||||||
data, err := db.Get(rcStatsKey)
|
defer func() {
|
||||||
var statsRlp requestCostStatsRlp
|
eventSub.Unsubscribe()
|
||||||
if err == nil {
|
headSub.Unsubscribe()
|
||||||
err = rlp.DecodeBytes(data, &statsRlp)
|
ticker.Stop()
|
||||||
}
|
}()
|
||||||
if err == nil {
|
|
||||||
for _, r := range statsRlp {
|
for {
|
||||||
if stats[r.MsgCode] != nil {
|
select {
|
||||||
if l := linRegFromBytes(r.Data); l != nil {
|
case event := <-eventCh:
|
||||||
stats[r.MsgCode] = l
|
if event == nil {
|
||||||
}
|
// This should never happen.
|
||||||
|
log.Info("Ignore empty checkpoint event")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Note several events have same index may be received because of chain reorg and
|
||||||
|
// 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:
|
||||||
|
number := head.Block.NumberU64()
|
||||||
|
if number < light.CheckpointConfirmations+light.CheckpointFrequency {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if checkpoint == nil {
|
||||||
|
checkpoint = s.recoverCheckpoint()
|
||||||
|
}
|
||||||
|
idx := (number-light.CheckpointConfirmations)/light.CheckpointFrequency - 1
|
||||||
|
if checkpoint == nil || idx > checkpoint.SectionIdx {
|
||||||
|
hash, ok := announcement[idx]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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 &requestCostStats{
|
|
||||||
db: db,
|
|
||||||
stats: stats,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *requestCostStats) store() {
|
// recoveryCheckpoint filters checkpoint announcement events and recovers stable checkpoint.
|
||||||
s.lock.Lock()
|
func (s *LesServer) recoverCheckpoint() *light.TrustedCheckpoint {
|
||||||
defer s.lock.Unlock()
|
var (
|
||||||
|
sectionCnt, _, _ = s.bloomTrieIndexer.Sections()
|
||||||
statsRlp := make(requestCostStatsRlp, len(reqList))
|
stable = light.ReadTrustedCheckpoint(s.chaindb)
|
||||||
for i, code := range reqList {
|
headHash = rawdb.ReadHeadHeaderHash(s.chaindb)
|
||||||
statsRlp[i].MsgCode = code
|
headNumber = rawdb.ReadHeaderNumber(s.chaindb, headHash)
|
||||||
statsRlp[i].Data = s.stats[code].toBytes()
|
)
|
||||||
|
// Short circuit if there is no local checkpoint generated.
|
||||||
|
if headNumber == nil || sectionCnt == 0 {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
unstableIdx := sectionCnt - 1
|
||||||
if data, err := rlp.EncodeToBytes(statsRlp); err == nil {
|
for stable == nil || stable.SectionIdx < unstableIdx {
|
||||||
s.db.Put(rcStatsKey, data)
|
if (unstableIdx+1)*light.CheckpointFrequency+light.CheckpointConfirmations <= *headNumber {
|
||||||
}
|
iter, err := s.registrar.FilterNewCheckpointEvent(*headNumber, unstableIdx, light.CheckpointFrequency, light.CheckpointProcessConfirmations)
|
||||||
}
|
if err != nil {
|
||||||
|
continue
|
||||||
func (s *requestCostStats) getCurrentList() RequestCostList {
|
}
|
||||||
s.lock.Lock()
|
for iter.Next() {
|
||||||
defer s.lock.Unlock()
|
sectionHead := s.bloomTrieIndexer.SectionHead(unstableIdx)
|
||||||
|
checkpoint := &light.TrustedCheckpoint{
|
||||||
list := make(RequestCostList, len(reqList))
|
SectionIdx: unstableIdx,
|
||||||
//fmt.Println("RequestCostList")
|
SectionHead: sectionHead,
|
||||||
for idx, code := range reqList {
|
ChtRoot: light.GetChtV2Root(s.chaindb, unstableIdx, sectionHead),
|
||||||
b, m := s.stats[code].calc()
|
BloomTrieRoot: light.GetBloomTrieRoot(s.chaindb, unstableIdx, sectionHead),
|
||||||
//fmt.Println(code, s.stats[code].cnt, b/1000000, m/1000000)
|
}
|
||||||
if m < 0 {
|
if checkpoint.HashEqual(common.Hash(iter.Event.CheckpointHash)) {
|
||||||
b += m
|
light.WriteTrustedCheckpoint(s.chaindb, checkpoint)
|
||||||
m = 0
|
iter.Close()
|
||||||
|
log.Info("Recover stable checkpoint", "index", checkpoint.SectionIdx, "hash", checkpoint.Hash().Hex())
|
||||||
|
return checkpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iter.Close()
|
||||||
}
|
}
|
||||||
if b < 0 {
|
if unstableIdx == 0 {
|
||||||
b = 0
|
break
|
||||||
}
|
}
|
||||||
|
unstableIdx -= 1
|
||||||
list[idx].MsgCode = code
|
|
||||||
list[idx].BaseCost = uint64(b * 2)
|
|
||||||
list[idx].ReqCost = uint64(m * 2)
|
|
||||||
}
|
}
|
||||||
return list
|
if stable == nil {
|
||||||
}
|
log.Info("No stable checkpoint")
|
||||||
|
} else {
|
||||||
func (s *requestCostStats) update(msgCode, reqCnt, cost uint64) {
|
log.Info("Recover stable checkpoint", "index", stable.SectionIdx, "hash", stable.Hash().Hex())
|
||||||
s.lock.Lock()
|
|
||||||
defer s.lock.Unlock()
|
|
||||||
|
|
||||||
c, ok := s.stats[msgCode]
|
|
||||||
if !ok || reqCnt == 0 {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
c.add(float64(reqCnt), float64(cost))
|
return stable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ProtocolManager) blockLoop() {
|
func (pm *ProtocolManager) blockLoop() {
|
||||||
|
|
|
||||||
144
light/checkpoint.go
Normal file
144
light/checkpoint.go
Normal 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
55
light/checkpoint_test.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
41
node/node.go
41
node/node.go
|
|
@ -48,8 +48,9 @@ type Node struct {
|
||||||
serverConfig p2p.Config
|
serverConfig p2p.Config
|
||||||
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)
|
||||||
services map[reflect.Type]Service // Currently running services
|
callbacks map[reflect.Type][]ServiceCallback // Service callback functions
|
||||||
|
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
|
||||||
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
|
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue