// 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 chain import ( corelog "dappco.re/go/core/log" "dappco.re/go/core/blockchain/p2p" levinpkg "dappco.re/go/core/p2p/node/levin" ) // LevinP2PConn adapts a Levin connection to the P2PConnection interface. type LevinP2PConn struct { conn *levinpkg.Connection peerHeight uint64 localSync p2p.CoreSyncData } // NewLevinP2PConn wraps a Levin connection for P2P sync. // peerHeight is obtained from the handshake CoreSyncData. // localSync is our local sync state sent in timed_sync responses. func NewLevinP2PConn(conn *levinpkg.Connection, peerHeight uint64, localSync p2p.CoreSyncData) *LevinP2PConn { return &LevinP2PConn{conn: conn, peerHeight: peerHeight, localSync: localSync} } // PeerHeight returns the remote peer's advertised chain height, // obtained during the Levin handshake. // // height := conn.PeerHeight() func (c *LevinP2PConn) PeerHeight() uint64 { return c.peerHeight } // handleMessage processes non-target messages received while waiting for // a specific response. It replies to timed_sync requests to keep the // connection alive. func (c *LevinP2PConn) handleMessage(hdr levinpkg.Header, data []byte) error { if hdr.Command == p2p.CommandTimedSync && hdr.ExpectResponse { // Respond to keep-alive. The daemon expects a timed_sync // response with our payload_data. resp := p2p.TimedSyncRequest{PayloadData: c.localSync} payload, err := resp.Encode() if err != nil { return corelog.E("LevinP2PConn.handleMessage", "encode timed_sync response", err) } if err := c.conn.WriteResponse(p2p.CommandTimedSync, payload, levinpkg.ReturnOK); err != nil { return corelog.E("LevinP2PConn.handleMessage", "write timed_sync response", err) } corelog.Info("p2p responded to timed_sync") return nil } // Silently skip other messages (new_block notifications, etc.) return nil } // RequestChain sends NOTIFY_REQUEST_CHAIN with our sparse block ID list // and returns the start height and block IDs from the peer's response. // // startHeight, blockIDs, err := conn.RequestChain(historyBytes) func (c *LevinP2PConn) RequestChain(blockIDs [][]byte) (uint64, [][]byte, error) { req := p2p.RequestChain{BlockIDs: blockIDs} payload, err := req.Encode() if err != nil { return 0, nil, corelog.E("LevinP2PConn.RequestChain", "encode request_chain", err) } // Send as notification (expectResponse=false) per CryptoNote protocol. if err := c.conn.WritePacket(p2p.CommandRequestChain, payload, false); err != nil { return 0, nil, corelog.E("LevinP2PConn.RequestChain", "write request_chain", err) } // Read until we get RESPONSE_CHAIN_ENTRY. for { hdr, data, err := c.conn.ReadPacket() if err != nil { return 0, nil, corelog.E("LevinP2PConn.RequestChain", "read response_chain", err) } if hdr.Command == p2p.CommandResponseChain { var resp p2p.ResponseChainEntry if err := resp.Decode(data); err != nil { return 0, nil, corelog.E("LevinP2PConn.RequestChain", "decode response_chain", err) } return resp.StartHeight, resp.BlockIDs, nil } if err := c.handleMessage(hdr, data); err != nil { return 0, nil, err } } } // RequestObjects sends NOTIFY_REQUEST_GET_OBJECTS for the given block // hashes and returns the raw block and transaction blobs. // // entries, err := conn.RequestObjects(batchHashes) func (c *LevinP2PConn) RequestObjects(blockHashes [][]byte) ([]BlockBlobEntry, error) { req := p2p.RequestGetObjects{Blocks: blockHashes} payload, err := req.Encode() if err != nil { return nil, corelog.E("LevinP2PConn.RequestObjects", "encode request_get_objects", err) } if err := c.conn.WritePacket(p2p.CommandRequestObjects, payload, false); err != nil { return nil, corelog.E("LevinP2PConn.RequestObjects", "write request_get_objects", err) } // Read until we get RESPONSE_GET_OBJECTS. for { hdr, data, err := c.conn.ReadPacket() if err != nil { return nil, corelog.E("LevinP2PConn.RequestObjects", "read response_get_objects", err) } if hdr.Command == p2p.CommandResponseObjects { var resp p2p.ResponseGetObjects if err := resp.Decode(data); err != nil { return nil, corelog.E("LevinP2PConn.RequestObjects", "decode response_get_objects", err) } entries := make([]BlockBlobEntry, len(resp.Blocks)) for i, b := range resp.Blocks { entries[i] = BlockBlobEntry{Block: b.Block, Txs: b.Txs} } return entries, nil } if err := c.handleMessage(hdr, data); err != nil { return nil, err } } }