/
calc.go
129 lines (111 loc) · 3.27 KB
/
calc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package main
import (
"bytes"
"encoding/binary"
"github.com/gcash/bchd/bchec"
"github.com/gcash/bchd/blockchain"
"github.com/gcash/bchd/chaincfg/chainhash"
"github.com/gcash/bchd/database"
"github.com/gcash/bchd/wire"
"io"
)
var (
utxoSetBucketName = []byte("utxosetv2")
byteBuf32 = make([]byte, 4)
byteBuf64 = make([]byte, 8)
buf bytes.Buffer
)
// serializeV0Utxo returns a Utxo serialized into the v0 Utxo commitment format
func serializeV0Utxo(entry *blockchain.UtxoEntry, outpoint *wire.OutPoint) []byte {
buf = bytes.Buffer{}
buf.Write(outpoint.Hash.CloneBytes())
binary.LittleEndian.PutUint32(byteBuf32, outpoint.Index)
buf.Write(byteBuf32)
binary.LittleEndian.PutUint32(byteBuf32, uint32(entry.BlockHeight()))
// If this is a coinbase then the least significant bit of the height should be set to 1
if entry.IsCoinBase() {
byteBuf32[3] |= 0x01
}
buf.Write(byteBuf32)
binary.LittleEndian.PutUint64(byteBuf64, uint64(entry.Amount()))
buf.Write(byteBuf64)
binary.LittleEndian.PutUint32(byteBuf32, uint32(len(entry.PkScript())))
buf.Write(byteBuf32)
buf.Write(entry.PkScript())
return buf.Bytes()
}
// CalcUtxoSet rolls back the chain to the given block height then loads
// the Utxo set and calculates the ECMH hash.
func CalcUtxoSet(db database.DB, height int32, utxoWriter io.Writer) (*chainhash.Hash, int, error) {
chain, err := blockchain.New(&blockchain.Config{
DB: db,
ChainParams: activeNetParams,
TimeSource: blockchain.NewMedianTime(),
// No nice way to get the main configuration here.
// For now just accept up to the default.
ExcessiveBlockSize: 32000000,
})
if err != nil {
return nil, 0, err
}
view, err := chain.RollbackUtxoSet(height)
if err != nil {
return nil, 0, err
}
log.Info("Loading Utxo set from disk. This is going to take a while...")
m := bchec.NewMultiset(bchec.S256())
// Let's avoid allocating new memory when iterating over utxos
var (
entry *blockchain.UtxoEntry
viewEntry *blockchain.UtxoEntry
outpoint *wire.OutPoint
serializedUtxo []byte
totalSize int
)
err = db.View(func(tx database.Tx) error {
utxoBucket := tx.Metadata().Bucket(utxoSetBucketName)
return utxoBucket.ForEach(func(k, v []byte) error {
entry, err = blockchain.DeserializeUtxoEntry(v)
if err != nil {
return err
}
outpoint = blockchain.DeserializeOutpointKey(k)
// If it's in the view let's just exit as we'll deal
// with the view later.
viewEntry = view.LookupEntry(*outpoint)
if viewEntry != nil {
return nil
}
serializedUtxo = serializeV0Utxo(entry, outpoint)
m.Add(serializedUtxo)
totalSize += len(serializedUtxo)
if utxoWriter != nil {
_, err = utxoWriter.Write(serializedUtxo)
if err != nil {
return err
}
}
return nil
})
})
if err != nil {
return nil, 0, err
}
// Finally loop through the entries in the view and add all that aren't spent.
for outpoint, entry := range view.Entries() {
if entry.IsSpent() {
continue
}
serializedUtxo = serializeV0Utxo(entry, &outpoint)
m.Add(serializedUtxo)
totalSize += len(serializedUtxo)
if utxoWriter != nil {
_, err = utxoWriter.Write(serializedUtxo)
if err != nil {
return nil, 0, err
}
}
}
h := m.Hash()
return &h, totalSize, nil
}