tendermint/blockchain/v2/scheduler.go

492 lines
12 KiB
Go
Raw Normal View History

2019-07-09 15:17:42 +02:00
package v2
import (
"errors"
"math"
"math/rand"
"time"
"github.com/tendermint/tendermint/p2p"
2019-07-12 14:13:53 +03:00
"github.com/tendermint/tendermint/types"
2019-07-09 15:17:42 +02:00
)
type height int64
2019-07-12 14:13:53 +03:00
// errors
var (
2019-07-12 14:26:20 +03:00
// new
errDuplicatePeer = errors.New("fast sync tried to add a peer twice")
errPeerNotFound = errors.New("Peer not found")
errPeerRemoved = errors.New("try to remove a removed peer")
2019-07-14 21:45:16 +03:00
errBadSchedule = errors.New("Invalid Schedule transition")
2019-07-12 14:26:20 +03:00
2019-07-12 14:13:53 +03:00
// internal to the package
errNoErrorFinished = errors.New("fast sync is finished")
errInvalidEvent = errors.New("invalid event in current state")
errMissingBlock = errors.New("missing blocks")
errNilPeerForBlockRequest = errors.New("peer for block request does not exist in the switch")
errSendQueueFull = errors.New("block request not made, send-queue is full")
errPeerTooShort = errors.New("peer height too low, old peer removed/ new peer not added")
errSwitchRemovesPeer = errors.New("switch is removing peer")
errTimeoutEventWrongState = errors.New("timeout event for a state different than the current one")
errNoTallerPeer = errors.New("fast sync timed out on waiting for a peer taller than this node")
// reported eventually to the switch
errPeerLowersItsHeight = errors.New("fast sync peer reports a height lower than previous") // handle return
errNoPeerResponseForCurrentHeights = errors.New("fast sync timed out on peer block response for current heights") // handle return
errNoPeerResponse = errors.New("fast sync timed out on peer block response") // xx
errBadDataFromPeer = errors.New("fast sync received block from wrong peer or block is bad") // xx
errDuplicateBlock = errors.New("fast sync received duplicate block from peer")
errBlockVerificationFailure = errors.New("fast sync block verification failure") // xx
errSlowPeer = errors.New("fast sync peer is not sending us data fast enough") // xx
)
type Event interface{}
type schedulerErrorEv struct {
peerID p2p.ID
error error
}
2019-07-09 15:17:42 +02:00
type blockState int
const (
2019-07-14 23:50:58 +03:00
blockStateUnknown = iota
blockStateNew
blockStatePending
blockStateReceived
blockStateProcessed
2019-07-09 15:17:42 +02:00
)
type scBlockRequestMessage struct {
peerID p2p.ID
height int64
}
type peerState int
const (
peerStateNew = iota
2019-07-14 23:50:58 +03:00
peerStateReady
2019-07-09 15:17:42 +02:00
peerStateRemoved
)
type scPeer struct {
peerID p2p.ID
2019-07-14 23:50:58 +03:00
state peerState
height int64
2019-07-09 15:17:42 +02:00
lastTouched time.Time
2019-07-14 23:50:58 +03:00
lastRate int64
2019-07-09 15:17:42 +02:00
}
func newScPeer(peerID p2p.ID) *scPeer {
return &scPeer{
peerID: peerID,
state: peerStateNew,
2019-07-14 23:50:58 +03:00
height: -1,
2019-07-09 15:17:42 +02:00
}
}
type schedule struct {
2019-07-14 23:50:58 +03:00
initHeight int64
2019-07-09 15:17:42 +02:00
// a list of blocks in which blockState
2019-07-14 23:50:58 +03:00
blockStates map[int64]blockState
2019-07-09 15:17:42 +02:00
// a map of peerID to schedule specific peer struct `scPeer`
2019-07-14 23:50:58 +03:00
peers map[p2p.ID]*scPeer
2019-07-09 15:17:42 +02:00
// a map of heights to the peer we are waiting for a response from
2019-07-14 23:50:58 +03:00
pending map[int64]p2p.ID
pendingTime map[int64]time.Time
2019-07-09 15:17:42 +02:00
peerTimeout uint
peerMinSpeed uint
}
2019-07-14 23:50:58 +03:00
func newSchedule(initHeight int64) *schedule {
2019-07-14 21:45:16 +03:00
sc := schedule{
2019-07-14 23:50:58 +03:00
initHeight: initHeight,
2019-07-09 15:17:42 +02:00
}
2019-07-14 21:45:16 +03:00
2019-07-14 23:50:58 +03:00
sc.setStateAtHeight(initHeight, blockStateNew)
2019-07-14 21:45:16 +03:00
return &sc
2019-07-09 15:17:42 +02:00
}
func (sc *schedule) addPeer(peerID p2p.ID) error {
2019-07-14 23:50:58 +03:00
if _, ok := sc.peers[peerID]; ok {
2019-07-12 14:26:20 +03:00
return errDuplicatePeer
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
sc.peers[peerID] = newScPeer(peerID)
return nil
2019-07-09 15:17:42 +02:00
}
func (sc *schedule) touchPeer(peerID p2p.ID, time time.Time) error {
2019-07-14 23:50:58 +03:00
var peer scPeer
if peer, ok := sc.peers[peerID]; !ok && peer.state == peerStateRemoved {
2019-07-12 14:26:20 +03:00
return errPeerNotFound
2019-07-09 15:17:42 +02:00
}
2019-07-14 21:45:16 +03:00
peer.lastTouched = time
2019-07-14 23:50:58 +03:00
return nil
2019-07-09 15:17:42 +02:00
}
func (sc *schedule) removePeer(peerID p2p.ID) error {
2019-07-14 23:50:58 +03:00
var peer scPeer
if peer, ok := sc.peers[peerID]; !ok {
2019-07-12 14:26:20 +03:00
return errPeerNotFound
2019-07-09 15:17:42 +02:00
}
if peer.state == peerStateRemoved {
2019-07-12 14:26:20 +03:00
return errPeerRemoved
2019-07-09 15:17:42 +02:00
}
for height, pendingPeerID := range sc.pending {
if peerID == pendingPeerID {
2019-07-14 23:50:58 +03:00
delete(sc.pending, height)
2019-07-09 15:17:42 +02:00
sc.blockStates[height] = blockStateNew
}
}
peer.state = peerStateRemoved
return nil
}
2019-07-12 14:13:53 +03:00
func (sc *schedule) setPeerHeight(peerID p2p.ID, height int64) error {
2019-07-14 23:50:58 +03:00
var peer scPeer
if peer, ok := sc.peers[peerID]; !ok || peer.state != peerStateRemoved {
2019-07-09 15:17:42 +02:00
return errors.New("new peer not found")
}
if height < peer.height {
return errors.New("peer height descending")
}
peer.height = height
peer.state = peerStateReady
for i := sc.minHeight(); i <= height; i++ {
2019-07-14 23:50:58 +03:00
if sc.getStateAtHeight(i) == blockStateUnknown {
sc.setStateAtHeight(i, blockStateNew)
2019-07-09 15:17:42 +02:00
}
2019-07-12 14:13:53 +03:00
}
2019-07-09 15:17:42 +02:00
2019-07-12 14:13:53 +03:00
return nil
}
func (sc *schedule) getStateAtHeight(height int64) blockState {
2019-07-14 23:50:58 +03:00
if height < sc.initHeight {
2019-07-14 21:45:16 +03:00
return blockStateProcessed
2019-07-14 23:50:58 +03:00
} else if state, ok := sc.blockStates[height]; ok {
2019-07-12 14:13:53 +03:00
return state
} else {
return blockStateUnknown
}
}
2019-07-14 23:50:58 +03:00
func (sc *schedule) getPeersAtHeight(height int64) []*scPeer {
peers := []*scPeer{}
2019-07-12 14:13:53 +03:00
for perrID, peer := range sc.peers {
if peer.height >= height {
peers = append(peers, peer)
}
2019-07-09 15:17:42 +02:00
}
2019-07-12 14:13:53 +03:00
return peers
}
2019-07-14 21:45:16 +03:00
// XXX: probably needs a better name
2019-07-14 23:50:58 +03:00
func (sc *schedule) peersSince(duration time.Duration, now time.Time) []*scPeer {
peers := []*scPeer{}
2019-07-14 21:45:16 +03:00
for id, peer := range sc.peers {
2019-07-14 23:50:58 +03:00
if now.Sub(peer.lastTouched) > duration {
2019-07-14 21:45:16 +03:00
peers = append(peers, peer)
}
}
return peers
}
2019-07-12 14:13:53 +03:00
func (sc *schedule) setStateAtHeight(height int64, state blockState) {
sc.blockStates[height] = state
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
// TODO keep track of when i received this block
func (sc *schedule) markReceived(peerID p2p.ID, height int64, size int64, now time.Time) error {
var peer scPeer
if peer, ok := sc.peers[peerID]; !ok || peer.state != peerStateReady {
2019-07-12 14:26:20 +03:00
return errPeerNotFound
2019-07-09 15:17:42 +02:00
}
2019-07-12 14:13:53 +03:00
if sc.getStateAtHeight(height) != blockStatePending {
2019-07-14 21:45:16 +03:00
// received a block not in pending
return errBadSchedule
2019-07-09 15:17:42 +02:00
}
// check if the block is pending from that peer
if sc.pending[height] != peerID {
2019-07-14 21:45:16 +03:00
return errBadSchedule
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
var pendingTime time.Time
if pendingTime, ok := sc.pendingTime[height]; !ok || pendingTime.Sub(now) < 0 {
return errBadSchedule
}
peer.lastRate = size / int64(now.Sub(pendingTime).Seconds())
2019-07-12 14:13:53 +03:00
sc.setStateAtHeight(height, blockStateReceived)
2019-07-14 23:50:58 +03:00
delete(sc.pending, height)
delete(sc.pendingTime, height)
2019-07-09 15:17:42 +02:00
return nil
}
2019-07-14 23:50:58 +03:00
// todo keep track of when i requested this block
func (sc *schedule) markPending(peerID p2p.ID, height int64, time time.Time) error {
var peer scPeer
if peer, ok := sc.peers[peerID]; !ok || peer.state != peerStateReady {
2019-07-14 21:45:16 +03:00
return errPeerNotFound
}
if height > peer.height {
// tried to request a block from a peer who doesn't have it
return errBadSchedule
}
2019-07-12 14:13:53 +03:00
sc.setStateAtHeight(height, blockStatePending)
2019-07-14 23:50:58 +03:00
sc.pending[height] = peerID
// XXX: to make htis more accurate we can introduce a message from
// the IO rutine which indicates the time the request was put on the wire
sc.pendingTime[height] = time
2019-07-14 21:45:16 +03:00
return nil
2019-07-12 14:13:53 +03:00
}
2019-07-09 15:17:42 +02:00
func (sc *schedule) markProcessed(height int64) error {
2019-07-12 14:13:53 +03:00
if sc.getStateAtHeight(height) != blockStateReceived {
2019-07-14 21:45:16 +03:00
return errBadSchedule
2019-07-09 15:17:42 +02:00
}
2019-07-12 14:13:53 +03:00
sc.setStateAtHeight(height, blockStateProcessed)
2019-07-14 23:50:58 +03:00
return nil
2019-07-12 14:13:53 +03:00
}
func (sc *schedule) allBlocksProcessed() bool {
for height, state := range sc.blockStates {
if state != blockStateProcessed {
return false
}
}
return true
2019-07-09 15:17:42 +02:00
}
// heighter block | state == blockStateNew
func (sc *schedule) maxHeight() int64 {
2019-07-14 23:50:58 +03:00
var max int64 = 0
2019-07-09 15:17:42 +02:00
for height, state := range sc.blockStates {
if state == blockStateNew && height > max {
max = height
}
}
return max
}
// lowest block | state == blockStateNew
func (sc *schedule) minHeight() int64 {
2019-07-14 23:50:58 +03:00
min := sc.initHeight
2019-07-09 15:17:42 +02:00
for height, state := range sc.blockStates {
if state == blockStateNew && height < min {
min = height
}
}
return min
}
2019-07-14 23:50:58 +03:00
// XXX: THis is not yet used
func (sc *schedule) pendingFrom(peerID p2p.ID) []int64 {
heights := []int64{}
for height, pendingPeerID := range sc.pending {
if pendingPeerID == peerID {
heights = append(heights, height)
}
}
return heights
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
// XXX: Split up read and write paths here
2019-07-09 15:17:42 +02:00
func (sc *schedule) resetBlocks(peerID p2p.ID) error {
2019-07-14 23:50:58 +03:00
var peer scPeer
if peer, ok := sc.peers[peerID]; !ok {
2019-07-12 14:26:20 +03:00
return errPeerNotFound
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
// this should use pendingFrom
for height, pendingPeerID := range sc.pending {
if pendingPeerID == peerID {
sc.setStateAtHeight(height, blockStateNew)
}
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
return nil
2019-07-09 15:17:42 +02:00
}
func (sc *schedule) selectPeer(peers []scPeer) scPeer {
2019-07-12 14:13:53 +03:00
// FIXME: properPeerSelector
2019-07-09 15:17:42 +02:00
s := rand.NewSource(time.Now().Unix())
r := rand.New(s)
return peers[r.Intn(len(peers))]
}
func (sc *schedule) prunablePeers(time time.Time, minSpeed int) []p2p.ID {
// TODO
return []p2p.ID{}
}
func (sc *schedule) numBlockInState(targetState blockState) uint32 {
2019-07-14 23:50:58 +03:00
var num uint32 = 0
for height, state := range sc.blockStates {
2019-07-09 15:17:42 +02:00
if state == targetState {
num++
}
}
return num
}
2019-07-12 14:13:53 +03:00
type Scheduler struct {
2019-07-14 23:50:58 +03:00
sc *schedule
targetPending uint // the number of blocks we want in blockStatePending
targetReceived uint // the number of blocks we want in blockStateReceived
2019-07-12 14:13:53 +03:00
}
func NewScheduler(minHeight int64, targetPending uint, targetReceived uint) *Scheduler {
return &Scheduler{
2019-07-14 23:50:58 +03:00
sc: newSchedule(minHeight),
targetPending: targetPending,
targetReceived: targetReceived,
2019-07-09 15:17:42 +02:00
}
}
2019-07-14 23:50:58 +03:00
type Skip struct {
}
func (sdr *Scheduler) handleAddPeer(peerID p2p.ID) Event {
2019-07-12 14:13:53 +03:00
err := sdr.sc.addPeer(peerID)
if err != nil {
2019-07-14 23:50:58 +03:00
return schedulerErrorEv{peerID, err}
2019-07-12 14:13:53 +03:00
}
2019-07-14 23:50:58 +03:00
return Skip{}
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
func (sdr *Scheduler) handleRemovePeer(peerID p2p.ID) Event {
2019-07-12 14:13:53 +03:00
err := sdr.sc.removePeer(peerID)
if err != nil {
2019-07-14 23:50:58 +03:00
return schedulerErrorEv{peerID, err}
2019-07-12 14:13:53 +03:00
}
2019-07-09 15:17:42 +02:00
2019-07-14 23:50:58 +03:00
return Skip{}
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
func (sdr *Scheduler) handleStatusResponse(peerID p2p.ID, height int64, now time.Time) Event {
err := sdr.sc.touchPeer(peerID, now)
2019-07-12 14:13:53 +03:00
if err != nil {
2019-07-14 23:50:58 +03:00
return schedulerErrorEv{peerID, err}
2019-07-12 14:13:53 +03:00
}
err = sdr.sc.setPeerHeight(peerID, height)
if err != nil {
2019-07-14 23:50:58 +03:00
return schedulerErrorEv{peerID, err}
2019-07-12 14:13:53 +03:00
}
2019-07-14 23:50:58 +03:00
return Skip{}
2019-07-09 15:17:42 +02:00
}
2019-07-12 14:13:53 +03:00
type bcBlockResponseEv struct {
peerID p2p.ID
height int64
block *types.Block
msgSize int64
2019-07-09 15:17:42 +02:00
}
2019-07-14 23:50:58 +03:00
type scBlockReceivedEv struct {
peerID p2p.ID
}
func (sdr *Scheduler) handleBlockResponse(peerID p2p.ID, msg *bcBlockResponseEv, now time.Time) Event {
err := sdr.sc.touchPeer(peerID, now)
2019-07-12 14:13:53 +03:00
if err != nil {
2019-07-14 23:50:58 +03:00
return schedulerErrorEv{peerID, err}
2019-07-12 14:13:53 +03:00
}
2019-07-14 23:50:58 +03:00
err = sdr.sc.markReceived(peerID, msg.height, msg.msgSize, now)
2019-07-12 14:13:53 +03:00
if err != nil {
2019-07-14 23:50:58 +03:00
return schedulerErrorEv{peerID, err}
2019-07-12 14:13:53 +03:00
}
2019-07-14 23:50:58 +03:00
return scBlockReceivedEv{peerID}
2019-07-09 15:17:42 +02:00
}
2019-07-12 14:13:53 +03:00
type scFinishedEv struct{}
2019-07-09 15:17:42 +02:00
2019-07-14 23:50:58 +03:00
func (sdr *Scheduler) handleBlockProcessed(peerID p2p.ID, height int64) Event {
err := sdr.sc.markProcessed(height)
2019-07-12 14:13:53 +03:00
if err != nil {
2019-07-14 23:50:58 +03:00
return schedulerErrorEv{peerID, err}
2019-07-12 14:13:53 +03:00
}
if sdr.sc.allBlocksProcessed() {
2019-07-14 23:50:58 +03:00
return scFinishedEv{}
2019-07-12 14:13:53 +03:00
}
2019-07-14 23:50:58 +03:00
return Skip{}
2019-07-12 14:13:53 +03:00
}
2019-07-14 23:50:58 +03:00
func (sdr *Scheduler) handleBlockProcessError(peerID p2p.ID, height int64) Event {
2019-07-12 14:13:53 +03:00
// remove the peer
2019-07-14 23:50:58 +03:00
sdr.sc.removePeer(peerID)
2019-07-12 14:13:53 +03:00
// reSchdule all the blocks we are waiting
/*
XXX: This is wrong as we need to
foreach block where state != blockStateProcessed
state => blockStateNew
*/
2019-07-14 23:50:58 +03:00
sdr.sc.resetBlocks(peerID)
2019-07-12 14:13:53 +03:00
2019-07-14 23:50:58 +03:00
return Skip{}
2019-07-12 14:13:53 +03:00
}
2019-07-09 15:17:42 +02:00
2019-07-14 23:50:58 +03:00
func (sdr *Scheduler) handleTimeCheck(peerID p2p.ID) []Event {
2019-07-12 14:13:53 +03:00
// prune peers
// TODO
2019-07-09 15:17:42 +02:00
2019-07-12 14:13:53 +03:00
// produce new schedule
events := []scBlockRequestMessage{}
2019-07-14 23:50:58 +03:00
pendingBlocks := sdr.sc.numBlockInState(blockStatePending)
2019-07-12 14:13:53 +03:00
receivedBlocks := sdr.sc.numBlockInState(blockStateReceived)
todo := math.Min(sdr.targetPending-pendingBlocks, sdr.targetReceived-receivedBlocks)
for height := sdr.sc.minHeight(); height <= sdr.sc.maxHeight(); height++ {
if todo == 0 {
break
}
if sdr.sc.getStateAt(height) == blockStateNew {
allPeers := sdr.sc.getPeersAtHeight(height)
bestPeer := sdr.sc.selectPeer(allPeers)
err := sc.markPending(peerID, height)
if err != nil {
// TODO
}
events = append(events, scBlockRequestMessage{peerID: bestPeer.peerID, height: height})
todo--
}
}
return events
2019-07-09 15:17:42 +02:00
}