Skip to content

Commit d3fc1c3

Browse files
committed
Add go-subtree source code
1 parent 70a7e85 commit d3fc1c3

24 files changed

+3804
-164
lines changed

coinbase_placeholder.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package subtree
2+
3+
import (
4+
"github.com/libsv/go-bt/v2"
5+
"github.com/libsv/go-bt/v2/chainhash"
6+
)
7+
8+
var (
9+
// CoinbasePlaceholder hard code this value to avoid having to calculate it every time
10+
// to help the compiler optimize the code.
11+
CoinbasePlaceholder = [32]byte{
12+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
13+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
14+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
15+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
16+
}
17+
CoinbasePlaceholderHashValue = chainhash.Hash(CoinbasePlaceholder)
18+
CoinbasePlaceholderHash = &CoinbasePlaceholderHashValue
19+
20+
FrozenBytes = [36]byte{
21+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
22+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
23+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
24+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
25+
0xFF, 0xFF, 0xFF, 0xFF,
26+
}
27+
FrozenBytesTxBytes = FrozenBytes[0:32]
28+
FrozenBytesTxHash = chainhash.Hash(FrozenBytesTxBytes)
29+
)
30+
31+
var (
32+
CoinbasePlaceholderTx *bt.Tx
33+
coinbasePlaceholderTxHash *chainhash.Hash
34+
)
35+
36+
func init() {
37+
CoinbasePlaceholderTx = bt.NewTx()
38+
CoinbasePlaceholderTx.Version = 0xFFFFFFFF
39+
CoinbasePlaceholderTx.LockTime = 0xFFFFFFFF
40+
41+
coinbasePlaceholderTxHash = CoinbasePlaceholderTx.TxIDChainHash()
42+
}
43+
44+
func IsCoinbasePlaceHolderTx(tx *bt.Tx) bool {
45+
return tx.TxIDChainHash().IsEqual(coinbasePlaceholderTxHash)
46+
}

coinbase_placeholder_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package subtree
2+
3+
import (
4+
"testing"
5+
6+
"github.com/libsv/go-bt/v2"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestCoinbasePlaceholderTx(t *testing.T) {
11+
assert.True(t, IsCoinbasePlaceHolderTx(CoinbasePlaceholderTx))
12+
assert.Equal(t, CoinbasePlaceholderTx.Version, uint32(0xFFFFFFFF))
13+
assert.Equal(t, CoinbasePlaceholderTx.LockTime, uint32(0xFFFFFFFF))
14+
assert.Equal(t, CoinbasePlaceholderTx.TxIDChainHash(), coinbasePlaceholderTxHash)
15+
assert.False(t, IsCoinbasePlaceHolderTx(bt.NewTx()))
16+
assert.Equal(t, "a8502e9c08b3c851201a71d25bf29fd38a664baedb777318b12d19242f0e46ab", CoinbasePlaceholderTx.TxIDChainHash().String())
17+
}

compare.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package subtree
2+
3+
import "golang.org/x/exp/constraints"
4+
5+
func Min[T constraints.Ordered](a, b T) T {
6+
if a < b {
7+
return a
8+
}
9+
10+
return b
11+
}
12+
13+
func Max[T constraints.Ordered](a, b T) T {
14+
if a > b {
15+
return a
16+
}
17+
18+
return b
19+
}

compare_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package subtree
2+
3+
import "testing"
4+
5+
func TestMin(t *testing.T) {
6+
tests := []struct {
7+
a, b, expected int
8+
}{
9+
{1, 2, 1},
10+
{2, 1, 1},
11+
{3, 3, 3},
12+
{-1, 1, -1},
13+
}
14+
15+
for _, tt := range tests {
16+
t.Run("", func(t *testing.T) {
17+
result := Min(tt.a, tt.b)
18+
if result != tt.expected {
19+
t.Errorf("Min(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
20+
}
21+
})
22+
}
23+
}
24+
25+
func TestMax(t *testing.T) {
26+
tests := []struct {
27+
a, b, expected int
28+
}{
29+
{1, 2, 2},
30+
{2, 1, 2},
31+
{3, 3, 3},
32+
{-1, 1, 1},
33+
}
34+
35+
for _, tt := range tests {
36+
t.Run("", func(t *testing.T) {
37+
result := Max(tt.a, tt.b)
38+
if result != tt.expected {
39+
t.Errorf("Max(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
40+
}
41+
})
42+
}
43+
}

examples/example.go

Lines changed: 0 additions & 15 deletions
This file was deleted.

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ require (
99
github.com/pmezard/go-difflib v1.0.0 // indirect
1010
gopkg.in/yaml.v3 v3.0.1 // indirect
1111
)
12+
13+
replace github.com/libsv/go-bt/v2 => github.com/ordishs/go-bt/v2 v2.2.22

inpoints.go

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package subtree
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"fmt"
7+
"io"
8+
"math"
9+
"slices"
10+
11+
"github.com/libsv/go-bt/v2"
12+
"github.com/libsv/go-bt/v2/chainhash"
13+
)
14+
15+
type Inpoint struct {
16+
Hash chainhash.Hash
17+
Index uint32
18+
}
19+
20+
type TxInpoints struct {
21+
ParentTxHashes []chainhash.Hash
22+
Idxs [][]uint32
23+
24+
// internal variable
25+
nrInpoints int
26+
}
27+
28+
func NewTxInpoints() TxInpoints {
29+
return TxInpoints{
30+
ParentTxHashes: make([]chainhash.Hash, 0, 8), // initial capacity of 8, can grow as needed
31+
Idxs: make([][]uint32, 0, 16), // initial capacity of 16, can grow as needed
32+
}
33+
}
34+
35+
func NewTxInpointsFromTx(tx *bt.Tx) (TxInpoints, error) {
36+
p := NewTxInpoints()
37+
if err := p.addTx(tx); err != nil {
38+
return p, err
39+
}
40+
41+
return p, nil
42+
}
43+
44+
func NewTxInpointsFromInputs(inputs []*bt.Input) (TxInpoints, error) {
45+
p := TxInpoints{}
46+
47+
tx := &bt.Tx{}
48+
tx.Inputs = inputs
49+
50+
if err := p.addTx(tx); err != nil {
51+
return p, err
52+
}
53+
54+
return p, nil
55+
}
56+
57+
func NewTxInpointsFromBytes(data []byte) (TxInpoints, error) {
58+
p := TxInpoints{}
59+
60+
if err := p.deserializeFromReader(bytes.NewReader(data)); err != nil {
61+
return p, err
62+
}
63+
64+
return p, nil
65+
}
66+
67+
func NewTxInpointsFromReader(buf io.Reader) (TxInpoints, error) {
68+
p := TxInpoints{}
69+
70+
if err := p.deserializeFromReader(buf); err != nil {
71+
return p, err
72+
}
73+
74+
return p, nil
75+
}
76+
77+
func (p *TxInpoints) String() string {
78+
return fmt.Sprintf("TxInpoints{ParentTxHashes: %v, Idxs: %v}", p.ParentTxHashes, p.Idxs)
79+
}
80+
81+
func (p *TxInpoints) addTx(tx *bt.Tx) error {
82+
// Do not error out for transactions without inputs, seeded Teranodes will have txs without inputs
83+
84+
for _, input := range tx.Inputs {
85+
hash := *input.PreviousTxIDChainHash()
86+
87+
index := slices.Index(p.ParentTxHashes, hash)
88+
if index != -1 {
89+
p.Idxs[index] = append(p.Idxs[index], input.PreviousTxOutIndex)
90+
} else {
91+
p.ParentTxHashes = append(p.ParentTxHashes, hash)
92+
p.Idxs = append(p.Idxs, []uint32{input.PreviousTxOutIndex})
93+
}
94+
95+
p.nrInpoints++
96+
}
97+
98+
return nil
99+
}
100+
101+
// GetParentTxHashes returns the unique parent tx hashes
102+
func (p *TxInpoints) GetParentTxHashes() []chainhash.Hash {
103+
return p.ParentTxHashes
104+
}
105+
106+
func (p *TxInpoints) GetParentTxHashAtIndex(index int) (chainhash.Hash, error) {
107+
if index >= len(p.ParentTxHashes) {
108+
return chainhash.Hash{}, fmt.Errorf("index out of range")
109+
}
110+
111+
return p.ParentTxHashes[index], nil
112+
}
113+
114+
// GetTxInpoints returns the unique parent inpoints for the tx
115+
func (p *TxInpoints) GetTxInpoints() []Inpoint {
116+
inpoints := make([]Inpoint, 0, p.nrInpoints)
117+
118+
for i, hash := range p.ParentTxHashes {
119+
for _, index := range p.Idxs[i] {
120+
inpoints = append(inpoints, Inpoint{
121+
Hash: hash,
122+
Index: index,
123+
})
124+
}
125+
}
126+
127+
return inpoints
128+
}
129+
130+
func (p *TxInpoints) GetParentVoutsAtIndex(index int) ([]uint32, error) {
131+
if index >= len(p.ParentTxHashes) {
132+
return nil, fmt.Errorf("index out of range")
133+
}
134+
135+
return p.Idxs[index], nil
136+
}
137+
138+
func (p *TxInpoints) Serialize() ([]byte, error) {
139+
if len(p.ParentTxHashes) != len(p.Idxs) {
140+
return nil, fmt.Errorf("parent tx hashes and indexes length mismatch")
141+
}
142+
143+
bufBytes := make([]byte, 0, 1024) // 1KB (arbitrary size, should be enough for most cases)
144+
buf := bytes.NewBuffer(bufBytes)
145+
146+
var (
147+
err error
148+
bytesUint32 [4]byte
149+
)
150+
151+
binary.LittleEndian.PutUint32(bytesUint32[:], len32(p.ParentTxHashes))
152+
153+
if _, err = buf.Write(bytesUint32[:]); err != nil {
154+
return nil, fmt.Errorf("unable to write number of parent inpoints: %s", err)
155+
}
156+
157+
// write the parent tx hashes
158+
for _, hash := range p.ParentTxHashes {
159+
if _, err = buf.Write(hash[:]); err != nil {
160+
return nil, fmt.Errorf("unable to write parent tx hash: %s", err)
161+
}
162+
}
163+
164+
// write the parent indexes
165+
for _, indexes := range p.Idxs {
166+
binary.LittleEndian.PutUint32(bytesUint32[:], len32(indexes))
167+
168+
if _, err = buf.Write(bytesUint32[:]); err != nil {
169+
return nil, fmt.Errorf("unable to write number of parent indexes: %s", err)
170+
}
171+
172+
for _, idx := range indexes {
173+
binary.LittleEndian.PutUint32(bytesUint32[:], idx)
174+
175+
if _, err = buf.Write(bytesUint32[:]); err != nil {
176+
return nil, fmt.Errorf("unable to write parent index: %s", err)
177+
}
178+
}
179+
}
180+
181+
return buf.Bytes(), nil
182+
}
183+
184+
func (p *TxInpoints) deserializeFromReader(buf io.Reader) error {
185+
// read the number of parent inpoints
186+
var bytesUint32 [4]byte
187+
188+
if _, err := io.ReadFull(buf, bytesUint32[:]); err != nil {
189+
return fmt.Errorf("unable to read number of parent inpoints: %s", err)
190+
}
191+
192+
totalInpointsLen := binary.LittleEndian.Uint32(bytesUint32[:])
193+
194+
if totalInpointsLen == 0 {
195+
return nil
196+
}
197+
198+
p.nrInpoints = int(totalInpointsLen)
199+
200+
// read the parent inpoints
201+
p.ParentTxHashes = make([]chainhash.Hash, totalInpointsLen)
202+
p.Idxs = make([][]uint32, totalInpointsLen)
203+
204+
// read the parent tx hash
205+
for i := uint32(0); i < totalInpointsLen; i++ {
206+
if _, err := io.ReadFull(buf, p.ParentTxHashes[i][:]); err != nil {
207+
return fmt.Errorf("unable to read parent tx hash: %s", err)
208+
}
209+
}
210+
211+
// read the number of parent indexes
212+
for i := uint32(0); i < totalInpointsLen; i++ {
213+
if _, err := io.ReadFull(buf, bytesUint32[:]); err != nil {
214+
return fmt.Errorf("unable to read number of parent indexes: %s", err)
215+
}
216+
217+
parentIndexesLen := binary.LittleEndian.Uint32(bytesUint32[:])
218+
219+
// read the parent indexes
220+
p.Idxs[i] = make([]uint32, parentIndexesLen)
221+
222+
for j := uint32(0); j < parentIndexesLen; j++ {
223+
if _, err := io.ReadFull(buf, bytesUint32[:]); err != nil {
224+
return fmt.Errorf("unable to read parent index: %s", err)
225+
}
226+
227+
p.Idxs[i][j] = binary.LittleEndian.Uint32(bytesUint32[:])
228+
}
229+
}
230+
231+
return nil
232+
}
233+
234+
func len32[V any](b []V) uint32 {
235+
if b == nil {
236+
return 0
237+
}
238+
239+
l := len(b)
240+
241+
if l > math.MaxUint32 {
242+
return math.MaxInt32
243+
}
244+
245+
return uint32(l)
246+
}

0 commit comments

Comments
 (0)