github.com/aergoio/aergo@v1.3.1/syncer/finder.go (about) 1 package syncer 2 3 import ( 4 "bytes" 5 "github.com/aergoio/aergo/p2p/p2putil" 6 "sync" 7 "time" 8 9 "github.com/aergoio/aergo/chain" 10 "github.com/aergoio/aergo/internal/enc" 11 "github.com/aergoio/aergo/message" 12 "github.com/aergoio/aergo/pkg/component" 13 "github.com/aergoio/aergo/types" 14 "github.com/pkg/errors" 15 ) 16 17 type Finder struct { 18 compRequester component.IComponentRequester //for communicate with other service 19 chain types.ChainAccessor 20 21 anchorCh chan chain.ChainAnchor 22 lScanCh chan *types.BlockInfo 23 fScanCh chan *message.GetHashByNoRsp 24 25 quitCh chan interface{} 26 27 lastAnchor []byte //point last block during lightscan 28 ctx types.SyncContext 29 30 dfltTimeout time.Duration 31 32 cfg *SyncerConfig 33 34 isRunning bool 35 waitGroup *sync.WaitGroup 36 } 37 38 type FinderResult struct { 39 ancestor *types.BlockInfo 40 err error 41 } 42 43 var ( 44 ErrFinderQuit = errors.New("sync finder quit") 45 ErrorGetSyncAncestorTimeout = errors.New("timeout for GetSyncAncestor") 46 ErrFinderTimeout = errors.New("Finder timeout") 47 ErrAlreadySyncDone = errors.New("Already sync done") 48 ) 49 50 func newFinder(ctx *types.SyncContext, compRequester component.IComponentRequester, chain types.ChainAccessor, cfg *SyncerConfig) *Finder { 51 finder := &Finder{ctx: *ctx, compRequester: compRequester, chain: chain, cfg: cfg} 52 53 finder.dfltTimeout = cfg.fetchTimeOut 54 finder.quitCh = make(chan interface{}) 55 finder.lScanCh = make(chan *types.BlockInfo) 56 finder.lScanCh = make(chan *types.BlockInfo) 57 finder.fScanCh = make(chan *message.GetHashByNoRsp) 58 59 return finder 60 } 61 62 //TODO refactoring: move logic to SyncContext (sync Object) 63 func (finder *Finder) start() { 64 finder.waitGroup = &sync.WaitGroup{} 65 finder.waitGroup.Add(1) 66 finder.isRunning = true 67 68 run := func() { 69 var ancestor *types.BlockInfo 70 var err error 71 72 defer RecoverSyncer(NameFinder, finder.GetSeq(), finder.compRequester, func() { finder.waitGroup.Done() }) 73 74 logger.Debug().Msg("start to find common ancestor") 75 76 //1. light sync 77 // gather summary of my chain nodes, runTask searching ancestor to remote node 78 ancestor, err = finder.lightscan() 79 80 //2. heavy sync 81 // full binary search in my chain 82 if ancestor == nil && err == nil { 83 ancestor, err = finder.fullscan() 84 } 85 86 if err != nil { 87 logger.Debug().Msg("quit finder") 88 stopSyncer(finder.compRequester, finder.GetSeq(), NameFinder, err) 89 return 90 } 91 92 finder.compRequester.TellTo(message.SyncerSvc, &message.FinderResult{Seq: finder.GetSeq(), Ancestor: ancestor, Err: nil}) 93 logger.Info().Msg("stopped finder successfully") 94 } 95 96 go run() 97 } 98 99 func (finder *Finder) stop() { 100 if finder == nil { 101 return 102 } 103 104 logger.Info().Msg("finder stop#1") 105 106 if finder.isRunning { 107 logger.Debug().Msg("finder closed quitChannel") 108 109 close(finder.quitCh) 110 finder.isRunning = false 111 } 112 113 finder.waitGroup.Wait() 114 115 logger.Info().Msg("finder stop#2") 116 } 117 118 func (finder *Finder) GetSeq() uint64 { 119 return finder.ctx.Seq 120 } 121 122 func (finder *Finder) GetHashByNoRsp(rsp *message.GetHashByNoRsp) { 123 finder.fScanCh <- rsp 124 } 125 126 func (finder *Finder) lightscan() (*types.BlockInfo, error) { 127 if finder.cfg.useFullScanOnly { 128 finder.ctx.LastAnchor = finder.ctx.BestNo + 1 129 return nil, nil 130 } 131 132 var ancestor *types.BlockInfo 133 134 anchors, err := finder.getAnchors() 135 if err != nil { 136 return nil, err 137 } 138 139 ancestor, err = finder.getAncestor(anchors) 140 141 if ancestor == nil { 142 logger.Debug().Msg("not found ancestor in lightscan") 143 } else { 144 logger.Info().Str("hash", enc.ToString(ancestor.Hash)).Uint64("no", ancestor.No).Msg("find ancestor in lightscan") 145 146 if ancestor.No >= finder.ctx.TargetNo { 147 logger.Info().Msg("already synchronized") 148 return nil, ErrAlreadySyncDone 149 } 150 } 151 152 return ancestor, err 153 } 154 155 func (finder *Finder) getAnchors() ([][]byte, error) { 156 result, err := finder.compRequester.RequestToFutureResult(message.ChainSvc, &message.GetAnchors{finder.GetSeq()}, finder.dfltTimeout, "Finder/getAnchors") 157 if err != nil { 158 logger.Error().Err(err).Msg("failed to get anchors") 159 return nil, err 160 } 161 162 anchors := result.(message.GetAnchorsRsp).Hashes 163 if len(anchors) > 0 { 164 finder.ctx.LastAnchor = result.(message.GetAnchorsRsp).LastNo 165 } 166 167 logger.Info().Str("start", enc.ToString(anchors[0])).Int("count", len(anchors)).Uint64("last", finder.ctx.LastAnchor).Msg("get anchors from chain") 168 169 return anchors, nil 170 } 171 172 func (finder *Finder) getAncestor(anchors [][]byte) (*types.BlockInfo, error) { 173 // send remote Peer 174 logger.Debug().Str("peer", p2putil.ShortForm(finder.ctx.PeerID)).Msg("send GetAncestor message to peer") 175 finder.compRequester.TellTo(message.P2PSvc, &message.GetSyncAncestor{Seq: finder.GetSeq(), ToWhom: finder.ctx.PeerID, Hashes: anchors}) 176 177 timer := time.NewTimer(finder.dfltTimeout) 178 179 for { 180 select { 181 case result := <-finder.lScanCh: 182 //valid response 183 if result == nil || result.No >= finder.ctx.LastAnchor { 184 return result, nil 185 } 186 case <-timer.C: 187 logger.Error().Float64("sec", finder.dfltTimeout.Seconds()).Msg("get ancestor response timeout") 188 return nil, ErrorGetSyncAncestorTimeout 189 case <-finder.quitCh: 190 return nil, ErrFinderQuit 191 } 192 } 193 } 194 195 //TODO binary search scan 196 func (finder *Finder) fullscan() (*types.BlockInfo, error) { 197 logger.Debug().Msg("finder fullscan") 198 199 ancestor, err := finder.binarySearch(0, finder.ctx.LastAnchor-1) 200 if err != nil { 201 logger.Error().Err(err).Msg("finder fullscan failed") 202 return nil, err 203 } 204 205 if ancestor == nil { 206 logger.Info().Msg("failed to search ancestor in fullscan") 207 } else { 208 logger.Info().Uint64("no", ancestor.No).Str("hash", enc.ToString(ancestor.Hash)).Msg("find ancestor in fullscan") 209 } 210 211 return ancestor, err 212 } 213 214 func (finder *Finder) binarySearch(left uint64, right uint64) (*types.BlockInfo, error) { 215 var mid uint64 216 var lastMatch *types.BlockInfo 217 for left <= right { 218 // get median 219 mid = (left + right) / 2 220 // request hash of median from remote 221 logger.Debug().Uint64("left", left).Uint64("right", right).Uint64("mid", mid).Msg("finder scan") 222 223 midHash, err := finder.chain.GetHashByNo(mid) 224 if err != nil { 225 logger.Error().Uint64("no", mid).Err(err).Msg("finder failed to get local hash") 226 return nil, err 227 } 228 229 exist, err := finder.hasSameHash(mid, midHash) 230 if err != nil { 231 logger.Error().Err(err).Msg("finder failed to check remote hash") 232 return nil, err 233 } 234 235 if exist { 236 left = mid + 1 237 238 lastMatch = &types.BlockInfo{Hash: midHash, No: mid} 239 logger.Debug().Uint64("mid", mid).Msg("matched") 240 } else { 241 if mid == 0 { 242 break 243 } else { 244 right = mid - 1 245 } 246 } 247 } 248 249 return lastMatch, nil 250 } 251 252 func (finder *Finder) hasSameHash(no types.BlockNo, localHash []byte) (bool, error) { 253 finder.compRequester.TellTo(message.P2PSvc, &message.GetHashByNo{Seq: finder.GetSeq(), ToWhom: finder.ctx.PeerID, BlockNo: no}) 254 255 recvHashRsp := func() (*message.GetHashByNoRsp, error) { 256 timer := time.NewTimer(finder.dfltTimeout) 257 258 for { 259 select { 260 case result := <-finder.fScanCh: 261 return result, result.Err 262 case <-timer.C: 263 logger.Error().Float64("sec", finder.dfltTimeout.Seconds()).Msg("finder get response timeout") 264 return nil, ErrFinderTimeout 265 case <-finder.quitCh: 266 return nil, ErrFinderQuit 267 } 268 } 269 } 270 271 rspMsg, err := recvHashRsp() 272 if err != nil || rspMsg.BlockHash == nil { 273 logger.Error().Err(err).Msg("finder failed to get remote hash") 274 return false, err 275 } 276 277 if bytes.Equal(localHash, rspMsg.BlockHash) { 278 logger.Debug().Uint64("no", no).Msg("exist hash") 279 return true, nil 280 } else { 281 logger.Debug().Uint64("no", no).Msg("not exist hash") 282 return false, nil 283 } 284 }