mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[DEV-92] Package blockdag tests (#177)
* [DEV-92] Covered blocknode.go with tests. * [DEV-92] Added test for blockSet.highest. Fixed a bug in it. * [DEV-92] Added tests for blockSet.subtract and blockSet.addSet. * [DEV-92] Covered blockset.go with tests. * [DEV-92] Got rid of some old stuff related to STXOs. * [DEV-92] Covered error.go with tests. * [DEV-92] Covered utxoSet with tests. * [DEV-92] Fixed formatting.
This commit is contained in:
parent
100fbbaaa4
commit
a82e6ae24a
@ -78,6 +78,9 @@ func TestChainHeight(t *testing.T) {
|
||||
if test.node.chainHeight != test.expectedChainHeight {
|
||||
t.Errorf("block %v expected chain height %v but got %v", test.node, test.expectedChainHeight, test.node.chainHeight)
|
||||
}
|
||||
if calculateChainHeight(test.node) != test.expectedChainHeight {
|
||||
t.Errorf("block %v expected calculated chain height %v but got %v", test.node, test.expectedChainHeight, test.node.chainHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,7 +37,10 @@ func (bs blockSet) maxHeight() int32 {
|
||||
func (bs blockSet) highest() *blockNode {
|
||||
var highest *blockNode
|
||||
for _, node := range bs {
|
||||
if highest == nil || highest.height < node.height || daghash.Less(&node.hash, &highest.hash) {
|
||||
if highest == nil ||
|
||||
highest.height < node.height ||
|
||||
(highest.height == node.height && daghash.Less(&node.hash, &highest.hash)) {
|
||||
|
||||
highest = node
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
@ -33,3 +34,303 @@ func TestHashes(t *testing.T) {
|
||||
t.Errorf("TestHashes: hashes are not ordered as expected")
|
||||
}
|
||||
}
|
||||
func TestBlockSetHighest(t *testing.T) {
|
||||
node1 := &blockNode{hash: daghash.Hash{10}, height: 1}
|
||||
node2a := &blockNode{hash: daghash.Hash{20}, height: 2}
|
||||
node2b := &blockNode{hash: daghash.Hash{21}, height: 2}
|
||||
node3 := &blockNode{hash: daghash.Hash{30}, height: 3}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
set blockSet
|
||||
expectedHighest *blockNode
|
||||
}{
|
||||
{
|
||||
name: "empty set",
|
||||
set: setFromSlice(),
|
||||
expectedHighest: nil,
|
||||
},
|
||||
{
|
||||
name: "set with one member",
|
||||
set: setFromSlice(node1),
|
||||
expectedHighest: node1,
|
||||
},
|
||||
{
|
||||
name: "same-height highest members in set",
|
||||
set: setFromSlice(node2b, node1, node2a),
|
||||
expectedHighest: node2a,
|
||||
},
|
||||
{
|
||||
name: "typical set",
|
||||
set: setFromSlice(node2b, node3, node1, node2a),
|
||||
expectedHighest: node3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
highest := test.set.highest()
|
||||
if highest != test.expectedHighest {
|
||||
t.Errorf("blockSet.highest: unexpected value in test '%s'. "+
|
||||
"Expected: %v, got: %v", test.name, test.expectedHighest, highest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockSetSubtract(t *testing.T) {
|
||||
node1 := &blockNode{hash: daghash.Hash{10}}
|
||||
node2 := &blockNode{hash: daghash.Hash{20}}
|
||||
node3 := &blockNode{hash: daghash.Hash{30}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setA blockSet
|
||||
setB blockSet
|
||||
expectedResult blockSet
|
||||
}{
|
||||
{
|
||||
name: "both sets empty",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "subtract an empty set",
|
||||
setA: setFromSlice(node1),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "subtract from empty set",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "subtract unrelated set",
|
||||
setA: setFromSlice(node1),
|
||||
setB: setFromSlice(node2),
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "typical case",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node2, node3),
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := test.setA.subtract(test.setB)
|
||||
if !reflect.DeepEqual(result, test.expectedResult) {
|
||||
t.Errorf("blockSet.subtract: unexpected result in test '%s'. "+
|
||||
"Expected: %v, got: %v", test.name, test.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockSetAddSet(t *testing.T) {
|
||||
node1 := &blockNode{hash: daghash.Hash{10}}
|
||||
node2 := &blockNode{hash: daghash.Hash{20}}
|
||||
node3 := &blockNode{hash: daghash.Hash{30}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setA blockSet
|
||||
setB blockSet
|
||||
expectedResult blockSet
|
||||
}{
|
||||
{
|
||||
name: "both sets empty",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "add an empty set",
|
||||
setA: setFromSlice(node1),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "add to empty set",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "add already added member",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(node1, node2),
|
||||
},
|
||||
{
|
||||
name: "typical case",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node2, node3),
|
||||
expectedResult: setFromSlice(node1, node2, node3),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test.setA.addSet(test.setB)
|
||||
if !reflect.DeepEqual(test.setA, test.expectedResult) {
|
||||
t.Errorf("blockSet.addSet: unexpected result in test '%s'. "+
|
||||
"Expected: %v, got: %v", test.name, test.expectedResult, test.setA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockSetAddSlice(t *testing.T) {
|
||||
node1 := &blockNode{hash: daghash.Hash{10}}
|
||||
node2 := &blockNode{hash: daghash.Hash{20}}
|
||||
node3 := &blockNode{hash: daghash.Hash{30}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
set blockSet
|
||||
slice []*blockNode
|
||||
expectedResult blockSet
|
||||
}{
|
||||
{
|
||||
name: "add empty slice to empty set",
|
||||
set: setFromSlice(),
|
||||
slice: []*blockNode{},
|
||||
expectedResult: setFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "add an empty slice",
|
||||
set: setFromSlice(node1),
|
||||
slice: []*blockNode{},
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "add to empty set",
|
||||
set: setFromSlice(),
|
||||
slice: []*blockNode{node1},
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "add already added member",
|
||||
set: setFromSlice(node1, node2),
|
||||
slice: []*blockNode{node1},
|
||||
expectedResult: setFromSlice(node1, node2),
|
||||
},
|
||||
{
|
||||
name: "typical case",
|
||||
set: setFromSlice(node1, node2),
|
||||
slice: []*blockNode{node2, node3},
|
||||
expectedResult: setFromSlice(node1, node2, node3),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test.set.addSlice(test.slice)
|
||||
if !reflect.DeepEqual(test.set, test.expectedResult) {
|
||||
t.Errorf("blockSet.addSlice: unexpected result in test '%s'. "+
|
||||
"Expected: %v, got: %v", test.name, test.expectedResult, test.set)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockSetUnion(t *testing.T) {
|
||||
node1 := &blockNode{hash: daghash.Hash{10}}
|
||||
node2 := &blockNode{hash: daghash.Hash{20}}
|
||||
node3 := &blockNode{hash: daghash.Hash{30}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setA blockSet
|
||||
setB blockSet
|
||||
expectedResult blockSet
|
||||
}{
|
||||
{
|
||||
name: "both sets empty",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(),
|
||||
},
|
||||
{
|
||||
name: "union against an empty set",
|
||||
setA: setFromSlice(node1),
|
||||
setB: setFromSlice(),
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "union from an empty set",
|
||||
setA: setFromSlice(),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(node1),
|
||||
},
|
||||
{
|
||||
name: "union with subset",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node1),
|
||||
expectedResult: setFromSlice(node1, node2),
|
||||
},
|
||||
{
|
||||
name: "typical case",
|
||||
setA: setFromSlice(node1, node2),
|
||||
setB: setFromSlice(node2, node3),
|
||||
expectedResult: setFromSlice(node1, node2, node3),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := test.setA.union(test.setB)
|
||||
if !reflect.DeepEqual(result, test.expectedResult) {
|
||||
t.Errorf("blockSet.union: unexpected result in test '%s'. "+
|
||||
"Expected: %v, got: %v", test.name, test.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockSetHashesEqual(t *testing.T) {
|
||||
node1 := &blockNode{hash: daghash.Hash{10}}
|
||||
node2 := &blockNode{hash: daghash.Hash{20}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
set blockSet
|
||||
hashes []daghash.Hash
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "empty set, no hashes",
|
||||
set: setFromSlice(),
|
||||
hashes: []daghash.Hash{},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "empty set, one hash",
|
||||
set: setFromSlice(),
|
||||
hashes: []daghash.Hash{node1.hash},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "set and hashes of different length",
|
||||
set: setFromSlice(node1, node2),
|
||||
hashes: []daghash.Hash{node1.hash},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "set equal to hashes",
|
||||
set: setFromSlice(node1, node2),
|
||||
hashes: []daghash.Hash{node1.hash, node2.hash},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "set equal to hashes, different order",
|
||||
set: setFromSlice(node1, node2),
|
||||
hashes: []daghash.Hash{node2.hash, node1.hash},
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := test.set.hashesEqual(test.hashes)
|
||||
if result != test.expectedResult {
|
||||
t.Errorf("blockSet.hashesEqual: unexpected result in test '%s'. "+
|
||||
"Expected: %t, got: %t", test.name, test.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,272 +103,6 @@ func dbPutVersion(dbTx database.Tx, key []byte, version uint32) error {
|
||||
return dbTx.Metadata().Put(key, serialized[:])
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The transaction spend journal consists of an entry for each block connected
|
||||
// to the main chain which contains the transaction outputs the block spends
|
||||
// serialized such that the order is the reverse of the order they were spent.
|
||||
//
|
||||
// This is required because reorganizing the chain necessarily entails
|
||||
// disconnecting blocks to get back to the point of the fork which implies
|
||||
// unspending all of the transaction outputs that each block previously spent.
|
||||
// Since the UTXO set, by definition, only contains unspent transaction outputs,
|
||||
// the spent transaction outputs must be resurrected from somewhere. There is
|
||||
// more than one way this could be done, however this is the most straight
|
||||
// forward method that does not require having a transaction index and unpruned
|
||||
// blockchain.
|
||||
//
|
||||
// NOTE: This format is NOT self describing. The additional details such as
|
||||
// the number of entries (transaction inputs) are expected to come from the
|
||||
// block itself and the UTXO set (for legacy entries). The rationale in doing
|
||||
// this is to save space. This is also the reason the spent outputs are
|
||||
// serialized in the reverse order they are spent because later transactions are
|
||||
// allowed to spend outputs from earlier ones in the same block.
|
||||
//
|
||||
// The reserved field below used to keep track of the version of the containing
|
||||
// transaction when the height in the header code was non-zero, however the
|
||||
// height is always non-zero now, but keeping the extra reserved field allows
|
||||
// backwards compatibility.
|
||||
//
|
||||
// The serialized format is:
|
||||
//
|
||||
// [<header code><reserved><compressed txout>],...
|
||||
//
|
||||
// Field Type Size
|
||||
// header code VLQ variable
|
||||
// reserved byte 1
|
||||
// compressed txout
|
||||
// compressed amount VLQ variable
|
||||
// compressed script []byte variable
|
||||
//
|
||||
// The serialized header code format is:
|
||||
// bit 0 - containing transaction is a coinbase
|
||||
// bits 1-x - height of the block that contains the spent txout
|
||||
//
|
||||
// Example 1:
|
||||
// From block 170 in main blockchain.
|
||||
//
|
||||
// 1300320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c
|
||||
// <><><------------------------------------------------------------------>
|
||||
// | | |
|
||||
// | reserved compressed txout
|
||||
// header code
|
||||
//
|
||||
// - header code: 0x13 (coinbase, height 9)
|
||||
// - reserved: 0x00
|
||||
// - compressed txout 0:
|
||||
// - 0x32: VLQ-encoded compressed amount for 5000000000 (50 BTC)
|
||||
// - 0x05: special script type pay-to-pubkey
|
||||
// - 0x11...5c: x-coordinate of the pubkey
|
||||
//
|
||||
// Example 2:
|
||||
// Adapted from block 100025 in main blockchain.
|
||||
//
|
||||
// 8b99700091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e868b99700086c64700b2fb57eadf61e106a100a7445a8c3f67898841ec
|
||||
// <----><><----------------------------------------------><----><><---------------------------------------------->
|
||||
// | | | | | |
|
||||
// | reserved compressed txout | reserved compressed txout
|
||||
// header code header code
|
||||
//
|
||||
// - Last spent output:
|
||||
// - header code: 0x8b9970 (not coinbase, height 100024)
|
||||
// - reserved: 0x00
|
||||
// - compressed txout:
|
||||
// - 0x91f20f: VLQ-encoded compressed amount for 34405000000 (344.05 BTC)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0x6e...86: pubkey hash
|
||||
// - Second to last spent output:
|
||||
// - header code: 0x8b9970 (not coinbase, height 100024)
|
||||
// - reserved: 0x00
|
||||
// - compressed txout:
|
||||
// - 0x86c647: VLQ-encoded compressed amount for 13761000000 (137.61 BTC)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0xb2...ec: pubkey hash
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// spentTxOut contains a spent transaction output and potentially additional
|
||||
// contextual information such as whether or not it was contained in a coinbase
|
||||
// transaction, the version of the transaction it was contained in, and which
|
||||
// block height the containing transaction was included in. As described in
|
||||
// the comments above, the additional contextual information will only be valid
|
||||
// when this spent txout is spending the last unspent output of the containing
|
||||
// transaction.
|
||||
type spentTxOut struct {
|
||||
amount int64 // The amount of the output.
|
||||
pkScript []byte // The public key script for the output.
|
||||
height int32 // Height of the the block containing the creating tx.
|
||||
isCoinBase bool // Whether creating tx is a coinbase.
|
||||
}
|
||||
|
||||
// spentTxOutHeaderCode returns the calculated header code to be used when
|
||||
// serializing the provided stxo entry.
|
||||
func spentTxOutHeaderCode(stxo *spentTxOut) uint64 {
|
||||
// As described in the serialization format comments, the header code
|
||||
// encodes the height shifted over one bit and the coinbase flag in the
|
||||
// lowest bit.
|
||||
headerCode := uint64(stxo.height) << 1
|
||||
if stxo.isCoinBase {
|
||||
headerCode |= 0x01
|
||||
}
|
||||
|
||||
return headerCode
|
||||
}
|
||||
|
||||
// spentTxOutSerializeSize returns the number of bytes it would take to
|
||||
// serialize the passed stxo according to the format described above.
|
||||
func spentTxOutSerializeSize(stxo *spentTxOut) int {
|
||||
size := serializeSizeVLQ(spentTxOutHeaderCode(stxo))
|
||||
if stxo.height > 0 {
|
||||
// The legacy v1 spend journal format conditionally tracked the
|
||||
// containing transaction version when the height was non-zero,
|
||||
// so this is required for backwards compat.
|
||||
size += serializeSizeVLQ(0)
|
||||
}
|
||||
return size + compressedTxOutSize(uint64(stxo.amount), stxo.pkScript)
|
||||
}
|
||||
|
||||
// putSpentTxOut serializes the passed stxo according to the format described
|
||||
// above directly into the passed target byte slice. The target byte slice must
|
||||
// be at least large enough to handle the number of bytes returned by the
|
||||
// spentTxOutSerializeSize function or it will panic.
|
||||
func putSpentTxOut(target []byte, stxo *spentTxOut) int {
|
||||
headerCode := spentTxOutHeaderCode(stxo)
|
||||
offset := putVLQ(target, headerCode)
|
||||
if stxo.height > 0 {
|
||||
// The legacy v1 spend journal format conditionally tracked the
|
||||
// containing transaction version when the height was non-zero,
|
||||
// so this is required for backwards compat.
|
||||
offset += putVLQ(target[offset:], 0)
|
||||
}
|
||||
return offset + putCompressedTxOut(target[offset:], uint64(stxo.amount),
|
||||
stxo.pkScript)
|
||||
}
|
||||
|
||||
// decodeSpentTxOut decodes the passed serialized stxo entry, possibly followed
|
||||
// by other data, into the passed stxo struct. It returns the number of bytes
|
||||
// read.
|
||||
func decodeSpentTxOut(serialized []byte, stxo *spentTxOut) (int, error) {
|
||||
// Ensure there are bytes to decode.
|
||||
if len(serialized) == 0 {
|
||||
return 0, errDeserialize("no serialized bytes")
|
||||
}
|
||||
|
||||
// Deserialize the header code.
|
||||
code, offset := deserializeVLQ(serialized)
|
||||
if offset >= len(serialized) {
|
||||
return offset, errDeserialize("unexpected end of data after " +
|
||||
"header code")
|
||||
}
|
||||
|
||||
// Decode the header code.
|
||||
//
|
||||
// Bit 0 indicates containing transaction is a coinbase.
|
||||
// Bits 1-x encode height of containing transaction.
|
||||
stxo.isCoinBase = code&0x01 != 0
|
||||
stxo.height = int32(code >> 1)
|
||||
if stxo.height > 0 {
|
||||
// The legacy v1 spend journal format conditionally tracked the
|
||||
// containing transaction version when the height was non-zero,
|
||||
// so this is required for backwards compat.
|
||||
_, bytesRead := deserializeVLQ(serialized[offset:])
|
||||
offset += bytesRead
|
||||
if offset >= len(serialized) {
|
||||
return offset, errDeserialize("unexpected end of data " +
|
||||
"after reserved")
|
||||
}
|
||||
}
|
||||
|
||||
// Decode the compressed txout.
|
||||
amount, pkScript, bytesRead, err := decodeCompressedTxOut(
|
||||
serialized[offset:])
|
||||
offset += bytesRead
|
||||
if err != nil {
|
||||
return offset, errDeserialize(fmt.Sprintf("unable to decode "+
|
||||
"txout: %v", err))
|
||||
}
|
||||
stxo.amount = int64(amount)
|
||||
stxo.pkScript = pkScript
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// deserializeSpendJournalEntry decodes the passed serialized byte slice into a
|
||||
// slice of spent txouts according to the format described in detail above.
|
||||
//
|
||||
// Since the serialization format is not self describing, as noted in the
|
||||
// format comments, this function also requires the transactions that spend the
|
||||
// txouts.
|
||||
func deserializeSpendJournalEntry(serialized []byte, txs []*wire.MsgTx) ([]spentTxOut, error) {
|
||||
// Calculate the total number of stxos.
|
||||
var numStxos int
|
||||
for _, tx := range txs {
|
||||
numStxos += len(tx.TxIn)
|
||||
}
|
||||
|
||||
// When a block has no spent txouts there is nothing to serialize.
|
||||
if len(serialized) == 0 {
|
||||
// Ensure the block actually has no stxos. This should never
|
||||
// happen unless there is database corruption or an empty entry
|
||||
// erroneously made its way into the database.
|
||||
if numStxos != 0 {
|
||||
return nil, AssertError(fmt.Sprintf("mismatched spend "+
|
||||
"journal serialization - no serialization for "+
|
||||
"expected %d stxos", numStxos))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Loop backwards through all transactions so everything is read in
|
||||
// reverse order to match the serialization order.
|
||||
stxoIdx := numStxos - 1
|
||||
offset := 0
|
||||
stxos := make([]spentTxOut, numStxos)
|
||||
for txIdx := len(txs) - 1; txIdx > -1; txIdx-- {
|
||||
tx := txs[txIdx]
|
||||
|
||||
// Loop backwards through all of the transaction inputs and read
|
||||
// the associated stxo.
|
||||
for txInIdx := len(tx.TxIn) - 1; txInIdx > -1; txInIdx-- {
|
||||
txIn := tx.TxIn[txInIdx]
|
||||
stxo := &stxos[stxoIdx]
|
||||
stxoIdx--
|
||||
|
||||
n, err := decodeSpentTxOut(serialized[offset:], stxo)
|
||||
offset += n
|
||||
if err != nil {
|
||||
return nil, errDeserialize(fmt.Sprintf("unable "+
|
||||
"to decode stxo for %v: %v",
|
||||
txIn.PreviousOutPoint, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stxos, nil
|
||||
}
|
||||
|
||||
// serializeSpendJournalEntry serializes all of the passed spent txouts into a
|
||||
// single byte slice according to the format described in detail above.
|
||||
func serializeSpendJournalEntry(stxos []spentTxOut) []byte {
|
||||
if len(stxos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Calculate the size needed to serialize the entire journal entry.
|
||||
var size int
|
||||
for i := range stxos {
|
||||
size += spentTxOutSerializeSize(&stxos[i])
|
||||
}
|
||||
serialized := make([]byte, size)
|
||||
|
||||
// Serialize each individual stxo directly into the slice in reverse
|
||||
// order one after the other.
|
||||
var offset int
|
||||
for i := len(stxos) - 1; i > -1; i-- {
|
||||
offset += putSpentTxOut(serialized[offset:], &stxos[i])
|
||||
}
|
||||
|
||||
return serialized
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The unspent transaction output (UTXO) set consists of an entry for each
|
||||
// unspent output using a format that is optimized to reduce space using domain
|
||||
|
@ -7,13 +7,11 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||
"github.com/daglabs/btcd/database"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
)
|
||||
|
||||
// TestErrNotInDAG ensures the functions related to errNotInDAG work
|
||||
@ -38,371 +36,6 @@ func TestErrNotInDAG(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestStxoSerialization ensures serializing and deserializing spent transaction
|
||||
// output entries works as expected.
|
||||
func TestStxoSerialization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
stxo spentTxOut
|
||||
serialized []byte
|
||||
}{
|
||||
// From block 170 in main blockchain.
|
||||
{
|
||||
name: "Spends last output of coinbase",
|
||||
stxo: spentTxOut{
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
isCoinBase: true,
|
||||
height: 9,
|
||||
},
|
||||
serialized: hexToBytes("1300320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
|
||||
},
|
||||
// Adapted from block 100025 in main blockchain.
|
||||
{
|
||||
name: "Spends last output of non coinbase",
|
||||
stxo: spentTxOut{
|
||||
amount: 13761000000,
|
||||
pkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"),
|
||||
isCoinBase: false,
|
||||
height: 100024,
|
||||
},
|
||||
serialized: hexToBytes("8b99700086c64700b2fb57eadf61e106a100a7445a8c3f67898841ec"),
|
||||
},
|
||||
// Adapted from block 100025 in main blockchain.
|
||||
{
|
||||
name: "Does not spend last output, legacy format",
|
||||
stxo: spentTxOut{
|
||||
amount: 34405000000,
|
||||
pkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
|
||||
},
|
||||
serialized: hexToBytes("0091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the function to calculate the serialized size without
|
||||
// actually serializing it is calculated properly.
|
||||
gotSize := spentTxOutSerializeSize(&test.stxo)
|
||||
if gotSize != len(test.serialized) {
|
||||
t.Errorf("spentTxOutSerializeSize (%s): did not get "+
|
||||
"expected size - got %d, want %d", test.name,
|
||||
gotSize, len(test.serialized))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the stxo serializes to the expected value.
|
||||
gotSerialized := make([]byte, gotSize)
|
||||
gotBytesWritten := putSpentTxOut(gotSerialized, &test.stxo)
|
||||
if !bytes.Equal(gotSerialized, test.serialized) {
|
||||
t.Errorf("putSpentTxOut (%s): did not get expected "+
|
||||
"bytes - got %x, want %x", test.name,
|
||||
gotSerialized, test.serialized)
|
||||
continue
|
||||
}
|
||||
if gotBytesWritten != len(test.serialized) {
|
||||
t.Errorf("putSpentTxOut (%s): did not get expected "+
|
||||
"number of bytes written - got %d, want %d",
|
||||
test.name, gotBytesWritten,
|
||||
len(test.serialized))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the serialized bytes are decoded back to the expected
|
||||
// stxo.
|
||||
var gotStxo spentTxOut
|
||||
gotBytesRead, err := decodeSpentTxOut(test.serialized, &gotStxo)
|
||||
if err != nil {
|
||||
t.Errorf("decodeSpentTxOut (%s): unexpected error: %v",
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(gotStxo, test.stxo) {
|
||||
t.Errorf("decodeSpentTxOut (%s) mismatched entries - "+
|
||||
"got %v, want %v", test.name, gotStxo, test.stxo)
|
||||
continue
|
||||
}
|
||||
if gotBytesRead != len(test.serialized) {
|
||||
t.Errorf("decodeSpentTxOut (%s): did not get expected "+
|
||||
"number of bytes read - got %d, want %d",
|
||||
test.name, gotBytesRead, len(test.serialized))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestStxoDecodeErrors performs negative tests against decoding spent
|
||||
// transaction outputs to ensure error paths work as expected.
|
||||
func TestStxoDecodeErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
stxo spentTxOut
|
||||
serialized []byte
|
||||
bytesRead int // Expected number of bytes read.
|
||||
errType error
|
||||
}{
|
||||
{
|
||||
name: "nothing serialized",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes(""),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 0,
|
||||
},
|
||||
{
|
||||
name: "no data after header code w/o reserved",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("00"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 1,
|
||||
},
|
||||
{
|
||||
name: "no data after header code with reserved",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("13"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 1,
|
||||
},
|
||||
{
|
||||
name: "no data after reserved",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("1300"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 2,
|
||||
},
|
||||
{
|
||||
name: "incomplete compressed txout",
|
||||
stxo: spentTxOut{},
|
||||
serialized: hexToBytes("1332"),
|
||||
errType: errDeserialize(""),
|
||||
bytesRead: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the expected error type is returned.
|
||||
gotBytesRead, err := decodeSpentTxOut(test.serialized,
|
||||
&test.stxo)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||
t.Errorf("decodeSpentTxOut (%s): expected error type "+
|
||||
"does not match - got %T, want %T", test.name,
|
||||
err, test.errType)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the expected number of bytes read is returned.
|
||||
if gotBytesRead != test.bytesRead {
|
||||
t.Errorf("decodeSpentTxOut (%s): unexpected number of "+
|
||||
"bytes read - got %d, want %d", test.name,
|
||||
gotBytesRead, test.bytesRead)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSpendJournalSerialization ensures serializing and deserializing spend
|
||||
// journal entries works as expected.
|
||||
func TestSpendJournalSerialization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entry []spentTxOut
|
||||
blockTxns []*wire.MsgTx
|
||||
serialized []byte
|
||||
}{
|
||||
// From block 2 in main blockchain.
|
||||
{
|
||||
name: "No spends",
|
||||
entry: nil,
|
||||
blockTxns: nil,
|
||||
serialized: nil,
|
||||
},
|
||||
// From block 170 in main blockchain.
|
||||
{
|
||||
name: "One tx with one input spends last output of coinbase",
|
||||
entry: []spentTxOut{{
|
||||
amount: 5000000000,
|
||||
pkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
isCoinBase: true,
|
||||
height: 9,
|
||||
}},
|
||||
blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
TxID: *newTxIDFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
|
||||
Index: 0,
|
||||
},
|
||||
SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
|
||||
Sequence: math.MaxUint64,
|
||||
}},
|
||||
TxOut: []*wire.TxOut{{
|
||||
Value: 1000000000,
|
||||
PkScript: hexToBytes("4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac"),
|
||||
}, {
|
||||
Value: 4000000000,
|
||||
PkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
serialized: hexToBytes("1300320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
|
||||
},
|
||||
// Adapted from block 100025 in main blockchain.
|
||||
{
|
||||
name: "Two txns when one spends last output, one doesn't",
|
||||
entry: []spentTxOut{{
|
||||
amount: 34405000000,
|
||||
pkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
|
||||
isCoinBase: false,
|
||||
height: 100024,
|
||||
}, {
|
||||
amount: 13761000000,
|
||||
pkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"),
|
||||
isCoinBase: false,
|
||||
height: 100024,
|
||||
}},
|
||||
blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
TxID: *newTxIDFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
|
||||
Index: 1,
|
||||
},
|
||||
SignatureScript: hexToBytes("493046022100c167eead9840da4a033c9a56470d7794a9bb1605b377ebe5688499b39f94be59022100fb6345cab4324f9ea0b9ee9169337534834638d818129778370f7d378ee4a325014104d962cac5390f12ddb7539507065d0def320d68c040f2e73337c3a1aaaab7195cb5c4d02e0959624d534f3c10c3cf3d73ca5065ebd62ae986b04c6d090d32627c"),
|
||||
Sequence: math.MaxUint64,
|
||||
}},
|
||||
TxOut: []*wire.TxOut{{
|
||||
Value: 5000000,
|
||||
PkScript: hexToBytes("76a914f419b8db4ba65f3b6fcc233acb762ca6f51c23d488ac"),
|
||||
}, {
|
||||
Value: 34400000000,
|
||||
PkScript: hexToBytes("76a914cadf4fc336ab3c6a4610b75f31ba0676b7f663d288ac"),
|
||||
}},
|
||||
LockTime: 0,
|
||||
}, {
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
TxID: *newTxIDFromStr("92fbe1d4be82f765dfabc9559d4620864b05cc897c4db0e29adac92d294e52b7"),
|
||||
Index: 0,
|
||||
},
|
||||
SignatureScript: hexToBytes("483045022100e256743154c097465cf13e89955e1c9ff2e55c46051b627751dee0144183157e02201d8d4f02cde8496aae66768f94d35ce54465bd4ae8836004992d3216a93a13f00141049d23ce8686fe9b802a7a938e8952174d35dd2c2089d4112001ed8089023ab4f93a3c9fcd5bfeaa9727858bf640dc1b1c05ec3b434bb59837f8640e8810e87742"),
|
||||
Sequence: math.MaxUint64,
|
||||
}},
|
||||
TxOut: []*wire.TxOut{{
|
||||
Value: 5000000,
|
||||
PkScript: hexToBytes("76a914a983ad7c92c38fc0e2025212e9f972204c6e687088ac"),
|
||||
}, {
|
||||
Value: 13756000000,
|
||||
PkScript: hexToBytes("76a914a6ebd69952ab486a7a300bfffdcb395dc7d47c2388ac"),
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
serialized: hexToBytes("8b99700086c64700b2fb57eadf61e106a100a7445a8c3f67898841ec8b99700091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
// Ensure the journal entry serializes to the expected value.
|
||||
gotBytes := serializeSpendJournalEntry(test.entry)
|
||||
if !bytes.Equal(gotBytes, test.serialized) {
|
||||
t.Errorf("serializeSpendJournalEntry #%d (%s): "+
|
||||
"mismatched bytes - got %x, want %x", i,
|
||||
test.name, gotBytes, test.serialized)
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize to a spend journal entry.
|
||||
gotEntry, err := deserializeSpendJournalEntry(test.serialized,
|
||||
test.blockTxns)
|
||||
if err != nil {
|
||||
t.Errorf("deserializeSpendJournalEntry #%d (%s) "+
|
||||
"unexpected error: %v", i, test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure that the deserialized spend journal entry has the
|
||||
// correct properties.
|
||||
if !reflect.DeepEqual(gotEntry, test.entry) {
|
||||
t.Errorf("deserializeSpendJournalEntry #%d (%s) "+
|
||||
"mismatched entries - got %v, want %v",
|
||||
i, test.name, gotEntry, test.entry)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSpendJournalErrors performs negative tests against deserializing spend
|
||||
// journal entries to ensure error paths work as expected.
|
||||
func TestSpendJournalErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
blockTxns []*wire.MsgTx
|
||||
serialized []byte
|
||||
errType error
|
||||
}{
|
||||
// Adapted from block 170 in main blockchain.
|
||||
{
|
||||
name: "Force assertion due to missing stxos",
|
||||
blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
TxID: *newTxIDFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
|
||||
Index: 0,
|
||||
},
|
||||
SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
|
||||
Sequence: math.MaxUint64,
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
serialized: hexToBytes(""),
|
||||
errType: AssertError(""),
|
||||
},
|
||||
{
|
||||
name: "Force deserialization error in stxos",
|
||||
blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
TxID: *newTxIDFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
|
||||
Index: 0,
|
||||
},
|
||||
SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
|
||||
Sequence: math.MaxUint64,
|
||||
}},
|
||||
LockTime: 0,
|
||||
}},
|
||||
serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the expected error type is returned and the returned
|
||||
// slice is nil.
|
||||
stxos, err := deserializeSpendJournalEntry(test.serialized,
|
||||
test.blockTxns)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||
t.Errorf("deserializeSpendJournalEntry (%s): expected "+
|
||||
"error type does not match - got %T, want %T",
|
||||
test.name, err, test.errType)
|
||||
continue
|
||||
}
|
||||
if stxos != nil {
|
||||
t.Errorf("deserializeSpendJournalEntry (%s): returned "+
|
||||
"slice of spent transaction outputs is not nil",
|
||||
test.name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUtxoSerialization ensures serializing and deserializing unspent
|
||||
// trasaction output entries works as expected.
|
||||
func TestUtxoSerialization(t *testing.T) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -131,5 +132,14 @@ func TestDeploymentError(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAssertError(t *testing.T) {
|
||||
message := "abc 123"
|
||||
err := AssertError(message)
|
||||
expectedMessage := fmt.Sprintf("assertion failed: %s", message)
|
||||
if expectedMessage != err.Error() {
|
||||
t.Errorf("Unexpected AssertError message. "+
|
||||
"Got: %s, want: %s", err.Error(), expectedMessage)
|
||||
}
|
||||
}
|
||||
|
@ -52,20 +52,6 @@ func (entry *UTXOEntry) PkScript() []byte {
|
||||
return entry.pkScript
|
||||
}
|
||||
|
||||
// Clone returns a shallow copy of the utxo entry.
|
||||
func (entry *UTXOEntry) Clone() *UTXOEntry {
|
||||
if entry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &UTXOEntry{
|
||||
amount: entry.amount,
|
||||
pkScript: entry.pkScript,
|
||||
blockHeight: entry.blockHeight,
|
||||
packedFlags: entry.packedFlags,
|
||||
}
|
||||
}
|
||||
|
||||
// txoFlags is a bitmask defining additional information and state for a
|
||||
// transaction output in a UTXO set.
|
||||
type txoFlags uint8
|
||||
|
@ -955,3 +955,107 @@ func (dus *DiffUTXOSet) collection() utxoCollection {
|
||||
|
||||
return clone.base.collection()
|
||||
}
|
||||
|
||||
func TestUTXOSetAddEntry(t *testing.T) {
|
||||
hash0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
hash1, _ := daghash.NewTxIDFromStr("1111111111111111111111111111111111111111111111111111111111111111")
|
||||
outPoint0 := wire.NewOutPoint(hash0, 0)
|
||||
outPoint1 := wire.NewOutPoint(hash1, 0)
|
||||
utxoEntry0 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 10}, true, 0)
|
||||
utxoEntry1 := NewUTXOEntry(&wire.TxOut{PkScript: []byte{}, Value: 20}, false, 1)
|
||||
|
||||
utxoDiff := NewUTXODiff()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
outPointToAdd *wire.OutPoint
|
||||
utxoEntryToAdd *UTXOEntry
|
||||
expectedUTXODiff *UTXODiff
|
||||
}{
|
||||
{
|
||||
name: "add an entry",
|
||||
outPointToAdd: outPoint0,
|
||||
utxoEntryToAdd: utxoEntry0,
|
||||
expectedUTXODiff: &UTXODiff{
|
||||
toAdd: utxoCollection{*outPoint0: utxoEntry0},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add another entry",
|
||||
outPointToAdd: outPoint1,
|
||||
utxoEntryToAdd: utxoEntry1,
|
||||
expectedUTXODiff: &UTXODiff{
|
||||
toAdd: utxoCollection{*outPoint0: utxoEntry0, *outPoint1: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add first entry again",
|
||||
outPointToAdd: outPoint0,
|
||||
utxoEntryToAdd: utxoEntry0,
|
||||
expectedUTXODiff: &UTXODiff{
|
||||
toAdd: utxoCollection{*outPoint0: utxoEntry0, *outPoint1: utxoEntry1},
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
utxoDiff.AddEntry(*test.outPointToAdd, test.utxoEntryToAdd)
|
||||
if !reflect.DeepEqual(utxoDiff, test.expectedUTXODiff) {
|
||||
t.Fatalf("utxoDiff.AddEntry: unexpected utxoDiff in test '%s'. "+
|
||||
"Expected: %v, got: %v", test.name, test.expectedUTXODiff, utxoDiff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUTXOSetRemoveTxOuts(t *testing.T) {
|
||||
tx0 := &wire.MsgTx{TxOut: []*wire.TxOut{{PkScript: []byte{1}, Value: 10}}}
|
||||
tx1 := &wire.MsgTx{TxOut: []*wire.TxOut{{PkScript: []byte{2}, Value: 20}}}
|
||||
hash0 := tx0.TxID()
|
||||
hash1 := tx1.TxID()
|
||||
outPoint0 := wire.NewOutPoint(&hash0, 0)
|
||||
outPoint1 := wire.NewOutPoint(&hash1, 0)
|
||||
|
||||
utxoDiff := NewUTXODiff()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
txToRemove *wire.MsgTx
|
||||
expectedUTXODiff *UTXODiff
|
||||
}{
|
||||
{
|
||||
name: "remove a transaction",
|
||||
txToRemove: tx0,
|
||||
expectedUTXODiff: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{*outPoint0: nil},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove another transaction",
|
||||
txToRemove: tx1,
|
||||
expectedUTXODiff: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{*outPoint0: nil, *outPoint1: nil},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove first entry again",
|
||||
txToRemove: tx0,
|
||||
expectedUTXODiff: &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{*outPoint0: nil, *outPoint1: nil},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
utxoDiff.RemoveTxOuts(test.txToRemove)
|
||||
if !reflect.DeepEqual(utxoDiff, test.expectedUTXODiff) {
|
||||
t.Fatalf("utxoDiff.AddEntry: unexpected utxoDiff in test '%s'. "+
|
||||
"Expected: %v, got: %v", test.name, test.expectedUTXODiff, utxoDiff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user