Skip to content

Commit e9e676a

Browse files
committed
les: light client protocol and API
1 parent 87518bb commit e9e676a

24 files changed

+5263
-16
lines changed

common/mclock/mclock.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2016 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
// package mclock is a wrapper for a monotonic clock source
18+
package mclock
19+
20+
import (
21+
"time"
22+
23+
"github.com/aristanetworks/goarista/atime"
24+
)
25+
26+
type AbsTime time.Duration // absolute monotonic time
27+
28+
func Now() AbsTime {
29+
return AbsTime(atime.NanoTime())
30+
}

eth/api_backend.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,17 @@ func (b *EthApiBackend) SetHead(number uint64) {
4444
b.eth.blockchain.SetHead(number)
4545
}
4646

47-
func (b *EthApiBackend) HeaderByNumber(blockNr rpc.BlockNumber) *types.Header {
47+
func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) {
4848
// Pending block is only known by the miner
4949
if blockNr == rpc.PendingBlockNumber {
5050
block, _ := b.eth.miner.Pending()
51-
return block.Header()
51+
return block.Header(), nil
5252
}
5353
// Otherwise resolve and return the block
5454
if blockNr == rpc.LatestBlockNumber {
55-
return b.eth.blockchain.CurrentBlock().Header()
55+
return b.eth.blockchain.CurrentBlock().Header(), nil
5656
}
57-
return b.eth.blockchain.GetHeaderByNumber(uint64(blockNr))
57+
return b.eth.blockchain.GetHeaderByNumber(uint64(blockNr)), nil
5858
}
5959

6060
func (b *EthApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) {
@@ -70,16 +70,16 @@ func (b *EthApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumb
7070
return b.eth.blockchain.GetBlockByNumber(uint64(blockNr)), nil
7171
}
7272

73-
func (b *EthApiBackend) StateAndHeaderByNumber(blockNr rpc.BlockNumber) (ethapi.State, *types.Header, error) {
73+
func (b *EthApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (ethapi.State, *types.Header, error) {
7474
// Pending state is only known by the miner
7575
if blockNr == rpc.PendingBlockNumber {
7676
block, state := b.eth.miner.Pending()
7777
return EthApiState{state}, block.Header(), nil
7878
}
7979
// Otherwise resolve the block number and return its state
80-
header := b.HeaderByNumber(blockNr)
81-
if header == nil {
82-
return nil, nil, nil
80+
header, err := b.HeaderByNumber(ctx, blockNr)
81+
if header == nil || err != nil {
82+
return nil, nil, err
8383
}
8484
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
8585
return EthApiState{stateDb}, header, err

eth/gasprice/lightprice.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright 2015 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package gasprice
18+
19+
import (
20+
"math/big"
21+
"sort"
22+
"sync"
23+
24+
"github.com/ethereum/go-ethereum/common"
25+
"github.com/ethereum/go-ethereum/internal/ethapi"
26+
"github.com/ethereum/go-ethereum/rpc"
27+
"golang.org/x/net/context"
28+
)
29+
30+
const (
31+
LpoAvgCount = 5
32+
LpoMinCount = 3
33+
LpoMaxBlocks = 20
34+
LpoSelect = 50
35+
LpoDefaultPrice = 20000000000
36+
)
37+
38+
// LightPriceOracle recommends gas prices based on the content of recent
39+
// blocks. Suitable for both light and full clients.
40+
type LightPriceOracle struct {
41+
backend ethapi.Backend
42+
lastHead common.Hash
43+
lastPrice *big.Int
44+
cacheLock sync.RWMutex
45+
fetchLock sync.Mutex
46+
}
47+
48+
// NewLightPriceOracle returns a new oracle.
49+
func NewLightPriceOracle(backend ethapi.Backend) *LightPriceOracle {
50+
return &LightPriceOracle{
51+
backend: backend,
52+
lastPrice: big.NewInt(LpoDefaultPrice),
53+
}
54+
}
55+
56+
// SuggestPrice returns the recommended gas price.
57+
func (self *LightPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
58+
self.cacheLock.RLock()
59+
lastHead := self.lastHead
60+
lastPrice := self.lastPrice
61+
self.cacheLock.RUnlock()
62+
63+
head, _ := self.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
64+
headHash := head.Hash()
65+
if headHash == lastHead {
66+
return lastPrice, nil
67+
}
68+
69+
self.fetchLock.Lock()
70+
defer self.fetchLock.Unlock()
71+
72+
// try checking the cache again, maybe the last fetch fetched what we need
73+
self.cacheLock.RLock()
74+
lastHead = self.lastHead
75+
lastPrice = self.lastPrice
76+
self.cacheLock.RUnlock()
77+
if headHash == lastHead {
78+
return lastPrice, nil
79+
}
80+
81+
blockNum := head.GetNumberU64()
82+
chn := make(chan lpResult, LpoMaxBlocks)
83+
sent := 0
84+
exp := 0
85+
var lps bigIntArray
86+
for sent < LpoAvgCount && blockNum > 0 {
87+
go self.getLowestPrice(ctx, blockNum, chn)
88+
sent++
89+
exp++
90+
blockNum--
91+
}
92+
maxEmpty := LpoAvgCount - LpoMinCount
93+
for exp > 0 {
94+
res := <-chn
95+
if res.err != nil {
96+
return nil, res.err
97+
}
98+
exp--
99+
if res.price != nil {
100+
lps = append(lps, res.price)
101+
} else {
102+
if maxEmpty > 0 {
103+
maxEmpty--
104+
} else {
105+
if blockNum > 0 && sent < LpoMaxBlocks {
106+
go self.getLowestPrice(ctx, blockNum, chn)
107+
sent++
108+
exp++
109+
blockNum--
110+
}
111+
}
112+
}
113+
}
114+
price := lastPrice
115+
if len(lps) > 0 {
116+
sort.Sort(lps)
117+
price = lps[(len(lps)-1)*LpoSelect/100]
118+
}
119+
120+
self.cacheLock.Lock()
121+
self.lastHead = headHash
122+
self.lastPrice = price
123+
self.cacheLock.Unlock()
124+
return price, nil
125+
}
126+
127+
type lpResult struct {
128+
price *big.Int
129+
err error
130+
}
131+
132+
// getLowestPrice calculates the lowest transaction gas price in a given block
133+
// and sends it to the result channel. If the block is empty, price is nil.
134+
func (self *LightPriceOracle) getLowestPrice(ctx context.Context, blockNum uint64, chn chan lpResult) {
135+
block, err := self.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
136+
if block == nil {
137+
chn <- lpResult{nil, err}
138+
return
139+
}
140+
txs := block.Transactions()
141+
if len(txs) == 0 {
142+
chn <- lpResult{nil, nil}
143+
return
144+
}
145+
// find smallest gasPrice
146+
minPrice := txs[0].GasPrice()
147+
for i := 1; i < len(txs); i++ {
148+
price := txs[i].GasPrice()
149+
if price.Cmp(minPrice) < 0 {
150+
minPrice = price
151+
}
152+
}
153+
chn <- lpResult{minPrice, nil}
154+
}
155+
156+
type bigIntArray []*big.Int
157+
158+
func (s bigIntArray) Len() int { return len(s) }
159+
func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
160+
func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

internal/ethapi/api.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,15 @@ func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI {
305305

306306
// BlockNumber returns the block number of the chain head.
307307
func (s *PublicBlockChainAPI) BlockNumber() *big.Int {
308-
return s.b.HeaderByNumber(rpc.LatestBlockNumber).Number
308+
header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
309+
return header.Number
309310
}
310311

311312
// GetBalance returns the amount of wei for the given address in the state of the
312313
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
313314
// block numbers are also allowed.
314315
func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*big.Int, error) {
315-
state, _, err := s.b.StateAndHeaderByNumber(blockNr)
316+
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
316317
if state == nil || err != nil {
317318
return nil, err
318319
}
@@ -397,7 +398,7 @@ func (s *PublicBlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, bloc
397398

398399
// GetCode returns the code stored at the given address in the state for the given block number.
399400
func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (string, error) {
400-
state, _, err := s.b.StateAndHeaderByNumber(blockNr)
401+
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
401402
if state == nil || err != nil {
402403
return "", err
403404
}
@@ -412,7 +413,7 @@ func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Addres
412413
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
413414
// numbers are also allowed.
414415
func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, key string, blockNr rpc.BlockNumber) (string, error) {
415-
state, _, err := s.b.StateAndHeaderByNumber(blockNr)
416+
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
416417
if state == nil || err != nil {
417418
return "0x", err
418419
}
@@ -456,7 +457,7 @@ type CallArgs struct {
456457
func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (string, *big.Int, error) {
457458
defer func(start time.Time) { glog.V(logger.Debug).Infof("call took %v", time.Since(start)) }(time.Now())
458459

459-
state, header, err := s.b.StateAndHeaderByNumber(blockNr)
460+
state, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
460461
if state == nil || err != nil {
461462
return "0x", common.Big0, err
462463
}
@@ -798,7 +799,7 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockHashAndIndex(ctx cont
798799

799800
// GetTransactionCount returns the number of transactions the given address has sent for the given block number
800801
func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*rpc.HexNumber, error) {
801-
state, _, err := s.b.StateAndHeaderByNumber(blockNr)
802+
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
802803
if state == nil || err != nil {
803804
return nil, err
804805
}

internal/ethapi/backend.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ type Backend interface {
4444
AccountManager() *accounts.Manager
4545
// BlockChain API
4646
SetHead(number uint64)
47-
HeaderByNumber(blockNr rpc.BlockNumber) *types.Header
47+
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
4848
BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error)
49-
StateAndHeaderByNumber(blockNr rpc.BlockNumber) (State, *types.Header, error)
49+
StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (State, *types.Header, error)
5050
GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error)
5151
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
5252
GetTd(blockHash common.Hash) *big.Int

0 commit comments

Comments
 (0)