github.com/decred/dcrd/blockchain@v1.2.1/chainio_test.go (about)

     1  // Copyright (c) 2015-2016 The btcsuite developers
     2  // Copyright (c) 2015-2018 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package blockchain
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/hex"
    11  	"errors"
    12  	"math/big"
    13  	"reflect"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/decred/dcrd/blockchain/stake"
    18  	"github.com/decred/dcrd/blockchain/standalone"
    19  	"github.com/decred/dcrd/chaincfg/chainhash"
    20  	"github.com/decred/dcrd/database"
    21  	"github.com/decred/dcrd/wire"
    22  )
    23  
    24  // newHashFromStr converts the passed big-endian hex string into a
    25  // chainhash.Hash.  It only differs from the one available in chainhash in that
    26  // it ignores the error since it will only (and must only) be called with
    27  // hard-coded, and therefore known good, hashes.
    28  func newHashFromStr(hexStr string) *chainhash.Hash {
    29  	hash, _ := chainhash.NewHashFromStr(hexStr)
    30  	return hash
    31  }
    32  
    33  // hexToFinalState converts the passed hex string into an array of 6 bytes and
    34  // will panic if there is an error.  This is only provided for the hard-coded
    35  // constants so errors in the source code can be detected. It will only (and
    36  // must only) be called with hard-coded values.
    37  func hexToFinalState(s string) [6]byte {
    38  	b, err := hex.DecodeString(s)
    39  	if err != nil {
    40  		panic("invalid hex in source file: " + s)
    41  	}
    42  
    43  	var finalState [6]byte
    44  	if len(b) != len(finalState) {
    45  		panic("invalid hex in source file: " + s)
    46  	}
    47  	copy(finalState[:], b)
    48  	return finalState
    49  }
    50  
    51  // hexToExtraData converts the passed hex string into an array of 32 bytes and
    52  // will panic if there is an error.  This is only provided for the hard-coded
    53  // constants so errors in the source code can be detected. It will only (and
    54  // must only) be called with hard-coded values.
    55  func hexToExtraData(s string) [32]byte {
    56  	b, err := hex.DecodeString(s)
    57  	if err != nil {
    58  		panic("invalid hex in source file: " + s)
    59  	}
    60  
    61  	var extraData [32]byte
    62  	if len(b) != len(extraData) {
    63  		panic("invalid hex in source file: " + s)
    64  	}
    65  	copy(extraData[:], b)
    66  	return extraData
    67  }
    68  
    69  // isNotInMainChainErr returns whether or not the passed error is an
    70  // errNotInMainChain error.
    71  func isNotInMainChainErr(err error) bool {
    72  	_, ok := err.(errNotInMainChain)
    73  	return ok
    74  }
    75  
    76  // TestErrNotInMainChain ensures the functions related to errNotInMainChain work
    77  // as expected.
    78  func TestErrNotInMainChain(t *testing.T) {
    79  	errStr := "no block at height 1 exists"
    80  	err := error(errNotInMainChain(errStr))
    81  
    82  	// Ensure the stringized output for the error is as expected.
    83  	if err.Error() != errStr {
    84  		t.Fatalf("errNotInMainChain retuned unexpected error string - "+
    85  			"got %q, want %q", err.Error(), errStr)
    86  	}
    87  
    88  	// Ensure error is detected as the correct type.
    89  	if !isNotInMainChainErr(err) {
    90  		t.Fatalf("isNotInMainChainErr did not detect as expected type")
    91  	}
    92  	err = errors.New("something else")
    93  	if isNotInMainChainErr(err) {
    94  		t.Fatalf("isNotInMainChainErr detected incorrect type")
    95  	}
    96  }
    97  
    98  // TestBlockIndexSerialization ensures serializing and deserializing block index
    99  // entries works as expected.
   100  func TestBlockIndexSerialization(t *testing.T) {
   101  	t.Parallel()
   102  
   103  	// base data is based on block 150287 on mainnet and serves as a template
   104  	// for the various tests below.
   105  	baseHeader := wire.BlockHeader{
   106  		Version:      4,
   107  		PrevBlock:    *newHashFromStr("000000000000016916671ae225343a5ee131c999d5cadb6348805db25737731f"),
   108  		MerkleRoot:   *newHashFromStr("5ef2bb79795d7503c0ccc5cb6e0d4731992fc8c8c5b332c1c0e2c687d864c666"),
   109  		StakeRoot:    *newHashFromStr("022965059b7527dc2bc18daaa533f806eda1f96fd0b04bbda2381f5552d7c2de"),
   110  		VoteBits:     0x0001,
   111  		FinalState:   hexToFinalState("313e16e64c0b"),
   112  		Voters:       4,
   113  		FreshStake:   3,
   114  		Revocations:  2,
   115  		PoolSize:     41332,
   116  		Bits:         0x1a016f98,
   117  		SBits:        7473162478,
   118  		Height:       150287,
   119  		Size:         11295,
   120  		Timestamp:    time.Unix(1499907127, 0),
   121  		Nonce:        4116576260,
   122  		ExtraData:    hexToExtraData("8f01ed92645e0a6b11ee3b3c0000000000000000000000000000000000000000"),
   123  		StakeVersion: 4,
   124  	}
   125  	baseTicketsVoted := []chainhash.Hash{
   126  		*newHashFromStr("8b62a877544753ea80a822142a48ec066170e9381d21a9e8a84bc7373f0f9b2e"),
   127  		*newHashFromStr("4427a003a7aceb1404ffd9072e9aff1e128a24333a543332030e91668a389db7"),
   128  		*newHashFromStr("4415b88ac74881d7b6b15d41df465257cd1cc92d55e95f1b648434aef3a2110b"),
   129  		*newHashFromStr("9d2621b57352088809d3a069b04b76c832f30a76da14e56aece72208b3e5b87a"),
   130  	}
   131  	baseTicketsRevoked := []chainhash.Hash{
   132  		*newHashFromStr("8146f01b8ffca8008ebc80293d2978d63b1dffa5c456a73e7b39a9b1e695e8eb"),
   133  		*newHashFromStr("2292ff2461e725c58cc6e2051eac2a10e6ee6d1f62327ed676b7a196fb94be0c"),
   134  	}
   135  	baseVoteInfo := []stake.VoteVersionTuple{
   136  		{Version: 4, Bits: 0x0001},
   137  		{Version: 4, Bits: 0x0015},
   138  		{Version: 4, Bits: 0x0015},
   139  		{Version: 4, Bits: 0x0001},
   140  	}
   141  
   142  	tests := []struct {
   143  		name       string
   144  		entry      blockIndexEntry
   145  		serialized []byte
   146  	}{
   147  		{
   148  			name: "no votes, no revokes",
   149  			entry: blockIndexEntry{
   150  				header:         baseHeader,
   151  				status:         statusDataStored | statusValid,
   152  				voteInfo:       nil,
   153  				ticketsVoted:   nil,
   154  				ticketsRevoked: nil,
   155  			},
   156  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c000000000000000000000000000000000000000004000000030000"),
   157  		},
   158  		{
   159  			name: "1 vote, no revokes",
   160  			entry: blockIndexEntry{
   161  				header:         baseHeader,
   162  				status:         statusDataStored | statusValid,
   163  				voteInfo:       baseVoteInfo[:1],
   164  				ticketsVoted:   baseTicketsVoted[:1],
   165  				ticketsRevoked: nil,
   166  			},
   167  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c00000000000000000000000000000000000000000400000003012e9b0f3f37c74ba8e8a9211d38e9706106ec482a1422a880ea53475477a8628b040100"),
   168  		},
   169  		{
   170  			name: "no votes, 1 revoke",
   171  			entry: blockIndexEntry{
   172  				header:         baseHeader,
   173  				status:         statusDataStored | statusValid,
   174  				voteInfo:       nil,
   175  				ticketsVoted:   nil,
   176  				ticketsRevoked: baseTicketsRevoked[:1],
   177  			},
   178  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c000000000000000000000000000000000000000004000000030001ebe895e6b1a9397b3ea756c4a5ff1d3bd678293d2980bc8e00a8fc8f1bf04681"),
   179  		},
   180  		{
   181  			name: "4 votes, same vote versions, different vote bits, 2 revokes",
   182  			entry: blockIndexEntry{
   183  				header:         baseHeader,
   184  				status:         statusDataStored | statusValid,
   185  				voteInfo:       baseVoteInfo,
   186  				ticketsVoted:   baseTicketsVoted,
   187  				ticketsRevoked: baseTicketsRevoked,
   188  			},
   189  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c00000000000000000000000000000000000000000400000003042e9b0f3f37c74ba8e8a9211d38e9706106ec482a1422a880ea53475477a8628b0401b79d388a66910e033233543a33248a121eff9a2e07d9ff0414ebaca703a0274404150b11a2f3ae3484641b5fe9552dc91ccd575246df415db1b6d78148c78ab8154404157ab8e5b30822e7ec6ae514da760af332c8764bb069a0d30988085273b521269d040102ebe895e6b1a9397b3ea756c4a5ff1d3bd678293d2980bc8e00a8fc8f1bf046810cbe94fb96a1b776d67e32621f6deee6102aac1e05e2c68cc525e76124ff9222"),
   190  		},
   191  	}
   192  
   193  	for _, test := range tests {
   194  		// Ensure the function to calculate the serialized size without
   195  		// actually serializing it is calculated properly.
   196  		gotSize := blockIndexEntrySerializeSize(&test.entry)
   197  		if gotSize != len(test.serialized) {
   198  			t.Errorf("blockIndexEntrySerializeSize (%s): did not "+
   199  				"get expected size - got %d, want %d", test.name,
   200  				gotSize, len(test.serialized))
   201  		}
   202  
   203  		// Ensure the block index entry serializes to the expected value.
   204  		gotSerialized, err := serializeBlockIndexEntry(&test.entry)
   205  		if err != nil {
   206  			t.Errorf("serializeBlockIndexEntry (%s): unexpected "+
   207  				"error: %v", test.name, err)
   208  			continue
   209  		}
   210  		if !bytes.Equal(gotSerialized, test.serialized) {
   211  			t.Errorf("serializeBlockIndexEntry (%s): did not get "+
   212  				"expected bytes - got %x, want %x", test.name,
   213  				gotSerialized, test.serialized)
   214  			continue
   215  		}
   216  
   217  		// Ensure the block index entry serializes to the expected value
   218  		// and produces the expected number of bytes written via a
   219  		// direct put.
   220  		gotSerialized2 := make([]byte, gotSize)
   221  		gotBytesWritten, err := putBlockIndexEntry(gotSerialized2,
   222  			&test.entry)
   223  		if err != nil {
   224  			t.Errorf("putBlockIndexEntry (%s): unexpected error: %v",
   225  				test.name, err)
   226  			continue
   227  		}
   228  		if !bytes.Equal(gotSerialized2, test.serialized) {
   229  			t.Errorf("putBlockIndexEntry (%s): did not get "+
   230  				"expected bytes - got %x, want %x", test.name,
   231  				gotSerialized2, test.serialized)
   232  			continue
   233  		}
   234  		if gotBytesWritten != len(test.serialized) {
   235  			t.Errorf("putBlockIndexEntry (%s): did not get "+
   236  				"expected number of bytes written - got %d, "+
   237  				"want %d", test.name, gotBytesWritten,
   238  				len(test.serialized))
   239  			continue
   240  		}
   241  
   242  		// Ensure the serialized bytes are decoded back to the expected
   243  		// block index entry.
   244  		gotEntry, err := deserializeBlockIndexEntry(test.serialized)
   245  		if err != nil {
   246  			t.Errorf("deserializeBlockIndexEntry (%s): unexpected "+
   247  				"error: %v", test.name, err)
   248  			continue
   249  		}
   250  		if !reflect.DeepEqual(*gotEntry, test.entry) {
   251  			t.Errorf("deserializeBlockIndexEntry (%s): mismatched "+
   252  				"entries\ngot %+v\nwant %+v", test.name,
   253  				gotEntry, test.entry)
   254  			continue
   255  		}
   256  
   257  		// Ensure the serialized bytes are decoded back to the expected
   258  		// block index entry.
   259  		var gotEntry2 blockIndexEntry
   260  		bytesRead, err := decodeBlockIndexEntry(test.serialized, &gotEntry2)
   261  		if err != nil {
   262  			t.Errorf("decodeBlockIndexEntry (%s): unexpected "+
   263  				"error: %v", test.name, err)
   264  			continue
   265  		}
   266  		if !reflect.DeepEqual(gotEntry2, test.entry) {
   267  			t.Errorf("decodeBlockIndexEntry (%s): mismatched "+
   268  				"entries\ngot %+v\nwant %+v", test.name,
   269  				gotEntry2, test.entry)
   270  			continue
   271  		}
   272  		if bytesRead != len(test.serialized) {
   273  			t.Errorf("decodeBlockIndexEntry (%s): did not get "+
   274  				"expected number of bytes read - got %d, "+
   275  				"want %d", test.name, bytesRead,
   276  				len(test.serialized))
   277  			continue
   278  		}
   279  	}
   280  }
   281  
   282  // TestBlockIndexDecodeErrors performs negative tests against decoding block
   283  // index entries to ensure error paths work as expected.
   284  func TestBlockIndexDecodeErrors(t *testing.T) {
   285  	t.Parallel()
   286  	tests := []struct {
   287  		name       string
   288  		entry      blockIndexEntry
   289  		serialized []byte
   290  		bytesRead  int // Expected number of bytes read.
   291  		errType    error
   292  	}{
   293  		{
   294  			name:       "nothing serialized",
   295  			entry:      blockIndexEntry{},
   296  			serialized: hexToBytes(""),
   297  			errType:    errDeserialize(""),
   298  			bytesRead:  0,
   299  		},
   300  		{
   301  			name:       "no data after block header",
   302  			entry:      blockIndexEntry{},
   303  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c000000000000000000000000000000000000000004000000"),
   304  			errType:    errDeserialize(""),
   305  			bytesRead:  180,
   306  		},
   307  		{
   308  			name:       "no data after status",
   309  			entry:      blockIndexEntry{},
   310  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c00000000000000000000000000000000000000000400000003"),
   311  			errType:    errDeserialize(""),
   312  			bytesRead:  181,
   313  		},
   314  		{
   315  			name:       "no data after num votes with no votes",
   316  			entry:      blockIndexEntry{},
   317  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c0000000000000000000000000000000000000000040000000300"),
   318  			errType:    errDeserialize(""),
   319  			bytesRead:  182,
   320  		},
   321  		{
   322  			name:       "no data after num votes with votes",
   323  			entry:      blockIndexEntry{},
   324  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c0000000000000000000000000000000000000000040000000301"),
   325  			errType:    errDeserialize(""),
   326  			bytesRead:  182,
   327  		},
   328  		{
   329  			name:       "short data in vote ticket hash",
   330  			entry:      blockIndexEntry{},
   331  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c00000000000000000000000000000000000000000400000003012e9b0f3f37c74ba8e8a9211d38e9706106ec482a1422a880ea53475477a862"),
   332  			errType:    errDeserialize(""),
   333  			bytesRead:  182,
   334  		},
   335  		{
   336  			name:       "no data after vote ticket hash",
   337  			entry:      blockIndexEntry{},
   338  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c00000000000000000000000000000000000000000400000003012e9b0f3f37c74ba8e8a9211d38e9706106ec482a1422a880ea53475477a8628b"),
   339  			errType:    errDeserialize(""),
   340  			bytesRead:  214,
   341  		},
   342  		{
   343  			name:       "no data after vote version",
   344  			entry:      blockIndexEntry{},
   345  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c00000000000000000000000000000000000000000400000003012e9b0f3f37c74ba8e8a9211d38e9706106ec482a1422a880ea53475477a8628b04"),
   346  			errType:    errDeserialize(""),
   347  			bytesRead:  215,
   348  		},
   349  		{
   350  			name:       "no data after votes",
   351  			entry:      blockIndexEntry{},
   352  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c00000000000000000000000000000000000000000400000003012e9b0f3f37c74ba8e8a9211d38e9706106ec482a1422a880ea53475477a8628b0401"),
   353  			errType:    errDeserialize(""),
   354  			bytesRead:  216,
   355  		},
   356  		{
   357  			name:       "no data after num revokes with revokes",
   358  			entry:      blockIndexEntry{},
   359  			serialized: hexToBytes("040000001f733757b25d804863dbcad599c931e15e3a3425e21a6716690100000000000066c664d887c6e2c0c132b3c5c8c82f9931470d6ecbc5ccc003755d7979bbf25edec2d752551f38a2bd4bb0d06ff9a1ed06f833a5aa8dc12bdc27759b056529020100313e16e64c0b0400030274a10000986f011aee686fbd010000000f4b02001f2c000037c4665904f85df58f01ed92645e0a6b11ee3b3c000000000000000000000000000000000000000004000000030001"),
   360  			errType:    errDeserialize(""),
   361  			bytesRead:  183,
   362  		},
   363  	}
   364  
   365  	for _, test := range tests {
   366  		// Ensure the expected error type is returned.
   367  		gotBytesRead, err := decodeBlockIndexEntry(test.serialized,
   368  			&test.entry)
   369  		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
   370  			t.Errorf("decodeBlockIndexEntry (%s): expected error "+
   371  				"type does not match - got %T, want %T",
   372  				test.name, err, test.errType)
   373  			continue
   374  		}
   375  
   376  		// Ensure the expected number of bytes read is returned.
   377  		if gotBytesRead != test.bytesRead {
   378  			t.Errorf("decodeBlockIndexEntry (%s): unexpected "+
   379  				"number of bytes read - got %d, want %d",
   380  				test.name, gotBytesRead, test.bytesRead)
   381  			continue
   382  		}
   383  	}
   384  }
   385  
   386  // TestStxoSerialization ensures serializing and deserializing spent transaction
   387  // output entries works as expected.
   388  func TestStxoSerialization(t *testing.T) {
   389  	t.Parallel()
   390  
   391  	tests := []struct {
   392  		name       string
   393  		stxo       spentTxOut
   394  		txVersion  int32 // When the txout is not fully spent.
   395  		serialized []byte
   396  	}{
   397  		{
   398  			name: "Spends last output of coinbase",
   399  			stxo: spentTxOut{
   400  				amount:        9999,
   401  				scriptVersion: 0,
   402  				pkScript:      hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
   403  				height:        12345,
   404  				index:         54321,
   405  				isCoinBase:    true,
   406  				hasExpiry:     false,
   407  				txFullySpent:  true,
   408  				txType:        0,
   409  				txVersion:     1,
   410  			},
   411  			serialized: hexToBytes("1100006edbc6c4d31bae9f1ccc38538a114bf42de65e8601"),
   412  		},
   413  		{
   414  			name: "Spends last output of non coinbase and is a ticket",
   415  			stxo: spentTxOut{
   416  				amount:        9999,
   417  				scriptVersion: 0,
   418  				pkScript:      hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
   419  				height:        12345,
   420  				index:         54321,
   421  				isCoinBase:    false,
   422  				hasExpiry:     false,
   423  				txFullySpent:  true,
   424  				txType:        1,
   425  				txVersion:     1,
   426  				stakeExtra:    []byte{0x00},
   427  			},
   428  			serialized: hexToBytes("1400006edbc6c4d31bae9f1ccc38538a114bf42de65e860100"),
   429  		},
   430  		{
   431  			name: "Does not spend last output",
   432  			stxo: spentTxOut{
   433  				amount:        34405000000,
   434  				pkScript:      hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
   435  				scriptVersion: 0,
   436  			},
   437  			txVersion:  1,
   438  			serialized: hexToBytes("0000006edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
   439  		},
   440  	}
   441  
   442  	for _, test := range tests {
   443  		// Ensure the function to calculate the serialized size without
   444  		// actually serializing it is calculated properly.
   445  		gotSize := spentTxOutSerializeSize(&test.stxo)
   446  		if gotSize != len(test.serialized) {
   447  			t.Errorf("spentTxOutSerializeSize (%s): did not get "+
   448  				"expected size - got %d, want %d", test.name,
   449  				gotSize, len(test.serialized))
   450  		}
   451  
   452  		// Ensure the stxo serializes to the expected value.
   453  		gotSerialized := make([]byte, gotSize)
   454  		gotBytesWritten := putSpentTxOut(gotSerialized, &test.stxo)
   455  		if !bytes.Equal(gotSerialized, test.serialized) {
   456  			t.Errorf("putSpentTxOut (%s): did not get expected "+
   457  				"bytes - got %x, want %x", test.name,
   458  				gotSerialized, test.serialized)
   459  			continue
   460  		}
   461  		if gotBytesWritten != len(test.serialized) {
   462  			t.Errorf("putSpentTxOut (%s): did not get expected "+
   463  				"number of bytes written - got %d, want %d",
   464  				test.name, gotBytesWritten,
   465  				len(test.serialized))
   466  			continue
   467  		}
   468  
   469  		// Ensure the serialized bytes are decoded back to the expected
   470  		// stxo.
   471  		var gotStxo spentTxOut
   472  		offset, err := decodeSpentTxOut(test.serialized, &gotStxo,
   473  			test.stxo.amount, test.stxo.height, test.stxo.index)
   474  		if err != nil {
   475  			t.Errorf("decodeSpentTxOut (%s): unexpected error: %v",
   476  				test.name, err)
   477  			continue
   478  		}
   479  		if offset != len(test.serialized) {
   480  			t.Errorf("decodeSpentTxOut (%s): did not get expected "+
   481  				"number of bytes read - got %d, want %d",
   482  				test.name, offset, len(test.serialized))
   483  			continue
   484  		}
   485  	}
   486  }
   487  
   488  // TestStxoDecodeErrors performs negative tests against decoding spent
   489  // transaction outputs to ensure error paths work as expected.
   490  func TestStxoDecodeErrors(t *testing.T) {
   491  	t.Parallel()
   492  
   493  	tests := []struct {
   494  		name       string
   495  		stxo       spentTxOut
   496  		txVersion  int32 // When the txout is not fully spent.
   497  		serialized []byte
   498  		bytesRead  int // Expected number of bytes read.
   499  		errType    error
   500  	}{
   501  		{
   502  			name:       "nothing serialized",
   503  			stxo:       spentTxOut{},
   504  			serialized: hexToBytes(""),
   505  			errType:    errDeserialize(""),
   506  			bytesRead:  0,
   507  		},
   508  		{
   509  			name:       "no data after flags w/o version",
   510  			stxo:       spentTxOut{},
   511  			serialized: hexToBytes("00"),
   512  			errType:    errDeserialize(""),
   513  			bytesRead:  1,
   514  		},
   515  		{
   516  			name:       "no data after flags code",
   517  			stxo:       spentTxOut{},
   518  			serialized: hexToBytes("14"),
   519  			errType:    errDeserialize(""),
   520  			bytesRead:  1,
   521  		},
   522  		{
   523  			name:       "no tx version data after script",
   524  			stxo:       spentTxOut{},
   525  			serialized: hexToBytes("1400016edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
   526  			errType:    errDeserialize(""),
   527  			bytesRead:  23,
   528  		},
   529  		{
   530  			name:       "no stakeextra data after script for ticket",
   531  			stxo:       spentTxOut{},
   532  			serialized: hexToBytes("1400016edbc6c4d31bae9f1ccc38538a114bf42de65e8601"),
   533  			errType:    errDeserialize(""),
   534  			bytesRead:  24,
   535  		},
   536  		{
   537  			name:       "incomplete compressed txout",
   538  			stxo:       spentTxOut{},
   539  			txVersion:  1,
   540  			serialized: hexToBytes("1432"),
   541  			errType:    errDeserialize(""),
   542  			bytesRead:  2,
   543  		},
   544  	}
   545  
   546  	for _, test := range tests {
   547  		// Ensure the expected error type is returned.
   548  		gotBytesRead, err := decodeSpentTxOut(test.serialized,
   549  			&test.stxo, test.stxo.amount, test.stxo.height, test.stxo.index)
   550  		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
   551  			t.Errorf("decodeSpentTxOut (%s): expected error type "+
   552  				"does not match - got %T, want %T", test.name,
   553  				err, test.errType)
   554  			continue
   555  		}
   556  
   557  		// Ensure the expected number of bytes read is returned.
   558  		if gotBytesRead != test.bytesRead {
   559  			t.Errorf("decodeSpentTxOut (%s): unexpected number of "+
   560  				"bytes read - got %d, want %d", test.name,
   561  				gotBytesRead, test.bytesRead)
   562  			continue
   563  		}
   564  	}
   565  }
   566  
   567  // TestSpendJournalSerialization ensures serializing and deserializing spend
   568  // journal entries works as expected.
   569  func TestSpendJournalSerialization(t *testing.T) {
   570  	t.Parallel()
   571  
   572  	tests := []struct {
   573  		name       string
   574  		entry      []spentTxOut
   575  		blockTxns  []*wire.MsgTx
   576  		utxoView   *UtxoViewpoint
   577  		serialized []byte
   578  	}{
   579  		{
   580  			name:       "No spends",
   581  			entry:      nil,
   582  			blockTxns:  nil,
   583  			utxoView:   NewUtxoViewpoint(),
   584  			serialized: nil,
   585  		},
   586  		{
   587  			name: "One tx with one input spends last output of coinbase",
   588  			entry: []spentTxOut{{
   589  				amount:        5000000000,
   590  				scriptVersion: 0,
   591  				pkScript:      hexToBytes("0511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
   592  				compressed:    true,
   593  				height:        9,
   594  				index:         0,
   595  				isCoinBase:    true,
   596  				hasExpiry:     false,
   597  				txFullySpent:  true,
   598  				txType:        0,
   599  				txVersion:     1,
   600  				stakeExtra:    nil,
   601  			}},
   602  			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
   603  				SerType: wire.TxSerializeFull,
   604  				Version: 1,
   605  				TxIn: []*wire.TxIn{{
   606  					PreviousOutPoint: wire.OutPoint{
   607  						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
   608  						Index: 0,
   609  						Tree:  0,
   610  					},
   611  					SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
   612  					Sequence:        0xffffffff,
   613  					BlockHeight:     9,
   614  					BlockIndex:      0,
   615  					ValueIn:         5000000000,
   616  				}},
   617  				TxOut: []*wire.TxOut{{
   618  					Value:    1000000000,
   619  					Version:  0,
   620  					PkScript: hexToBytes("4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac"),
   621  				}, {
   622  					Value:    4000000000,
   623  					Version:  0,
   624  					PkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
   625  				}},
   626  				LockTime: 0,
   627  				Expiry:   0,
   628  			}},
   629  			utxoView:   NewUtxoViewpoint(),
   630  			serialized: hexToBytes("11000511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c01"),
   631  		},
   632  		{
   633  			name: "Two txns when one spends last output, one doesn't",
   634  			entry: []spentTxOut{{
   635  				amount:        34405000000,
   636  				height:        321,
   637  				index:         123,
   638  				scriptVersion: 0,
   639  				pkScript:      hexToBytes("016edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
   640  				compressed:    true,
   641  			}, {
   642  				amount:        13761000000,
   643  				scriptVersion: 0,
   644  				pkScript:      hexToBytes("01b2fb57eadf61e106a100a7445a8c3f67898841ec"),
   645  				compressed:    true,
   646  				height:        3214,
   647  				index:         1234,
   648  				isCoinBase:    false,
   649  				hasExpiry:     false,
   650  				txFullySpent:  true,
   651  				txType:        0,
   652  				txVersion:     1,
   653  				stakeExtra:    nil,
   654  			}},
   655  			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
   656  				SerType: wire.TxSerializeFull,
   657  				Version: 1,
   658  				TxIn: []*wire.TxIn{{
   659  					PreviousOutPoint: wire.OutPoint{
   660  						Hash:  *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
   661  						Index: 1,
   662  						Tree:  0,
   663  					},
   664  					SignatureScript: hexToBytes("493046022100c167eead9840da4a033c9a56470d7794a9bb1605b377ebe5688499b39f94be59022100fb6345cab4324f9ea0b9ee9169337534834638d818129778370f7d378ee4a325014104d962cac5390f12ddb7539507065d0def320d68c040f2e73337c3a1aaaab7195cb5c4d02e0959624d534f3c10c3cf3d73ca5065ebd62ae986b04c6d090d32627c"),
   665  					Sequence:        0xffffffff,
   666  					BlockHeight:     321,
   667  					BlockIndex:      123,
   668  					ValueIn:         34405000000,
   669  				}},
   670  				TxOut: []*wire.TxOut{{
   671  					Value:    5000000,
   672  					Version:  0,
   673  					PkScript: hexToBytes("76a914f419b8db4ba65f3b6fcc233acb762ca6f51c23d488ac"),
   674  				}, {
   675  					Value:    34400000000,
   676  					Version:  0,
   677  					PkScript: hexToBytes("76a914cadf4fc336ab3c6a4610b75f31ba0676b7f663d288ac"),
   678  				}},
   679  				LockTime: 0,
   680  				Expiry:   0,
   681  			}, {
   682  				SerType: wire.TxSerializeFull,
   683  				Version: 1,
   684  				TxIn: []*wire.TxIn{{
   685  					PreviousOutPoint: wire.OutPoint{
   686  						Hash:  *newHashFromStr("92fbe1d4be82f765dfabc9559d4620864b05cc897c4db0e29adac92d294e52b7"),
   687  						Index: 0,
   688  						Tree:  0,
   689  					},
   690  					SignatureScript: hexToBytes("483045022100e256743154c097465cf13e89955e1c9ff2e55c46051b627751dee0144183157e02201d8d4f02cde8496aae66768f94d35ce54465bd4ae8836004992d3216a93a13f00141049d23ce8686fe9b802a7a938e8952174d35dd2c2089d4112001ed8089023ab4f93a3c9fcd5bfeaa9727858bf640dc1b1c05ec3b434bb59837f8640e8810e87742"),
   691  					Sequence:        0xffffffff,
   692  					BlockHeight:     3214,
   693  					BlockIndex:      1234,
   694  					ValueIn:         13761000000,
   695  				}},
   696  				TxOut: []*wire.TxOut{{
   697  					Value:    5000000,
   698  					Version:  0,
   699  					PkScript: hexToBytes("76a914a983ad7c92c38fc0e2025212e9f972204c6e687088ac"),
   700  				}, {
   701  					Value:    13756000000,
   702  					Version:  0,
   703  					PkScript: hexToBytes("76a914a6ebd69952ab486a7a300bfffdcb395dc7d47c2388ac"),
   704  				}},
   705  				LockTime: 0,
   706  				Expiry:   0,
   707  			}},
   708  			utxoView: &UtxoViewpoint{entries: map[chainhash.Hash]*UtxoEntry{
   709  				*newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): {
   710  					txVersion:  1,
   711  					isCoinBase: false,
   712  					hasExpiry:  false,
   713  					height:     321,
   714  					index:      123,
   715  					sparseOutputs: map[uint32]*utxoOutput{
   716  						1: {
   717  							amount:        34405000000,
   718  							scriptVersion: 0,
   719  							pkScript:      hexToBytes("76a9142084541c3931677527a7eafe56fd90207c344eb088ac"),
   720  							compressed:    false,
   721  						},
   722  					},
   723  				},
   724  			}},
   725  			serialized: hexToBytes("100001b2fb57eadf61e106a100a7445a8c3f67898841ec010000016edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
   726  		},
   727  		{
   728  			name: "One tx, two inputs from same tx, neither spend last output",
   729  			entry: []spentTxOut{{
   730  				amount:        159747816,
   731  				height:        1111,
   732  				index:         2222,
   733  				scriptVersion: 0,
   734  				pkScript:      hexToBytes("4151"),
   735  				compressed:    true,
   736  			}, {
   737  				amount:        159747816,
   738  				height:        3333,
   739  				index:         4444,
   740  				scriptVersion: 0,
   741  				pkScript:      hexToBytes("4151"),
   742  				compressed:    true,
   743  			}},
   744  			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
   745  				SerType: wire.TxSerializeFull,
   746  				Version: 1,
   747  				TxIn: []*wire.TxIn{{
   748  					PreviousOutPoint: wire.OutPoint{
   749  						Hash:  *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
   750  						Index: 1,
   751  					},
   752  					SignatureScript: hexToBytes(""),
   753  					Sequence:        0xffffffff,
   754  					BlockHeight:     1111,
   755  					BlockIndex:      2222,
   756  					ValueIn:         159747816,
   757  				}, {
   758  					PreviousOutPoint: wire.OutPoint{
   759  						Hash:  *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
   760  						Index: 2,
   761  					},
   762  					SignatureScript: hexToBytes(""),
   763  					Sequence:        0xffffffff,
   764  					BlockHeight:     3333,
   765  					BlockIndex:      4444,
   766  					ValueIn:         159747816,
   767  				}},
   768  				TxOut: []*wire.TxOut{{
   769  					Value:    165125632,
   770  					Version:  0,
   771  					PkScript: hexToBytes("51"),
   772  				}, {
   773  					Value:    154370000,
   774  					Version:  0,
   775  					PkScript: hexToBytes("51"),
   776  				}},
   777  				LockTime: 0,
   778  				Expiry:   0,
   779  			}},
   780  			utxoView: &UtxoViewpoint{entries: map[chainhash.Hash]*UtxoEntry{
   781  				*newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): {
   782  					txVersion:  1,
   783  					isCoinBase: false,
   784  					hasExpiry:  false,
   785  					height:     100000,
   786  					sparseOutputs: map[uint32]*utxoOutput{
   787  						0: {
   788  							amount:   165712179,
   789  							pkScript: hexToBytes("51"),
   790  						},
   791  					},
   792  				},
   793  			}},
   794  			serialized: hexToBytes("0000415100004151"),
   795  		},
   796  	}
   797  
   798  	for i, test := range tests {
   799  		// Ensure the journal entry serializes to the expected value.
   800  		gotBytes, err := serializeSpendJournalEntry(test.entry)
   801  		if err != nil {
   802  			t.Errorf("serializeSpendJournalEntry #%d (%s) "+
   803  				"unexpected error: %v", i, test.name, err)
   804  			continue
   805  		}
   806  		if !bytes.Equal(gotBytes, test.serialized) {
   807  			t.Errorf("serializeSpendJournalEntry #%d (%s): "+
   808  				"mismatched bytes - got %x, want %x", i,
   809  				test.name, gotBytes, test.serialized)
   810  			continue
   811  		}
   812  
   813  		// Deserialize to a spend journal entry.
   814  		gotEntry, err := deserializeSpendJournalEntry(test.serialized,
   815  			test.blockTxns)
   816  		if err != nil {
   817  			t.Errorf("deserializeSpendJournalEntry #%d (%s) "+
   818  				"unexpected error: %v", i, test.name, err)
   819  			continue
   820  		}
   821  
   822  		// Ensure that the deserialized spend journal entry has the
   823  		// correct properties.
   824  		for j := range gotEntry {
   825  			if !reflect.DeepEqual(gotEntry[j], test.entry[j]) {
   826  				t.Errorf("deserializeSpendJournalEntry #%d (%s) "+
   827  					"mismatched entries in idx %v - got %v, want %v",
   828  					i, test.name, j, gotEntry[j], test.entry[j])
   829  				continue
   830  			}
   831  		}
   832  	}
   833  }
   834  
   835  // TestSpendJournalErrors performs negative tests against deserializing spend
   836  // journal entries to ensure error paths work as expected.
   837  func TestSpendJournalErrors(t *testing.T) {
   838  	t.Parallel()
   839  
   840  	tests := []struct {
   841  		name       string
   842  		blockTxns  []*wire.MsgTx
   843  		serialized []byte
   844  		errType    error
   845  	}{
   846  		// Adapted from block 170 in main blockchain.
   847  		{
   848  			name: "Force assertion due to missing stxos",
   849  			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
   850  				SerType: wire.TxSerializeFull,
   851  				Version: 1,
   852  				TxIn: []*wire.TxIn{{
   853  					PreviousOutPoint: wire.OutPoint{
   854  						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
   855  						Index: 0,
   856  					},
   857  					SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
   858  					Sequence:        0xffffffff,
   859  				}},
   860  				LockTime: 0,
   861  			}},
   862  			serialized: hexToBytes(""),
   863  			errType:    AssertError(""),
   864  		},
   865  		{
   866  			name: "Force deserialization error in stxos",
   867  			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
   868  				SerType: wire.TxSerializeFull,
   869  				Version: 1,
   870  				TxIn: []*wire.TxIn{{
   871  					PreviousOutPoint: wire.OutPoint{
   872  						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
   873  						Index: 0,
   874  					},
   875  					SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
   876  					Sequence:        0xffffffff,
   877  				}},
   878  				LockTime: 0,
   879  			}},
   880  			serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a"),
   881  			errType:    errDeserialize(""),
   882  		},
   883  	}
   884  
   885  	for _, test := range tests {
   886  		// Ensure the expected error type is returned and the returned
   887  		// slice is nil.
   888  		stxos, err := deserializeSpendJournalEntry(test.serialized,
   889  			test.blockTxns)
   890  		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
   891  			t.Errorf("deserializeSpendJournalEntry (%s): expected "+
   892  				"error type does not match - got %T, want %T",
   893  				test.name, err, test.errType)
   894  			continue
   895  		}
   896  		if stxos != nil {
   897  			t.Errorf("deserializeSpendJournalEntry (%s): returned "+
   898  				"slice of spent transaction outputs is not nil",
   899  				test.name)
   900  			continue
   901  		}
   902  	}
   903  }
   904  
   905  // TestUtxoSerialization ensures serializing and deserializing unspent
   906  // trasaction output entries works as expected.
   907  func TestUtxoSerialization(t *testing.T) {
   908  	t.Parallel()
   909  
   910  	tests := []struct {
   911  		name       string
   912  		entry      *UtxoEntry
   913  		serialized []byte
   914  	}{
   915  		{
   916  			name: "Only output 0, coinbase, even uncomp pubkey",
   917  			entry: &UtxoEntry{
   918  				txVersion:  1,
   919  				isCoinBase: true,
   920  				hasExpiry:  false,
   921  				txType:     0,
   922  				height:     12345,
   923  				index:      54321,
   924  				stakeExtra: nil,
   925  				sparseOutputs: map[uint32]*utxoOutput{
   926  					0: {
   927  						amount:        5000000000,
   928  						scriptVersion: 0,
   929  						pkScript:      hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
   930  						compressed:    false,
   931  					},
   932  				},
   933  			},
   934  			serialized: hexToBytes("01df3982a731010132000496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"),
   935  		},
   936  		{
   937  			name: "Only output 0, coinbase, odd uncomp pubkey",
   938  			entry: &UtxoEntry{
   939  				txVersion:  1,
   940  				isCoinBase: true,
   941  				hasExpiry:  false,
   942  				txType:     0,
   943  				height:     12345,
   944  				index:      54321,
   945  				stakeExtra: nil,
   946  				sparseOutputs: map[uint32]*utxoOutput{
   947  					0: {
   948  						amount:        5000000000,
   949  						scriptVersion: 0,
   950  						pkScript:      hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52258a76c86aea2b1f59fb07ebe87e19dd6b8dee99409de18c57d340dbbd37a341ac"),
   951  						compressed:    false,
   952  					},
   953  				},
   954  			},
   955  			serialized: hexToBytes("01df3982a731010132000596b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"),
   956  		},
   957  		{
   958  			name: "Only output 1, not coinbase",
   959  			entry: &UtxoEntry{
   960  				txVersion:  1,
   961  				isCoinBase: false,
   962  				hasExpiry:  false,
   963  				txType:     0,
   964  				height:     55555,
   965  				index:      1,
   966  				stakeExtra: nil,
   967  				sparseOutputs: map[uint32]*utxoOutput{
   968  					1: {
   969  						amount:        1000000,
   970  						scriptVersion: 0,
   971  						pkScript:      hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
   972  						compressed:    false,
   973  					},
   974  				},
   975  			},
   976  			serialized: hexToBytes("0182b103010002070000ee8bd501094a7d5ca318da2506de35e1cb025ddc"),
   977  		},
   978  		{
   979  			name: "Ticket with one output",
   980  			entry: &UtxoEntry{
   981  				txVersion:  1,
   982  				isCoinBase: false,
   983  				hasExpiry:  false,
   984  				txType:     1,
   985  				height:     55555,
   986  				index:      1,
   987  				stakeExtra: hexToBytes("030f001aba76a9140cdf9941c0c221243cb8672cd1ad2c4c0933850588ac0000206a1e1a221182c26bbae681e4d96d452794e1951e70a208520000000000000054b5f466001abd76a9146c4f8b15918566534d134be7d7004b7f481bf36988ac"),
   988  				sparseOutputs: map[uint32]*utxoOutput{
   989  					1: {
   990  						amount:        1000000,
   991  						scriptVersion: 0,
   992  						pkScript:      hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
   993  						compressed:    false,
   994  					},
   995  				},
   996  			},
   997  			serialized: hexToBytes("0182b103010402070000ee8bd501094a7d5ca318da2506de35e1cb025ddc030f001aba76a9140cdf9941c0c221243cb8672cd1ad2c4c0933850588ac0000206a1e1a221182c26bbae681e4d96d452794e1951e70a208520000000000000054b5f466001abd76a9146c4f8b15918566534d134be7d7004b7f481bf36988ac"),
   998  		},
   999  		{
  1000  			name: "Only output 2, coinbase, non-zero script version",
  1001  			entry: &UtxoEntry{
  1002  				txVersion:  1,
  1003  				isCoinBase: true,
  1004  				hasExpiry:  false,
  1005  				txType:     0,
  1006  				height:     12345,
  1007  				index:      1,
  1008  				sparseOutputs: map[uint32]*utxoOutput{
  1009  					2: {
  1010  						amount:        100937281,
  1011  						scriptVersion: 0xffff,
  1012  						pkScript:      hexToBytes("76a914da33f77cee27c2a975ed5124d7e4f7f97513510188ac"),
  1013  						compressed:    false,
  1014  					},
  1015  				},
  1016  			},
  1017  			serialized: hexToBytes("01df390101000182b095bf4182fe7f00da33f77cee27c2a975ed5124d7e4f7f975135101"),
  1018  		},
  1019  		{
  1020  			name: "outputs 0 and 2 not coinbase",
  1021  			entry: &UtxoEntry{
  1022  				txVersion:  1,
  1023  				isCoinBase: false,
  1024  				hasExpiry:  false,
  1025  				txType:     0,
  1026  				height:     99999,
  1027  				index:      3,
  1028  				sparseOutputs: map[uint32]*utxoOutput{
  1029  					0: {
  1030  						amount:        20000000,
  1031  						scriptVersion: 0,
  1032  						pkScript:      hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
  1033  						compressed:    false,
  1034  					},
  1035  					2: {
  1036  						amount:        15000000,
  1037  						scriptVersion: 0,
  1038  						pkScript:      hexToBytes("76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac"),
  1039  						compressed:    false,
  1040  					},
  1041  				},
  1042  			},
  1043  			serialized: hexToBytes("01858c1f03000501120000e2ccd6ec7c6e2e581349c77e067385fa8236bf8a80090000b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
  1044  		},
  1045  		{
  1046  			name: "outputs 0 and 2 not coinbase, has expiry",
  1047  			entry: &UtxoEntry{
  1048  				txVersion:  1,
  1049  				isCoinBase: false,
  1050  				hasExpiry:  true,
  1051  				txType:     0,
  1052  				height:     99999,
  1053  				index:      3,
  1054  				sparseOutputs: map[uint32]*utxoOutput{
  1055  					0: {
  1056  						amount:        20000000,
  1057  						scriptVersion: 0,
  1058  						pkScript:      hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
  1059  						compressed:    false,
  1060  					},
  1061  					2: {
  1062  						amount:        15000000,
  1063  						scriptVersion: 0,
  1064  						pkScript:      hexToBytes("76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac"),
  1065  						compressed:    false,
  1066  					},
  1067  				},
  1068  			},
  1069  			serialized: hexToBytes("01858c1f03020501120000e2ccd6ec7c6e2e581349c77e067385fa8236bf8a80090000b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
  1070  		},
  1071  		{
  1072  			name: "outputs 0 and 2, not coinbase, 1 marked spent",
  1073  			entry: &UtxoEntry{
  1074  				txVersion:  1,
  1075  				isCoinBase: false,
  1076  				hasExpiry:  false,
  1077  				txType:     0,
  1078  				height:     12345,
  1079  				index:      1,
  1080  				sparseOutputs: map[uint32]*utxoOutput{
  1081  					0: {
  1082  						amount:        20000000,
  1083  						scriptVersion: 0,
  1084  						pkScript:      hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
  1085  						compressed:    false,
  1086  					},
  1087  					1: { // This won't be serialized.
  1088  						spent:         true,
  1089  						amount:        1000000,
  1090  						scriptVersion: 0,
  1091  						pkScript:      hexToBytes("76a914e43031c3e46f20bf1ccee9553ce815de5a48467588ac"),
  1092  						compressed:    false,
  1093  					},
  1094  					2: {
  1095  						amount:        15000000,
  1096  						scriptVersion: 0,
  1097  						pkScript:      hexToBytes("76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac"),
  1098  						compressed:    false,
  1099  					},
  1100  				},
  1101  			},
  1102  			serialized: hexToBytes("01df3901000501120000e2ccd6ec7c6e2e581349c77e067385fa8236bf8a80090000b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
  1103  		},
  1104  		{
  1105  			name: "outputs 0 and 2, not coinbase, output 2 compressed",
  1106  			entry: &UtxoEntry{
  1107  				txVersion:  1,
  1108  				isCoinBase: false,
  1109  				hasExpiry:  false,
  1110  				txType:     0,
  1111  				height:     12345,
  1112  				index:      1,
  1113  				sparseOutputs: map[uint32]*utxoOutput{
  1114  					0: {
  1115  						amount:        20000000,
  1116  						scriptVersion: 0,
  1117  						pkScript:      hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
  1118  						compressed:    false,
  1119  					},
  1120  					2: {
  1121  						// Uncompressed Amount: 15000000
  1122  						// Uncompressed PkScript: 76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac
  1123  						amount: 137,
  1124  
  1125  						pkScript:   hexToBytes("00b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
  1126  						compressed: true,
  1127  					},
  1128  				},
  1129  			},
  1130  			serialized: hexToBytes("01df3901000501120000e2ccd6ec7c6e2e581349c77e067385fa8236bf8a884f0000b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
  1131  		},
  1132  		{
  1133  			name: "outputs 0 and 2, not coinbase, output 2 compressed, packed indexes reversed",
  1134  			entry: &UtxoEntry{
  1135  				txVersion:  1,
  1136  				isCoinBase: false,
  1137  				hasExpiry:  false,
  1138  				txType:     0,
  1139  				height:     33333,
  1140  				index:      21,
  1141  				sparseOutputs: map[uint32]*utxoOutput{
  1142  					0: {
  1143  						amount:        20000000,
  1144  						scriptVersion: 0,
  1145  						pkScript:      hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"),
  1146  						compressed:    false,
  1147  					},
  1148  					2: {
  1149  						// Uncompressed Amount: 15000000
  1150  						// Uncompressed PkScript: 76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac
  1151  						amount:        137,
  1152  						scriptVersion: 0,
  1153  						pkScript:      hexToBytes("00b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
  1154  						compressed:    true,
  1155  					},
  1156  				},
  1157  			},
  1158  			serialized: hexToBytes("0181833515000501120000e2ccd6ec7c6e2e581349c77e067385fa8236bf8a884f0000b8025be1b3efc63b0ad48e7f9f10e87544528d58"),
  1159  		},
  1160  		{
  1161  			name: "Only output 0, coinbase, fully spent",
  1162  			entry: &UtxoEntry{
  1163  				txVersion:  1,
  1164  				isCoinBase: false,
  1165  				hasExpiry:  false,
  1166  				txType:     0,
  1167  				height:     33333,
  1168  				index:      231,
  1169  				sparseOutputs: map[uint32]*utxoOutput{
  1170  					0: {
  1171  						spent:         true,
  1172  						amount:        5000000000,
  1173  						scriptVersion: 0,
  1174  						pkScript:      hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
  1175  						compressed:    false,
  1176  					},
  1177  				},
  1178  			},
  1179  			serialized: nil,
  1180  		},
  1181  		{
  1182  			name: "Only output 22, not coinbase",
  1183  			entry: &UtxoEntry{
  1184  				txVersion:  1,
  1185  				isCoinBase: false,
  1186  				hasExpiry:  false,
  1187  				txType:     0,
  1188  				height:     3221,
  1189  				index:      211,
  1190  				sparseOutputs: map[uint32]*utxoOutput{
  1191  					22: {
  1192  						spent:         false,
  1193  						amount:        366875659,
  1194  						scriptVersion: 0,
  1195  						pkScript:      hexToBytes("a9141dd46a006572d820e448e12d2bbb38640bc718e687"),
  1196  						compressed:    false,
  1197  					},
  1198  				},
  1199  			},
  1200  			serialized: hexToBytes("019815805300080000108ba5b9e76300011dd46a006572d820e448e12d2bbb38640bc718e6"),
  1201  		},
  1202  	}
  1203  
  1204  	for i, test := range tests {
  1205  		// Ensure the utxo entry serializes to the expected value.
  1206  		gotBytes, err := serializeUtxoEntry(test.entry)
  1207  		if err != nil {
  1208  			t.Errorf("serializeUtxoEntry #%d (%s) unexpected "+
  1209  				"error: %v", i, test.name, err)
  1210  			continue
  1211  		}
  1212  		if !bytes.Equal(gotBytes, test.serialized) {
  1213  			t.Errorf("serializeUtxoEntry #%d (%s): mismatched "+
  1214  				"bytes - got %x, want %x", i, test.name,
  1215  				gotBytes, test.serialized)
  1216  			continue
  1217  		}
  1218  
  1219  		// Don't try to deserialize if the test entry was fully spent
  1220  		// since it will have a nil serialization.
  1221  		if test.entry.IsFullySpent() {
  1222  			continue
  1223  		}
  1224  
  1225  		// Deserialize to a utxo entry.
  1226  		utxoEntry, err := deserializeUtxoEntry(test.serialized)
  1227  		if err != nil {
  1228  			t.Errorf("deserializeUtxoEntry #%d (%s) unexpected "+
  1229  				"error: %v", i, test.name, err)
  1230  			continue
  1231  		}
  1232  
  1233  		// Ensure that the deserialized utxo entry has the same
  1234  		// properties for the containing transaction and block height.
  1235  		if utxoEntry.TxVersion() != test.entry.TxVersion() {
  1236  			t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
  1237  				" txVersion: got %d, want %d", i, test.name,
  1238  				utxoEntry.TxVersion(), test.entry.TxVersion())
  1239  			continue
  1240  		}
  1241  		if utxoEntry.IsCoinBase() != test.entry.IsCoinBase() {
  1242  			t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
  1243  				"coinbase flag: got %v, want %v", i, test.name,
  1244  				utxoEntry.IsCoinBase(), test.entry.IsCoinBase())
  1245  			continue
  1246  		}
  1247  		if utxoEntry.BlockHeight() != test.entry.BlockHeight() {
  1248  			t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
  1249  				"block height: got %d, want %d", i, test.name,
  1250  				utxoEntry.BlockHeight(),
  1251  				test.entry.BlockHeight())
  1252  			continue
  1253  		}
  1254  		if utxoEntry.IsFullySpent() != test.entry.IsFullySpent() {
  1255  			t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
  1256  				"fully spent: got %v, want %v", i, test.name,
  1257  				utxoEntry.IsFullySpent(),
  1258  				test.entry.IsFullySpent())
  1259  			continue
  1260  		}
  1261  
  1262  		// Ensure all of the outputs in the test entry match the
  1263  		// spentness of the output in the deserialized entry and the
  1264  		// deserialized entry does not contain any additional utxos.
  1265  		var numUnspent int
  1266  		for outputIndex := range test.entry.sparseOutputs {
  1267  			gotSpent := utxoEntry.IsOutputSpent(outputIndex)
  1268  			wantSpent := test.entry.IsOutputSpent(outputIndex)
  1269  			if !wantSpent {
  1270  				numUnspent++
  1271  			}
  1272  			if gotSpent != wantSpent {
  1273  				t.Errorf("deserializeUtxoEntry #%d (%s) output "+
  1274  					"#%d: mismatched spent: got %v, want "+
  1275  					"%v", i, test.name, outputIndex,
  1276  					gotSpent, wantSpent)
  1277  				continue
  1278  
  1279  			}
  1280  		}
  1281  		if len(utxoEntry.sparseOutputs) != numUnspent {
  1282  			t.Errorf("deserializeUtxoEntry #%d (%s): mismatched "+
  1283  				"number of unspent outputs: got %d, want %d", i,
  1284  				test.name, len(utxoEntry.sparseOutputs),
  1285  				numUnspent)
  1286  			continue
  1287  		}
  1288  
  1289  		// Ensure all of the amounts and scripts of the utxos in the
  1290  		// deserialized entry match the ones in the test entry.
  1291  		for outputIndex := range utxoEntry.sparseOutputs {
  1292  			gotAmount := utxoEntry.AmountByIndex(outputIndex)
  1293  			wantAmount := test.entry.AmountByIndex(outputIndex)
  1294  			if gotAmount != wantAmount {
  1295  				t.Errorf("deserializeUtxoEntry #%d (%s) "+
  1296  					"output #%d: mismatched amounts: got "+
  1297  					"%d, want %d", i, test.name,
  1298  					outputIndex, gotAmount, wantAmount)
  1299  				continue
  1300  			}
  1301  
  1302  			gotPkScript := utxoEntry.PkScriptByIndex(outputIndex)
  1303  			wantPkScript := test.entry.PkScriptByIndex(outputIndex)
  1304  			if !bytes.Equal(gotPkScript, wantPkScript) {
  1305  				t.Errorf("deserializeUtxoEntry #%d (%s) "+
  1306  					"output #%d mismatched scripts: got "+
  1307  					"%x, want %x", i, test.name,
  1308  					outputIndex, gotPkScript, wantPkScript)
  1309  				continue
  1310  			}
  1311  		}
  1312  	}
  1313  }
  1314  
  1315  // TestUtxoEntryDeserializeErrors performs negative tests against deserializing
  1316  // unspent transaction outputs to ensure error paths work as expected.
  1317  func TestUtxoEntryDeserializeErrors(t *testing.T) {
  1318  	t.Parallel()
  1319  
  1320  	tests := []struct {
  1321  		name       string
  1322  		serialized []byte
  1323  		errType    error
  1324  	}{
  1325  		{
  1326  			name:       "no data after version",
  1327  			serialized: hexToBytes("01"),
  1328  			errType:    errDeserialize(""),
  1329  		},
  1330  		{
  1331  			name:       "no data after block height",
  1332  			serialized: hexToBytes("0101"),
  1333  			errType:    errDeserialize(""),
  1334  		},
  1335  		{
  1336  			name:       "no data after header code",
  1337  			serialized: hexToBytes("010102"),
  1338  			errType:    errDeserialize(""),
  1339  		},
  1340  		{
  1341  			name:       "not enough bytes for unspentness bitmap",
  1342  			serialized: hexToBytes("01017800"),
  1343  			errType:    errDeserialize(""),
  1344  		},
  1345  		{
  1346  			name:       "incomplete compressed txout",
  1347  			serialized: hexToBytes("01010232"),
  1348  			errType:    errDeserialize(""),
  1349  		},
  1350  	}
  1351  
  1352  	for _, test := range tests {
  1353  		// Ensure the expected error type is returned and the returned
  1354  		// entry is nil.
  1355  		entry, err := deserializeUtxoEntry(test.serialized)
  1356  		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
  1357  			t.Errorf("deserializeUtxoEntry (%s): expected error "+
  1358  				"type does not match - got %T, want %T",
  1359  				test.name, err, test.errType)
  1360  			continue
  1361  		}
  1362  		if entry != nil {
  1363  			t.Errorf("deserializeUtxoEntry (%s): returned entry "+
  1364  				"is not nil", test.name)
  1365  			continue
  1366  		}
  1367  	}
  1368  }
  1369  
  1370  // TestBestChainStateSerialization ensures serializing and deserializing the
  1371  // best chain state works as expected.
  1372  func TestBestChainStateSerialization(t *testing.T) {
  1373  	t.Parallel()
  1374  
  1375  	workSum := new(big.Int)
  1376  	tests := []struct {
  1377  		name       string
  1378  		state      bestChainState
  1379  		serialized []byte
  1380  	}{
  1381  		{
  1382  			name: "genesis",
  1383  			state: bestChainState{
  1384  				hash:         *newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
  1385  				height:       0,
  1386  				totalTxns:    1,
  1387  				totalSubsidy: 0,
  1388  				workSum: func() *big.Int {
  1389  					workSum.Add(workSum, standalone.CalcWork(486604799))
  1390  					return new(big.Int).Set(workSum)
  1391  				}(), // 0x0100010001
  1392  			},
  1393  			serialized: hexToBytes("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000001000000000000000000000000000000050000000100010001"),
  1394  		},
  1395  		{
  1396  			name: "block 1",
  1397  			state: bestChainState{
  1398  				hash:         *newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"),
  1399  				height:       1,
  1400  				totalTxns:    2,
  1401  				totalSubsidy: 123456789,
  1402  				workSum: func() *big.Int {
  1403  					workSum.Add(workSum, standalone.CalcWork(486604799))
  1404  					return new(big.Int).Set(workSum)
  1405  				}(), // 0x0200020002,
  1406  			},
  1407  			serialized: hexToBytes("4860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a830000000001000000020000000000000015cd5b0700000000050000000200020002"),
  1408  		},
  1409  	}
  1410  
  1411  	for i, test := range tests {
  1412  		// Ensure the state serializes to the expected value.
  1413  		gotBytes := serializeBestChainState(test.state)
  1414  		if !bytes.Equal(gotBytes, test.serialized) {
  1415  			t.Errorf("serializeBestChainState #%d (%s): mismatched "+
  1416  				"bytes - got %x, want %x", i, test.name,
  1417  				gotBytes, test.serialized)
  1418  			continue
  1419  		}
  1420  
  1421  		// Ensure the serialized bytes are decoded back to the expected
  1422  		// state.
  1423  		state, err := deserializeBestChainState(test.serialized)
  1424  		if err != nil {
  1425  			t.Errorf("deserializeBestChainState #%d (%s) "+
  1426  				"unexpected error: %v", i, test.name, err)
  1427  			continue
  1428  		}
  1429  		if !reflect.DeepEqual(state, test.state) {
  1430  			t.Errorf("deserializeBestChainState #%d (%s) "+
  1431  				"mismatched state - got %v, want %v", i,
  1432  				test.name, state, test.state)
  1433  			continue
  1434  
  1435  		}
  1436  	}
  1437  }
  1438  
  1439  // TestBestChainStateDeserializeErrors performs negative tests against
  1440  // deserializing the chain state to ensure error paths work as expected.
  1441  func TestBestChainStateDeserializeErrors(t *testing.T) {
  1442  	t.Parallel()
  1443  
  1444  	tests := []struct {
  1445  		name       string
  1446  		serialized []byte
  1447  		errType    error
  1448  	}{
  1449  		{
  1450  			name:       "nothing serialized",
  1451  			serialized: hexToBytes(""),
  1452  			errType:    database.Error{ErrorCode: database.ErrCorruption},
  1453  		},
  1454  		{
  1455  			name:       "short data in hash",
  1456  			serialized: hexToBytes("0000"),
  1457  			errType:    database.Error{ErrorCode: database.ErrCorruption},
  1458  		},
  1459  		{
  1460  			name:       "short data in work sum",
  1461  			serialized: hexToBytes("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000001000000000000000500000001000100"),
  1462  			errType:    database.Error{ErrorCode: database.ErrCorruption},
  1463  		},
  1464  	}
  1465  
  1466  	for _, test := range tests {
  1467  		// Ensure the expected error type and code is returned.
  1468  		_, err := deserializeBestChainState(test.serialized)
  1469  		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
  1470  			t.Errorf("deserializeBestChainState (%s): expected "+
  1471  				"error type does not match - got %T, want %T",
  1472  				test.name, err, test.errType)
  1473  			continue
  1474  		}
  1475  		if derr, ok := err.(database.Error); ok {
  1476  			tderr := test.errType.(database.Error)
  1477  			if derr.ErrorCode != tderr.ErrorCode {
  1478  				t.Errorf("deserializeBestChainState (%s): "+
  1479  					"wrong  error code got: %v, want: %v",
  1480  					test.name, derr.ErrorCode,
  1481  					tderr.ErrorCode)
  1482  				continue
  1483  			}
  1484  		}
  1485  	}
  1486  }