Skip to content

Commit 9b12c62

Browse files
committed
added support for spent UTXO filter and index
1 parent 14606c2 commit 9b12c62

File tree

17 files changed

+524
-138
lines changed

17 files changed

+524
-138
lines changed

src/common/types/spentoutpoints.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package types
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"errors"
7+
8+
"SilentPaymentAppBackend/src/common"
9+
)
10+
11+
const LenOutpointHashShort = 8
12+
13+
type SpentOutpointsIndex struct {
14+
BlockHash string `json:"block_hash"`
15+
BlockHeight uint32 `json:"block_height"`
16+
Data [][LenOutpointHashShort]byte `json:"data"`
17+
}
18+
19+
func PairFactorySpentOutpointsIndex() Pair {
20+
var filter Pair = &SpentOutpointsIndex{}
21+
return filter
22+
}
23+
24+
func (v *SpentOutpointsIndex) SerialiseKey() ([]byte, error) {
25+
return GetDBKeyTweakIndex(v.BlockHash)
26+
}
27+
28+
func (v *SpentOutpointsIndex) SerialiseData() ([]byte, error) {
29+
30+
// todo can this be made more efficiently?
31+
totalLength := len(v.Data) * LenOutpointHashShort
32+
flattened := make([]byte, 0, totalLength)
33+
34+
for _, byteArray := range v.Data {
35+
flattened = append(flattened, byteArray[:]...)
36+
}
37+
38+
return flattened, nil
39+
}
40+
41+
func (v *SpentOutpointsIndex) DeSerialiseKey(key []byte) error {
42+
if len(key) != 32 {
43+
common.ErrorLogger.Printf("wrong key length: %+v", key)
44+
return errors.New("key is wrong length. should not happen")
45+
}
46+
47+
v.BlockHash = hex.EncodeToString(key)
48+
49+
return nil
50+
}
51+
52+
func (v *SpentOutpointsIndex) DeSerialiseData(data []byte) error {
53+
if len(data)%LenOutpointHashShort != 0 {
54+
common.ErrorLogger.Printf("wrong data length: %+v", data)
55+
return errors.New("data is wrong length. should not happen")
56+
}
57+
58+
numArrays := len(data) / LenOutpointHashShort
59+
v.Data = make([][LenOutpointHashShort]byte, numArrays)
60+
// Iterate and copy segments from the flat slice into the new array of arrays
61+
for i := 0; i < numArrays; i++ {
62+
copy(v.Data[i][:], data[i*LenOutpointHashShort:(i+1)*LenOutpointHashShort])
63+
}
64+
return nil
65+
}
66+
67+
func GetDBKeySpentSpentOutpointsIndex(blockHash string) ([]byte, error) {
68+
var buf bytes.Buffer
69+
blockHashBytes, err := hex.DecodeString(blockHash)
70+
if err != nil {
71+
common.ErrorLogger.Println(err)
72+
return nil, err
73+
}
74+
buf.Write(blockHashBytes)
75+
76+
return buf.Bytes(), nil
77+
}

src/common/util.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package common
22

33
import (
44
"crypto/sha256"
5-
"github.com/shopspring/decimal"
6-
"golang.org/x/crypto/ripemd160"
75
"os/user"
86
"strings"
7+
8+
"github.com/shopspring/decimal"
9+
"golang.org/x/crypto/ripemd160"
910
)
1011

12+
// ReverseBytes reverses the bytes inside the byte slice and returns the same slice. It does not return a copy.
1113
func ReverseBytes(bytes []byte) []byte {
1214
for i, j := 0, len(bytes)-1; i < j; i, j = i+1, j-1 {
1315
bytes[i], bytes[j] = bytes[j], bytes[i]

src/common/vars.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ var (
5555

5656
// one has to call SetDirectories otherwise common.DBPath will be empty
5757
var (
58-
DBPathHeaders = DBPath + "/headers"
59-
DBPathHeadersInv = DBPath + "/headers-inv" // for height to blockHash mapping
60-
DBPathFilters = DBPath + "/filters"
61-
DBPathTweaks = DBPath + "/tweaks"
62-
DBPathTweakIndex = DBPath + "/tweak-index"
63-
DBPathUTXOs = DBPath + "/utxos"
58+
DBPathHeaders string
59+
DBPathHeadersInv string // for height to blockHash mapping
60+
DBPathFilters string
61+
DBPathTweaks string
62+
DBPathTweakIndex string
63+
DBPathUTXOs string
64+
DBPathSpentOutpointsIndex string
65+
DBPathSpentOutpointsFilter string
6466
)
6567

6668
// NumsH = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
@@ -78,6 +80,8 @@ func SetDirectories() {
7880
DBPathTweaks = DBPath + "/tweaks"
7981
DBPathTweakIndex = DBPath + "/tweak-index"
8082
DBPathUTXOs = DBPath + "/utxos"
83+
DBPathSpentOutpointsIndex = DBPath + "/spent-index"
84+
DBPathSpentOutpointsFilter = DBPath + "/spent-filter"
8185
}
8286

8387
func HeaderMustSyncHeight() uint32 {

src/core/cfilter.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package core
33
import (
44
"SilentPaymentAppBackend/src/common"
55
"SilentPaymentAppBackend/src/common/types"
6+
"bytes"
7+
"encoding/binary"
68
"encoding/hex"
9+
710
"github.com/btcsuite/btcd/btcutil/gcs/builder"
811
"github.com/btcsuite/btcd/chaincfg/chainhash"
912
"github.com/btcsuite/btcutil/gcs"
1013
)
1114

1215
// BuildTaprootOnlyFilter creates the taproot only filter
13-
func BuildTaprootOnlyFilter(block *types.Block) (types.Filter, error) {
16+
func BuildNewUTXOsFilter(block *types.Block) (types.Filter, error) {
1417
var taprootOutput [][]byte
1518

1619
for _, tx := range block.Txs {
@@ -64,3 +67,68 @@ func BuildTaprootOnlyFilter(block *types.Block) (types.Filter, error) {
6467
BlockHash: block.Hash,
6568
}, nil
6669
}
70+
71+
// BuildSpentUTXOsFilter creates a filter based on the spent
72+
func BuildSpentUTXOsFilter(spentOutpointsIndex types.SpentOutpointsIndex) (types.Filter, error) {
73+
74+
blockHashBytes, err := hex.DecodeString(spentOutpointsIndex.BlockHash)
75+
if err != nil {
76+
common.DebugLogger.Println("blockHash", spentOutpointsIndex.BlockHash)
77+
common.ErrorLogger.Fatalln(err)
78+
return types.Filter{}, err
79+
}
80+
c := chainhash.Hash{}
81+
82+
err = c.SetBytes(common.ReverseBytes(blockHashBytes))
83+
if err != nil {
84+
common.DebugLogger.Println("blockHash", spentOutpointsIndex.BlockHash)
85+
common.ErrorLogger.Fatalln(err)
86+
return types.Filter{}, err
87+
88+
}
89+
key := builder.DeriveKey(&c)
90+
91+
// convert to slices
92+
data := make([][]byte, len(spentOutpointsIndex.Data))
93+
for i, outpointHash := range spentOutpointsIndex.Data {
94+
var newHash [8]byte
95+
copy(newHash[:], outpointHash[:])
96+
data[i] = newHash[:]
97+
}
98+
99+
filter, err := gcs.BuildGCSFilter(builder.DefaultP, builder.DefaultM, key, data)
100+
if err != nil {
101+
common.ErrorLogger.Fatalln(err)
102+
return types.Filter{}, err
103+
}
104+
105+
nBytes, err := filter.NBytes()
106+
if err != nil {
107+
common.ErrorLogger.Fatalln(err)
108+
return types.Filter{}, err
109+
}
110+
111+
return types.Filter{
112+
FilterType: 4,
113+
BlockHeight: spentOutpointsIndex.BlockHeight,
114+
Data: nBytes,
115+
BlockHash: spentOutpointsIndex.BlockHash,
116+
}, nil
117+
}
118+
119+
func SerialiseToOutpoint(utxo types.UTXO) ([]byte, error) {
120+
var buf bytes.Buffer
121+
122+
txidBytes, err := hex.DecodeString(utxo.Txid)
123+
if err != nil {
124+
common.DebugLogger.Println(utxo.Txid)
125+
common.ErrorLogger.Println(err)
126+
return nil, err
127+
}
128+
129+
// err is always nil
130+
buf.Write(common.ReverseBytes(txidBytes))
131+
132+
binary.Write(&buf, binary.LittleEndian, utxo.Value)
133+
return buf.Bytes(), err
134+
}

src/core/extractutxos.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ func extractSpentTaprootPubKeysFromTx(tx *types.Transaction, block *types.Block)
7575
blockHash = headerInv.Hash
7676
}
7777

78-
//832974
7978
spentUTXOs = append(spentUTXOs, types.UTXO{
8079
Txid: vin.Txid,
8180
Vout: vin.Vout,

src/core/routine.go

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,17 @@ import (
1212
func CheckForNewBlockRoutine() {
1313
common.InfoLogger.Println("starting check_for_new_block_routine")
1414
for {
15-
select {
16-
case <-time.NewTicker(3 * time.Second).C:
17-
blockHash, err := GetBestBlockHash()
18-
if err != nil {
19-
common.ErrorLogger.Println(err)
20-
// todo fail or restart after too many fails?
21-
continue
22-
}
23-
err = FullProcessBlockHash(blockHash)
24-
if err != nil {
25-
common.ErrorLogger.Println(err)
26-
return
27-
}
15+
<-time.NewTicker(3 * time.Second).C
16+
blockHash, err := GetBestBlockHash()
17+
if err != nil {
18+
common.ErrorLogger.Println(err)
19+
// todo fail or restart after too many fails?
20+
continue
21+
}
22+
err = FullProcessBlockHash(blockHash)
23+
if err != nil {
24+
common.ErrorLogger.Println(err)
25+
return
2826
}
2927
}
3028
}
@@ -58,7 +56,7 @@ func FullProcessBlockHash(blockHash string) error {
5856
func PullBlock(blockHash string) (*types.Block, error) {
5957
if len(blockHash) != 64 {
6058
common.ErrorLogger.Println("block_hash invalid:", blockHash)
61-
return nil, errors.New(fmt.Sprintf("block_hash invalid: %s", blockHash))
59+
return nil, fmt.Errorf("block_hash invalid: %s", blockHash)
6260
}
6361
// this method is preferred over lastHeader because then this function can be called for PreviousBlockHash
6462
header, err := dblevel.FetchByBlockHashBlockHeader(blockHash)
@@ -185,12 +183,38 @@ func HandleBlock(block *types.Block) error {
185183
}
186184

187185
// create special block filter
188-
cFilterTaproot, err := BuildTaprootOnlyFilter(block)
186+
cFilterNewUTXOs, err := BuildNewUTXOsFilter(block)
187+
if err != nil {
188+
common.ErrorLogger.Println(err)
189+
return err
190+
}
191+
192+
//
193+
err = dblevel.InsertNewUTXOsFilter(cFilterNewUTXOs)
194+
if err != nil {
195+
common.ErrorLogger.Println(err)
196+
return err
197+
}
198+
199+
spentOutpointsIndex, err := BuildSpentUTXOIndex(taprootSpent, block)
200+
if err != nil {
201+
common.ErrorLogger.Println(err)
202+
return err
203+
}
204+
205+
err = dblevel.InsertSpentOutpointsIndex(&spentOutpointsIndex)
189206
if err != nil {
190207
common.ErrorLogger.Println(err)
191208
return err
192209
}
193-
err = dblevel.InsertFilter(cFilterTaproot)
210+
211+
cFilterSpentUTXOs, err := BuildSpentUTXOsFilter(spentOutpointsIndex)
212+
if err != nil {
213+
common.ErrorLogger.Println(err)
214+
return err
215+
}
216+
217+
err = dblevel.InsertSpentOutpointsFilter(cFilterSpentUTXOs)
194218
if err != nil {
195219
common.ErrorLogger.Println(err)
196220
return err

src/core/spentutxos.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package core
2+
3+
import (
4+
"SilentPaymentAppBackend/src/common"
5+
"SilentPaymentAppBackend/src/common/types"
6+
"crypto/sha256"
7+
"encoding/hex"
8+
)
9+
10+
func BuildSpentUTXOIndex(utxos []types.UTXO, block *types.Block) (types.SpentOutpointsIndex, error) {
11+
12+
blockHashBytes, err := hex.DecodeString(block.Hash)
13+
if err != nil {
14+
common.ErrorLogger.Println(err)
15+
return types.SpentOutpointsIndex{}, err
16+
}
17+
18+
spentOutpointsIndex := types.SpentOutpointsIndex{
19+
BlockHash: block.Hash,
20+
BlockHeight: block.Height,
21+
}
22+
23+
for _, utxo := range utxos {
24+
var outpoint []byte
25+
outpoint, err = SerialiseToOutpoint(utxo)
26+
if err != nil {
27+
common.ErrorLogger.Println(err)
28+
return types.SpentOutpointsIndex{}, err
29+
}
30+
31+
hashedOutpoint := sha256.Sum256(append(outpoint, blockHashBytes...))
32+
spentOutpointsIndex.Data = append(spentOutpointsIndex.Data, [types.LenOutpointHashShort]byte(hashedOutpoint[:]))
33+
}
34+
35+
return spentOutpointsIndex, err
36+
}

src/dataexport/exportcsv.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func convertUTXOsToRecords(utxos []types.UTXO) ([][]string, error) {
7878
// Filters
7979

8080
func ExportFilters(path string) error {
81-
allEntries, err := dblevel.FetchAllFilters()
81+
allEntries, err := dblevel.FetchAllNewUTXOsFilters()
8282
if err != nil {
8383
common.ErrorLogger.Println(err)
8484
return err

src/db/dblevel/client.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ func (e NoEntryErr) Error() string {
2020
}
2121

2222
var (
23-
HeadersDB *leveldb.DB
24-
HeadersInvDB *leveldb.DB
25-
FiltersDB *leveldb.DB
26-
TweaksDB *leveldb.DB
27-
TweakIndexDB *leveldb.DB
28-
UTXOsDB *leveldb.DB
23+
HeadersDB *leveldb.DB
24+
HeadersInvDB *leveldb.DB
25+
NewUTXOsFiltersDB *leveldb.DB
26+
TweaksDB *leveldb.DB
27+
TweakIndexDB *leveldb.DB
28+
UTXOsDB *leveldb.DB
29+
SpentOutpointsIndexDB *leveldb.DB
30+
SpentOutpointsFilterDB *leveldb.DB
2931
)
3032

3133
// OpenDBConnection opens a connection to the through path specified db instance
@@ -99,10 +101,10 @@ func retrieveByBlockHash(db *leveldb.DB, blockHash string, pair types.Pair) erro
99101
}
100102

101103
data, err := db.Get(blockHashBytes, nil)
102-
if err != nil && !errors.Is(err, NoEntryErr{}) { // todo this error probably exists as var/type somewhere
104+
if err != nil && !errors.Is(err, leveldb.ErrNotFound) { // todo this error probably exists as var/type somewhere
103105
common.ErrorLogger.Println(err)
104106
return err
105-
} else if err != nil && errors.Is(err, NoEntryErr{}) { // todo this error probably exists as var/type somewhere
107+
} else if err != nil && errors.Is(err, leveldb.ErrNotFound) { // todo this error probably exists as var/type somewhere
106108
// todo we don't need separate patterns if just return the errors anyways? or maybe just to avoid unnecessary logging
107109
return NoEntryErr{}
108110
}

0 commit comments

Comments
 (0)