lbcwallet/txstore/serialization.go
2014-10-01 17:27:55 +01:00

1217 lines
30 KiB
Go

/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package txstore
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/conformal/btcutil"
"github.com/conformal/btcwallet/rename"
"github.com/conformal/btcwire"
)
// filename is the name of the file typically used to save a transaction
// store on disk.
const filename = "tx.bin"
// All Store versions (both old and current).
const (
versFirst uint32 = iota
// versRecvTxIndex is the version where the txout index
// was added to the RecvTx struct.
versRecvTxIndex
// versMarkSentChange is the version where serialized SentTx
// added a flags field, used for marking a sent transaction
// as change.
versMarkSentChange
// versCombined is the version where the old utxo and tx stores
// were combined into a single data structure.
versCombined
// versFastRewrite is the version where the combined store was
// rewritten with a focus on insertion and lookup speed.
versFastRewrite
// versCurrent is the current tx file version.
versCurrent = versFastRewrite
)
// byteOrder is the byte order used to read and write txstore binary data.
var byteOrder = binary.LittleEndian
// ReadFrom satisifies the io.ReaderFrom interface by deserializing a
// transaction store from an io.Reader.
func (s *Store) ReadFrom(r io.Reader) (int64, error) {
// Don't bother locking this. The mutex gets overwritten anyways.
var buf [4]byte
uint32Bytes := buf[:4]
// Read current file version.
n, err := io.ReadFull(r, uint32Bytes)
n64 := int64(n)
if err != nil {
return n64, err
}
vers := byteOrder.Uint32(uint32Bytes)
// Reading files with versions before versFastRewrite is unsupported.
if vers < versFastRewrite {
return n64, ErrUnsupportedVersion
}
// Reset store.
*s = *New(s.path)
// Read block structures. Begin by reading the total number of block
// structures to be read, and then iterate that many times to read
// each block.
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
blockCount := byteOrder.Uint32(uint32Bytes)
// The blocks slice is *not* preallocated to blockCount size to prevent
// accidentally allocating so much memory that the process dies.
for i := uint32(0); i < blockCount; i++ {
b := &blockTxCollection{
txIndexes: map[int]uint32{},
}
tmpn64, err := b.ReadFrom(r)
n64 += tmpn64
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
s.blocks = append(s.blocks, b)
s.blockIndexes[b.Height] = i
// Recreate store unspent map.
for blockIndex, i := range b.txIndexes {
tx := b.txs[i]
for outputIdx, cred := range tx.credits {
if cred == nil {
continue
}
if cred.spentBy == nil {
op := btcwire.OutPoint{
Hash: *tx.tx.Sha(),
Index: uint32(outputIdx),
}
s.unspent[op] = BlockTxKey{
BlockIndex: blockIndex,
BlockHeight: b.Height,
}
}
}
}
}
// Read unconfirmed transactions and their spend tracking.
tmpn64, err := s.unconfirmed.ReadFrom(r)
n64 += tmpn64
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
return n64, nil
}
// WriteTo satisifies the io.WriterTo interface by serializing a transaction
// store to an io.Writer.
func (s *Store) WriteTo(w io.Writer) (int64, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.writeTo(w)
}
func (s *Store) writeTo(w io.Writer) (int64, error) {
var buf [4]byte
uint32Bytes := buf[:4]
// Write current file version.
byteOrder.PutUint32(uint32Bytes, versCurrent)
n, err := w.Write(uint32Bytes)
n64 := int64(n)
if err != nil {
return n64, err
}
// Write block structures. This begins with a uint32 specifying that
// some N blocks have been written, followed by N serialized transaction
// store blocks.
//
// The store's blockIndexes map is intentionally not written. Instead,
// it is recreated on reads after reading each block.
byteOrder.PutUint32(uint32Bytes, uint32(len(s.blocks)))
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
for _, b := range s.blocks {
n, err := b.WriteTo(w)
n64 += n
if err != nil {
return n64, err
}
}
// Write unconfirmed transactions and their spend tracking.
tmpn64, err := s.unconfirmed.WriteTo(w)
n64 += tmpn64
if err != nil {
return n64, err
}
// The store's unspent map is intentionally not written. Instead, it
// is recreated on reads after each block transaction collection has
// been read. This makes reads more expensive, but writing faster, and
// as writes are far more common in application use, this was deemed to
// be an acceptable tradeoff.
return n64, nil
}
func (b *blockTxCollection) ReadFrom(r io.Reader) (int64, error) {
var buf [8]byte
uint64Bytes := buf[:8]
uint32Bytes := buf[:4]
// Read block hash, unix time (int64), and height (int32).
n, err := io.ReadFull(r, b.Hash[:])
n64 := int64(n)
if err != nil {
return n64, err
}
n, err = io.ReadFull(r, uint64Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
b.Time = time.Unix(int64(byteOrder.Uint64(uint64Bytes)), 0)
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
b.Height = int32(byteOrder.Uint32(uint32Bytes))
// Read amount deltas as a result of transactions in this block. This
// is the net total spendable balance as a result of transaction debits
// and credits, and the block reward (not immediately spendable) for
// coinbase outputs. Both are int64s.
n, err = io.ReadFull(r, uint64Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
b.amountDeltas.Spendable = btcutil.Amount(byteOrder.Uint64(uint64Bytes))
n, err = io.ReadFull(r, uint64Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
b.amountDeltas.Reward = btcutil.Amount(byteOrder.Uint64(uint64Bytes))
// Read number of transaction records (as a uint32) followed by a read
// for each expected record.
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
txCount := byteOrder.Uint32(uint32Bytes)
// The txs slice is *not* preallocated to txcount size to prevent
// accidentally allocating so much memory that the process dies.
for i := uint32(0); i < txCount; i++ {
t := &txRecord{}
tmpn64, err := t.ReadFrom(r)
n64 += tmpn64
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
b.txs = append(b.txs, t)
// Recreate txIndexes map. For each transaction record, map the
// block index of the underlying transaction to the slice index
// of the record.
b.txIndexes[t.tx.Index()] = i
}
return n64, nil
}
func (b *blockTxCollection) WriteTo(w io.Writer) (int64, error) {
var buf [8]byte
uint64Bytes := buf[:8]
uint32Bytes := buf[:4]
// Write block hash, unix time (int64), and height (int32).
n, err := w.Write(b.Hash[:])
n64 := int64(n)
if err != nil {
return n64, err
}
byteOrder.PutUint64(uint64Bytes, uint64(b.Time.Unix()))
n, err = w.Write(uint64Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
byteOrder.PutUint32(uint32Bytes, uint32(b.Height))
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
// Write amount deltas as a result of transactions in this block.
// This is the net total spendable balance as a result of transaction
// debits and credits, and the block reward (not immediately spendable)
// for coinbase outputs. Both are int64s.
byteOrder.PutUint64(uint64Bytes, uint64(b.amountDeltas.Spendable))
n, err = w.Write(uint64Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
byteOrder.PutUint64(uint64Bytes, uint64(b.amountDeltas.Reward))
n, err = w.Write(uint64Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
// Write number of transaction records (as a uint32) followed by each
// transaction record.
byteOrder.PutUint32(uint32Bytes, uint32(len(b.txs)))
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
for _, t := range b.txs {
n, err := t.WriteTo(w)
n64 += n
if err != nil {
return n64, err
}
}
// The block's txIndexes and unspent bookkeeping maps are intentionally
// not written. They are instead recreated on reads. This makes reads
// more expensive, but writing faster, and as writes are far more common
// in application use, this was deemed to be an acceptable tradeoff.
return n64, nil
}
const (
nilPointer byte = iota
validPointer
)
func byteMarksValidPointer(b byte) (bool, error) {
switch b {
case nilPointer:
return false, nil
case validPointer:
return true, nil
default:
s := "invalid byte representation of valid pointer"
return false, errors.New(s)
}
}
const (
falseByte byte = iota
trueByte
)
func byteAsBool(b byte) (bool, error) {
switch b {
case falseByte:
return false, nil
case trueByte:
return true, nil
default:
return false, errors.New("invalid byte representation of bool")
}
}
func (t *txRecord) ReadFrom(r io.Reader) (int64, error) {
var buf [8]byte
uint64Bytes := buf[:8]
uint32Bytes := buf[:4]
singleByte := buf[:1]
// Read transaction index (as a uint32).
n, err := io.ReadFull(r, uint32Bytes)
n64 := int64(n)
if err != nil {
return n64, err
}
txIndex := int(byteOrder.Uint32(uint32Bytes))
// Deserialize transaction.
msgTx := new(msgTx)
tmpn64, err := msgTx.ReadFrom(r)
n64 += tmpn64
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
// Create and save the btcutil.Tx of the read MsgTx and set its index.
tx := btcutil.NewTx((*btcwire.MsgTx)(msgTx))
tx.SetIndex(txIndex)
t.tx = tx
// Read identifier for existance of debits.
n, err = io.ReadFull(r, singleByte)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
hasDebits, err := byteMarksValidPointer(singleByte[0])
if err != nil {
return n64, err
}
// If debits have been set, read them. Otherwise, set to nil.
if hasDebits {
// Read debited amount (int64).
n, err := io.ReadFull(r, uint64Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
amount := btcutil.Amount(byteOrder.Uint64(uint64Bytes))
// Read number of written outputs (as a uint32) this record
// debits from.
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
spendsCount := byteOrder.Uint32(uint32Bytes)
// For each expected output key, allocate and read the key,
// appending the result to the spends slice. This slice is
// originally set empty (*not* preallocated to spendsCount
// size) to prevent accidentally allocating so much memory that
// the process dies.
var spends []BlockOutputKey
for i := uint32(0); i < spendsCount; i++ {
k := BlockOutputKey{}
tmpn64, err := k.ReadFrom(r)
n64 += tmpn64
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
spends = append(spends, k)
}
t.debits = &debits{amount, spends}
} else {
t.debits = nil
}
// Read number of pointers (as a uint32) written to be read into the
// credits slice (although some may be nil).
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
creditsCount := byteOrder.Uint32(uint32Bytes)
// For each expected credits slice element, check whether the credit
// exists or the pointer is nil. If nil, append nil to credits and
// continue with the next. If non-nil, allocated and read the full
// credit structure. This slice is originally set to nil (*not*
// preallocated to creditsCount size) to prevent accidentally allocating
// so much memory that the process dies.
var credits []*credit
for i := uint32(0); i < creditsCount; i++ {
// Read identifer for a valid pointer.
n, err := io.ReadFull(r, singleByte)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
validCredit, err := byteMarksValidPointer(singleByte[0])
if err != nil {
return n64, err
}
if !validCredit {
credits = append(credits, nil)
} else {
// Read single byte that specifies whether this credit
// was added as change.
n, err = io.ReadFull(r, singleByte)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
change, err := byteAsBool(singleByte[0])
if err != nil {
return n64, err
}
// Read single byte. This was previously used to
// specify whether an unspent credit was locked or not,
// but this was removed as lockedness is volatile and
// should not be saved.
//
// This space can be used for additional flags in the
// future.
n, err = io.ReadFull(r, singleByte)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
// Read identifier for a valid pointer.
n, err = io.ReadFull(r, singleByte)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
validSpentBy, err := byteMarksValidPointer(singleByte[0])
if err != nil {
return n64, err
}
// If spentBy pointer is valid, allocate and read a
// transaction lookup key.
var spentBy *BlockTxKey
if validSpentBy {
spentBy = &BlockTxKey{}
tmpn64, err := spentBy.ReadFrom(r)
n64 += tmpn64
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
}
c := &credit{change, spentBy}
credits = append(credits, c)
}
}
t.credits = credits
// Read received unix time (int64).
n, err = io.ReadFull(r, uint64Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
received := int64(byteOrder.Uint64(uint64Bytes))
t.received = time.Unix(received, 0)
return n64, nil
}
func (t *txRecord) WriteTo(w io.Writer) (int64, error) {
var buf [8]byte
uint64Bytes := buf[:8]
uint32Bytes := buf[:4]
// Write transaction index (as a uint32).
byteOrder.PutUint32(uint32Bytes, uint32(t.tx.Index()))
n, err := w.Write(uint32Bytes)
n64 := int64(n)
if err != nil {
return n64, err
}
// Serialize and write transaction.
tmpn64, err := (*msgTx)(t.tx.MsgTx()).WriteTo(w)
n64 += tmpn64
if err != nil {
return n64, err
}
// Write debit records, if any. This begins with a single byte to
// identify whether the record contains any debits or not.
if t.debits == nil {
// Write identifier for nil debits.
n, err = w.Write([]byte{nilPointer})
n64 += int64(n)
if err != nil {
return n64, err
}
} else {
// Write identifier for valid debits.
n, err = w.Write([]byte{validPointer})
n64 += int64(n)
if err != nil {
return n64, err
}
// Write debited amount (int64).
byteOrder.PutUint64(uint64Bytes, uint64(t.debits.amount))
n, err := w.Write(uint64Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
// Write number of outputs (as a uint32) this record debits
// from.
byteOrder.PutUint32(uint32Bytes, uint32(len(t.debits.spends)))
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
// Write each lookup key for a spent transaction output.
for _, k := range t.debits.spends {
tmpn64, err := k.WriteTo(w)
n64 += tmpn64
if err != nil {
return n64, err
}
}
}
// Write number of pointers (as a uint32) in the credits slice (although
// some may be nil). Then, for each element in the credits slice, write
// an identifier whether the element is nil or valid, and if valid,
// write the credit structure.
byteOrder.PutUint32(uint32Bytes, uint32(len(t.credits)))
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
for _, c := range t.credits {
if c == nil {
// Write identifier for nil credit.
n, err := w.Write([]byte{nilPointer})
n64 += int64(n)
if err != nil {
return n64, err
}
} else {
// Write identifier for valid credit.
n, err := w.Write([]byte{validPointer})
n64 += int64(n)
if err != nil {
return n64, err
}
// Write a single byte to specify whether this credit
// was added as change, plus an extra empty byte which
// used to specify whether the credit was locked. This
// extra byte is currently unused and may be used for
// other flags in the future.
changeByte := falseByte
if c.change {
changeByte = trueByte
}
n, err = w.Write([]byte{changeByte, 0})
n64 += int64(n)
if err != nil {
return n64, err
}
// If this credit is unspent, write an identifier for
// an invalid pointer. Otherwise, write the identifier
// for a valid pointer and write the spending tx key.
if c.spentBy == nil {
// Write identifier for an unspent credit.
n, err := w.Write([]byte{nilPointer})
n64 += int64(n)
if err != nil {
return n64, err
}
} else {
// Write identifier for an unspent credit.
n, err := w.Write([]byte{validPointer})
n64 += int64(n)
if err != nil {
return n64, err
}
// Write transaction lookup key.
tmpn64, err := c.spentBy.WriteTo(w)
n64 += tmpn64
if err != nil {
return n64, err
}
}
}
}
// Write received unix time (int64).
byteOrder.PutUint64(uint64Bytes, uint64(t.received.Unix()))
n, err = w.Write(uint64Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
return n64, nil
}
type msgTx btcwire.MsgTx
func (tx *msgTx) ReadFrom(r io.Reader) (int64, error) {
// Read from a TeeReader to return the number of read bytes.
buf := bytes.Buffer{}
tr := io.TeeReader(r, &buf)
if err := (*btcwire.MsgTx)(tx).Deserialize(tr); err != nil {
if buf.Len() != 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return int64(buf.Len()), err
}
return int64((*btcwire.MsgTx)(tx).SerializeSize()), nil
}
func (tx *msgTx) WriteTo(w io.Writer) (int64, error) {
// Write to a buffer and then copy to w so the total number of bytes
// written can be returned to the caller. Writing to a to a
// bytes.Buffer never fails except for OOM panics, so check and panic
// on any unexpected non-nil returned errors.
buf := bytes.Buffer{}
if err := (*btcwire.MsgTx)(tx).Serialize(&buf); err != nil {
panic(err)
}
return io.Copy(w, &buf)
}
// ReadFrom reads a mined transaction output lookup key from r. The total
// number of bytes read is returned.
func (k *BlockOutputKey) ReadFrom(r io.Reader) (int64, error) {
var buf [4]byte
uint32Bytes := buf[:4]
// Read embedded BlockTxKey.
n64, err := k.BlockTxKey.ReadFrom(r)
if err != nil {
return n64, err
}
// Read output index (uint32).
n, err := io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
k.OutputIndex = byteOrder.Uint32(uint32Bytes)
return n64, nil
}
// WriteTo writes a mined transaction output lookup key to w. The total number
// of bytes written is returned.
func (k *BlockOutputKey) WriteTo(w io.Writer) (int64, error) {
var buf [4]byte
uint32Bytes := buf[:4]
// Write embedded BlockTxKey.
n64, err := k.BlockTxKey.WriteTo(w)
if err != nil {
return n64, err
}
// Write output index (uint32).
byteOrder.PutUint32(uint32Bytes, k.OutputIndex)
n, err := w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
return n64, nil
}
// ReadFrom reads a mined transaction lookup key from r. The total number of
// bytes read is returned.
func (k *BlockTxKey) ReadFrom(r io.Reader) (int64, error) {
var buf [4]byte
uint32Bytes := buf[:4]
// Read block index (as a uint32).
n, err := io.ReadFull(r, uint32Bytes)
n64 := int64(n)
if err != nil {
return n64, err
}
k.BlockIndex = int(byteOrder.Uint32(uint32Bytes))
// Read block height (int32).
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
k.BlockHeight = int32(byteOrder.Uint32(uint32Bytes))
return n64, nil
}
// WriteTo writes a mined transaction lookup key to w. The total number of
// bytes written is returned.
func (k *BlockTxKey) WriteTo(w io.Writer) (int64, error) {
var buf [4]byte
uint32Bytes := buf[:4]
// Write block index (as a uint32).
byteOrder.PutUint32(uint32Bytes, uint32(k.BlockIndex))
n, err := w.Write(uint32Bytes)
n64 := int64(n)
if err != nil {
return n64, err
}
// Write block height (int32).
byteOrder.PutUint32(uint32Bytes, uint32(k.BlockHeight))
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
return n64, nil
}
func (u *unconfirmedStore) ReadFrom(r io.Reader) (int64, error) {
var buf [4]byte
uint32Bytes := buf[:4]
// Read length (as a uint32) of transaction record key/value pairs,
// followed by each transaction record.
n, err := io.ReadFull(r, uint32Bytes)
n64 := int64(n)
if err != nil {
return n64, err
}
txCount := byteOrder.Uint32(uint32Bytes)
for i := uint32(0); i < txCount; i++ {
t := &txRecord{}
tmpn64, err := t.ReadFrom(r)
n64 += tmpn64
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
u.txs[*t.tx.Sha()] = t
}
// Read length (as a uint32) of key/value pairs in the
// spentBlockOutPoints and spentBlockOutPointKeys maps, followed by the
// outpoint, the block transaction lookup key, and the transaction hash
// of the spending transaction record.
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
spentBlockOutPointCount := byteOrder.Uint32(uint32Bytes)
for i := uint32(0); i < spentBlockOutPointCount; i++ {
// Read outpoint hash and index (uint32).
op := btcwire.OutPoint{}
n, err := io.ReadFull(r, op.Hash[:])
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
op.Index = byteOrder.Uint32(uint32Bytes)
// Read block transaction lookup key, and create the full block
// output key from it and the previously-read outpoint index.
opKey := BlockOutputKey{OutputIndex: op.Index}
tmpn64, err := opKey.BlockTxKey.ReadFrom(r)
n64 += tmpn64
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
// Read transaction record hash and check that it was previously
// read into the txs map. Use full record as the map value.
var txHash btcwire.ShaHash
n, err = io.ReadFull(r, txHash[:])
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
t, ok := u.txs[txHash]
if !ok {
return n64, fmt.Errorf("missing unconfirmed "+
"transaction record for transaction %v", txHash)
}
u.spentBlockOutPoints[opKey] = t
u.spentBlockOutPointKeys[op] = opKey
}
// Read length (as a uint32) of key/value pairs in the spentUnconfirmed
// map, followed by the outpoint and hash of the transaction record.
// Use this hash as the lookup key for the full transaction record
// previously read into the txs map.
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
spentUnconfirmedCount := byteOrder.Uint32(uint32Bytes)
for i := uint32(0); i < spentUnconfirmedCount; i++ {
// Read outpoint hash and index (uint32).
op := btcwire.OutPoint{}
n, err := io.ReadFull(r, op.Hash[:])
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
n, err = io.ReadFull(r, uint32Bytes)
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
op.Index = byteOrder.Uint32(uint32Bytes)
// Read transaction record hash and check that it was previously
// read into the txs map. Use full record as the map value.
var txHash btcwire.ShaHash
n, err = io.ReadFull(r, txHash[:])
n64 += int64(n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return n64, err
}
t, ok := u.txs[txHash]
if !ok {
return n64, fmt.Errorf("missing unconfirmed "+
"transaction record for transaction %v", txHash)
}
u.spentUnconfirmed[op] = t
}
// Recreate the previousOutpoints map. For each transaction record
// saved in the txs map, map each previous outpoint to the record
// itself.
for _, t := range u.txs {
for _, input := range t.tx.MsgTx().TxIn {
u.previousOutpoints[input.PreviousOutPoint] = t
}
}
return n64, nil
}
func (u *unconfirmedStore) WriteTo(w io.Writer) (int64, error) {
var buf [4]byte
uint32Bytes := buf[:4]
// Write length of key/values pairs in txs map, followed by each
// transaction record.
byteOrder.PutUint32(uint32Bytes, uint32(len(u.txs)))
n, err := w.Write(uint32Bytes)
n64 := int64(n)
if err != nil {
return n64, err
}
for _, t := range u.txs {
tmpn64, err := t.WriteTo(w)
n64 += tmpn64
if err != nil {
return n64, err
}
}
// Write length (as a uint32) of key/value pairs in the
// spentBlockOutPoints and spentBlockOutPointKeys maps (these lengths
// must be equal), followed by the outpoint, the block transaction
// lookup key, and the hash of the transaction record.
if len(u.spentBlockOutPoints) != len(u.spentBlockOutPointKeys) {
return n64, errors.New("spent block tx maps lengths differ")
}
byteOrder.PutUint32(uint32Bytes, uint32(len(u.spentBlockOutPoints)))
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
for op, opKey := range u.spentBlockOutPointKeys {
// Write outpoint hash and the index (uint32).
n, err := w.Write(op.Hash[:])
n64 += int64(n)
if err != nil {
return n64, err
}
byteOrder.PutUint32(uint32Bytes, op.Index)
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
// Write the block transaction lookup key. This is not the full
// output key, as the index has already been serialized as part
// of the outpoint written above.
tmpn64, err := opKey.BlockTxKey.WriteTo(w)
n64 += tmpn64
if err != nil {
return n64, err
}
// Lookup transaction record and write the transaction hash.
t, ok := u.spentBlockOutPoints[opKey]
if !ok {
return n64, MissingCreditError(opKey)
}
n, err = w.Write(t.tx.Sha()[:])
n64 += int64(n)
if err != nil {
return n64, err
}
}
// Write length (as a uint32) of key/value pairs in the spentUnconfirmed
// map, followed by the outpoint and hash of the transaction record.
byteOrder.PutUint32(uint32Bytes, uint32(len(u.spentUnconfirmed)))
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
for op, t := range u.spentUnconfirmed {
// Write outpoint hash and the index (uint32).
n, err := w.Write(op.Hash[:])
n64 += int64(n)
if err != nil {
return n64, err
}
byteOrder.PutUint32(uint32Bytes, op.Index)
n, err = w.Write(uint32Bytes)
n64 += int64(n)
if err != nil {
return n64, err
}
// Write transaction record hash.
n, err = w.Write(t.tx.Sha()[:])
n64 += int64(n)
if err != nil {
return n64, err
}
}
// The previousOutpoints map is intentionally not written, as it can
// be fully recreated by iterating each transaction record and adding
// a key/value pair for each prevous outpoint. This is performed when
// reading the unconfirmed store. This makes reads slightly more
// expensive, but writing faster, and as writes are far more common in
// application use, this was deemed to be an acceptable tradeoff.
return n64, nil
}
// TODO: set this automatically.
func (s *Store) MarkDirty() {
s.mtx.Lock()
defer s.mtx.Unlock()
s.dirty = true
}
func (s *Store) WriteIfDirty() error {
s.mtx.RLock()
if !s.dirty {
s.mtx.RUnlock()
return nil
}
// TempFile creates the file 0600, so no need to chmod it.
fi, err := ioutil.TempFile(s.dir, s.file)
if err != nil {
s.mtx.RUnlock()
return err
}
fiPath := fi.Name()
_, err = s.writeTo(fi)
if err != nil {
s.mtx.RUnlock()
fi.Close()
return err
}
err = fi.Sync()
if err != nil {
s.mtx.RUnlock()
fi.Close()
return err
}
fi.Close()
err = rename.Atomic(fiPath, s.path)
s.mtx.RUnlock()
if err == nil {
s.mtx.Lock()
s.dirty = false
s.mtx.Unlock()
}
return err
}
// OpenDir opens a new transaction store from the specified directory.
// If the file does not exist, the error from the os package will be
// returned, and can be checked with os.IsNotExist to differentiate missing
// file errors from others (including deserialization).
func OpenDir(dir string) (*Store, error) {
path := filepath.Join(dir, filename)
fi, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
defer fi.Close()
store := new(Store)
_, err = store.ReadFrom(fi)
if err != nil {
return nil, err
}
store.path = path
store.dir = dir
return store, nil
}