Unverified Commit 7a3c8900 authored by gary rong's avatar gary rong Committed by GitHub

les, light: improve txstatus retrieval (#22349)

Transaction unindexing will be enabled by default as of 1.10, which causes tx status retrieval will be broken without this PR. 

This PR introduces a retry mechanism in TxStatus retrieval.
parent 378e961d
...@@ -122,7 +122,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { ...@@ -122,7 +122,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.getTimeout) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.getTimeout)
leth.relay = newLesTxRelay(peers, leth.retriever) leth.relay = newLesTxRelay(peers, leth.retriever)
leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever) leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever)
leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune) leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune)
leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune) leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune)
leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer) leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer)
......
...@@ -66,7 +66,12 @@ func TestSequentialAnnouncementsLes2(t *testing.T) { testSequentialAnnouncements ...@@ -66,7 +66,12 @@ func TestSequentialAnnouncementsLes2(t *testing.T) { testSequentialAnnouncements
func TestSequentialAnnouncementsLes3(t *testing.T) { testSequentialAnnouncements(t, 3) } func TestSequentialAnnouncementsLes3(t *testing.T) { testSequentialAnnouncements(t, 3) }
func testSequentialAnnouncements(t *testing.T, protocol int) { func testSequentialAnnouncements(t *testing.T, protocol int) {
s, c, teardown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, false, true) netconfig := testnetConfig{
blocks: 4,
protocol: protocol,
nopruning: true,
}
s, c, teardown := newClientServerEnv(t, netconfig)
defer teardown() defer teardown()
// Create connected peer pair. // Create connected peer pair.
...@@ -101,7 +106,12 @@ func TestGappedAnnouncementsLes2(t *testing.T) { testGappedAnnouncements(t, 2) } ...@@ -101,7 +106,12 @@ func TestGappedAnnouncementsLes2(t *testing.T) { testGappedAnnouncements(t, 2) }
func TestGappedAnnouncementsLes3(t *testing.T) { testGappedAnnouncements(t, 3) } func TestGappedAnnouncementsLes3(t *testing.T) { testGappedAnnouncements(t, 3) }
func testGappedAnnouncements(t *testing.T, protocol int) { func testGappedAnnouncements(t *testing.T, protocol int) {
s, c, teardown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, false, true) netconfig := testnetConfig{
blocks: 4,
protocol: protocol,
nopruning: true,
}
s, c, teardown := newClientServerEnv(t, netconfig)
defer teardown() defer teardown()
// Create connected peer pair. // Create connected peer pair.
...@@ -183,7 +193,13 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { ...@@ -183,7 +193,13 @@ func testTrustedAnnouncement(t *testing.T, protocol int) {
ids = append(ids, n.String()) ids = append(ids, n.String())
} }
} }
_, c, teardown := newClientServerEnv(t, 0, protocol, nil, ids, 60, false, false, true) netconfig := testnetConfig{
protocol: protocol,
nopruning: true,
ulcServers: ids,
ulcFraction: 60,
}
_, c, teardown := newClientServerEnv(t, netconfig)
defer teardown() defer teardown()
defer func() { defer func() {
for i := 0; i < len(teardowns); i++ { for i := 0; i < len(teardowns); i++ {
...@@ -233,8 +249,17 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { ...@@ -233,8 +249,17 @@ func testTrustedAnnouncement(t *testing.T, protocol int) {
check([]uint64{10}, 10, func() { <-newHead }) // Sync the whole chain. check([]uint64{10}, 10, func() { <-newHead }) // Sync the whole chain.
} }
func TestInvalidAnnounces(t *testing.T) { func TestInvalidAnnouncesLES2(t *testing.T) { testInvalidAnnounces(t, lpv2) }
s, c, teardown := newClientServerEnv(t, 4, lpv3, nil, nil, 0, false, false, true) func TestInvalidAnnouncesLES3(t *testing.T) { testInvalidAnnounces(t, lpv3) }
func TestInvalidAnnouncesLES4(t *testing.T) { testInvalidAnnounces(t, lpv4) }
func testInvalidAnnounces(t *testing.T, protocol int) {
netconfig := testnetConfig{
blocks: 4,
protocol: protocol,
nopruning: true,
}
s, c, teardown := newClientServerEnv(t, netconfig)
defer teardown() defer teardown()
// Create connected peer pair. // Create connected peer pair.
......
This diff is collapsed.
...@@ -18,6 +18,7 @@ package les ...@@ -18,6 +18,7 @@ package les
import ( import (
"context" "context"
"sort"
"time" "time"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
...@@ -31,14 +32,16 @@ type LesOdr struct { ...@@ -31,14 +32,16 @@ type LesOdr struct {
db ethdb.Database db ethdb.Database
indexerConfig *light.IndexerConfig indexerConfig *light.IndexerConfig
chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer
peers *serverPeerSet
retriever *retrieveManager retriever *retrieveManager
stop chan struct{} stop chan struct{}
} }
func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, retriever *retrieveManager) *LesOdr { func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, peers *serverPeerSet, retriever *retrieveManager) *LesOdr {
return &LesOdr{ return &LesOdr{
db: db, db: db,
indexerConfig: config, indexerConfig: config,
peers: peers,
retriever: retriever, retriever: retriever,
stop: make(chan struct{}), stop: make(chan struct{}),
} }
...@@ -98,7 +101,101 @@ type Msg struct { ...@@ -98,7 +101,101 @@ type Msg struct {
Obj interface{} Obj interface{}
} }
// Retrieve tries to fetch an object from the LES network. // peerByTxHistory is a heap.Interface implementation which can sort
// the peerset by transaction history.
type peerByTxHistory []*serverPeer
func (h peerByTxHistory) Len() int { return len(h) }
func (h peerByTxHistory) Less(i, j int) bool {
if h[i].txHistory == txIndexUnlimited {
return false
}
if h[j].txHistory == txIndexUnlimited {
return true
}
return h[i].txHistory < h[j].txHistory
}
func (h peerByTxHistory) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
const (
maxTxStatusRetry = 3 // The maximum retrys will be made for tx status request.
maxTxStatusCandidates = 5 // The maximum les servers the tx status requests will be sent to.
)
// RetrieveTxStatus retrieves the transaction status from the LES network.
// There is no guarantee in the LES protocol that the mined transaction will
// be retrieved back for sure because of different reasons(the transaction
// is unindexed, the malicous server doesn't reply it deliberately, etc).
// Therefore, unretrieved transactions(UNKNOWN) will receive a certain number
// of retries, thus giving a weak guarantee.
func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequest) error {
// Sort according to the transaction history supported by the peer and
// select the peers with longest history.
var (
retries int
peers []*serverPeer
missing = len(req.Hashes)
result = make([]light.TxStatus, len(req.Hashes))
canSend = make(map[string]bool)
)
for _, peer := range odr.peers.allPeers() {
if peer.txHistory == txIndexDisabled {
continue
}
peers = append(peers, peer)
}
sort.Sort(sort.Reverse(peerByTxHistory(peers)))
for i := 0; i < maxTxStatusCandidates && i < len(peers); i++ {
canSend[peers[i].id] = true
}
// Send out the request and assemble the result.
for {
if retries >= maxTxStatusRetry || len(canSend) == 0 {
break
}
var (
// Deep copy the request, so that the partial result won't be mixed.
req = &TxStatusRequest{Hashes: req.Hashes}
id = genReqID()
distreq = &distReq{
getCost: func(dp distPeer) uint64 { return req.GetCost(dp.(*serverPeer)) },
canSend: func(dp distPeer) bool { return canSend[dp.(*serverPeer).id] },
request: func(dp distPeer) func() {
p := dp.(*serverPeer)
p.fcServer.QueuedRequest(id, req.GetCost(p))
delete(canSend, p.id)
return func() { req.Request(id, p) }
},
}
)
if err := odr.retriever.retrieve(ctx, id, distreq, func(p distPeer, msg *Msg) error { return req.Validate(odr.db, msg) }, odr.stop); err != nil {
return err
}
// Collect the response and assemble them to the final result.
// All the response is not verifiable, so always pick the first
// one we get.
for index, status := range req.Status {
if result[index].Status != core.TxStatusUnknown {
continue
}
if status.Status == core.TxStatusUnknown {
continue
}
result[index], missing = status, missing-1
}
// Abort the procedure if all the status are retrieved
if missing == 0 {
break
}
retries += 1
}
req.Status = result
return nil
}
// Retrieve tries to fetch an object from the LES network. It's a common API
// for most of the LES requests except for the TxStatusRequest which needs
// the additional retry mechanism.
// If the network retrieval was successful, it stores the object in local db. // If the network retrieval was successful, it stores the object in local db.
func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) { func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) {
lreq := LesRequest(req) lreq := LesRequest(req)
......
...@@ -487,7 +487,7 @@ func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 { ...@@ -487,7 +487,7 @@ func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 {
// CanSend tells if a certain peer is suitable for serving the given request // CanSend tells if a certain peer is suitable for serving the given request
func (r *TxStatusRequest) CanSend(peer *serverPeer) bool { func (r *TxStatusRequest) CanSend(peer *serverPeer) bool {
return peer.serveTxLookup return peer.txHistory != txIndexDisabled
} }
// Request sends an ODR request to the LES network (implementation of LesOdrRequest) // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
...@@ -496,13 +496,12 @@ func (r *TxStatusRequest) Request(reqID uint64, peer *serverPeer) error { ...@@ -496,13 +496,12 @@ func (r *TxStatusRequest) Request(reqID uint64, peer *serverPeer) error {
return peer.requestTxStatus(reqID, r.Hashes) return peer.requestTxStatus(reqID, r.Hashes)
} }
// Valid processes an ODR request reply message from the LES network // Validate processes an ODR request reply message from the LES network
// returns true and stores results in memory if the message was a valid reply // returns true and stores results in memory if the message was a valid reply
// to the request (implementation of LesOdrRequest) // to the request (implementation of LesOdrRequest)
func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error { func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error {
log.Debug("Validating transaction status", "count", len(r.Hashes)) log.Debug("Validating transaction status", "count", len(r.Hashes))
// Ensure we have a correct message with a single block body
if msg.MsgType != MsgTxStatus { if msg.MsgType != MsgTxStatus {
return errInvalidMessageType return errInvalidMessageType
} }
......
...@@ -19,7 +19,10 @@ package les ...@@ -19,7 +19,10 @@ package les
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/rand"
"fmt"
"math/big" "math/big"
"reflect"
"testing" "testing"
"time" "time"
...@@ -190,7 +193,13 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon ...@@ -190,7 +193,13 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon
// testOdr tests odr requests whose validation guaranteed by block headers. // testOdr tests odr requests whose validation guaranteed by block headers.
func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) { func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) {
// Assemble the test environment // Assemble the test environment
server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true, true) netconfig := testnetConfig{
blocks: 4,
protocol: protocol,
connect: true,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown() defer tearDown()
// Ensure the client has synced all necessary data. // Ensure the client has synced all necessary data.
...@@ -246,3 +255,184 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od ...@@ -246,3 +255,184 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od
test(5) test(5)
} }
} }
func TestGetTxStatusFromUnindexedPeersLES4(t *testing.T) { testGetTxStatusFromUnindexedPeers(t, lpv4) }
func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) {
var (
blocks = 8
netconfig = testnetConfig{
blocks: blocks,
protocol: protocol,
nopruning: true,
}
)
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown()
// Iterate the chain, create the tx indexes locally
var (
testHash common.Hash
testStatus light.TxStatus
txs = make(map[common.Hash]*types.Transaction) // Transaction objects set
blockNumbers = make(map[common.Hash]uint64) // Transaction hash to block number mappings
blockHashes = make(map[common.Hash]common.Hash) // Transaction hash to block hash mappings
intraIndex = make(map[common.Hash]uint64) // Transaction intra-index in block
)
for number := uint64(1); number < server.backend.Blockchain().CurrentBlock().NumberU64(); number++ {
block := server.backend.Blockchain().GetBlockByNumber(number)
if block == nil {
t.Fatalf("Failed to retrieve block %d", number)
}
for index, tx := range block.Transactions() {
txs[tx.Hash()] = tx
blockNumbers[tx.Hash()] = number
blockHashes[tx.Hash()] = block.Hash()
intraIndex[tx.Hash()] = uint64(index)
if testHash == (common.Hash{}) {
testHash = tx.Hash()
testStatus = light.TxStatus{
Status: core.TxStatusIncluded,
Lookup: &rawdb.LegacyTxLookupEntry{
BlockHash: block.Hash(),
BlockIndex: block.NumberU64(),
Index: uint64(index),
},
}
}
}
}
// serveMsg processes incoming GetTxStatusMsg and sends the response back.
serveMsg := func(peer *testPeer, txLookup uint64) error {
msg, err := peer.app.ReadMsg()
if err != nil {
return err
}
if msg.Code != GetTxStatusMsg {
return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, GetTxStatusMsg)
}
var r GetTxStatusPacket
if err := msg.Decode(&r); err != nil {
return err
}
stats := make([]light.TxStatus, len(r.Hashes))
for i, hash := range r.Hashes {
number, exist := blockNumbers[hash]
if !exist {
continue // Filter out unknown transactions
}
min := uint64(blocks) - txLookup
if txLookup != txIndexUnlimited && (txLookup == txIndexDisabled || number < min) {
continue // Filter out unindexed transactions
}
stats[i].Status = core.TxStatusIncluded
stats[i].Lookup = &rawdb.LegacyTxLookupEntry{
BlockHash: blockHashes[hash],
BlockIndex: number,
Index: intraIndex[hash],
}
}
data, _ := rlp.EncodeToBytes(stats)
reply := &reply{peer.app, TxStatusMsg, r.ReqID, data}
reply.send(testBufLimit)
return nil
}
var testspecs = []struct {
peers int
txLookups []uint64
txs []common.Hash
results []light.TxStatus
}{
// Retrieve mined transaction from the empty peerset
{
peers: 0,
txLookups: []uint64{},
txs: []common.Hash{testHash},
results: []light.TxStatus{{}},
},
// Retrieve unknown transaction from the full peers
{
peers: 3,
txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
txs: []common.Hash{randomHash()},
results: []light.TxStatus{{}},
},
// Retrieve mined transaction from the full peers
{
peers: 3,
txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
txs: []common.Hash{testHash},
results: []light.TxStatus{testStatus},
},
// Retrieve mixed transactions from the full peers
{
peers: 3,
txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
txs: []common.Hash{randomHash(), testHash},
results: []light.TxStatus{{}, testStatus},
},
// Retrieve mixed transactions from unindexed peer(but the target is still available)
{
peers: 3,
txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2},
txs: []common.Hash{randomHash(), testHash},
results: []light.TxStatus{{}, testStatus},
},
// Retrieve mixed transactions from unindexed peer(but the target is not available)
{
peers: 3,
txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2},
txs: []common.Hash{randomHash(), testHash},
results: []light.TxStatus{{}, {}},
},
}
for _, testspec := range testspecs {
// Create a bunch of server peers with different tx history
var (
serverPeers []*testPeer
closeFns []func()
)
for i := 0; i < testspec.peers; i++ {
peer, closePeer, _ := client.newRawPeer(t, fmt.Sprintf("server-%d", i), protocol, testspec.txLookups[i])
serverPeers = append(serverPeers, peer)
closeFns = append(closeFns, closePeer)
// Create a one-time routine for serving message
go func(i int, peer *testPeer) {
serveMsg(peer, testspec.txLookups[i])
}(i, peer)
}
// Send out the GetTxStatus requests, compare the result with
// expected value.
r := &light.TxStatusRequest{Hashes: testspec.txs}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
err := client.handler.backend.odr.RetrieveTxStatus(ctx, r)
if err != nil {
t.Errorf("Failed to retrieve tx status %v", err)
} else {
if !reflect.DeepEqual(testspec.results, r.Status) {
t.Errorf("Result mismatch, diff")
}
}
// Close all connected peers and start the next round
for _, closeFn := range closeFns {
closeFn()
}
}
}
// randomHash generates a random blob of data and returns it as a hash.
func randomHash() common.Hash {
var hash common.Hash
if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
panic(err)
}
return hash
}
...@@ -341,7 +341,7 @@ type serverPeer struct { ...@@ -341,7 +341,7 @@ type serverPeer struct {
onlyAnnounce bool // The flag whether the server sends announcement only. onlyAnnounce bool // The flag whether the server sends announcement only.
chainSince, chainRecent uint64 // The range of chain server peer can serve. chainSince, chainRecent uint64 // The range of chain server peer can serve.
stateSince, stateRecent uint64 // The range of state server peer can serve. stateSince, stateRecent uint64 // The range of state server peer can serve.
serveTxLookup bool // The server peer can serve tx lookups. txHistory uint64 // The length of available tx history, 0 means all, 1 means disabled
// Advertised checkpoint fields // Advertised checkpoint fields
checkpointNumber uint64 // The block height which the checkpoint is registered. checkpointNumber uint64 // The block height which the checkpoint is registered.
...@@ -634,13 +634,13 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter ...@@ -634,13 +634,13 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter
if err := recv.get("recentTxLookup", &recentTx); err != nil { if err := recv.get("recentTxLookup", &recentTx); err != nil {
return err return err
} }
// Note: in the current version we only consider the tx index service useful p.txHistory = uint64(recentTx)
// if it is unlimited. This can be made configurable in the future.
p.serveTxLookup = recentTx == txIndexUnlimited
} else { } else {
p.serveTxLookup = true // The weak assumption is held here that legacy les server(les2,3)
// has unlimited transaction history. The les serving in these legacy
// versions is disabled if the transaction is unindexed.
p.txHistory = txIndexUnlimited
} }
if p.onlyAnnounce && !p.trusted { if p.onlyAnnounce && !p.trusted {
return errResp(ErrUselessPeer, "peer cannot serve requests") return errResp(ErrUselessPeer, "peer cannot serve requests")
} }
......
...@@ -28,9 +28,8 @@ import ( ...@@ -28,9 +28,8 @@ import (
) )
func TestLightPruner(t *testing.T) { func TestLightPruner(t *testing.T) {
config := light.TestClientIndexerConfig var (
waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
for { for {
cs, _, _ := cIndexer.Sections() cs, _, _ := cIndexer.Sections()
bts, _, _ := btIndexer.Sections() bts, _, _ := btIndexer.Sections()
...@@ -40,7 +39,15 @@ func TestLightPruner(t *testing.T) { ...@@ -40,7 +39,15 @@ func TestLightPruner(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} }
} }
server, client, tearDown := newClientServerEnv(t, int(3*config.ChtSize+config.ChtConfirms), 2, waitIndexers, nil, 0, false, true, false) config = light.TestClientIndexerConfig
netconfig = testnetConfig{
blocks: int(3*config.ChtSize + config.ChtConfirms),
protocol: 3,
indexFn: waitIndexers,
connect: true,
}
)
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown() defer tearDown()
// checkDB iterates the chain with given prefix, resolves the block number // checkDB iterates the chain with given prefix, resolves the block number
......
...@@ -83,7 +83,14 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq ...@@ -83,7 +83,14 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq
func testAccess(t *testing.T, protocol int, fn accessTestFn) { func testAccess(t *testing.T, protocol int, fn accessTestFn) {
// Assemble the test environment // Assemble the test environment
server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true, true) netconfig := testnetConfig{
blocks: 4,
protocol: protocol,
indexFn: nil,
connect: true,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown() defer tearDown()
// Ensure the client has synced all necessary data. // Ensure the client has synced all necessary data.
......
...@@ -31,15 +31,15 @@ import ( ...@@ -31,15 +31,15 @@ import (
) )
// Test light syncing which will download all headers from genesis. // Test light syncing which will download all headers from genesis.
func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 0) } func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 0) }
// Test legacy checkpoint syncing which will download tail headers // Test legacy checkpoint syncing which will download tail headers
// based on a hardcoded checkpoint. // based on a hardcoded checkpoint.
func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 1) } func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 1) }
// Test checkpoint syncing which will download tail headers based // Test checkpoint syncing which will download tail headers based
// on a verified checkpoint. // on a verified checkpoint.
func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 2) } func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 2) }
func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
config := light.TestServerIndexerConfig config := light.TestServerIndexerConfig
...@@ -55,7 +55,13 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { ...@@ -55,7 +55,13 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
} }
} }
// Generate 128+1 blocks (totally 1 CHT section) // Generate 128+1 blocks (totally 1 CHT section)
server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true) netconfig := testnetConfig{
blocks: int(config.ChtSize + config.ChtConfirms),
protocol: protocol,
indexFn: waitIndexers,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown() defer tearDown()
expected := config.ChtSize + config.ChtConfirms expected := config.ChtSize + config.ChtConfirms
...@@ -78,7 +84,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { ...@@ -78,7 +84,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
// Register the assembled checkpoint into oracle. // Register the assembled checkpoint into oracle.
header := server.backend.Blockchain().CurrentHeader() header := server.backend.Blockchain().CurrentHeader()
data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) data := append([]byte{0x19, 0x00}, append(oracleAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337))
...@@ -128,10 +134,10 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { ...@@ -128,10 +134,10 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
} }
} }
func TestMissOracleBackend(t *testing.T) { testMissOracleBackend(t, true) } func TestMissOracleBackendLES3(t *testing.T) { testMissOracleBackend(t, true, lpv3) }
func TestMissOracleBackendNoCheckpoint(t *testing.T) { testMissOracleBackend(t, false) } func TestMissOracleBackendNoCheckpointLES3(t *testing.T) { testMissOracleBackend(t, false, lpv3) }
func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) {
config := light.TestServerIndexerConfig config := light.TestServerIndexerConfig
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
...@@ -145,7 +151,13 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { ...@@ -145,7 +151,13 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) {
} }
} }
// Generate 128+1 blocks (totally 1 CHT section) // Generate 128+1 blocks (totally 1 CHT section)
server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) netconfig := testnetConfig{
blocks: int(config.ChtSize + config.ChtConfirms),
protocol: protocol,
indexFn: waitIndexers,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown() defer tearDown()
expected := config.ChtSize + config.ChtConfirms expected := config.ChtSize + config.ChtConfirms
...@@ -160,7 +172,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { ...@@ -160,7 +172,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) {
// Register the assembled checkpoint into oracle. // Register the assembled checkpoint into oracle.
header := server.backend.Blockchain().CurrentHeader() header := server.backend.Blockchain().CurrentHeader()
data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) data := append([]byte{0x19, 0x00}, append(oracleAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337))
...@@ -220,7 +232,9 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { ...@@ -220,7 +232,9 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) {
} }
} }
func TestSyncFromConfiguredCheckpoint(t *testing.T) { func TestSyncFromConfiguredCheckpointLES3(t *testing.T) { testSyncFromConfiguredCheckpoint(t, lpv3) }
func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig config := light.TestServerIndexerConfig
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
...@@ -234,7 +248,13 @@ func TestSyncFromConfiguredCheckpoint(t *testing.T) { ...@@ -234,7 +248,13 @@ func TestSyncFromConfiguredCheckpoint(t *testing.T) {
} }
} }
// Generate 256+1 blocks (totally 2 CHT sections) // Generate 256+1 blocks (totally 2 CHT sections)
server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) netconfig := testnetConfig{
blocks: int(2*config.ChtSize + config.ChtConfirms),
protocol: protocol,
indexFn: waitIndexers,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown() defer tearDown()
// Configure the local checkpoint(the first section) // Configure the local checkpoint(the first section)
...@@ -296,7 +316,9 @@ func TestSyncFromConfiguredCheckpoint(t *testing.T) { ...@@ -296,7 +316,9 @@ func TestSyncFromConfiguredCheckpoint(t *testing.T) {
} }
} }
func TestSyncAll(t *testing.T) { func TestSyncAll(t *testing.T) { testSyncAll(t, lpv3) }
func testSyncAll(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig config := light.TestServerIndexerConfig
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
...@@ -310,7 +332,13 @@ func TestSyncAll(t *testing.T) { ...@@ -310,7 +332,13 @@ func TestSyncAll(t *testing.T) {
} }
} }
// Generate 256+1 blocks (totally 2 CHT sections) // Generate 256+1 blocks (totally 2 CHT sections)
server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) netconfig := testnetConfig{
blocks: int(2*config.ChtSize + config.ChtConfirms),
protocol: protocol,
indexFn: waitIndexers,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown() defer tearDown()
client.handler.backend.config.SyncFromCheckpoint = true client.handler.backend.config.SyncFromCheckpoint = true
......
This diff is collapsed.
...@@ -126,7 +126,12 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr ...@@ -126,7 +126,12 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr
// newTestServerPeer creates server peer. // newTestServerPeer creates server peer.
func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) { func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) {
s, teardown := newServerEnv(t, blocks, protocol, nil, false, false, 0) netconfig := testnetConfig{
blocks: blocks,
protocol: protocol,
nopruning: true,
}
s, _, teardown := newClientServerEnv(t, netconfig)
key, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
if err != nil { if err != nil {
t.Fatal("generate key err:", err) t.Fatal("generate key err:", err)
...@@ -138,6 +143,12 @@ func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *en ...@@ -138,6 +143,12 @@ func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *en
// newTestLightPeer creates node with light sync mode // newTestLightPeer creates node with light sync mode
func newTestLightPeer(t *testing.T, protocol int, ulcServers []string, ulcFraction int) (*testClient, func()) { func newTestLightPeer(t *testing.T, protocol int, ulcServers []string, ulcFraction int) (*testClient, func()) {
_, c, teardown := newClientServerEnv(t, 0, protocol, nil, ulcServers, ulcFraction, false, false, true) netconfig := testnetConfig{
protocol: protocol,
ulcServers: ulcServers,
ulcFraction: ulcFraction,
nopruning: true,
}
_, c, teardown := newClientServerEnv(t, netconfig)
return c, teardown return c, teardown
} }
...@@ -42,6 +42,7 @@ type OdrBackend interface { ...@@ -42,6 +42,7 @@ type OdrBackend interface {
BloomTrieIndexer() *core.ChainIndexer BloomTrieIndexer() *core.ChainIndexer
BloomIndexer() *core.ChainIndexer BloomIndexer() *core.ChainIndexer
Retrieve(ctx context.Context, req OdrRequest) error Retrieve(ctx context.Context, req OdrRequest) error
RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error
IndexerConfig() *IndexerConfig IndexerConfig() *IndexerConfig
} }
......
...@@ -269,10 +269,15 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint ...@@ -269,10 +269,15 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint
return result, nil return result, nil
} }
// GetTransaction retrieves a canonical transaction by hash and also returns its position in the chain // GetTransaction retrieves a canonical transaction by hash and also returns
// its position in the chain. There is no guarantee in the LES protocol that
// the mined transaction will be retrieved back for sure because of different
// reasons(the transaction is unindexed, the malicous server doesn't reply it
// deliberately, etc). Therefore, unretrieved transactions will receive a certain
// number of retrys, thus giving a weak guarantee.
func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
r := &TxStatusRequest{Hashes: []common.Hash{txHash}} r := &TxStatusRequest{Hashes: []common.Hash{txHash}}
if err := odr.Retrieve(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded { if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded {
return nil, common.Hash{}, 0, 0, err return nil, common.Hash{}, 0, 0, err
} }
pos := r.Status[0].Lookup pos := r.Status[0].Lookup
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment