neoneo-go/pkg/syncmanager/syncman.go
2019-02-25 22:44:14 +00:00

152 lines
4.7 KiB
Go

// The syncmanager will use a modified verison of the initial block download in bitcoin
// Seen here: https://en.bitcoinwiki.org/wiki/Bitcoin_Core_0.11_(ch_5):_Initial_Block_Download
// MovingWindow is a desired featured from the original codebase
package syncmanager
import (
"fmt"
"github.com/CityOfZion/neo-go/pkg/peermanager"
"github.com/CityOfZion/neo-go/pkg/blockchain"
"github.com/CityOfZion/neo-go/pkg/peer"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
var (
// This is the maximum amount of inflight objects that we would like to have
// Number taken from original codebase
maxBlockRequest = 1024
// This is the maximum amount of blocks that we will ask for from a single peer
// Number taken from original codebase
maxBlockRequestPerPeer = 16
)
type Syncmanager struct {
pmgr *peermanager.PeerMgr
Mode int // 1 = headersFirst, 2 = Blocks, 3 = Maintain
chain *blockchain.Chain
headers []util.Uint256
inflightBlockReqs map[util.Uint256]*peer.Peer // when we send a req for block, we will put hash in here, along with peer who we requested it from
}
// New will setup the syncmanager with the required
// parameters
func New(cfg Config) *Syncmanager {
return &Syncmanager{
peermanager.New(),
1,
cfg.Chain,
[]util.Uint256{},
make(map[util.Uint256]*peer.Peer, 2000),
}
}
func (s *Syncmanager) AddPeer(peer *peer.Peer) error {
return s.pmgr.AddPeer(peer)
}
func (s *Syncmanager) OnHeaders(p *peer.Peer, msg *payload.HeadersMessage) {
fmt.Println("Sync manager On Headers called")
// On receipt of Headers
// check what mode we are in
// HeadersMode, we check if there is 2k. If so call again. If not then change mode into BlocksOnly
if s.Mode == 1 {
err := s.HeadersFirstMode(p, msg)
if err != nil {
fmt.Println("Error re blocks", err)
return // We should custom name error so, that we can do something on WrongHash Error, Peer disconnect error
}
return
}
}
func (s *Syncmanager) HeadersFirstMode(p *peer.Peer, msg *payload.HeadersMessage) error {
fmt.Println("Headers first mode")
// Validate Headers
err := s.chain.ValidateHeaders(msg)
if err != nil {
// Re-request headers from a different peer
s.pmgr.Disconnect(p)
fmt.Println("Error Validating headers", err)
return err
}
// Add Headers into db
err = s.chain.AddHeaders(msg)
if err != nil {
// Try addding them into the db again?
// Since this is simply a db insert, any problems here means trouble
//TODO(KEV) : Should we Switch off system or warn the user that the system is corrupted?
fmt.Println("Error Adding headers", err)
//TODO: Batching is not yet implemented,
// So here we would need to remove headers which have been added
// from the slice
return err
}
// Add header hashes into slice
// Requets first batch of blocks here
var hashes []util.Uint256
for _, header := range msg.Headers {
hashes = append(hashes, header.Hash)
}
s.headers = append(s.headers, hashes...)
if len(msg.Headers) == 2*1e3 { // should be less than 2000, leave it as this for tests
fmt.Println("Switching to BlocksOnly Mode")
s.Mode = 2 // switch to BlocksOnly. XXX: because HeadersFirst is not in parallel, no race condition here.
return s.RequestMoreBlocks()
}
lastHeader := msg.Headers[len(msg.Headers)-1]
_, err = s.pmgr.RequestHeaders(lastHeader.Hash)
return err
}
func (s *Syncmanager) RequestMoreBlocks() error {
var blockReq []util.Uint256
var reqAmount int
if len(s.headers) >= maxBlockRequestPerPeer {
reqAmount = maxBlockRequestPerPeer
blockReq = s.headers[:reqAmount]
} else {
reqAmount = len(s.headers)
blockReq = s.headers[:reqAmount]
}
peer, err := s.pmgr.RequestBlocks(blockReq)
if err != nil { // This could happen if the peermanager has no valid peers to connect to. We should wait a bit and re-request
return err // alternatively we could make RequestBlocks blocking, then make sure it is not triggered when a block is received
}
//XXX: Possible race condition, between us requesting the block and adding it to
// the inflight block map? Give that node a medal.
for _, hash := range s.headers {
s.inflightBlockReqs[hash] = peer
}
s.headers = s.headers[reqAmount:]
// NONONO: Here we do not pass all of the hashes to peermanager because
// it is not the peermanagers responsibility to mange inflight blocks
return err
}
// OnBlock receives a block from a peer, then passes it to the blockchain to process.
// For now we will only use this simple setup, to allow us to test the other parts of the system.
// See Issue #24
func (s *Syncmanager) OnBlock(p *peer.Peer, msg *payload.BlockMessage) {
err := s.chain.AddBlock(msg)
if err != nil {
// Put headers back in front of queue to fetch block for.
fmt.Println("Block had an error", err)
}
}