mirror of https://github.com/BTCPrivate/lnd.git
new stxo struct and more db methods
I'm getting away from having both in-ram and on-disk stores for the transaction store data. it should all be on disk, it's safer that way. It might be slower but this will not process many txs / second anyway.
This commit is contained in:
parent
851d3533e5
commit
d9afd623eb
|
@ -222,10 +222,19 @@ func (s *SPVCon) AskForTx(txid wire.ShaHash) {
|
||||||
// We don't have it in our header file so when we get it we do both operations:
|
// We don't have it in our header file so when we get it we do both operations:
|
||||||
// appending and checking the header, and checking spv proofs
|
// appending and checking the header, and checking spv proofs
|
||||||
func (s *SPVCon) AskForBlock(hsh wire.ShaHash) {
|
func (s *SPVCon) AskForBlock(hsh wire.ShaHash) {
|
||||||
|
|
||||||
|
fmt.Printf("mBlockQueue len %d\n", len(s.mBlockQueue))
|
||||||
|
// wait until all mblocks are done before adding
|
||||||
|
for len(s.mBlockQueue) != 0 {
|
||||||
|
// fmt.Printf("mBlockQueue len %d\n", len(s.mBlockQueue))
|
||||||
|
}
|
||||||
|
|
||||||
gdata := wire.NewMsgGetData()
|
gdata := wire.NewMsgGetData()
|
||||||
inv := wire.NewInvVect(wire.InvTypeFilteredBlock, &hsh)
|
inv := wire.NewInvVect(wire.InvTypeFilteredBlock, &hsh)
|
||||||
gdata.AddInvVect(inv)
|
gdata.AddInvVect(inv)
|
||||||
|
|
||||||
|
// TODO - wait until headers are sync'd before checking height
|
||||||
|
|
||||||
info, err := s.headerFile.Stat() // get
|
info, err := s.headerFile.Stat() // get
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err) // crash if header file disappears
|
log.Fatal(err) // crash if header file disappears
|
||||||
|
@ -437,7 +446,11 @@ func (s *SPVCon) AskForMerkBlocks(current, last int32) error {
|
||||||
}
|
}
|
||||||
fmt.Printf("will request merkleblocks %d to %d\n", current, last)
|
fmt.Printf("will request merkleblocks %d to %d\n", current, last)
|
||||||
// track number of utxos
|
// track number of utxos
|
||||||
track := len(s.TS.Utxos)
|
track, err := s.TS.NumUtxos()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// create initial filter
|
// create initial filter
|
||||||
filt, err := s.TS.GimmeFilter()
|
filt, err := s.TS.GimmeFilter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -454,15 +467,20 @@ func (s *SPVCon) AskForMerkBlocks(current, last int32) error {
|
||||||
// loop through all heights where we want merkleblocks.
|
// loop through all heights where we want merkleblocks.
|
||||||
for current < last {
|
for current < last {
|
||||||
// check if we need to update filter... diff of 5 utxos...?
|
// check if we need to update filter... diff of 5 utxos...?
|
||||||
if track < len(s.TS.Utxos)-4 || track > len(s.TS.Utxos)+4 {
|
nTrack, err := s.TS.NumUtxos()
|
||||||
track = len(s.TS.Utxos)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if track < nTrack-4 || track > nTrack+4 {
|
||||||
|
track = nTrack
|
||||||
filt, err := s.TS.GimmeFilter()
|
filt, err := s.TS.GimmeFilter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.SendFilter(filt)
|
s.SendFilter(filt)
|
||||||
|
|
||||||
fmt.Printf("sent filter %x\n", filt.MsgFilterLoad().Filter)
|
fmt.Printf("sent %d byte filter\n", len(filt.MsgFilterLoad().Filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
// load header from file
|
// load header from file
|
||||||
|
|
|
@ -54,18 +54,14 @@ func (s *SPVCon) incomingMessageHandler() {
|
||||||
log.Printf("Rejected! cmd: %s code: %s tx: %s reason: %s",
|
log.Printf("Rejected! cmd: %s code: %s tx: %s reason: %s",
|
||||||
m.Cmd, m.Code.String(), m.Hash.String(), m.Reason)
|
m.Cmd, m.Code.String(), m.Hash.String(), m.Reason)
|
||||||
case *wire.MsgInv:
|
case *wire.MsgInv:
|
||||||
log.Printf("got inv. Contains:\n")
|
go s.InvHandler(m)
|
||||||
|
|
||||||
|
case *wire.MsgNotFound:
|
||||||
|
log.Printf("Got not found response from remote:")
|
||||||
for i, thing := range m.InvList {
|
for i, thing := range m.InvList {
|
||||||
log.Printf("\t%d)%s : %s",
|
log.Printf("\t$d) %s: %s", i, thing.Type, thing.Hash)
|
||||||
i, thing.Type.String(), thing.Hash.String())
|
|
||||||
if thing.Type == wire.InvTypeTx { // new tx, ingest
|
|
||||||
s.TS.OKTxids[thing.Hash] = 0 // unconfirmed
|
|
||||||
s.AskForTx(thing.Hash)
|
|
||||||
}
|
|
||||||
if thing.Type == wire.InvTypeBlock { // new block, ingest
|
|
||||||
s.AskForBlock(thing.Hash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Printf("Got unknown message type %s\n", m.Command())
|
log.Printf("Got unknown message type %s\n", m.Command())
|
||||||
}
|
}
|
||||||
|
@ -86,3 +82,18 @@ func (s *SPVCon) outgoingMessageHandler() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SPVCon) InvHandler(m *wire.MsgInv) {
|
||||||
|
log.Printf("got inv. Contains:\n")
|
||||||
|
for i, thing := range m.InvList {
|
||||||
|
log.Printf("\t%d)%s : %s",
|
||||||
|
i, thing.Type.String(), thing.Hash.String())
|
||||||
|
if thing.Type == wire.InvTypeTx { // new tx, ingest
|
||||||
|
s.TS.OKTxids[thing.Hash] = 0 // unconfirmed
|
||||||
|
s.AskForTx(thing.Hash)
|
||||||
|
}
|
||||||
|
if thing.Type == wire.InvTypeBlock { // new block, ingest
|
||||||
|
s.AskForBlock(thing.Hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,12 +32,21 @@ type TxStore struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Utxo struct { // cash money.
|
type Utxo struct { // cash money.
|
||||||
|
Op wire.OutPoint // where
|
||||||
|
|
||||||
// all the info needed to spend
|
// all the info needed to spend
|
||||||
AtHeight int32 // block height where this tx was confirmed, 0 for unconf
|
AtHeight int32 // block height where this tx was confirmed, 0 for unconf
|
||||||
KeyIdx uint32 // index for private key needed to sign / spend
|
KeyIdx uint32 // index for private key needed to sign / spend
|
||||||
Value int64 // higher is better
|
Value int64 // higher is better
|
||||||
|
|
||||||
Op wire.OutPoint // where
|
// IsCoinbase bool // can't spend for a while
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stxo is a utxo that has moved on.
|
||||||
|
type Stxo struct {
|
||||||
|
Utxo // when it used to be a utxo
|
||||||
|
SpendHeight int32 // height at which it met its demise
|
||||||
|
SpendTxid wire.ShaHash // the tx that consumed it
|
||||||
}
|
}
|
||||||
|
|
||||||
type MyAdr struct { // an address I have the private key for
|
type MyAdr struct { // an address I have the private key for
|
||||||
|
@ -79,7 +88,12 @@ func (t *TxStore) GimmeFilter() (*bloom.Filter, error) {
|
||||||
return nil, fmt.Errorf("no addresses to filter for")
|
return nil, fmt.Errorf("no addresses to filter for")
|
||||||
}
|
}
|
||||||
// add addresses to look for incoming
|
// add addresses to look for incoming
|
||||||
elem := uint32(len(t.Adrs) + len(t.Utxos))
|
nutxo, err := t.NumUtxos()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := uint32(len(t.Adrs)) + nutxo
|
||||||
f := bloom.NewFilter(elem, 0, 0.001, wire.BloomUpdateAll)
|
f := bloom.NewFilter(elem, 0, 0.001, wire.BloomUpdateAll)
|
||||||
for _, a := range t.Adrs {
|
for _, a := range t.Adrs {
|
||||||
f.Add(a.PkhAdr.ScriptAddress())
|
f.Add(a.PkhAdr.ScriptAddress())
|
||||||
|
@ -121,7 +135,7 @@ func (t *TxStore) AbsorbTx(tx *wire.MsgTx, height int32) error {
|
||||||
newTxid := tx.TxSha()
|
newTxid := tx.TxSha()
|
||||||
var hits uint32 // how many outputs of this tx are ours
|
var hits uint32 // how many outputs of this tx are ours
|
||||||
var acq int64 // total acquirement from this tx
|
var acq int64 // total acquirement from this tx
|
||||||
// check if any of the tx's outputs match my adrs
|
// check if any of the tx's outputs match my known outpoints
|
||||||
for i, out := range tx.TxOut { // in each output of tx
|
for i, out := range tx.TxOut { // in each output of tx
|
||||||
dup := false // start by assuming its new until found duplicate
|
dup := false // start by assuming its new until found duplicate
|
||||||
newOp := wire.NewOutPoint(&newTxid, uint32(i))
|
newOp := wire.NewOutPoint(&newTxid, uint32(i))
|
||||||
|
@ -133,7 +147,7 @@ func (t *TxStore) AbsorbTx(tx *wire.MsgTx, height int32) error {
|
||||||
fmt.Printf(" %s is dupe\t", newOp.String())
|
fmt.Printf(" %s is dupe\t", newOp.String())
|
||||||
u.AtHeight = height // ONLY difference is height
|
u.AtHeight = height // ONLY difference is height
|
||||||
// save modified utxo to db, overwriting old one
|
// save modified utxo to db, overwriting old one
|
||||||
err := u.SaveToDB(t.StateDB)
|
err := t.SaveUtxo(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -145,15 +159,15 @@ func (t *TxStore) AbsorbTx(tx *wire.MsgTx, height int32) error {
|
||||||
// when it matches an address, just go to the next outpoint
|
// when it matches an address, just go to the next outpoint
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// check if this is a new txout matching one of my addresses
|
||||||
for _, a := range t.Adrs { // compare to each adr we have
|
for _, a := range t.Adrs { // compare to each adr we have
|
||||||
// check for full script to eliminate false positives
|
// check for full script to eliminate false positives
|
||||||
aPKscript, err := txscript.PayToAddrScript(a.PkhAdr)
|
aPKscript, err := txscript.PayToAddrScript(a.PkhAdr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// already checked for dupes, this must be a new outpoint
|
|
||||||
if bytes.Equal(out.PkScript, aPKscript) { // hit
|
if bytes.Equal(out.PkScript, aPKscript) { // hit
|
||||||
|
// already checked for dupes, so this must be a new outpoint
|
||||||
var newu Utxo
|
var newu Utxo
|
||||||
newu.AtHeight = height
|
newu.AtHeight = height
|
||||||
newu.KeyIdx = a.KeyIdx
|
newu.KeyIdx = a.KeyIdx
|
||||||
|
@ -163,7 +177,7 @@ func (t *TxStore) AbsorbTx(tx *wire.MsgTx, height int32) error {
|
||||||
newop.Hash = tx.TxSha()
|
newop.Hash = tx.TxSha()
|
||||||
newop.Index = uint32(i)
|
newop.Index = uint32(i)
|
||||||
newu.Op = newop
|
newu.Op = newop
|
||||||
err = newu.SaveToDB(t.StateDB)
|
err = t.SaveUtxo(&newu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -193,7 +207,7 @@ func (t *TxStore) ExpellTx(tx *wire.MsgTx, height int32) error {
|
||||||
if OutPointsEqual(myutxo.Op, in.PreviousOutPoint) {
|
if OutPointsEqual(myutxo.Op, in.PreviousOutPoint) {
|
||||||
hits++
|
hits++
|
||||||
loss += myutxo.Value
|
loss += myutxo.Value
|
||||||
err := t.MarkSpent(&myutxo.Op, height, tx)
|
err := t.MarkSpent(*myutxo, height, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
187
uspv/utxodb.go
187
uspv/utxodb.go
|
@ -14,7 +14,8 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest
|
BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest
|
||||||
BKTOld = []byte("SpentTxs") // for bookkeeping
|
BKTStxos = []byte("SpentTxs") // for bookkeeping
|
||||||
|
BKTTxns = []byte("Txns") // all txs we care about, for replays
|
||||||
BKTState = []byte("MiscState") // last state of DB
|
BKTState = []byte("MiscState") // last state of DB
|
||||||
|
|
||||||
KEYNumKeys = []byte("NumKeys") // number of keys used
|
KEYNumKeys = []byte("NumKeys") // number of keys used
|
||||||
|
@ -27,16 +28,20 @@ func (ts *TxStore) OpenDB(filename string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// create buckets if they're not already there
|
// create buckets if they're not already there
|
||||||
return ts.StateDB.Update(func(tx *bolt.Tx) error {
|
return ts.StateDB.Update(func(btx *bolt.Tx) error {
|
||||||
_, err = tx.CreateBucketIfNotExists(BKTUtxos)
|
_, err = btx.CreateBucketIfNotExists(BKTUtxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = tx.CreateBucketIfNotExists(BKTOld)
|
_, err = btx.CreateBucketIfNotExists(BKTStxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = tx.CreateBucketIfNotExists(BKTState)
|
_, err = btx.CreateBucketIfNotExists(BKTTxns)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = btx.CreateBucketIfNotExists(BKTState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -69,8 +74,8 @@ func (ts *TxStore) NewAdr() (*btcutil.AddressPubKeyHash, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// write to db file
|
// write to db file
|
||||||
err = ts.StateDB.Update(func(tx *bolt.Tx) error {
|
err = ts.StateDB.Update(func(btx *bolt.Tx) error {
|
||||||
stt := tx.Bucket(BKTState)
|
stt := btx.Bucket(BKTState)
|
||||||
return stt.Put(KEYNumKeys, buf.Bytes())
|
return stt.Put(KEYNumKeys, buf.Bytes())
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -81,6 +86,24 @@ func (ts *TxStore) NewAdr() (*btcutil.AddressPubKeyHash, error) {
|
||||||
return newAdr, nil
|
return newAdr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumUtxos returns the number of utxos in the DB.
|
||||||
|
func (ts *TxStore) NumUtxos() (uint32, error) {
|
||||||
|
var n uint32
|
||||||
|
err := ts.StateDB.View(func(btx *bolt.Tx) error {
|
||||||
|
duf := btx.Bucket(BKTUtxos)
|
||||||
|
if duf == nil {
|
||||||
|
return fmt.Errorf("no duffel bag")
|
||||||
|
}
|
||||||
|
stats := duf.Stats()
|
||||||
|
n = uint32(stats.KeyN)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PopulateAdrs just puts a bunch of adrs in ram; it doesn't touch the DB
|
// PopulateAdrs just puts a bunch of adrs in ram; it doesn't touch the DB
|
||||||
func (ts *TxStore) PopulateAdrs(lastKey uint32) error {
|
func (ts *TxStore) PopulateAdrs(lastKey uint32) error {
|
||||||
for k := uint32(0); k < lastKey; k++ {
|
for k := uint32(0); k < lastKey; k++ {
|
||||||
|
@ -101,10 +124,9 @@ func (ts *TxStore) PopulateAdrs(lastKey uint32) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveToDB write a utxo to disk, overwriting an old utxo of the same outpoint
|
// SaveToDB write a utxo to disk, overwriting an old utxo of the same outpoint
|
||||||
func (u *Utxo) SaveToDB(dbx *bolt.DB) error {
|
func (ts *TxStore) SaveUtxo(u *Utxo) error {
|
||||||
|
err := ts.StateDB.Update(func(btx *bolt.Tx) error {
|
||||||
err := dbx.Update(func(tx *bolt.Tx) error {
|
duf := btx.Bucket(BKTUtxos)
|
||||||
duf := tx.Bucket(BKTUtxos)
|
|
||||||
b, err := u.ToBytes()
|
b, err := u.ToBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -124,26 +146,47 @@ func (u *Utxo) SaveToDB(dbx *bolt.DB) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *TxStore) MarkSpent(op *wire.OutPoint, h int32, stx *wire.MsgTx) error {
|
func (ts *TxStore) MarkSpent(ut Utxo, h int32, stx *wire.MsgTx) error {
|
||||||
// we write in key = outpoint (32 hash, 4 index)
|
// we write in key = outpoint (32 hash, 4 index)
|
||||||
// value = spending txid
|
// value = spending txid
|
||||||
// if we care about the spending tx we can store that in another bucket.
|
// 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)
|
var st Stxo
|
||||||
opb, err := outPointToBytes(op)
|
st.Utxo = ut
|
||||||
|
st.SpendHeight = h
|
||||||
|
st.SpendTxid = stx.TxSha()
|
||||||
|
|
||||||
|
return ts.StateDB.Update(func(btx *bolt.Tx) error {
|
||||||
|
duf := btx.Bucket(BKTUtxos)
|
||||||
|
old := btx.Bucket(BKTStxos)
|
||||||
|
txns := btx.Bucket(BKTTxns)
|
||||||
|
|
||||||
|
opb, err := outPointToBytes(&st.Op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
|
||||||
err = binary.Write(&buf, binary.BigEndian, h)
|
err = duf.Delete(opb) // not utxo anymore
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stxb, err := st.ToBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = old.Put(opb, stxb) // write k:v outpoint:stxo bytes
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// store spending tx
|
||||||
sha := stx.TxSha()
|
sha := stx.TxSha()
|
||||||
err = old.Put(opb, sha.Bytes()) // write k:v outpoint:txid
|
var buf bytes.Buffer
|
||||||
if err != nil {
|
stx.Serialize(&buf)
|
||||||
return err
|
txns.Put(sha.Bytes(), buf.Bytes())
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -155,16 +198,16 @@ func (ts *TxStore) LoadFromDB() error {
|
||||||
if ts.rootPrivKey == nil {
|
if ts.rootPrivKey == nil {
|
||||||
return fmt.Errorf("LoadFromDB needs rootPrivKey loaded")
|
return fmt.Errorf("LoadFromDB needs rootPrivKey loaded")
|
||||||
}
|
}
|
||||||
return ts.StateDB.View(func(tx *bolt.Tx) error {
|
return ts.StateDB.View(func(btx *bolt.Tx) error {
|
||||||
duf := tx.Bucket(BKTUtxos)
|
duf := btx.Bucket(BKTUtxos)
|
||||||
if duf == nil {
|
if duf == nil {
|
||||||
return fmt.Errorf("no duffel bag")
|
return fmt.Errorf("no duffel bag")
|
||||||
}
|
}
|
||||||
spent := tx.Bucket(BKTOld)
|
spent := btx.Bucket(BKTStxos)
|
||||||
if spent == nil {
|
if spent == nil {
|
||||||
return fmt.Errorf("no spenttx bucket")
|
return fmt.Errorf("no spenttx bucket")
|
||||||
}
|
}
|
||||||
state := tx.Bucket(BKTState)
|
state := btx.Bucket(BKTState)
|
||||||
if state == nil {
|
if state == nil {
|
||||||
return fmt.Errorf("no state bucket")
|
return fmt.Errorf("no state bucket")
|
||||||
}
|
}
|
||||||
|
@ -268,8 +311,8 @@ func UtxoFromBytes(b []byte) (Utxo, error) {
|
||||||
return u, fmt.Errorf("nil input slice")
|
return u, fmt.Errorf("nil input slice")
|
||||||
}
|
}
|
||||||
buf := bytes.NewBuffer(b)
|
buf := bytes.NewBuffer(b)
|
||||||
if buf.Len() < 52 { // minimum 52 bytes with no pkscript
|
if buf.Len() < 52 { // utxos are 52 bytes
|
||||||
return u, fmt.Errorf("Got %d bytes for sender, expect > 52", buf.Len())
|
return u, fmt.Errorf("Got %d bytes for utxo, expect 52", buf.Len())
|
||||||
}
|
}
|
||||||
// read 32 byte txid
|
// read 32 byte txid
|
||||||
err := u.Op.Hash.SetBytes(buf.Next(32))
|
err := u.Op.Hash.SetBytes(buf.Next(32))
|
||||||
|
@ -298,3 +341,93 @@ func UtxoFromBytes(b []byte) (Utxo, error) {
|
||||||
}
|
}
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToBytes turns an Stxo into some bytes.
|
||||||
|
// outpoint txid, outpoint idx, height, key idx, amt, spendheight, spendtxid
|
||||||
|
func (s *Stxo) ToBytes() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
// write 32 byte txid of the utxo
|
||||||
|
_, err := buf.Write(s.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, s.Op.Index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// write 4 byte height of utxo
|
||||||
|
err = binary.Write(&buf, binary.BigEndian, s.AtHeight)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// write 4 byte key index of utxo
|
||||||
|
err = binary.Write(&buf, binary.BigEndian, s.KeyIdx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// write 8 byte amount of money at the utxo
|
||||||
|
err = binary.Write(&buf, binary.BigEndian, s.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// write 4 byte height where the txo was spent
|
||||||
|
err = binary.Write(&buf, binary.BigEndian, s.SpendHeight)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// write 32 byte txid of the spending transaction
|
||||||
|
_, err = buf.Write(s.SpendTxid.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StxoFromBytes turns bytes into a Stxo.
|
||||||
|
func StxoFromBytes(b []byte) (Stxo, error) {
|
||||||
|
var s Stxo
|
||||||
|
if b == nil {
|
||||||
|
return s, fmt.Errorf("nil input slice")
|
||||||
|
}
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
if buf.Len() < 88 { // stxos are 88 bytes
|
||||||
|
return s, fmt.Errorf("Got %d bytes for stxo, expect 88", buf.Len())
|
||||||
|
}
|
||||||
|
// read 32 byte txid
|
||||||
|
err := s.Op.Hash.SetBytes(buf.Next(32))
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
// read 4 byte outpoint index within the tx to spend
|
||||||
|
err = binary.Read(buf, binary.BigEndian, &s.Op.Index)
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
// read 4 byte height of utxo
|
||||||
|
err = binary.Read(buf, binary.BigEndian, &s.AtHeight)
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
// read 4 byte key index of utxo
|
||||||
|
err = binary.Read(buf, binary.BigEndian, &s.KeyIdx)
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
// read 8 byte amount of money at the utxo
|
||||||
|
err = binary.Read(buf, binary.BigEndian, &s.Value)
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
// read 4 byte spend height
|
||||||
|
err = binary.Read(buf, binary.BigEndian, &s.SpendHeight)
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
// read 32 byte txid
|
||||||
|
err = s.SpendTxid.SetBytes(buf.Next(32))
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue