Skip to content

Commit 7a0699b

Browse files
committed
utxos for fully spent txs are pruned from db now
1 parent 0046f59 commit 7a0699b

File tree

8 files changed

+134
-23
lines changed

8 files changed

+134
-23
lines changed

src/core/cleanup.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,38 @@ func overwriteUTXOsWithLookUp(utxos []types.UTXO) error {
1111
var utxosToOverwrite []types.UTXO
1212

1313
for _, utxo := range utxos {
14-
fetchedUTXOs, err := dblevel.FetchByBlockHashAndTxidUTXOs(utxo.BlockHash, utxo.Txid)
14+
_, err := dblevel.FetchByBlockHashAndTxidUTXOs(utxo.BlockHash, utxo.Txid)
1515
if err != nil && !errors.Is(err, dblevel.NoEntryErr{}) {
1616
common.ErrorLogger.Println(err)
1717
return err
1818
} else if err != nil && errors.Is(err, dblevel.NoEntryErr{}) {
1919
// we skip if no entry was found. We don't want to insert those
2020
continue
2121
}
22-
// we actually don't have to check the fetched UTXOs. if any utxos were found for this transaction it means that it was eligible.
22+
// we actually don't have to check the fetched UTXOs. If any utxos were found for this transaction it means that it was eligible.
2323
// hence all taproot utxos have to be present
24-
_ = fetchedUTXOs
2524
utxosToOverwrite = append(utxosToOverwrite, utxo)
2625
}
2726
err := dblevel.InsertUTXOs(utxosToOverwrite)
27+
alreadyCheckedTxids := make(map[string]struct{})
28+
for _, utxo := range utxosToOverwrite {
29+
if _, ok := alreadyCheckedTxids[utxo.Txid]; ok {
30+
continue
31+
}
32+
var key []byte
33+
key, err = utxo.SerialiseKey()
34+
if err != nil {
35+
common.ErrorLogger.Println(err)
36+
return err
37+
}
38+
_ = key
39+
err = dblevel.PruneUTXOs(key[:64])
40+
if err != nil {
41+
common.ErrorLogger.Println(err)
42+
return err
43+
}
44+
alreadyCheckedTxids[utxo.Txid] = struct{}{}
45+
}
2846
if err != nil {
2947
common.ErrorLogger.Println(err)
3048
return err
@@ -88,7 +106,7 @@ func markSpentUTXOsAndTweaks(utxos []types.UTXO) error {
88106
return err
89107
} else if err != nil && errors.Is(err, dblevel.NoEntryErr{}) {
90108
// this case should not even occur at this stage as utxos are not deleted before this query and are only marked as spent
91-
common.DebugLogger.Printf("txid: %x\n", utxo.Txid)
109+
common.DebugLogger.Printf("txid: %s\n", utxo.Txid)
92110
common.DebugLogger.Println("no UTXOs were found for transaction")
93111
continue
94112
}
@@ -151,12 +169,11 @@ func ReindexDustLimitsOnly() error {
151169
}
152170
common.InfoLogger.Println("Reindexing dust limit done")
153171
return nil
154-
155172
}
156173

157-
// Prune
158-
// This function checks the utxo set and deletes all utxos of a transaction where all utxos are marked as spent.
159-
// it can then also remove the tweak of completely spent transaction
160-
func Prune() error {
161-
return nil
174+
// PruneUTXOs
175+
// This function searches the UTXO set for transactions where all UTXOs are marked as spent, and removes those UTXOs.
176+
func PruneAllUTXOs() error {
177+
common.InfoLogger.Println("Pruning All UTXOs")
178+
return dblevel.PruneUTXOs(nil)
162179
}

src/core/routine.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ func PullBlock(blockHash string) (*types.Block, error) {
8484

8585
// CheckBlock checks whether the block hash has already been processed and will process the block if needed
8686
func CheckBlock(block *types.Block) {
87+
// todo add return type error
8788
// todo this should fail at the highest instance were its wrapped in,
8889
// fatal made sense here while it only had one use,
8990
// but might not want to exit the program if used in other locations
9091
//common.InfoLogger.Println("Processing block:", block.Height)
92+
common.DebugLogger.Println("block:", block.Height)
9193

9294
err := HandleBlock(block)
9395
if err != nil {

src/dataexport/exportcsv.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import (
66
"SilentPaymentAppBackend/src/db/dblevel"
77
"encoding/csv"
88
"encoding/hex"
9+
"fmt"
910
"log"
1011
"os"
1112
"strconv"
1213
)
1314

1415
func writeToCSV(path string, records [][]string) error {
1516
// Create a new file
17+
os.MkdirAll(fmt.Sprintf("%s/export", common.BaseDirectory), 0750)
1618
file, err := os.Create(path)
1719
if err != nil {
1820
log.Fatalf("failed creating file: %s", err)

src/db/dblevel/blockheaderinv.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"SilentPaymentAppBackend/src/common/types"
66
"bytes"
77
"encoding/binary"
8+
89
"github.com/syndtr/goleveldb/leveldb/util"
910
)
1011

@@ -59,7 +60,6 @@ func FetchHighestBlockHeaderInv() (*types.BlockHeaderInv, error) {
5960
var result types.BlockHeaderInv
6061

6162
if iter.Last() {
62-
// Deserialize data first
6363
err := result.DeSerialiseData(iter.Value())
6464
if err != nil {
6565
common.ErrorLogger.Println(err)

src/db/dblevel/tweak.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
// InsertBatchTweaks index implements cut through and dust
1414
func InsertBatchTweaks(tweaks []types.Tweak) error {
15-
common.DebugLogger.Println("Inserting tweaks...")
15+
// common.DebugLogger.Println("Inserting tweaks...")
1616
// Create a slice of types.Pair with the same length as pairs
1717
pairs := make([]types.Pair, len(tweaks))
1818

@@ -45,7 +45,7 @@ func OverWriteTweaks(tweaks []types.Tweak) error {
4545
return err // keep this as an error. if this happens we have to know
4646
}
4747

48-
// this will be removed as we still check
48+
// this will be removed as we still test, see below
4949
if len(pairs) != 1 {
5050
// this scenario should never happen. The database should not have >1 entries for one transaction. <1 (0) should give no entry error
5151
// prev

src/db/dblevel/utxo.go

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"SilentPaymentAppBackend/src/common"
55
"SilentPaymentAppBackend/src/common/types"
66
"errors"
7+
8+
"github.com/syndtr/goleveldb/leveldb/util"
79
)
810

911
func InsertUTXOs(utxos []types.UTXO) error {
10-
common.DebugLogger.Println("Inserting UTXOs...")
12+
// common.DebugLogger.Println("Inserting UTXOs...")
1113
// Create a slice of types.Pair with the same length as pairs
1214
pairs := make([]types.Pair, len(utxos))
1315

@@ -83,7 +85,7 @@ func FetchByBlockHashAndTxidUTXOs(blockHash, txid string) ([]types.UTXO, error)
8385
}
8486

8587
func DeleteBatchUTXOs(utxos []types.UTXO) error {
86-
common.DebugLogger.Println("Deleting UTXOs...")
88+
// common.DebugLogger.Println("Deleting UTXOs...")
8789
// Create a slice of types.Pair with the same length as pairs
8890
pairs := make([]types.Pair, len(utxos))
8991

@@ -125,3 +127,83 @@ func FetchAllUTXOs() ([]types.UTXO, error) {
125127
}
126128
return result, err
127129
}
130+
131+
// PruneUTXOs iterates over a set of utxos according to a prefix (all if set to nil).
132+
// The function checks whether the utxos are eligible for removal.
133+
func PruneUTXOs(prefix []byte) error {
134+
iter := UTXOsDB.NewIterator(util.BytesPrefix(prefix), nil)
135+
defer iter.Release()
136+
137+
// totalSet is for the final batch deletion
138+
var totalSetToDelete []types.UTXO
139+
140+
var lastTxid string
141+
var canBeRemoved = true
142+
var currentSet []types.UTXO
143+
144+
var err error
145+
146+
for iter.Next() {
147+
148+
var value types.UTXO
149+
150+
err = value.DeSerialiseKey(iter.Key())
151+
if err != nil {
152+
common.ErrorLogger.Println(err)
153+
return err
154+
}
155+
156+
if lastTxid == "" {
157+
lastTxid = value.Txid
158+
}
159+
160+
if !canBeRemoved && value.Txid == lastTxid {
161+
continue
162+
}
163+
164+
err = value.DeSerialiseData(iter.Value())
165+
if err != nil {
166+
common.ErrorLogger.Println(err)
167+
return err
168+
}
169+
170+
if value.Spent {
171+
currentSet = append(currentSet, value)
172+
} else {
173+
canBeRemoved = false
174+
}
175+
176+
if value.Txid != lastTxid {
177+
// delete the current set of UTXOs if eligible
178+
179+
// do deletion
180+
if lastTxid != "" && canBeRemoved {
181+
totalSetToDelete = append(totalSetToDelete, currentSet...)
182+
// common.DebugLogger.Printf("Added %d UTXOs for deletion - %s\n", len(currentSet), lastTxid)
183+
}
184+
185+
// reset state
186+
currentSet = nil
187+
canBeRemoved = true
188+
}
189+
lastTxid = value.Txid
190+
}
191+
192+
// Handle the last batch of UTXOs after the loop
193+
if canBeRemoved && len(currentSet) > 0 {
194+
totalSetToDelete = append(totalSetToDelete, currentSet...)
195+
// common.DebugLogger.Printf("Added %d UTXOs for deletion - %s\n", len(currentSet), lastTxid)
196+
}
197+
err = iter.Error()
198+
if err != nil {
199+
common.ErrorLogger.Println(err)
200+
return err
201+
}
202+
203+
err = DeleteBatchUTXOs(totalSetToDelete)
204+
if err != nil {
205+
common.ErrorLogger.Println(err)
206+
return err
207+
}
208+
return nil
209+
}

src/main.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"log"
1313
"path"
1414

15-
//_ "net/http/pprof" // Import for side effects: registers pprof handlers with the default mux.
1615
"os"
1716
"os/signal"
1817
"strings"
@@ -21,17 +20,16 @@ import (
2120

2221
var (
2322
displayVersion bool
23+
pruneOnStart bool
24+
exportData bool
2425
Version = "0.0.0"
2526
)
2627

2728
func init() {
28-
// for profiling or testing iirc
29-
//go func() {
30-
// log.Println(http.ListenAndServe("localhost:6060", nil))
31-
//}()
32-
3329
flag.StringVar(&common.BaseDirectory, "datadir", common.DefaultBaseDirectory, "Set the base directory for blindbit oracle. Default directory is ~/.blindbit-oracle")
3430
flag.BoolVar(&displayVersion, "version", false, "show version of blindbit-oracle")
31+
flag.BoolVar(&pruneOnStart, "reprune", false, "set this flag if you want to prune on startup")
32+
flag.BoolVar(&exportData, "export-data", false, "export the databases")
3533
flag.Parse()
3634

3735
if displayVersion {
@@ -123,10 +121,20 @@ func main() {
123121
// make sure everything is ready before we receive data
124122

125123
//todo create proper handling for exporting data
126-
//exportAll()
124+
125+
if exportData {
126+
common.InfoLogger.Println("Exporting data")
127+
dataexport.ExportUTXOs(fmt.Sprintf("%s/export/utxos.csv", common.BaseDirectory))
128+
return
129+
}
127130

128131
//moved into go routine such that the interrupt signal will apply properly
129132
go func() {
133+
if pruneOnStart {
134+
startPrune := time.Now()
135+
core.PruneAllUTXOs()
136+
common.InfoLogger.Printf("Pruning took: %s", time.Since(startPrune).String())
137+
}
130138
startSync := time.Now()
131139
err := core.PreSyncHeaders()
132140
if err != nil {

src/server/middleware.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func FetchHeaderInvMiddleware(c *gin.Context) {
2828
headerInv, err := dblevel.FetchByBlockHeightBlockHeaderInv(uint32(height))
2929
if err != nil {
3030
common.ErrorLogger.Println(err)
31-
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not fetch header inventory"})
31+
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not fetch header inv"})
3232
c.Abort()
3333
return
3434
}

0 commit comments

Comments
 (0)