lnd.xprv/uspv/utxodb.go

288 lines
7.1 KiB
Go

package uspv
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/boltdb/bolt"
)
var (
BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest
BKTOld = []byte("SpentTxs") // for bookkeeping
BKTState = []byte("MiscState") // last state of DB
KEYNumKeys = []byte("NumKeys") // number of keys used
)
func (ts *TxStore) OpenDB(filename string) error {
var err error
ts.StateDB, err = bolt.Open(filename, 0644, nil)
if err != nil {
return err
}
// create buckets if they're not already there
return ts.StateDB.Update(func(tx *bolt.Tx) error {
_, err = tx.CreateBucketIfNotExists(BKTUtxos)
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists(BKTOld)
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists(BKTState)
if err != nil {
return err
}
return nil
})
}
// NewAdr creates a new, never before seen address, and increments the
// DB counter as well as putting it in the ram Adrs store, and returns it
func (ts *TxStore) NewAdr() (*btcutil.AddressPubKeyHash, error) {
if ts.Param == nil {
return nil, fmt.Errorf("nil param")
}
n := uint32(len(ts.Adrs))
priv, err := ts.rootPrivKey.Child(n) // + hdkeychain.HardenedKeyStart)
if err != nil {
return nil, err
}
newAdr, err := priv.Address(ts.Param)
if err != nil {
return nil, err
}
// total number of keys (now +1) into 4 bytes
var buf bytes.Buffer
err = binary.Write(&buf, binary.BigEndian, n+1)
if err != nil {
return nil, err
}
// write to db file
err = ts.StateDB.Update(func(tx *bolt.Tx) error {
stt := tx.Bucket(BKTState)
return stt.Put(KEYNumKeys, buf.Bytes())
})
if err != nil {
return nil, err
}
// add in to ram.
ts.AddAdr(newAdr, n)
return newAdr, nil
}
// PopulateAdrs just puts a bunch of adrs in ram; it doesn't touch the DB
func (ts *TxStore) PopulateAdrs(lastKey uint32) error {
for k := uint32(0); k < lastKey; k++ {
priv, err := ts.rootPrivKey.Child(k) // + hdkeychain.HardenedKeyStart)
if err != nil {
return err
}
newAdr, err := priv.Address(ts.Param)
if err != nil {
return err
}
ts.AddAdr(newAdr, k)
}
return nil
}
func (u *Utxo) SaveToDB(dbx *bolt.DB) error {
return dbx.Update(func(tx *bolt.Tx) error {
duf := tx.Bucket(BKTUtxos)
b, err := u.ToBytes()
if err != nil {
return err
}
// key : val is txid:everything else
return duf.Put(b[:36], b[36:])
})
}
func (ts *TxStore) MarkSpent(op *wire.OutPoint, h int32, stx *wire.MsgTx) error {
// we write in key = outpoint (32 hash, 4 index)
// value = spending txid
// if we care about the spending tx we can store that in another bucket.
return ts.StateDB.Update(func(tx *bolt.Tx) error {
old := tx.Bucket(BKTOld)
opb, err := outPointToBytes(op)
if err != nil {
return err
}
var buf bytes.Buffer
err = binary.Write(&buf, binary.BigEndian, h)
if err != nil {
return err
}
sha := stx.TxSha()
err = old.Put(opb, sha.Bytes()) // write k:v outpoint:txid
if err != nil {
return err
}
return nil
})
}
// LoadFromDB loads everything in the db file into ram, rebuilding the TxStore
// (except the rootPrivKey, that should be done before calling this --
// this will error if ts.rootPrivKey hasn't been loaded)
func (ts *TxStore) LoadFromDB() error {
if ts.rootPrivKey == nil {
return fmt.Errorf("LoadFromDB needs rootPrivKey loaded")
}
return ts.StateDB.View(func(tx *bolt.Tx) error {
duf := tx.Bucket(BKTUtxos)
if duf == nil {
return fmt.Errorf("no duffel bag")
}
spent := tx.Bucket(BKTOld)
if spent == nil {
return fmt.Errorf("no spenttx bucket")
}
state := tx.Bucket(BKTState)
if state == nil {
return fmt.Errorf("no state bucket")
}
// first populate addresses from state bucket
numKeysBytes := state.Get(KEYNumKeys)
if numKeysBytes != nil { // NumKeys exists, read into uint32
buf := bytes.NewBuffer(numKeysBytes)
var numKeys uint32
err := binary.Read(buf, binary.BigEndian, &numKeys)
if err != nil {
return err
}
fmt.Printf("db says %d keys\n", numKeys)
err = ts.PopulateAdrs(numKeys)
if err != nil {
return err
}
}
// next load all utxos from db into ram
duf.ForEach(func(k, v []byte) error {
// have to copy k and v here, otherwise append will crash it.
// not quite sure why but append does weird stuff I guess.
stx := spent.Get(k)
if stx == nil { // if it's not in the spent bucket
// create a new utxo
x := make([]byte, len(k)+len(v))
copy(x, k)
copy(x[len(k):], v)
newU, err := UtxoFromBytes(x)
if err != nil {
return err
}
// and add it to ram
ts.Sum += newU.Value
ts.Utxos = append(ts.Utxos, newU)
} else {
fmt.Printf("had utxo %x but spent by tx %x...\n",
k, stx[:8])
}
return nil
})
return nil
})
}
// outPointToBytes turns an outpoint into 36 bytes.
func outPointToBytes(op *wire.OutPoint) ([]byte, error) {
var buf bytes.Buffer
_, err := buf.Write(op.Hash.Bytes())
if err != nil {
return nil, err
}
// write 4 byte outpoint index within the tx to spend
err = binary.Write(&buf, binary.BigEndian, op.Index)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// ToBytes turns a Utxo into some bytes.
// note that the txid is the first 36 bytes and in our use cases will be stripped
// off, but is left here for other applications
func (u *Utxo) ToBytes() ([]byte, error) {
var buf bytes.Buffer
// write 32 byte txid of the utxo
_, err := buf.Write(u.Op.Hash.Bytes())
if err != nil {
return nil, err
}
// write 4 byte outpoint index within the tx to spend
err = binary.Write(&buf, binary.BigEndian, u.Op.Index)
if err != nil {
return nil, err
}
// write 4 byte height of utxo
err = binary.Write(&buf, binary.BigEndian, u.AtHeight)
if err != nil {
return nil, err
}
// write 4 byte key index of utxo
err = binary.Write(&buf, binary.BigEndian, u.KeyIdx)
if err != nil {
return nil, err
}
// write 8 byte amount of money at the utxo
err = binary.Write(&buf, binary.BigEndian, u.Value)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// UtxoFromBytes turns bytes into a Utxo. Note it wants the txid and outindex
// in the first 36 bytes, which isn't stored that way in the boldDB,
// but can be easily appended.
func UtxoFromBytes(b []byte) (Utxo, error) {
var u Utxo
if b == nil {
return u, fmt.Errorf("nil input slice")
}
buf := bytes.NewBuffer(b)
if buf.Len() < 52 { // minimum 52 bytes with no pkscript
return u, fmt.Errorf("Got %d bytes for sender, expect > 52", buf.Len())
}
// read 32 byte txid
err := u.Op.Hash.SetBytes(buf.Next(32))
if err != nil {
return u, err
}
// read 4 byte outpoint index within the tx to spend
err = binary.Read(buf, binary.BigEndian, &u.Op.Index)
if err != nil {
return u, err
}
// read 4 byte height of utxo
err = binary.Read(buf, binary.BigEndian, &u.AtHeight)
if err != nil {
return u, err
}
// read 4 byte key index of utxo
err = binary.Read(buf, binary.BigEndian, &u.KeyIdx)
if err != nil {
return u, err
}
// read 8 byte amount of money at the utxo
err = binary.Read(buf, binary.BigEndian, &u.Value)
if err != nil {
return u, err
}
return u, nil
}