The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
woop/api/service/legacysync/epoch_syncing.go

224 lines
7.0 KiB

package legacysync
import (
"fmt"
"sync"
"time"
"github.com/Workiva/go-datastructures/queue"
"github.com/woop-chain/woop/consensus"
"github.com/woop-chain/woop/core"
"github.com/woop-chain/woop/core/types"
"github.com/woop-chain/woop/internal/utils"
"github.com/woop-chain/woop/p2p"
"github.com/woop-chain/woop/shard"
"github.com/pkg/errors"
libp2p_peer "github.com/libp2p/go-libp2p/core/peer"
)
type EpochSync struct {
beaconChain blockChain
selfip string
selfport string
selfPeerHash [20]byte // hash of ip and address combination
commonBlocks map[int]*types.Block
lastMileBlocks []*types.Block // last mile blocks to catch up with the consensus
syncConfig *SyncConfig
isExplorer bool
stateSyncTaskQueue *queue.Queue
syncMux sync.Mutex
lastMileMux sync.Mutex
syncStatus syncStatus
}
// GetSyncStatus get the last sync status for other modules (E.g. RPC, explorer).
// If the last sync result is not expired, return the sync result immediately.
// If the last result is expired, ask the remote DNS nodes for latest height and return the result.
func (ss *EpochSync) GetSyncStatus() SyncCheckResult {
return ss.syncStatus.Get(func() SyncCheckResult {
return ss.isSynchronized(false)
})
}
// isSynchronized query the remote DNS node for the latest height to check what is the current
// sync status
func (ss *EpochSync) isSynchronized(_ bool) SyncCheckResult {
if ss.syncConfig == nil {
return SyncCheckResult{} // If syncConfig is not instantiated, return not in sync
}
otherHeight1, errMaxHeight := getMaxPeerHeight(ss.syncConfig)
if errMaxHeight != nil {
utils.Logger().Error().
Uint64("OtherHeight", otherHeight1).
Int("Peers count", ss.syncConfig.PeersCount()).
Err(errMaxHeight).
Msg("[EPOCHSYNC] No peers for get height")
return SyncCheckResult{}
}
curEpoch := ss.beaconChain.CurrentBlock().Epoch().Uint64()
otherEpoch := shard.Schedule.CalcEpochNumber(otherHeight1).Uint64()
normalizedOtherEpoch := otherEpoch - 1
inSync := curEpoch == normalizedOtherEpoch
epochDiff := normalizedOtherEpoch - curEpoch
if normalizedOtherEpoch < curEpoch {
epochDiff = 0
}
utils.Logger().Info().
Uint64("OtherEpoch", otherEpoch).
Uint64("CurrentEpoch", curEpoch).
Msg("[EPOCHSYNC] Checking sync status")
return SyncCheckResult{
IsSynchronized: inSync,
OtherHeight: otherHeight1,
HeightDiff: epochDiff,
}
}
// GetActivePeerNumber returns the number of active peers
func (ss *EpochSync) GetActivePeerNumber() int {
if ss.syncConfig == nil {
return 0
}
return ss.syncConfig.PeersCount()
}
// SyncLoop will keep syncing with peers until catches up
func (ss *EpochSync) SyncLoop(bc core.BlockChain, consensus *consensus.Consensus) time.Duration {
return time.Duration(syncLoop(bc, ss.syncConfig)) * time.Second
}
func syncLoop(bc core.BlockChain, syncConfig *SyncConfig) (timeout int) {
isBeacon := bc.ShardID() == shard.BeaconChainShardID
maxHeight, errMaxHeight := getMaxPeerHeight(syncConfig)
if errMaxHeight != nil {
utils.Logger().Info().
Msgf("[EPOCHSYNC] No peers to sync (isBeacon: %t, ShardID: %d, peersCount: %d)",
isBeacon, bc.ShardID(), syncConfig.PeersCount())
return 10
}
for {
curEpoch := bc.CurrentBlock().Epoch().Uint64()
otherEpoch := shard.Schedule.CalcEpochNumber(maxHeight).Uint64()
if otherEpoch == curEpoch+1 {
utils.Logger().Info().
Msgf("[EPOCHSYNC] Node is now IN SYNC! (isBeacon: %t, ShardID: %d, otherEpoch: %d, currentEpoch: %d, peersCount: %d)",
isBeacon, bc.ShardID(), otherEpoch, curEpoch, syncConfig.PeersCount())
return 60
}
if otherEpoch < curEpoch {
for _, peerCfg := range syncConfig.GetPeers() {
syncConfig.RemovePeer(peerCfg, fmt.Sprintf("[EPOCHSYNC]: current height is higher than others, remove peers: %s", peerCfg.String()))
}
return 2
}
utils.Logger().Info().
Msgf("[EPOCHSYNC] Node is OUT OF SYNC (isBeacon: %t, ShardID: %d, otherEpoch: %d, currentEpoch: %d, peers count %d)",
isBeacon, bc.ShardID(), otherEpoch, curEpoch, syncConfig.PeersCount())
var heights []uint64
loopEpoch := curEpoch + 1
for len(heights) < int(SyncLoopBatchSize) {
if loopEpoch >= otherEpoch {
break
}
heights = append(heights, shard.Schedule.EpochLastBlock(loopEpoch))
loopEpoch = loopEpoch + 1
}
if len(heights) == 0 {
// We currently on top.
return 10
}
err := ProcessStateSync(syncConfig, heights, bc)
if err != nil {
if errors.Is(err, core.ErrKnownBlock) {
return 10
}
utils.Logger().Error().Err(err).
Msgf("[EPOCHSYNC] ProcessStateSync failed (isBeacon: %t, ShardID: %d, otherEpoch: %d, currentEpoch: %d)",
isBeacon, bc.ShardID(), otherEpoch, curEpoch)
return 2
}
}
}
// ProcessStateSync processes state sync from the blocks received but not yet processed so far
func ProcessStateSync(syncConfig *SyncConfig, heights []uint64, bc core.BlockChain) error {
var payload [][]byte
var peerCfg *SyncPeerConfig
peers := syncConfig.GetPeers()
if len(peers) == 0 {
return errors.New("no peers to sync")
}
for index, peerConfig := range peers {
resp := peerConfig.GetClient().GetBlocksByHeights(heights)
if resp == nil {
syncConfig.RemovePeer(peerConfig, fmt.Sprintf("[EPOCHSYNC]: no response from peer: #%d %s, count %d", index, peerConfig.String(), len(peers)))
continue
}
if len(resp.Payload) == 0 {
syncConfig.RemovePeer(peerConfig, fmt.Sprintf("[EPOCHSYNC]: empty payload response from peer: #%d %s, count %d", index, peerConfig.String(), len(peers)))
continue
}
payload = resp.Payload
peerCfg = peerConfig
break
}
if len(payload) == 0 {
return errors.Errorf("empty payload: no blocks were returned by GetBlocksByHeights for all peers, currentPeersCount %d", syncConfig.PeersCount())
}
err := processWithPayload(payload, bc)
if err != nil {
// Assume that node sent us invalid data.
syncConfig.RemovePeer(peerCfg, fmt.Sprintf("[EPOCHSYNC]: failed to process with payload from peer: %s", err.Error()))
utils.Logger().Error().Err(err).
Msgf("[EPOCHSYNC] Removing peer %s for invalid data", peerCfg.String())
return err
}
return nil
}
func processWithPayload(payload [][]byte, bc core.BlockChain) error {
decoded := make([]*types.Block, 0, len(payload))
for idx, blockBytes := range payload {
block, err := RlpDecodeBlockOrBlockWithSig(blockBytes)
if err != nil {
return errors.Wrap(err, "failed decode")
}
if !block.IsLastBlockInEpoch() {
return errors.Errorf("block is not last index %d hash %s", idx, block.Hash().Hex())
}
decoded = append(decoded, block)
}
for _, block := range decoded {
_, err := bc.InsertChain([]*types.Block{block}, true)
switch {
case errors.Is(err, core.ErrKnownBlock):
continue
case err != nil:
return err
default:
}
}
return nil
}
// CreateSyncConfig creates SyncConfig for StateSync object.
func (ss *EpochSync) CreateSyncConfig(peers []p2p.Peer, shardID uint32, selfPeerID libp2p_peer.ID, waitForEachPeerToConnect bool) error {
var err error
ss.syncConfig, err = createSyncConfig(ss.syncConfig, peers, shardID, selfPeerID, waitForEachPeerToConnect)
return err
}