mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-06 14:16:43 +00:00

* [NOD-848] Optimize allocations when serializing UTXO diffs * [NOD-848] Use same UTXO serialization everywhere, and use compression as well * [NOD-848] Fix usage of wrong buffer * [NOD-848] Fix tests * [NOD-848] Fix wire tests * [NOD-848] Fix tests * [NOD-848] Remove VLQ * [NOD-848] Fix comments * [NOD-848] Add varint for big endian encoding * [NOD-848] In TestVarIntWire, assume the expected decoded value is the same as the serialization input * [NOD-848] Serialize outpoint index with big endian varint * [NOD-848] Remove p2pk from compression support * [NOD-848] Fix comments * [NOD-848] Remove p2pk from decompression support * [NOD-848] Make entry compression optional * [NOD-848] Fix tests * [NOD-848] Fix comments and var names * [NOD-848] Remove UTXO compression * [NOD-848] Fix tests * [NOD-848] Remove big endian varint * [NOD-848] Fix comments * [NOD-848] Rename ReadVarIntLittleEndian->ReadVarInt and fix WriteVarInt comment * [NOD-848] Add outpointIndexByteOrder variable * [NOD-848] Remove redundant comment * [NOD-848] Fix outpointMaxSerializeSize to the correct value * [NOD-848] Move subBuffer to utils
293 lines
9.1 KiB
Go
293 lines
9.1 KiB
Go
// Copyright (c) 2015-2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockdag
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"github.com/pkg/errors"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/kaspanet/kaspad/database"
|
|
"github.com/kaspanet/kaspad/util/daghash"
|
|
)
|
|
|
|
// TestErrNotInDAG ensures the functions related to errNotInDAG work
|
|
// as expected.
|
|
func TestErrNotInDAG(t *testing.T) {
|
|
errStr := "no block at height 1 exists"
|
|
err := error(errNotInDAG(errStr))
|
|
|
|
// Ensure the stringized output for the error is as expected.
|
|
if err.Error() != errStr {
|
|
t.Fatalf("errNotInDAG retuned unexpected error string - "+
|
|
"got %q, want %q", err.Error(), errStr)
|
|
}
|
|
|
|
// Ensure error is detected as the correct type.
|
|
if !isNotInDAGErr(err) {
|
|
t.Fatalf("isNotInDAGErr did not detect as expected type")
|
|
}
|
|
err = errors.New("something else")
|
|
if isNotInDAGErr(err) {
|
|
t.Fatalf("isNotInDAGErr detected incorrect type")
|
|
}
|
|
}
|
|
|
|
// hexToBytes converts the passed hex string into bytes and will panic if there
|
|
// is an error. This is only provided for the hard-coded constants so errors in
|
|
// the source code can be detected. It will only (and must only) be called with
|
|
// hard-coded values.
|
|
func hexToBytes(s string) []byte {
|
|
b, err := hex.DecodeString(s)
|
|
if err != nil {
|
|
panic("invalid hex in source file: " + s)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// TestUTXOSerialization ensures serializing and deserializing unspent
|
|
// trasaction output entries works as expected.
|
|
func TestUTXOSerialization(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
entry *UTXOEntry
|
|
serialized []byte
|
|
}{
|
|
{
|
|
name: "blue score 1, coinbase",
|
|
entry: &UTXOEntry{
|
|
amount: 5000000000,
|
|
scriptPubKey: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
|
blockBlueScore: 1,
|
|
packedFlags: tfCoinbase,
|
|
},
|
|
serialized: hexToBytes("030000000000000000f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
|
},
|
|
{
|
|
name: "blue score 100001, not coinbase",
|
|
entry: &UTXOEntry{
|
|
amount: 1000000,
|
|
scriptPubKey: hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
|
blockBlueScore: 100001,
|
|
packedFlags: 0,
|
|
},
|
|
serialized: hexToBytes("420d03000000000040420f00000000001976a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
// Ensure the utxo entry serializes to the expected value.
|
|
w := &bytes.Buffer{}
|
|
err := serializeUTXOEntry(w, test.entry)
|
|
if err != nil {
|
|
t.Errorf("serializeUTXOEntry #%d (%s) unexpected "+
|
|
"error: %v", i, test.name, err)
|
|
continue
|
|
}
|
|
|
|
gotBytes := w.Bytes()
|
|
if !bytes.Equal(gotBytes, test.serialized) {
|
|
t.Errorf("serializeUTXOEntry #%d (%s): mismatched "+
|
|
"bytes - got %x, want %x", i, test.name,
|
|
gotBytes, test.serialized)
|
|
continue
|
|
}
|
|
|
|
// Deserialize to a utxo entry.gotBytes
|
|
utxoEntry, err := deserializeUTXOEntry(bytes.NewReader(test.serialized))
|
|
if err != nil {
|
|
t.Errorf("deserializeUTXOEntry #%d (%s) unexpected "+
|
|
"error: %v", i, test.name, err)
|
|
continue
|
|
}
|
|
|
|
// Ensure the deserialized entry has the same properties as the
|
|
// ones in the test entry.
|
|
if utxoEntry.Amount() != test.entry.Amount() {
|
|
t.Errorf("deserializeUTXOEntry #%d (%s) mismatched "+
|
|
"amounts: got %d, want %d", i, test.name,
|
|
utxoEntry.Amount(), test.entry.Amount())
|
|
continue
|
|
}
|
|
|
|
if !bytes.Equal(utxoEntry.ScriptPubKey(), test.entry.ScriptPubKey()) {
|
|
t.Errorf("deserializeUTXOEntry #%d (%s) mismatched "+
|
|
"scripts: got %x, want %x", i, test.name,
|
|
utxoEntry.ScriptPubKey(), test.entry.ScriptPubKey())
|
|
continue
|
|
}
|
|
if utxoEntry.BlockBlueScore() != test.entry.BlockBlueScore() {
|
|
t.Errorf("deserializeUTXOEntry #%d (%s) mismatched "+
|
|
"block blue score: got %d, want %d", i, test.name,
|
|
utxoEntry.BlockBlueScore(), test.entry.BlockBlueScore())
|
|
continue
|
|
}
|
|
if utxoEntry.IsCoinbase() != test.entry.IsCoinbase() {
|
|
t.Errorf("deserializeUTXOEntry #%d (%s) mismatched "+
|
|
"coinbase flag: got %v, want %v", i, test.name,
|
|
utxoEntry.IsCoinbase(), test.entry.IsCoinbase())
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestUtxoEntryDeserializeErrors performs negative tests against deserializing
|
|
// unspent transaction outputs to ensure error paths work as expected.
|
|
func TestUtxoEntryDeserializeErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
serialized []byte
|
|
}{
|
|
{
|
|
name: "no data after header code",
|
|
serialized: hexToBytes("02"),
|
|
},
|
|
{
|
|
name: "incomplete compressed txout",
|
|
serialized: hexToBytes("0232"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
// Ensure the expected error type is returned and the returned
|
|
// entry is nil.
|
|
entry, err := deserializeUTXOEntry(bytes.NewReader(test.serialized))
|
|
if err == nil {
|
|
t.Errorf("deserializeUTXOEntry (%s): didn't return an error",
|
|
test.name)
|
|
continue
|
|
}
|
|
if entry != nil {
|
|
t.Errorf("deserializeUTXOEntry (%s): returned entry "+
|
|
"is not nil", test.name)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDAGStateSerialization ensures serializing and deserializing the
|
|
// DAG state works as expected.
|
|
func TestDAGStateSerialization(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
state *dagState
|
|
serialized []byte
|
|
}{
|
|
{
|
|
name: "genesis",
|
|
state: &dagState{
|
|
TipHashes: []*daghash.Hash{newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
|
|
LastFinalityPoint: newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
|
|
},
|
|
serialized: []byte("{\"TipHashes\":[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]],\"LastFinalityPoint\":[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]}"),
|
|
},
|
|
{
|
|
name: "block 1",
|
|
state: &dagState{
|
|
TipHashes: []*daghash.Hash{newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
|
|
LastFinalityPoint: newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
|
|
},
|
|
serialized: []byte("{\"TipHashes\":[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]],\"LastFinalityPoint\":[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]}"),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
gotBytes, err := serializeDAGState(test.state)
|
|
if err != nil {
|
|
t.Errorf("serializeDAGState #%d (%s) "+
|
|
"unexpected error: %v", i, test.name, err)
|
|
continue
|
|
}
|
|
|
|
// Ensure the dagState serializes to the expected value.
|
|
if !bytes.Equal(gotBytes, test.serialized) {
|
|
t.Errorf("serializeDAGState #%d (%s): mismatched "+
|
|
"bytes - got %s, want %s", i, test.name,
|
|
string(gotBytes), string(test.serialized))
|
|
continue
|
|
}
|
|
|
|
// Ensure the serialized bytes are decoded back to the expected
|
|
// dagState.
|
|
state, err := deserializeDAGState(test.serialized)
|
|
if err != nil {
|
|
t.Errorf("deserializeDAGState #%d (%s) "+
|
|
"unexpected error: %v", i, test.name, err)
|
|
continue
|
|
}
|
|
if !reflect.DeepEqual(state, test.state) {
|
|
t.Errorf("deserializeDAGState #%d (%s) "+
|
|
"mismatched state - got %v, want %v", i,
|
|
test.name, state, test.state)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDAGStateDeserializeErrors performs negative tests against
|
|
// deserializing the DAG state to ensure error paths work as expected.
|
|
func TestDAGStateDeserializeErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
serialized []byte
|
|
errType error
|
|
}{
|
|
{
|
|
name: "nothing serialized",
|
|
serialized: hexToBytes(""),
|
|
errType: database.Error{ErrorCode: database.ErrCorruption},
|
|
},
|
|
{
|
|
name: "corrupted data",
|
|
serialized: []byte("[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,7"),
|
|
errType: database.Error{ErrorCode: database.ErrCorruption},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
// Ensure the expected error type and code is returned.
|
|
_, err := deserializeDAGState(test.serialized)
|
|
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
|
t.Errorf("deserializeDAGState (%s): expected "+
|
|
"error type does not match - got %T, want %T",
|
|
test.name, err, test.errType)
|
|
continue
|
|
}
|
|
var dbErr database.Error
|
|
if ok := errors.As(err, &dbErr); ok {
|
|
tderr := test.errType.(database.Error)
|
|
if dbErr.ErrorCode != tderr.ErrorCode {
|
|
t.Errorf("deserializeDAGState (%s): "+
|
|
"wrong error code got: %v, want: %v",
|
|
test.name, dbErr.ErrorCode,
|
|
tderr.ErrorCode)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// newHashFromStr converts the passed big-endian hex string into a
|
|
// daghash.Hash. It only differs from the one available in daghash in that
|
|
// it panics in case of an error since it will only (and must only) be
|
|
// called with hard-coded, and therefore known good, hashes.
|
|
func newHashFromStr(hexStr string) *daghash.Hash {
|
|
hash, err := daghash.NewHashFromStr(hexStr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return hash
|
|
}
|