|
|
|
package downloader
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
sttypes "github.com/harmony-one/harmony/p2p/stream/types"
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestDownloader_doShortRangeSync(t *testing.T) {
|
|
|
|
chain := newTestBlockChain(100, nil)
|
|
|
|
|
|
|
|
d := &Downloader{
|
|
|
|
bc: chain,
|
|
|
|
syncProtocol: newTestSyncProtocol(105, 32, nil),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
ctx: context.Background(),
|
|
|
|
logger: zerolog.Logger{},
|
|
|
|
}
|
|
|
|
n, err := d.doShortRangeSync()
|
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
if n == 0 {
|
|
|
|
t.Error("not synced")
|
|
|
|
}
|
|
|
|
if curNum := d.bc.CurrentBlock().NumberU64(); curNum != 105 {
|
|
|
|
t.Errorf("unexpected block number after sync: %v / %v", curNum, 105)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSrHelper_getHashChain(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
curBN uint64
|
|
|
|
syncProtocol syncProtocol
|
|
|
|
config Config
|
|
|
|
|
|
|
|
expHashChainSize int
|
|
|
|
expStSize int
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
curBN: 100,
|
|
|
|
syncProtocol: newTestSyncProtocol(1000, 32, nil),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expHashChainSize: numBlockHashesPerRequest,
|
|
|
|
expStSize: 16, // Concurrency
|
|
|
|
},
|
|
|
|
{
|
|
|
|
curBN: 100,
|
|
|
|
syncProtocol: newTestSyncProtocol(100, 32, nil),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expHashChainSize: 0,
|
|
|
|
expStSize: 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
curBN: 100,
|
|
|
|
syncProtocol: newTestSyncProtocol(110, 32, nil),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expHashChainSize: 10,
|
|
|
|
expStSize: 16,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// stream size is smaller than concurrency
|
|
|
|
curBN: 100,
|
|
|
|
syncProtocol: newTestSyncProtocol(1000, 10, nil),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 8,
|
|
|
|
},
|
|
|
|
expHashChainSize: numBlockHashesPerRequest,
|
|
|
|
expStSize: 10,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// one stream reports an error, else are fine
|
|
|
|
curBN: 100,
|
|
|
|
syncProtocol: newTestSyncProtocol(1000, 32, makeOnceErrorFunc()),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expHashChainSize: numBlockHashesPerRequest,
|
|
|
|
expStSize: 15, // Concurrency
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// error happens at one block number, all stream removed
|
|
|
|
curBN: 100,
|
|
|
|
syncProtocol: newTestSyncProtocol(1000, 32, func(bn uint64) error {
|
|
|
|
if bn == 110 {
|
|
|
|
return errors.New("test error")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expHashChainSize: 0,
|
|
|
|
expStSize: 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
curBN: 100,
|
|
|
|
syncProtocol: newTestSyncProtocol(1000, 32, nil),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expHashChainSize: numBlockHashesPerRequest,
|
|
|
|
expStSize: 16, // Concurrency
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, test := range tests {
|
|
|
|
sh := &srHelper{
|
|
|
|
syncProtocol: test.syncProtocol,
|
|
|
|
ctx: context.Background(),
|
|
|
|
config: test.config,
|
|
|
|
}
|
|
|
|
hashChain, wl, err := sh.getHashChain(sh.prepareBlockHashNumbers(test.curBN))
|
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
if len(hashChain) != test.expHashChainSize {
|
|
|
|
t.Errorf("Test %v: hash chain size unexpected: %v / %v", i, len(hashChain), test.expHashChainSize)
|
|
|
|
}
|
|
|
|
if len(wl) != test.expStSize {
|
|
|
|
t.Errorf("Test %v: whitelist size unexpected: %v / %v", i, len(wl), test.expStSize)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSrHelper_GetBlocksByHashes(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
hashes []common.Hash
|
|
|
|
syncProtocol syncProtocol
|
|
|
|
config Config
|
|
|
|
|
|
|
|
expBlockNumbers []uint64
|
|
|
|
expErr error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
hashes: testNumberToHashes([]uint64{101, 102, 103, 104, 105, 106, 107, 108, 109, 110}),
|
|
|
|
syncProtocol: newTestSyncProtocol(1000, 32, nil),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expBlockNumbers: []uint64{101, 102, 103, 104, 105, 106, 107, 108, 109, 110},
|
|
|
|
expErr: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// remote node cannot give the block with the given hash
|
|
|
|
hashes: testNumberToHashes([]uint64{101, 102, 103, 104, 105, 106, 107, 108, 109, 110}),
|
|
|
|
syncProtocol: newTestSyncProtocol(100, 32, nil),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expBlockNumbers: []uint64{},
|
|
|
|
expErr: errors.New("all streams are bad"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// one request return an error, else are fine
|
|
|
|
hashes: testNumberToHashes([]uint64{101, 102, 103, 104, 105, 106, 107, 108, 109, 110}),
|
|
|
|
syncProtocol: newTestSyncProtocol(1000, 32, makeOnceErrorFunc()),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expBlockNumbers: []uint64{101, 102, 103, 104, 105, 106, 107, 108, 109, 110},
|
|
|
|
expErr: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// All nodes encounter an error
|
|
|
|
hashes: testNumberToHashes([]uint64{101, 102, 103, 104, 105, 106, 107, 108, 109, 110}),
|
|
|
|
syncProtocol: newTestSyncProtocol(1000, 32, func(n uint64) error {
|
|
|
|
if n == 109 {
|
|
|
|
return errors.New("test error")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}),
|
|
|
|
config: Config{
|
|
|
|
Concurrency: 16,
|
|
|
|
MinStreams: 16,
|
|
|
|
},
|
|
|
|
expErr: errors.New("error expected"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for i, test := range tests {
|
|
|
|
sh := &srHelper{
|
|
|
|
syncProtocol: test.syncProtocol,
|
|
|
|
ctx: context.Background(),
|
|
|
|
config: test.config,
|
|
|
|
}
|
|
|
|
blocks, _, err := sh.getBlocksByHashes(test.hashes, makeStreamIDs(5))
|
|
|
|
if (err == nil) != (test.expErr == nil) {
|
|
|
|
t.Errorf("Test %v: unexpected error %v / %v", i, err, test.expErr)
|
|
|
|
}
|
|
|
|
if len(blocks) != len(test.expBlockNumbers) {
|
|
|
|
t.Errorf("Test %v: unepxected block number size: %v / %v", i, len(blocks), len(test.expBlockNumbers))
|
|
|
|
}
|
|
|
|
for i, block := range blocks {
|
|
|
|
gotNum := testHashToNumber(block.Hash())
|
|
|
|
if gotNum != test.expBlockNumbers[i] {
|
|
|
|
t.Errorf("Test %v: unexpected block number", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBlockHashResult_ComputeLongestHashChain(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
bns []uint64
|
|
|
|
results map[sttypes.StreamID][]int64
|
|
|
|
expChain []uint64
|
|
|
|
expWhitelist map[sttypes.StreamID]struct{}
|
|
|
|
expErr error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
bns: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
|
|
|
results: map[sttypes.StreamID][]int64{
|
|
|
|
makeStreamID(0): {1, 2, 3, 4, 5, 6, 7},
|
|
|
|
makeStreamID(1): {1, 2, 3, 4, 5, 6, 7},
|
|
|
|
makeStreamID(2): {1, 2, 3, 4, 5}, // left behind
|
|
|
|
},
|
|
|
|
expChain: []uint64{1, 2, 3, 4, 5, 6, 7},
|
|
|
|
expWhitelist: map[sttypes.StreamID]struct{}{
|
|
|
|
makeStreamID(0): {},
|
|
|
|
makeStreamID(1): {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// minority fork
|
|
|
|
bns: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
|
|
|
results: map[sttypes.StreamID][]int64{
|
|
|
|
makeStreamID(0): {1, 2, 3, 4, 5, 6, 7},
|
|
|
|
makeStreamID(1): {1, 2, 3, 4, 5, 6, 7},
|
|
|
|
makeStreamID(2): {1, 2, 3, 4, 5, 7, 8, 9},
|
|
|
|
},
|
|
|
|
expChain: []uint64{1, 2, 3, 4, 5, 6, 7},
|
|
|
|
expWhitelist: map[sttypes.StreamID]struct{}{
|
|
|
|
makeStreamID(0): {},
|
|
|
|
makeStreamID(1): {},
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
// nil block
|
|
|
|
bns: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
|
|
|
results: map[sttypes.StreamID][]int64{
|
|
|
|
makeStreamID(0): {},
|
|
|
|
makeStreamID(1): {},
|
|
|
|
makeStreamID(2): {},
|
|
|
|
},
|
|
|
|
expChain: nil,
|
|
|
|
expWhitelist: nil,
|
|
|
|
}, {
|
|
|
|
// not continuous block
|
|
|
|
bns: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
|
|
|
results: map[sttypes.StreamID][]int64{
|
|
|
|
makeStreamID(0): {1, 2, 3, 4, 5, 6, 7, -1, 9},
|
|
|
|
makeStreamID(1): {1, 2, 3, 4, 5, 6, 7},
|
|
|
|
makeStreamID(2): {1, 2, 3, 4, 5, 7, 8, 9},
|
|
|
|
},
|
|
|
|
expChain: []uint64{1, 2, 3, 4, 5, 6, 7},
|
|
|
|
expWhitelist: map[sttypes.StreamID]struct{}{
|
|
|
|
makeStreamID(0): {},
|
|
|
|
makeStreamID(1): {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// not continuous block
|
|
|
|
bns: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
|
|
|
results: map[sttypes.StreamID][]int64{},
|
|
|
|
expErr: errors.New("zero result"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, test := range tests {
|
|
|
|
res := newBlockHashResults(test.bns)
|
|
|
|
for st, hs := range test.results {
|
|
|
|
res.addResult(makeTestBlockHashes(hs), st)
|
|
|
|
}
|
|
|
|
|
|
|
|
chain, wl := res.computeLongestHashChain()
|
|
|
|
|
|
|
|
if err := checkHashChainResult(chain, test.expChain); err != nil {
|
|
|
|
t.Errorf("Test %v: %v", i, err)
|
|
|
|
}
|
|
|
|
if err := checkStreamSetEqual(streamIDListToMap(wl), test.expWhitelist); err != nil {
|
|
|
|
t.Errorf("Test %v: %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkHashChainResult(gots []common.Hash, exps []uint64) error {
|
|
|
|
if len(gots) != len(exps) {
|
|
|
|
return errors.New("unexpected size")
|
|
|
|
}
|
|
|
|
for i, got := range gots {
|
|
|
|
exp := exps[i]
|
|
|
|
if got != makeTestBlockHash(exp) {
|
|
|
|
return errors.New("unexpected block hash")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestHashMaxVote(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
m map[sttypes.StreamID]common.Hash
|
|
|
|
whitelist map[sttypes.StreamID]struct{}
|
|
|
|
expRes common.Hash
|
|
|
|
expWhitelist map[sttypes.StreamID]struct{}
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
m: map[sttypes.StreamID]common.Hash{
|
|
|
|
makeStreamID(0): makeTestBlockHash(0),
|
|
|
|
makeStreamID(1): makeTestBlockHash(1),
|
|
|
|
makeStreamID(2): makeTestBlockHash(1),
|
|
|
|
},
|
|
|
|
whitelist: map[sttypes.StreamID]struct{}{
|
|
|
|
makeStreamID(0): {},
|
|
|
|
makeStreamID(1): {},
|
|
|
|
makeStreamID(2): {},
|
|
|
|
},
|
|
|
|
expRes: makeTestBlockHash(1),
|
|
|
|
expWhitelist: map[sttypes.StreamID]struct{}{
|
|
|
|
makeStreamID(1): {},
|
|
|
|
makeStreamID(2): {},
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
m: map[sttypes.StreamID]common.Hash{
|
|
|
|
makeStreamID(0): makeTestBlockHash(0),
|
|
|
|
makeStreamID(1): makeTestBlockHash(1),
|
|
|
|
makeStreamID(2): makeTestBlockHash(1),
|
|
|
|
},
|
|
|
|
whitelist: nil,
|
|
|
|
expRes: makeTestBlockHash(1),
|
|
|
|
expWhitelist: map[sttypes.StreamID]struct{}{
|
|
|
|
makeStreamID(1): {},
|
|
|
|
makeStreamID(2): {},
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
m: map[sttypes.StreamID]common.Hash{
|
|
|
|
makeStreamID(0): makeTestBlockHash(0),
|
|
|
|
makeStreamID(1): makeTestBlockHash(1),
|
|
|
|
makeStreamID(2): makeTestBlockHash(1),
|
|
|
|
makeStreamID(3): makeTestBlockHash(0),
|
|
|
|
makeStreamID(4): makeTestBlockHash(0),
|
|
|
|
},
|
|
|
|
whitelist: map[sttypes.StreamID]struct{}{
|
|
|
|
makeStreamID(0): {},
|
|
|
|
makeStreamID(1): {},
|
|
|
|
makeStreamID(2): {},
|
|
|
|
},
|
|
|
|
expRes: makeTestBlockHash(1),
|
|
|
|
expWhitelist: map[sttypes.StreamID]struct{}{
|
|
|
|
makeStreamID(1): {},
|
|
|
|
makeStreamID(2): {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, test := range tests {
|
|
|
|
h, wl := countHashMaxVote(test.m, test.whitelist)
|
|
|
|
|
|
|
|
if h != test.expRes {
|
|
|
|
t.Errorf("Test %v: unexpected hash: %x / %x", i, h, test.expRes)
|
|
|
|
}
|
|
|
|
if err := checkStreamSetEqual(wl, test.expWhitelist); err != nil {
|
|
|
|
t.Errorf("Test %v: %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkStreamSetEqual(m1, m2 map[sttypes.StreamID]struct{}) error {
|
|
|
|
if len(m1) != len(m2) {
|
|
|
|
return fmt.Errorf("unexpected size: %v / %v", len(m1), len(m2))
|
|
|
|
}
|
|
|
|
for st := range m1 {
|
|
|
|
if _, ok := m2[st]; !ok {
|
|
|
|
return errors.New("not equal")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeTestBlockHashes(bns []int64) []common.Hash {
|
|
|
|
hs := make([]common.Hash, 0, len(bns))
|
|
|
|
for _, bn := range bns {
|
|
|
|
if bn < 0 {
|
|
|
|
hs = append(hs, emptyHash)
|
|
|
|
} else {
|
|
|
|
hs = append(hs, makeTestBlockHash(uint64(bn)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hs
|
|
|
|
}
|
|
|
|
|
|
|
|
func streamIDListToMap(sts []sttypes.StreamID) map[sttypes.StreamID]struct{} {
|
|
|
|
res := make(map[sttypes.StreamID]struct{})
|
|
|
|
|
|
|
|
for _, st := range sts {
|
|
|
|
res[st] = struct{}{}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeTestBlockHash(bn uint64) common.Hash {
|
|
|
|
return makeTestBlock(bn).Hash()
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeOnceErrorFunc() func(num uint64) error {
|
|
|
|
var once sync.Once
|
|
|
|
return func(num uint64) error {
|
|
|
|
var err error
|
|
|
|
once.Do(func() {
|
|
|
|
err = errors.New("test error expected")
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|