Skip to content

Commit 3cce387

Browse files
committed
Stream legacy blocks straight to legacy without deserialization
1 parent 4e8b340 commit 3cce387

File tree

3 files changed

+148
-4
lines changed

3 files changed

+148
-4
lines changed

services/legacy/peer_server.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,9 +1387,12 @@ func (s *server) pushBlockMsg(sp *serverPeer, hash *chainhash.Hash, doneChan cha
13871387
_ = reader.Close()
13881388
}()
13891389

1390-
var msgBlock wire.MsgBlock
1391-
if err = msgBlock.Deserialize(reader); err != nil {
1392-
sp.server.logger.Errorf("Unable to deserialize requested block hash %v: %v", hash, err)
1390+
// Use RawBlockMessage to avoid deserialize/serialize overhead for large blocks.
1391+
// This reads raw bytes directly and writes them to the wire, bypassing the
1392+
// expensive process of creating Go structs for millions of transactions.
1393+
rawBlockMsg, err := NewRawBlockMessage(reader)
1394+
if err != nil {
1395+
sp.server.logger.Errorf("Unable to read requested block hash %v: %v", hash, err)
13931396

13941397
if doneChan != nil {
13951398
doneChan <- struct{}{}
@@ -1414,7 +1417,7 @@ func (s *server) pushBlockMsg(sp *serverPeer, hash *chainhash.Hash, doneChan cha
14141417
dc = doneChan
14151418
}
14161419

1417-
sp.QueueMessageWithEncoding(&msgBlock, dc, encoding)
1420+
sp.QueueMessageWithEncoding(rawBlockMsg, dc, encoding)
14181421

14191422
// When the peer requests the final block that was advertised in
14201423
// response to a getblocks message which requested more blocks than
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package legacy
2+
3+
import (
4+
"io"
5+
6+
"github.com/bsv-blockchain/go-wire"
7+
"github.com/bsv-blockchain/teranode/errors"
8+
)
9+
10+
// RawBlockMessage implements wire.Message for streaming raw block bytes
11+
// without the overhead of deserializing into wire.MsgBlock structure.
12+
//
13+
// This is significantly more efficient for large blocks (e.g., 4GB+) because:
14+
// - No CPU time spent deserializing millions of transactions into Go structs
15+
// - No CPU time spent re-serializing those structs back to bytes
16+
// - Reduced memory overhead (no Go struct allocation per transaction)
17+
//
18+
// The raw bytes are read from the source and written directly to the wire,
19+
// bypassing the deserialize/serialize roundtrip that would otherwise require
20+
// allocating memory for each transaction struct.
21+
type RawBlockMessage struct {
22+
data []byte
23+
}
24+
25+
// NewRawBlockMessage creates a RawBlockMessage by reading all bytes from the reader.
26+
// The data must be in wire-format block encoding (header + txcount + transactions).
27+
func NewRawBlockMessage(reader io.Reader) (*RawBlockMessage, error) {
28+
data, err := io.ReadAll(reader)
29+
if err != nil {
30+
return nil, errors.NewProcessingError("failed to read block data", err)
31+
}
32+
33+
return &RawBlockMessage{data: data}, nil
34+
}
35+
36+
// Command returns the protocol command string for the message.
37+
// Implements wire.Message interface.
38+
func (m *RawBlockMessage) Command() string {
39+
return wire.CmdBlock
40+
}
41+
42+
// BsvEncode writes the raw block bytes to the writer.
43+
// Implements wire.Message interface.
44+
func (m *RawBlockMessage) BsvEncode(w io.Writer, _ uint32, _ wire.MessageEncoding) error {
45+
_, err := w.Write(m.data)
46+
return err
47+
}
48+
49+
// Bsvdecode is not supported for RawBlockMessage.
50+
// Implements wire.Message interface.
51+
func (m *RawBlockMessage) Bsvdecode(_ io.Reader, _ uint32, _ wire.MessageEncoding) error {
52+
return errors.NewProcessingError("RawBlockMessage does not support decoding")
53+
}
54+
55+
// MaxPayloadLength returns the length of the raw block data.
56+
// Implements wire.Message interface.
57+
func (m *RawBlockMessage) MaxPayloadLength(_ uint32) uint64 {
58+
return uint64(len(m.data))
59+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package legacy
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"testing"
7+
8+
"github.com/bsv-blockchain/go-wire"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestRawBlockMessage_Command(t *testing.T) {
13+
msg := &RawBlockMessage{data: []byte{}}
14+
require.Equal(t, wire.CmdBlock, msg.Command())
15+
}
16+
17+
func TestRawBlockMessage_MaxPayloadLength(t *testing.T) {
18+
data := make([]byte, 1000)
19+
msg := &RawBlockMessage{data: data}
20+
require.Equal(t, uint64(1000), msg.MaxPayloadLength(0))
21+
}
22+
23+
func TestRawBlockMessage_BsvEncode(t *testing.T) {
24+
testData := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
25+
msg := &RawBlockMessage{data: testData}
26+
27+
var buf bytes.Buffer
28+
err := msg.BsvEncode(&buf, 0, wire.BaseEncoding)
29+
require.NoError(t, err)
30+
require.Equal(t, testData, buf.Bytes())
31+
}
32+
33+
func TestRawBlockMessage_Bsvdecode(t *testing.T) {
34+
msg := &RawBlockMessage{}
35+
err := msg.Bsvdecode(nil, 0, wire.BaseEncoding)
36+
require.Error(t, err)
37+
require.Contains(t, err.Error(), "does not support decoding")
38+
}
39+
40+
func TestNewRawBlockMessage(t *testing.T) {
41+
testData := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
42+
reader := bytes.NewReader(testData)
43+
44+
msg, err := NewRawBlockMessage(reader)
45+
require.NoError(t, err)
46+
require.Equal(t, testData, msg.data)
47+
}
48+
49+
func TestNewRawBlockMessage_ReadError(t *testing.T) {
50+
// Create a reader that will return an error
51+
reader := &errorReader{}
52+
53+
msg, err := NewRawBlockMessage(reader)
54+
require.Error(t, err)
55+
require.Nil(t, msg)
56+
}
57+
58+
// errorReader is a test helper that always returns an error
59+
type errorReader struct{}
60+
61+
func (r *errorReader) Read(_ []byte) (int, error) {
62+
return 0, io.ErrUnexpectedEOF
63+
}
64+
65+
func TestRawBlockMessage_LargeData(t *testing.T) {
66+
// Test with a larger block-like structure (1MB)
67+
data := make([]byte, 1024*1024)
68+
for i := range data {
69+
data[i] = byte(i % 256)
70+
}
71+
72+
reader := bytes.NewReader(data)
73+
msg, err := NewRawBlockMessage(reader)
74+
require.NoError(t, err)
75+
require.Equal(t, uint64(1024*1024), msg.MaxPayloadLength(0))
76+
77+
// Verify encode produces the same data
78+
var buf bytes.Buffer
79+
err = msg.BsvEncode(&buf, 0, wire.BaseEncoding)
80+
require.NoError(t, err)
81+
require.Equal(t, data, buf.Bytes())
82+
}

0 commit comments

Comments
 (0)