Skip to content

Commit 4c13db4

Browse files
authored
Adding NewTxFromReader to handle large blocks (#56)
1 parent 78c9d18 commit 4c13db4

File tree

6 files changed

+231
-55
lines changed

6 files changed

+231
-55
lines changed

block.bin

332 KB
Binary file not shown.

input.go

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package bt
22

33
import (
4+
"bytes"
45
"encoding/binary"
56
"encoding/hex"
67
"fmt"
8+
"io"
79

810
"github.com/libsv/go-bt/bscript"
911
)
@@ -46,28 +48,55 @@ func NewInput() *Input {
4648
}
4749

4850
// NewInputFromBytes returns a transaction input from the bytes provided.
49-
func NewInputFromBytes(bytes []byte) (*Input, int, error) {
50-
if len(bytes) < 36 {
51+
func NewInputFromBytes(b []byte) (*Input, int, error) {
52+
if len(b) < 36 {
5153
return nil, 0, fmt.Errorf("input length too short < 36")
5254
}
5355

54-
offset := 36
55-
l, size := DecodeVarInt(bytes[offset:])
56-
offset += size
56+
r := bytes.NewReader(b)
5757

58-
totalLength := offset + int(l) + 4 // 4 bytes for nSeq
58+
i, err := NewInputFromReader(r)
59+
if err != nil {
60+
return nil, 0, err
61+
}
62+
63+
return i, len(i.ToBytes(false)), nil
64+
}
65+
66+
// NewInputFromReader returns a transaction input from the io.Reader provided.
67+
func NewInputFromReader(r io.Reader) (*Input, error) {
68+
previousTxID := make([]byte, 32)
69+
if n, err := io.ReadFull(r, previousTxID); n != 32 || err != nil {
70+
return nil, fmt.Errorf("Could not read previousTxID(32), got %d bytes and err: %w", n, err)
71+
}
72+
73+
prevIndex := make([]byte, 4)
74+
if n, err := io.ReadFull(r, prevIndex); n != 4 || err != nil {
75+
return nil, fmt.Errorf("Could not read prevIndex(4), got %d bytes and err: %w", n, err)
76+
}
77+
78+
l, _, err := DecodeVarIntFromReader(r)
79+
if err != nil {
80+
return nil, fmt.Errorf("Could not read varint: %w", err)
81+
}
5982

60-
if len(bytes) < totalLength {
61-
return nil, 0, fmt.Errorf("input length too short < 36 + script + 4")
83+
script := make([]byte, l)
84+
if n, err := io.ReadFull(r, script); uint64(n) != l || err != nil {
85+
return nil, fmt.Errorf("Could not read script(%d), got %d bytes and err: %w", l, n, err)
86+
}
87+
88+
sequence := make([]byte, 4)
89+
if n, err := io.ReadFull(r, sequence); n != 4 || err != nil {
90+
return nil, fmt.Errorf("Could not read sequence(4), got %d bytes and err: %w", n, err)
6291
}
6392

6493
return &Input{
65-
PreviousTxIDBytes: ReverseBytes(bytes[0:32]),
66-
PreviousTxID: hex.EncodeToString(ReverseBytes(bytes[0:32])),
67-
PreviousTxOutIndex: binary.LittleEndian.Uint32(bytes[32:36]),
68-
SequenceNumber: binary.LittleEndian.Uint32(bytes[offset+int(l):]),
69-
UnlockingScript: bscript.NewFromBytes(bytes[offset : offset+int(l)]),
70-
}, totalLength, nil
94+
PreviousTxIDBytes: ReverseBytes(previousTxID),
95+
PreviousTxID: hex.EncodeToString(ReverseBytes(previousTxID)),
96+
PreviousTxOutIndex: binary.LittleEndian.Uint32(prevIndex),
97+
UnlockingScript: bscript.NewFromBytes(script),
98+
SequenceNumber: binary.LittleEndian.Uint32(sequence),
99+
}, nil
71100
}
72101

73102
// NewInputFromUTXO returns a transaction input from the UTXO fields provided.

output.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package bt
22

33
import (
4+
"bytes"
45
"encoding/binary"
56
"encoding/hex"
67
"fmt"
8+
"io"
79

810
"github.com/libsv/go-bt/bscript"
911
"github.com/libsv/go-bt/crypto"
@@ -28,27 +30,53 @@ type Output struct {
2830
}
2931

3032
// NewOutputFromBytes returns a transaction Output from the bytes provided
31-
func NewOutputFromBytes(bytes []byte) (*Output, int, error) {
32-
if len(bytes) < 8 {
33+
func NewOutputFromBytes(b []byte) (*Output, int, error) {
34+
if len(b) < 8 {
3335
return nil, 0, fmt.Errorf("output length too short < 8")
3436
}
3537

3638
offset := 8
37-
l, size := DecodeVarInt(bytes[offset:])
39+
l, size := DecodeVarInt(b[offset:])
3840
offset += size
3941

4042
totalLength := offset + int(l)
4143

42-
if len(bytes) < totalLength {
44+
if len(b) < totalLength {
4345
return nil, 0, fmt.Errorf("output length too short < 8 + script")
4446
}
4547

46-
s := bscript.Script(bytes[offset:totalLength])
48+
r := bytes.NewReader(b)
49+
50+
o, err := NewOutputFromReader(r)
51+
if err != nil {
52+
return nil, 0, err
53+
}
54+
55+
return o, len(o.ToBytes()), nil
56+
}
57+
58+
// NewOutputFromReader returns a transaction Output from the io.Reader provided
59+
func NewOutputFromReader(r io.Reader) (*Output, error) {
60+
satoshis := make([]byte, 8)
61+
if n, err := io.ReadFull(r, satoshis); n != 8 || err != nil {
62+
return nil, fmt.Errorf("Could not read satoshis(8), got %d bytes and err: %w", n, err)
63+
}
64+
65+
l, _, err := DecodeVarIntFromReader(r)
66+
if err != nil {
67+
return nil, fmt.Errorf("Could not read varint: %w", err)
68+
}
69+
70+
script := make([]byte, l)
71+
if n, err := io.ReadFull(r, script); uint64(n) != l || err != nil {
72+
return nil, fmt.Errorf("Could not read LockingScript(%d), got %d bytes and err: %w", l, n, err)
73+
}
74+
s := bscript.Script(script)
4775

4876
return &Output{
49-
Satoshis: binary.LittleEndian.Uint64(bytes[0:8]),
77+
Satoshis: binary.LittleEndian.Uint64(satoshis),
5078
LockingScript: &s,
51-
}, totalLength, nil
79+
}, nil
5280
}
5381

5482
// NewP2PKHOutputFromPubKeyHashStr makes an output to a PKH with a value.

tx.go

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/hex"
77
"errors"
88
"fmt"
9+
"io"
910

1011
"github.com/libsv/go-bt/bscript"
1112
"github.com/libsv/go-bt/crypto"
@@ -90,47 +91,69 @@ func NewTxFromStream(b []byte) (*Tx, int, error) {
9091
return nil, 0, fmt.Errorf("too short to be a tx - even an empty tx has 10 bytes")
9192
}
9293

93-
var offset int
94-
t := Tx{
95-
Version: binary.LittleEndian.Uint32(b[offset:4]),
94+
r := bytes.NewReader(b)
95+
96+
tx, err := NewTxFromReader(r)
97+
if err != nil {
98+
return nil, 0, err
9699
}
97-
offset += 4
98100

99-
inputCount, size := DecodeVarInt(b[offset:])
100-
offset += size
101+
return tx, len(tx.ToBytes()), nil
102+
}
103+
104+
// NewTxFromReader creates a transaction from an io.Reader
105+
func NewTxFromReader(r io.Reader) (*Tx, error) {
106+
t := Tx{}
107+
108+
version := make([]byte, 4)
109+
if n, err := io.ReadFull(r, version); n != 4 || err != nil {
110+
return nil, err
111+
}
112+
t.Version = binary.LittleEndian.Uint32(version)
101113

102-
// create inputs
103-
var i uint64
104114
var err error
115+
116+
inputCount, _, err := DecodeVarIntFromReader(r)
117+
if err != nil {
118+
return nil, err
119+
}
120+
121+
// create Inputs
122+
var i uint64
105123
var input *Input
124+
106125
for ; i < inputCount; i++ {
107-
input, size, err = NewInputFromBytes(b[offset:])
126+
input, err = NewInputFromReader(r)
108127
if err != nil {
109-
return nil, 0, err
128+
return nil, err
110129
}
111-
offset += size
112-
113130
t.Inputs = append(t.Inputs, input)
114131
}
115132

116-
// create outputs
117-
var outputCount uint64
133+
outputCount, _, err := DecodeVarIntFromReader(r)
134+
if err != nil {
135+
return nil, err
136+
}
137+
138+
// create Outputs
118139
var output *Output
119-
outputCount, size = DecodeVarInt(b[offset:])
120-
offset += size
140+
121141
for i = 0; i < outputCount; i++ {
122-
output, size, err = NewOutputFromBytes(b[offset:])
142+
output, err = NewOutputFromReader(r)
123143
if err != nil {
124-
return nil, 0, err
144+
return nil, err
125145
}
126-
offset += size
146+
127147
t.Outputs = append(t.Outputs, output)
128148
}
129149

130-
t.LockTime = binary.LittleEndian.Uint32(b[offset:])
131-
offset += 4
150+
locktime := make([]byte, 4)
151+
if n, err := io.ReadFull(r, version); n != 4 || err != nil {
152+
return nil, err
153+
}
154+
t.LockTime = binary.LittleEndian.Uint32(locktime)
132155

133-
return &t, offset, nil
156+
return &t, nil
134157
}
135158

136159
// AddInput adds a new input to the transaction.

tx_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package bt_test
22

33
import (
4+
"bufio"
45
"encoding/hex"
56
"errors"
7+
"io"
8+
"os"
69
"reflect"
710
"testing"
811

@@ -139,6 +142,69 @@ func TestAddInputFromTx(t *testing.T) {
139142
assert.Equal(t, newTx.GetTotalInputSatoshis(), uint64(200000))
140143
}
141144

145+
// This test reads a sample block from a file, but normally
146+
// we would get the block directly from the node via a REST GET...
147+
/*
148+
resp, err := http.Get(fmt.Sprintf("%s/rest/block/%s.bin", b.client.serverAddr, blockHash))
149+
if err != nil {
150+
return nil, fmt.Errorf("Could not GET block: %v", err)
151+
}
152+
if resp.StatusCode != 200 {
153+
defer resp.Body.Close()
154+
data, err := ioutil.ReadAll(resp.Body)
155+
if err != nil {
156+
return nil, fmt.Errorf("failed to read response body: %w", err)
157+
}
158+
return nil, fmt.Errorf("ERROR: code %d: %s", resp.StatusCode, data)
159+
}
160+
Then use resp.Body as in the test below
161+
*/
162+
func TestNewTxFromReader(t *testing.T) {
163+
var err error
164+
var n int
165+
166+
f, err := os.Open("./block.bin")
167+
if err != nil {
168+
t.Error(err)
169+
t.FailNow()
170+
}
171+
defer f.Close()
172+
173+
r := bufio.NewReader(f)
174+
175+
header := make([]byte, 80)
176+
if n, err = io.ReadFull(f, header); n != 80 || err != nil {
177+
t.Errorf("Read %d bytes, err: %v", n, err)
178+
}
179+
180+
txCount, _, err := bt.DecodeVarIntFromReader(r)
181+
if err != nil {
182+
t.Error(err)
183+
t.FailNow()
184+
}
185+
186+
if txCount != 648 {
187+
t.Errorf("Expected %d transactions, got %d", 648, txCount)
188+
t.FailNow()
189+
}
190+
191+
var tx *bt.Tx
192+
193+
for i := uint64(0); i < txCount; i++ {
194+
tx, err = bt.NewTxFromReader(r)
195+
if err != nil {
196+
t.Error(err)
197+
t.FailNow()
198+
}
199+
// t.Log(tx.TxID())
200+
// t.Logf("%x", tx.Bytes())
201+
}
202+
if tx.GetTxID() != "b7c59d7fa17a74bbe0a05e5381f42b9ac7fe23b8a1ca40005a74802fe5b8bb5a" {
203+
t.Errorf("Expected %q, got %q", "b7c59d7fa17a74bbe0a05e5381f42b9ac7fe23b8a1ca40005a74802fe5b8bb5a", tx.GetTxID())
204+
t.FailNow()
205+
}
206+
}
207+
142208
func TestTx_GetTxID(t *testing.T) {
143209
t.Parallel()
144210

0 commit comments

Comments
 (0)