-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutil.go
194 lines (168 loc) · 6.3 KB
/
util.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package main
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"log"
"net"
"time"
)
// readAndParseResponse reads and parses the response from the Bitcoin node.
// It verifies if the expected response was received.
func readAndParseResponse(command Command, conn net.Conn) error {
// Receive and handle response
resp := make([]byte, 1024)
n, err := conn.Read(resp)
if err != nil {
return fmt.Errorf("error receiving %s response: %s", command, err)
}
log.Printf("%s msg received, READ %d bytes", command, n)
// Parse response message
cmd, err := parseMessage(resp[:n])
if err != nil {
return fmt.Errorf("error parsing %s response: %s", command, err)
}
// Verify if expected response message was received
if cmd != string(command) {
return fmt.Errorf("unexpected response, expected %s, received %s msg: ", command, cmd)
}
return nil
}
// createVersionMessage creates a version message with the given nonce.
func createVersionMessage(nonce int) ([]byte, error) {
payload := new(bytes.Buffer)
err := binary.Write(payload, binary.LittleEndian, int32(ProtocolVersion)) // protocol version
if err != nil {
return nil, fmt.Errorf("failed to write protocol version: %s", err)
}
err = binary.Write(payload, binary.LittleEndian, uint64(Services)) // kind of services supported
if err != nil {
return nil, fmt.Errorf("failed to write local services: %s", err)
}
err = binary.Write(payload, binary.LittleEndian, uint64(time.Now().Unix())) // unix timestamp of client machine
if err != nil {
return nil, fmt.Errorf("failed to write timestamp: %s", err)
}
err = binary.Write(payload, binary.LittleEndian, uint64(0)) // services supported by remote node
if err != nil {
return nil, fmt.Errorf("failed to write remote node services: %s", err)
}
ip := net.ParseIP(RemoteNodeHost).To16() // convert remote node ip4 to ip6
_, err = payload.Write(ip) // set ip6 in payload
if err != nil {
return nil, fmt.Errorf("failed to write remote node host: %s", err)
}
err = binary.Write(payload, binary.BigEndian, uint16(RemoteNodePort)) // remote node port
if err != nil {
return nil, fmt.Errorf("failed to write remote node port: %s", err)
}
err = binary.Write(payload, binary.LittleEndian, uint64(0)) // list of local host services
if err != nil {
return nil, fmt.Errorf("failed to write localhost services: %s", err)
}
localIp := net.ParseIP(Localhost).To16() // convert local node ip4 to ip6
_, err = payload.Write(localIp) // local host IP, same as remote node for this test
if err != nil {
return nil, fmt.Errorf("failed to write localhost ip: %s", err)
}
err = binary.Write(payload, binary.BigEndian, uint16(LocalhostPort)) // local host port, same as remote node for this test
if err != nil {
return nil, fmt.Errorf("failed to write localhost port: %s", err)
}
err = binary.Write(payload, binary.LittleEndian, uint64(nonce)) // Nonce
if err != nil {
return nil, fmt.Errorf("failed to write nonce value: %s", err)
}
err = payload.WriteByte(byte(len(UserAgent))) // indicate length of the upcoming UserAgent string
if err != nil {
return nil, fmt.Errorf("failed to write user agent size: %s", err)
}
_, err = payload.WriteString(UserAgent) // UserAgent
if err != nil {
return nil, fmt.Errorf("failed to write user agent: %s", err)
}
err = binary.Write(payload, binary.LittleEndian, int32(StartHeight)) // We don't have any block to share
if err != nil {
return nil, fmt.Errorf("failed to write StartHeight for last block: %s", err)
}
err = binary.Write(payload, binary.LittleEndian, uint8(Relay)) // Announce relayed transactions
if err != nil {
return nil, fmt.Errorf("failed to write relay boolean: %s", err)
}
// Create the version message from magic bytes + command + payload size + checksum
// Add HEADER
message := new(bytes.Buffer)
_, err = message.Write(MagicBytes)
if err != nil {
return nil, fmt.Errorf("failed to write magic bytes: %s", err)
}
_, err = message.WriteString(string(Version))
if err != nil {
return nil, fmt.Errorf("failed to write version command string: %s", err)
}
_, err = message.Write(make([]byte, 12-len(Version))) // padding on the right with empty bytes.
if err != nil {
return nil, fmt.Errorf("failed to add padding to version cmd string: %s", err)
}
err = binary.Write(message, binary.LittleEndian, uint32(payload.Len()))
if err != nil {
return nil, fmt.Errorf("failed to write payload size: %s", err)
}
hash := sha256.Sum256(payload.Bytes())
hash = sha256.Sum256(hash[:])
_, err = message.Write(hash[:4])
if err != nil {
return nil, fmt.Errorf("failed to write checksum: %s", err)
}
// Add PAYLOAD
_, err = message.Write(payload.Bytes()) // add the actual payload to the version message
if err != nil {
return nil, fmt.Errorf("failed to write payload: %s", err)
}
return message.Bytes(), nil
}
// createVerackMessage creates a verack message.
func createVerackMessage() ([]byte, error) {
message := new(bytes.Buffer)
_, err := message.Write(MagicBytes)
if err != nil {
return nil, fmt.Errorf("failed to write magic bytes: %s", err)
}
_, err = message.WriteString(string(Verack))
if err != nil {
return nil, fmt.Errorf("failed to write verack command: %s", err)
}
_, err = message.Write(make([]byte, 12-len(Verack))) // padding on the right with empty bytes.
if err != nil {
return nil, fmt.Errorf("failed to add verack cmd padding: %s", err)
}
// Empty payload for verack message.
return message.Bytes(), nil
}
// parseMessage parses a Bitcoin message and returns the command.
// also prints out the received response and payload to the stdout.
func parseMessage(data []byte) (string, error) {
if len(data) < 24 {
return "", fmt.Errorf("message too short")
}
magic := data[:4] // First 4 bytes are magic bytes.
if !bytes.Equal(magic, MagicBytes) {
return "", fmt.Errorf("invalid magic bytes")
}
command := string(bytes.TrimRight(data[4:16], "\x00")) // Next 12 bytes contain the command.
s := binary.LittleEndian.Uint32(data[16:20]) // These 4 bytes tell the payload size.
if len(data[24:]) < int(s) {
return "", fmt.Errorf("payload length mismatch")
}
payload := data[24 : 24+s]
log.Printf(
"received message with:\n magicBytes: %v\n command: %s\n payloadLen: %d\n checksum: %v\n payload: %v\n",
magic,
command,
s,
data[20:24],
payload,
) // Empty payload for VERACK message.
return command, nil
}