func (sc *scheduler) markReceived(peerID p2p.ID, height int64, size int64, now time.Time) error {
	peer := sc.peers[peerID]
	if state := sc.getStateAtHeight(height); state != blockStatePending || sc.pendingBlocks[height] != peerID {
		return fmt.Errorf("received block %d from peer %s without being requested", height, peerID)
	}
	pendingTime, ok := sc.pendingTime[height]
	if !ok || now.Sub(pendingTime) <= 0 {
		return fmt.Errorf("clock error: block %d received at %s but requested at %s",
			height, pendingTime, now)
	}
	peer.lastRate = size / now.Sub(pendingTime).Nanoseconds()
	sc.setStateAtHeight(height, blockStateReceived)
	delete(sc.pendingBlocks, height)
	delete(sc.pendingTime, height)
	sc.receivedBlocks[height] = peerID
	return nil
}
CONTRACT: peer exists and in Ready state.
package v2
import (
	"bytes"
	"errors"
	"fmt"
	"math"
	"sort"
	"time"
	"github.com/line/ostracon/p2p"
	"github.com/line/ostracon/types"
)
type scFinishedEv struct {
	priorityNormal
	reason string
}
func (e scFinishedEv) String() string {
	return fmt.Sprintf("scFinishedEv{%v}", e.reason)
}
type scBlockRequest struct {
	priorityNormal
	peerID p2p.ID
	height int64
}
func (e scBlockRequest) String() string {
	return fmt.Sprintf("scBlockRequest{%d from %v}", e.height, e.peerID)
}
type scBlockReceived struct {
	priorityNormal
	peerID p2p.ID
	block  *types.Block
}
func (e scBlockReceived) String() string {
	return fmt.Sprintf("scBlockReceived{%d#%X from %v}", e.block.Height, e.block.Hash(), e.peerID)
}
type scPeerError struct {
	priorityHigh
	peerID p2p.ID
	reason error
}
func (e scPeerError) String() string {
	return fmt.Sprintf("scPeerError{%v errored with %v}", e.peerID, e.reason)
}
type scPeersPruned struct {
	priorityHigh
	peers []p2p.ID
}
func (e scPeersPruned) String() string {
	return fmt.Sprintf("scPeersPruned{%v}", e.peers)
}
type scSchedulerFail struct {
	priorityHigh
	reason error
}
func (e scSchedulerFail) String() string {
	return fmt.Sprintf("scSchedulerFail{%v}", e.reason)
}
type blockState int
const (
	blockStateUnknown   blockState = iota + 1  
	blockStateNew                              
	blockStatePending                          
	blockStateReceived                         
	blockStateProcessed                        
)
func (e blockState) String() string {
	switch e {
	case blockStateUnknown:
		return "Unknown"
	case blockStateNew:
		return "New"
	case blockStatePending:
		return "Pending"
	case blockStateReceived:
		return "Received"
	case blockStateProcessed:
		return "Processed"
	default:
		return fmt.Sprintf("invalid blockState: %d", e)
	}
}
type peerState int
const (
	peerStateNew = iota + 1
	peerStateReady
	peerStateRemoved
)
func (e peerState) String() string {
	switch e {
	case peerStateNew:
		return "New"
	case peerStateReady:
		return "Ready"
	case peerStateRemoved:
		return "Removed"
	default:
		panic(fmt.Sprintf("unknown peerState: %d", e))
	}
}
type scPeer struct {
	peerID p2p.ID
	state peerState
	base        int64  
	height      int64  
	lastTouched time.Time
	lastRate    int64  
}
func (p scPeer) String() string {
	return fmt.Sprintf("{state %v, base %d, height %d, lastTouched %v, lastRate %d, id %v}",
		p.state, p.base, p.height, p.lastTouched, p.lastRate, p.peerID)
}
func newScPeer(peerID p2p.ID) *scPeer {
	return &scPeer{
		peerID:      peerID,
		state:       peerStateNew,
		base:        -1,
		height:      -1,
		lastTouched: time.Time{},
	}
}
type scheduler struct {
	initHeight int64
	height int64
	lastAdvance time.Time
	syncTimeout time.Duration
	peers       map[p2p.ID]*scPeer
	peerTimeout time.Duration  
	minRecvRate int64          
	targetPending int
	blockStates map[int64]blockState
	pendingBlocks map[int64]p2p.ID
	pendingTime map[int64]time.Time
	receivedBlocks map[int64]p2p.ID
}
func (sc scheduler) String() string {
	return fmt.Sprintf("ih: %d, bst: %v, peers: %v, pblks: %v, ptm %v, rblks: %v",
		sc.initHeight, sc.blockStates, sc.peers, sc.pendingBlocks, sc.pendingTime, sc.receivedBlocks)
}
func newScheduler(initHeight int64, startTime time.Time) *scheduler {
	sc := scheduler{
		initHeight:     initHeight,
		lastAdvance:    startTime,
		syncTimeout:    60 * time.Second,
		height:         initHeight,
		blockStates:    make(map[int64]blockState),
		peers:          make(map[p2p.ID]*scPeer),
		pendingBlocks:  make(map[int64]p2p.ID),
		pendingTime:    make(map[int64]time.Time),
		receivedBlocks: make(map[int64]p2p.ID),
		targetPending:  10,                
		peerTimeout:    15 * time.Second,  
		minRecvRate:    0,                 
	}
	return &sc
}
func (sc *scheduler) ensurePeer(peerID p2p.ID) *scPeer {
	if _, ok := sc.peers[peerID]; !ok {
		sc.peers[peerID] = newScPeer(peerID)
	}
	return sc.peers[peerID]
}
func (sc *scheduler) touchPeer(peerID p2p.ID, time time.Time) error {
	peer, ok := sc.peers[peerID]
	if !ok {
		return fmt.Errorf("couldn't find peer %s", peerID)
	}
	if peer.state != peerStateReady {
		return fmt.Errorf("tried to touch peer in state %s, must be Ready", peer.state)
	}
	peer.lastTouched = time
	return nil
}
func (sc *scheduler) removePeer(peerID p2p.ID) {
	peer, ok := sc.peers[peerID]
	if !ok {
		return
	}
	if peer.state == peerStateRemoved {
		return
	}
	for height, pendingPeerID := range sc.pendingBlocks {
		if pendingPeerID == peerID {
			sc.setStateAtHeight(height, blockStateNew)
			delete(sc.pendingTime, height)
			delete(sc.pendingBlocks, height)
		}
	}
	for height, rcvPeerID := range sc.receivedBlocks {
		if rcvPeerID == peerID {
			sc.setStateAtHeight(height, blockStateNew)
			delete(sc.receivedBlocks, height)
		}
	}
	peer.state = peerStateRemoved
	maxPeerHeight := int64(0)
	for _, otherPeer := range sc.peers {
		if otherPeer.state != peerStateReady {
			continue
		}
		if otherPeer.peerID != peer.peerID && otherPeer.height > maxPeerHeight {
			maxPeerHeight = otherPeer.height
		}
	}
	for h := range sc.blockStates {
		if h > maxPeerHeight {
			delete(sc.blockStates, h)
		}
	}
}
func (sc *scheduler) addNewBlocks() {
	if len(sc.blockStates) >= sc.targetPending {
		return
	}
	for i := sc.height; i < int64(sc.targetPending)+sc.height; i++ {
		if i > sc.maxHeight() {
			break
		}
		if sc.getStateAtHeight(i) == blockStateUnknown {
			sc.setStateAtHeight(i, blockStateNew)
		}
	}
}
func (sc *scheduler) setPeerRange(peerID p2p.ID, base int64, height int64) error {
	peer := sc.ensurePeer(peerID)
	if peer.state == peerStateRemoved {
		return nil  
	}
	if height < peer.height {
		sc.removePeer(peerID)
		return fmt.Errorf("cannot move peer height lower. from %d to %d", peer.height, height)
	}
	if base > height {
		sc.removePeer(peerID)
		return fmt.Errorf("cannot set peer base higher than its height")
	}
	peer.base = base
	peer.height = height
	peer.state = peerStateReady
	sc.addNewBlocks()
	return nil
}
func (sc *scheduler) getStateAtHeight(height int64) blockState {
	if height < sc.height {
		return blockStateProcessed
	} else if state, ok := sc.blockStates[height]; ok {
		return state
	} else {
		return blockStateUnknown
	}
}
func (sc *scheduler) getPeersWithHeight(height int64) []p2p.ID {
	peers := make([]p2p.ID, 0)
	for _, peer := range sc.peers {
		if peer.state != peerStateReady {
			continue
		}
		if peer.base <= height && peer.height >= height {
			peers = append(peers, peer.peerID)
		}
	}
	return peers
}
func (sc *scheduler) prunablePeers(peerTimout time.Duration, minRecvRate int64, now time.Time) []p2p.ID {
	prunable := make([]p2p.ID, 0)
	for peerID, peer := range sc.peers {
		if peer.state != peerStateReady {
			continue
		}
		if now.Sub(peer.lastTouched) > peerTimout || peer.lastRate < minRecvRate {
			prunable = append(prunable, peerID)
		}
	}
	sort.Sort(PeerByID(prunable))
	return prunable
}
func (sc *scheduler) setStateAtHeight(height int64, state blockState) {
	sc.blockStates[height] = state
}