Skip to content

Commit f687a59

Browse files
authored
Merge pull request #9 from setavenger/spent_filter
Spent filter and Index added
2 parents bfe7604 + eeac351 commit f687a59

19 files changed

+539
-143
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ matches [other implementations](https://github.com/bitcoin/bitcoin/pull/28241#is
99

1010
The installation process is still very manual. Will be improved based on feedback and new findings. It is advised to look at the example [blindbit.toml](blindbit.example.toml). As new config options appear they will be listed and explained there.
1111

12+
## Breaking changes
13+
14+
- Endpoints were expanded and have a slightly different syntax now [see endpoints](#endpoints)
15+
1216
### Requirements
1317

1418
- RPC access to a bitcoin full node
@@ -84,8 +88,10 @@ No known issues.
8488
```text
8589
GET("/block-height") // returns the height of the indexing server
8690
GET("/tweaks/:blockheight?dustLimit=<sat_amount>") // returns tweak data (cut-through); optional parameter dustLimit can be omitted; filtering happens per request, so virtually any amount can be specified
87-
GET("/tweak-index/:blockheight") // returns the full tweak index (no cut-through)
88-
GET("/filter/:blockheight") // returns a custom taproot only filter (the underlying data is subject to change; changing scriptPubKey to x-only pubKey)
91+
GET("/tweak-index/:blockheight?dustLimit=<sat_amount>") // returns the full tweak index (no cut-through); optional parameter dustLimit can be omitted; filtering happens per request, so virtually any amount can be specified
92+
GET("/spent-index/:blockheight") // returns the spent outpoints index (see https://github.com/setavenger/BIP0352-light-client-specification?tab=readme-ov-file#spent-utxos)
93+
GET("/filter/spent/:blockheight") // returns a filter for shortened spent outpoint hashes (see https://github.com/setavenger/BIP0352-light-client-specification?tab=readme-ov-file#filters)
94+
GET("/filter/new-utxos/:blockheight") // returns a custom taproot only filter of x-only pubkey which received funds
8995
GET("/utxos/:blockheight") // UTXO data for that block (cut down to the essentials needed to spend)
9096
```
9197

src/common/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func LoadConfigs(pathToConfig string) {
7979
WarningLogger.Println("make sure your configuration loaded correctly, check example blindbit.toml for configuration")
8080
}
8181

82-
if TweaksCutThroughWithDust && !TweaksOnly {
82+
if TweaksCutThroughWithDust && TweaksOnly {
8383
err := errors.New("cut through requires tweaks_only to be set to 1")
8484
ErrorLogger.Println(err)
8585
os.Exit(1)

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: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ var (
5858

5959
// one has to call SetDirectories otherwise common.DBPath will be empty
6060
var (
61-
DBPathHeaders string
62-
DBPathHeadersInv string
63-
DBPathFilters string
64-
DBPathTweaks string
65-
DBPathTweakIndex string
66-
DBPathTweakIndexDust string
67-
DBPathUTXOs string
61+
DBPathHeaders string
62+
DBPathHeadersInv string // for height to blockHash mapping
63+
DBPathFilters string
64+
DBPathTweaks string
65+
DBPathTweakIndex string
66+
DBPathUTXOs string
67+
DBPathTweakIndexDust string
68+
DBPathSpentOutpointsIndex string
69+
DBPathSpentOutpointsFilter string
6870
)
6971

7072
// NumsH = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
@@ -83,6 +85,8 @@ func SetDirectories() {
8385
DBPathTweakIndex = DBPath + "/tweak-index"
8486
DBPathTweakIndexDust = DBPath + "/tweak-index-dust"
8587
DBPathUTXOs = DBPath + "/utxos"
88+
DBPathSpentOutpointsIndex = DBPath + "/spent-index"
89+
DBPathSpentOutpointsFilter = DBPath + "/spent-filter"
8690
}
8791

8892
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)
@@ -213,12 +211,38 @@ func HandleBlock(block *types.Block) error {
213211
}
214212

215213
// create special block filter
216-
cFilterTaproot, err := BuildTaprootOnlyFilter(block)
214+
cFilterNewUTXOs, err := BuildNewUTXOsFilter(block)
215+
if err != nil {
216+
common.ErrorLogger.Println(err)
217+
return err
218+
}
219+
220+
//
221+
err = dblevel.InsertNewUTXOsFilter(cFilterNewUTXOs)
217222
if err != nil {
218223
common.ErrorLogger.Println(err)
219224
return err
220225
}
221-
err = dblevel.InsertFilter(cFilterTaproot)
226+
227+
spentOutpointsIndex, err := BuildSpentUTXOIndex(taprootSpent, block)
228+
if err != nil {
229+
common.ErrorLogger.Println(err)
230+
return err
231+
}
232+
233+
err = dblevel.InsertSpentOutpointsIndex(&spentOutpointsIndex)
234+
if err != nil {
235+
common.ErrorLogger.Println(err)
236+
return err
237+
}
238+
239+
cFilterSpentUTXOs, err := BuildSpentUTXOsFilter(spentOutpointsIndex)
240+
if err != nil {
241+
common.ErrorLogger.Println(err)
242+
return err
243+
}
244+
245+
err = dblevel.InsertSpentOutpointsFilter(cFilterSpentUTXOs)
222246
if err != nil {
223247
common.ErrorLogger.Println(err)
224248
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

0 commit comments

Comments
 (0)