go-blockchain/sync_loop.go
Virgil 21c5d49ef9
Some checks are pending
Security Scan / security (push) Waiting to run
Test / Test (push) Waiting to run
fix(sync): validate peers and persist HTLC spends
Centralise handshake response validation so outbound sync checks both network identity and minimum peer build version through the p2p layer. Also record HTLC key images as spent during block processing, matching the HF1 input semantics and preventing those spends from being omitted from chain state.

Co-Authored-By: Charon <charon@lethean.io>
2026-04-04 19:01:07 +00:00

117 lines
3 KiB
Go

// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2
package blockchain
import (
"context"
"crypto/rand"
"encoding/binary"
"fmt"
"log"
"net"
"time"
coreerr "dappco.re/go/core/log"
"dappco.re/go/core/blockchain/chain"
"dappco.re/go/core/blockchain/config"
"dappco.re/go/core/blockchain/p2p"
levin "dappco.re/go/core/p2p/node/levin"
)
func runChainSyncLoop(ctx context.Context, blockchain *chain.Chain, chainConfig *config.ChainConfig, hardForks []config.HardFork, seed string) {
opts := chain.SyncOptions{
VerifySignatures: false,
Forks: hardForks,
}
for {
select {
case <-ctx.Done():
return
default:
}
if err := runChainSyncOnce(ctx, blockchain, chainConfig, opts, seed); err != nil {
log.Printf("sync: %v (retrying in 10s)", err)
select {
case <-ctx.Done():
return
case <-time.After(10 * time.Second):
}
continue
}
select {
case <-ctx.Done():
return
case <-time.After(30 * time.Second):
}
}
}
func runChainSyncOnce(ctx context.Context, blockchain *chain.Chain, chainConfig *config.ChainConfig, opts chain.SyncOptions, seed string) error {
conn, err := net.DialTimeout("tcp", seed, 10*time.Second)
if err != nil {
return coreerr.E("runChainSyncOnce", fmt.Sprintf("dial %s", seed), err)
}
defer conn.Close()
levinConn := levin.NewConnection(conn)
var peerIDBytes [8]byte
rand.Read(peerIDBytes[:])
peerID := binary.LittleEndian.Uint64(peerIDBytes[:])
localHeight, _ := blockchain.Height()
handshakeReq := p2p.HandshakeRequest{
NodeData: p2p.NodeData{
NetworkID: chainConfig.NetworkID,
PeerID: peerID,
LocalTime: time.Now().Unix(),
MyPort: 0,
},
PayloadData: p2p.CoreSyncData{
CurrentHeight: localHeight,
ClientVersion: config.ClientVersion,
NonPruningMode: true,
},
}
payload, err := p2p.EncodeHandshakeRequest(&handshakeReq)
if err != nil {
return coreerr.E("runChainSyncOnce", "encode handshake", err)
}
if err := levinConn.WritePacket(p2p.CommandHandshake, payload, true); err != nil {
return coreerr.E("runChainSyncOnce", "write handshake", err)
}
hdr, data, err := levinConn.ReadPacket()
if err != nil {
return coreerr.E("runChainSyncOnce", "read handshake", err)
}
if hdr.Command != uint32(p2p.CommandHandshake) {
return coreerr.E("runChainSyncOnce", fmt.Sprintf("unexpected command %d", hdr.Command), nil)
}
var handshakeResp p2p.HandshakeResponse
if err := handshakeResp.Decode(data); err != nil {
return coreerr.E("runChainSyncOnce", "decode handshake", err)
}
if err := p2p.ValidateHandshakeResponse(&handshakeResp, chainConfig.NetworkID, chainConfig.IsTestnet); err != nil {
return coreerr.E("runChainSyncOnce", "validate handshake", err)
}
localSync := p2p.CoreSyncData{
CurrentHeight: localHeight,
ClientVersion: config.ClientVersion,
NonPruningMode: true,
}
p2pConn := chain.NewLevinP2PConn(levinConn, handshakeResp.PayloadData.CurrentHeight, localSync)
return blockchain.P2PSync(ctx, p2pConn, opts)
}