mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-05 21:56:50 +00:00
[NOD-848] optimize utxo diffs serialize allocations (#666)
* [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
This commit is contained in:
parent
3c67215e76
commit
d3b1953deb
@ -73,15 +73,8 @@ func loadUTXOSet(filename string) (UTXOSet, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Serialized utxo entry.
|
||||
serialized := make([]byte, numBytes)
|
||||
_, err = io.ReadAtLeast(r, serialized, int(numBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Deserialize it and add it to the view.
|
||||
entry, err := deserializeUTXOEntry(serialized)
|
||||
// Deserialize the UTXO entry and add it to the UTXO set.
|
||||
entry, err := deserializeUTXOEntry(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1,584 +0,0 @@
|
||||
// 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 (
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// A variable length quantity (VLQ) is an encoding that uses an arbitrary number
|
||||
// of binary octets to represent an arbitrarily large integer. The scheme
|
||||
// employs a most significant byte (MSB) base-128 encoding where the high bit in
|
||||
// each byte indicates whether or not the byte is the final one. In addition,
|
||||
// to ensure there are no redundant encodings, an offset is subtracted every
|
||||
// time a group of 7 bits is shifted out. Therefore each integer can be
|
||||
// represented in exactly one way, and each representation stands for exactly
|
||||
// one integer.
|
||||
//
|
||||
// Another nice property of this encoding is that it provides a compact
|
||||
// representation of values that are typically used to indicate sizes. For
|
||||
// example, the values 0 - 127 are represented with a single byte, 128 - 16511
|
||||
// with two bytes, and 16512 - 2113663 with three bytes.
|
||||
//
|
||||
// While the encoding allows arbitrarily large integers, it is artificially
|
||||
// limited in this code to an unsigned 64-bit integer for efficiency purposes.
|
||||
//
|
||||
// Example encodings:
|
||||
// 0 -> [0x00]
|
||||
// 127 -> [0x7f] * Max 1-byte value
|
||||
// 128 -> [0x80 0x00]
|
||||
// 129 -> [0x80 0x01]
|
||||
// 255 -> [0x80 0x7f]
|
||||
// 256 -> [0x81 0x00]
|
||||
// 16511 -> [0xff 0x7f] * Max 2-byte value
|
||||
// 16512 -> [0x80 0x80 0x00]
|
||||
// 32895 -> [0x80 0xff 0x7f]
|
||||
// 2113663 -> [0xff 0xff 0x7f] * Max 3-byte value
|
||||
// 270549119 -> [0xff 0xff 0xff 0x7f] * Max 4-byte value
|
||||
// 2^64-1 -> [0x80 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0x7f]
|
||||
//
|
||||
// References:
|
||||
// https://en.wikipedia.org/wiki/Variable-length_quantity
|
||||
// http://www.codecodex.com/wiki/Variable-Length_Integers
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// serializeSizeVLQ returns the number of bytes it would take to serialize the
|
||||
// passed number as a variable-length quantity according to the format described
|
||||
// above.
|
||||
func serializeSizeVLQ(n uint64) int {
|
||||
size := 1
|
||||
for ; n > 0x7f; n = (n >> 7) - 1 {
|
||||
size++
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
// putVLQ serializes the provided number to a variable-length quantity according
|
||||
// to the format described above and returns the number of bytes of the encoded
|
||||
// value. The result is placed directly into the passed byte slice which must
|
||||
// be at least large enough to handle the number of bytes returned by the
|
||||
// serializeSizeVLQ function or it will panic.
|
||||
func putVLQ(target []byte, n uint64) int {
|
||||
offset := 0
|
||||
for ; ; offset++ {
|
||||
// The high bit is set when another byte follows.
|
||||
highBitMask := byte(0x80)
|
||||
if offset == 0 {
|
||||
highBitMask = 0x00
|
||||
}
|
||||
|
||||
target[offset] = byte(n&0x7f) | highBitMask
|
||||
if n <= 0x7f {
|
||||
break
|
||||
}
|
||||
n = (n >> 7) - 1
|
||||
}
|
||||
|
||||
// Reverse the bytes so it is MSB-encoded.
|
||||
for i, j := 0, offset; i < j; i, j = i+1, j-1 {
|
||||
target[i], target[j] = target[j], target[i]
|
||||
}
|
||||
|
||||
return offset + 1
|
||||
}
|
||||
|
||||
// deserializeVLQ deserializes the provided variable-length quantity according
|
||||
// to the format described above. It also returns the number of bytes
|
||||
// deserialized.
|
||||
func deserializeVLQ(serialized []byte) (uint64, int) {
|
||||
var n uint64
|
||||
var size int
|
||||
for _, val := range serialized {
|
||||
size++
|
||||
n = (n << 7) | uint64(val&0x7f)
|
||||
if val&0x80 != 0x80 {
|
||||
break
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
return n, size
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// In order to reduce the size of stored scripts, a domain specific compression
|
||||
// algorithm is used which recognizes standard scripts and stores them using
|
||||
// less bytes than the original script.
|
||||
//
|
||||
// The general serialized format is:
|
||||
//
|
||||
// <script size or type><script data>
|
||||
//
|
||||
// Field Type Size
|
||||
// script size or type VLQ variable
|
||||
// script data []byte variable
|
||||
//
|
||||
// The specific serialized format for each recognized standard script is:
|
||||
//
|
||||
// - Pay-to-pubkey-hash: (21 bytes) - <0><20-byte pubkey hash>
|
||||
// - Pay-to-script-hash: (21 bytes) - <1><20-byte script hash>
|
||||
// - Pay-to-pubkey**: (33 bytes) - <2, 3, 4, or 5><32-byte pubkey X value>
|
||||
// 2, 3 = compressed pubkey with bit 0 specifying the y coordinate to use
|
||||
// 4, 5 = uncompressed pubkey with bit 0 specifying the y coordinate to use
|
||||
// ** Only valid public keys starting with 0x02, 0x03, and 0x04 are supported.
|
||||
//
|
||||
// Any scripts which are not recognized as one of the aforementioned standard
|
||||
// scripts are encoded using the general serialized format and encode the script
|
||||
// size as the sum of the actual size of the script and the number of special
|
||||
// cases.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// The following constants specify the special constants used to identify a
|
||||
// special script type in the domain-specific compressed script encoding.
|
||||
//
|
||||
// NOTE: This section specifically does not use iota since these values are
|
||||
// serialized and must be stable for long-term storage.
|
||||
const (
|
||||
// cstPayToPubKeyHash identifies a compressed pay-to-pubkey-hash script.
|
||||
cstPayToPubKeyHash = 0
|
||||
|
||||
// cstPayToScriptHash identifies a compressed pay-to-script-hash script.
|
||||
cstPayToScriptHash = 1
|
||||
|
||||
// cstPayToPubKeyComp2 identifies a compressed pay-to-pubkey script to
|
||||
// a compressed pubkey. Bit 0 specifies which y-coordinate to use
|
||||
// to reconstruct the full uncompressed pubkey.
|
||||
cstPayToPubKeyComp2 = 2
|
||||
|
||||
// cstPayToPubKeyComp3 identifies a compressed pay-to-pubkey script to
|
||||
// a compressed pubkey. Bit 0 specifies which y-coordinate to use
|
||||
// to reconstruct the full uncompressed pubkey.
|
||||
cstPayToPubKeyComp3 = 3
|
||||
|
||||
// cstPayToPubKeyUncomp4 identifies a compressed pay-to-pubkey script to
|
||||
// an uncompressed pubkey. Bit 0 specifies which y-coordinate to use
|
||||
// to reconstruct the full uncompressed pubkey.
|
||||
cstPayToPubKeyUncomp4 = 4
|
||||
|
||||
// cstPayToPubKeyUncomp5 identifies a compressed pay-to-pubkey script to
|
||||
// an uncompressed pubkey. Bit 0 specifies which y-coordinate to use
|
||||
// to reconstruct the full uncompressed pubkey.
|
||||
cstPayToPubKeyUncomp5 = 5
|
||||
|
||||
// numSpecialScripts is the number of special scripts recognized by the
|
||||
// domain-specific script compression algorithm.
|
||||
numSpecialScripts = 6
|
||||
)
|
||||
|
||||
// isPubKeyHash returns whether or not the passed public key script is a
|
||||
// standard pay-to-pubkey-hash script along with the pubkey hash it is paying to
|
||||
// if it is.
|
||||
func isPubKeyHash(script []byte) (bool, []byte) {
|
||||
if len(script) == 25 && script[0] == txscript.OpDup &&
|
||||
script[1] == txscript.OpHash160 &&
|
||||
script[2] == txscript.OpData20 &&
|
||||
script[23] == txscript.OpEqualVerify &&
|
||||
script[24] == txscript.OpCheckSig {
|
||||
|
||||
return true, script[3:23]
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isScriptHash returns whether or not the passed public key script is a
|
||||
// standard pay-to-script-hash script along with the script hash it is paying to
|
||||
// if it is.
|
||||
func isScriptHash(script []byte) (bool, []byte) {
|
||||
if len(script) == 23 && script[0] == txscript.OpHash160 &&
|
||||
script[1] == txscript.OpData20 &&
|
||||
script[22] == txscript.OpEqual {
|
||||
|
||||
return true, script[2:22]
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isPubKey returns whether or not the passed public key script is a standard
|
||||
// pay-to-pubkey script that pays to a valid compressed or uncompressed public
|
||||
// key along with the serialized pubkey it is paying to if it is.
|
||||
//
|
||||
// NOTE: This function ensures the public key is actually valid since the
|
||||
// compression algorithm requires valid pubkeys. It does not support hybrid
|
||||
// pubkeys. This means that even if the script has the correct form for a
|
||||
// pay-to-pubkey script, this function will only return true when it is paying
|
||||
// to a valid compressed or uncompressed pubkey.
|
||||
func isPubKey(script []byte) (bool, []byte) {
|
||||
// Pay-to-compressed-pubkey script.
|
||||
if len(script) == 35 && script[0] == txscript.OpData33 &&
|
||||
script[34] == txscript.OpCheckSig && (script[1] == 0x02 ||
|
||||
script[1] == 0x03) {
|
||||
|
||||
// Ensure the public key is valid.
|
||||
serializedPubKey := script[1:34]
|
||||
_, err := ecc.ParsePubKey(serializedPubKey, ecc.S256())
|
||||
if err == nil {
|
||||
return true, serializedPubKey
|
||||
}
|
||||
}
|
||||
|
||||
// Pay-to-uncompressed-pubkey script.
|
||||
if len(script) == 67 && script[0] == txscript.OpData65 &&
|
||||
script[66] == txscript.OpCheckSig && script[1] == 0x04 {
|
||||
|
||||
// Ensure the public key is valid.
|
||||
serializedPubKey := script[1:66]
|
||||
_, err := ecc.ParsePubKey(serializedPubKey, ecc.S256())
|
||||
if err == nil {
|
||||
return true, serializedPubKey
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// compressedScriptSize returns the number of bytes the passed script would take
|
||||
// when encoded with the domain specific compression algorithm described above.
|
||||
func compressedScriptSize(scriptPubKey []byte) int {
|
||||
// Pay-to-pubkey-hash script.
|
||||
if valid, _ := isPubKeyHash(scriptPubKey); valid {
|
||||
return 21
|
||||
}
|
||||
|
||||
// Pay-to-script-hash script.
|
||||
if valid, _ := isScriptHash(scriptPubKey); valid {
|
||||
return 21
|
||||
}
|
||||
|
||||
// Pay-to-pubkey (compressed or uncompressed) script.
|
||||
if valid, _ := isPubKey(scriptPubKey); valid {
|
||||
return 33
|
||||
}
|
||||
|
||||
// When none of the above special cases apply, encode the script as is
|
||||
// preceded by the sum of its size and the number of special cases
|
||||
// encoded as a variable length quantity.
|
||||
return serializeSizeVLQ(uint64(len(scriptPubKey)+numSpecialScripts)) +
|
||||
len(scriptPubKey)
|
||||
}
|
||||
|
||||
// decodeCompressedScriptSize treats the passed serialized bytes as a compressed
|
||||
// script, possibly followed by other data, and returns the number of bytes it
|
||||
// occupies taking into account the special encoding of the script size by the
|
||||
// domain specific compression algorithm described above.
|
||||
func decodeCompressedScriptSize(serialized []byte) int {
|
||||
scriptSize, bytesRead := deserializeVLQ(serialized)
|
||||
if bytesRead == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch scriptSize {
|
||||
case cstPayToPubKeyHash:
|
||||
return 21
|
||||
|
||||
case cstPayToScriptHash:
|
||||
return 21
|
||||
|
||||
case cstPayToPubKeyComp2, cstPayToPubKeyComp3, cstPayToPubKeyUncomp4,
|
||||
cstPayToPubKeyUncomp5:
|
||||
return 33
|
||||
}
|
||||
|
||||
scriptSize -= numSpecialScripts
|
||||
scriptSize += uint64(bytesRead)
|
||||
return int(scriptSize)
|
||||
}
|
||||
|
||||
// putCompressedScript compresses the passed script according to the domain
|
||||
// specific compression algorithm 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 compressedScriptSize function or
|
||||
// it will panic.
|
||||
func putCompressedScript(target, scriptPubKey []byte) int {
|
||||
// Pay-to-pubkey-hash script.
|
||||
if valid, hash := isPubKeyHash(scriptPubKey); valid {
|
||||
target[0] = cstPayToPubKeyHash
|
||||
copy(target[1:21], hash)
|
||||
return 21
|
||||
}
|
||||
|
||||
// Pay-to-script-hash script.
|
||||
if valid, hash := isScriptHash(scriptPubKey); valid {
|
||||
target[0] = cstPayToScriptHash
|
||||
copy(target[1:21], hash)
|
||||
return 21
|
||||
}
|
||||
|
||||
// Pay-to-pubkey (compressed or uncompressed) script.
|
||||
if valid, serializedPubKey := isPubKey(scriptPubKey); valid {
|
||||
pubKeyFormat := serializedPubKey[0]
|
||||
switch pubKeyFormat {
|
||||
case 0x02, 0x03:
|
||||
target[0] = pubKeyFormat
|
||||
copy(target[1:33], serializedPubKey[1:33])
|
||||
return 33
|
||||
case 0x04:
|
||||
// Encode the oddness of the serialized pubkey into the
|
||||
// compressed script type.
|
||||
target[0] = pubKeyFormat | (serializedPubKey[64] & 0x01)
|
||||
copy(target[1:33], serializedPubKey[1:33])
|
||||
return 33
|
||||
}
|
||||
}
|
||||
|
||||
// When none of the above special cases apply, encode the unmodified
|
||||
// script preceded by the sum of its size and the number of special
|
||||
// cases encoded as a variable length quantity.
|
||||
encodedSize := uint64(len(scriptPubKey) + numSpecialScripts)
|
||||
vlqSizeLen := putVLQ(target, encodedSize)
|
||||
copy(target[vlqSizeLen:], scriptPubKey)
|
||||
return vlqSizeLen + len(scriptPubKey)
|
||||
}
|
||||
|
||||
// decompressScript returns the original script obtained by decompressing the
|
||||
// passed compressed script according to the domain specific compression
|
||||
// algorithm described above.
|
||||
//
|
||||
// NOTE: The script parameter must already have been proven to be long enough
|
||||
// to contain the number of bytes returned by decodeCompressedScriptSize or it
|
||||
// will panic. This is acceptable since it is only an internal function.
|
||||
func decompressScript(compressedScriptPubKey []byte) []byte {
|
||||
// In practice this function will not be called with a zero-length or
|
||||
// nil script since the nil script encoding includes the length, however
|
||||
// the code below assumes the length exists, so just return nil now if
|
||||
// the function ever ends up being called with a nil script in the
|
||||
// future.
|
||||
if len(compressedScriptPubKey) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode the script size and examine it for the special cases.
|
||||
encodedScriptSize, bytesRead := deserializeVLQ(compressedScriptPubKey)
|
||||
switch encodedScriptSize {
|
||||
// Pay-to-pubkey-hash script. The resulting script is:
|
||||
// <OP_DUP><OP_HASH160><20 byte hash><OP_EQUALVERIFY><OP_CHECKSIG>
|
||||
case cstPayToPubKeyHash:
|
||||
scriptPubKey := make([]byte, 25)
|
||||
scriptPubKey[0] = txscript.OpDup
|
||||
scriptPubKey[1] = txscript.OpHash160
|
||||
scriptPubKey[2] = txscript.OpData20
|
||||
copy(scriptPubKey[3:], compressedScriptPubKey[bytesRead:bytesRead+20])
|
||||
scriptPubKey[23] = txscript.OpEqualVerify
|
||||
scriptPubKey[24] = txscript.OpCheckSig
|
||||
return scriptPubKey
|
||||
|
||||
// Pay-to-script-hash script. The resulting script is:
|
||||
// <OP_HASH160><20 byte script hash><OP_EQUAL>
|
||||
case cstPayToScriptHash:
|
||||
scriptPubKey := make([]byte, 23)
|
||||
scriptPubKey[0] = txscript.OpHash160
|
||||
scriptPubKey[1] = txscript.OpData20
|
||||
copy(scriptPubKey[2:], compressedScriptPubKey[bytesRead:bytesRead+20])
|
||||
scriptPubKey[22] = txscript.OpEqual
|
||||
return scriptPubKey
|
||||
|
||||
// Pay-to-compressed-pubkey script. The resulting script is:
|
||||
// <OP_DATA_33><33 byte compressed pubkey><OP_CHECKSIG>
|
||||
case cstPayToPubKeyComp2, cstPayToPubKeyComp3:
|
||||
scriptPubKey := make([]byte, 35)
|
||||
scriptPubKey[0] = txscript.OpData33
|
||||
scriptPubKey[1] = byte(encodedScriptSize)
|
||||
copy(scriptPubKey[2:], compressedScriptPubKey[bytesRead:bytesRead+32])
|
||||
scriptPubKey[34] = txscript.OpCheckSig
|
||||
return scriptPubKey
|
||||
|
||||
// Pay-to-uncompressed-pubkey script. The resulting script is:
|
||||
// <OP_DATA_65><65 byte uncompressed pubkey><OP_CHECKSIG>
|
||||
case cstPayToPubKeyUncomp4, cstPayToPubKeyUncomp5:
|
||||
// Change the leading byte to the appropriate compressed pubkey
|
||||
// identifier (0x02 or 0x03) so it can be decoded as a
|
||||
// compressed pubkey. This really should never fail since the
|
||||
// encoding ensures it is valid before compressing to this type.
|
||||
compressedKey := make([]byte, 33)
|
||||
compressedKey[0] = byte(encodedScriptSize - 2)
|
||||
copy(compressedKey[1:], compressedScriptPubKey[1:])
|
||||
key, err := ecc.ParsePubKey(compressedKey, ecc.S256())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
scriptPubKey := make([]byte, 67)
|
||||
scriptPubKey[0] = txscript.OpData65
|
||||
copy(scriptPubKey[1:], key.SerializeUncompressed())
|
||||
scriptPubKey[66] = txscript.OpCheckSig
|
||||
return scriptPubKey
|
||||
}
|
||||
|
||||
// When none of the special cases apply, the script was encoded using
|
||||
// the general format, so reduce the script size by the number of
|
||||
// special cases and return the unmodified script.
|
||||
scriptSize := int(encodedScriptSize - numSpecialScripts)
|
||||
scriptPubKey := make([]byte, scriptSize)
|
||||
copy(scriptPubKey, compressedScriptPubKey[bytesRead:bytesRead+scriptSize])
|
||||
return scriptPubKey
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// In order to reduce the size of stored amounts, a domain specific compression
|
||||
// algorithm is used which relies on there typically being a lot of zeroes at
|
||||
// end of the amounts.
|
||||
//
|
||||
// While this is simply exchanging one uint64 for another, the resulting value
|
||||
// for typical amounts has a much smaller magnitude which results in fewer bytes
|
||||
// when encoded as variable length quantity. For example, consider the amount
|
||||
// of 0.1 KAS which is 10000000 sompi. Encoding 10000000 as a VLQ would take
|
||||
// 4 bytes while encoding the compressed value of 8 as a VLQ only takes 1 byte.
|
||||
//
|
||||
// Essentially the compression is achieved by splitting the value into an
|
||||
// exponent in the range [0-9] and a digit in the range [1-9], when possible,
|
||||
// and encoding them in a way that can be decoded. More specifically, the
|
||||
// encoding is as follows:
|
||||
// - 0 is 0
|
||||
// - Find the exponent, e, as the largest power of 10 that evenly divides the
|
||||
// value up to a maximum of 9
|
||||
// - When e < 9, the final digit can't be 0 so store it as d and remove it by
|
||||
// dividing the value by 10 (call the result n). The encoded value is thus:
|
||||
// 1 + 10*(9*n + d-1) + e
|
||||
// - When e==9, the only thing known is the amount is not 0. The encoded value
|
||||
// is thus:
|
||||
// 1 + 10*(n-1) + e == 10 + 10*(n-1)
|
||||
//
|
||||
// Example encodings:
|
||||
// (The numbers in parenthesis are the number of bytes when serialized as a VLQ)
|
||||
// 0 (1) -> 0 (1) * 0.00000000 KAS
|
||||
// 1000 (2) -> 4 (1) * 0.00001000 KAS
|
||||
// 10000 (2) -> 5 (1) * 0.00010000 KAS
|
||||
// 12345678 (4) -> 111111101(4) * 0.12345678 KAS
|
||||
// 50000000 (4) -> 47 (1) * 0.50000000 KAS
|
||||
// 100000000 (4) -> 9 (1) * 1.00000000 KAS
|
||||
// 500000000 (5) -> 49 (1) * 5.00000000 KAS
|
||||
// 1000000000 (5) -> 10 (1) * 10.00000000 KAS
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// compressTxOutAmount compresses the passed amount according to the domain
|
||||
// specific compression algorithm described above.
|
||||
func compressTxOutAmount(amount uint64) uint64 {
|
||||
// No need to do any work if it's zero.
|
||||
if amount == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Find the largest power of 10 (max of 9) that evenly divides the
|
||||
// value.
|
||||
exponent := uint64(0)
|
||||
for amount%10 == 0 && exponent < 9 {
|
||||
amount /= 10
|
||||
exponent++
|
||||
}
|
||||
|
||||
// The compressed result for exponents less than 9 is:
|
||||
// 1 + 10*(9*n + d-1) + e
|
||||
if exponent < 9 {
|
||||
lastDigit := amount % 10
|
||||
amount /= 10
|
||||
return 1 + 10*(9*amount+lastDigit-1) + exponent
|
||||
}
|
||||
|
||||
// The compressed result for an exponent of 9 is:
|
||||
// 1 + 10*(n-1) + e == 10 + 10*(n-1)
|
||||
return 10 + 10*(amount-1)
|
||||
}
|
||||
|
||||
// decompressTxOutAmount returns the original amount the passed compressed
|
||||
// amount represents according to the domain specific compression algorithm
|
||||
// described above.
|
||||
func decompressTxOutAmount(amount uint64) uint64 {
|
||||
// No need to do any work if it's zero.
|
||||
if amount == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// The decompressed amount is either of the following two equations:
|
||||
// x = 1 + 10*(9*n + d - 1) + e
|
||||
// x = 1 + 10*(n - 1) + 9
|
||||
amount--
|
||||
|
||||
// The decompressed amount is now one of the following two equations:
|
||||
// x = 10*(9*n + d - 1) + e
|
||||
// x = 10*(n - 1) + 9
|
||||
exponent := amount % 10
|
||||
amount /= 10
|
||||
|
||||
// The decompressed amount is now one of the following two equations:
|
||||
// x = 9*n + d - 1 | where e < 9
|
||||
// x = n - 1 | where e = 9
|
||||
n := uint64(0)
|
||||
if exponent < 9 {
|
||||
lastDigit := amount%9 + 1
|
||||
amount /= 9
|
||||
n = amount*10 + lastDigit
|
||||
} else {
|
||||
n = amount + 1
|
||||
}
|
||||
|
||||
// Apply the exponent.
|
||||
for ; exponent > 0; exponent-- {
|
||||
n *= 10
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Compressed transaction outputs consist of an amount and a public key script
|
||||
// both compressed using the domain specific compression algorithms previously
|
||||
// described.
|
||||
//
|
||||
// The serialized format is:
|
||||
//
|
||||
// <compressed amount><compressed script>
|
||||
//
|
||||
// Field Type Size
|
||||
// compressed amount VLQ variable
|
||||
// compressed script []byte variable
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// compressedTxOutSize returns the number of bytes the passed transaction output
|
||||
// fields would take when encoded with the format described above.
|
||||
func compressedTxOutSize(amount uint64, scriptPubKey []byte) int {
|
||||
return serializeSizeVLQ(compressTxOutAmount(amount)) +
|
||||
compressedScriptSize(scriptPubKey)
|
||||
}
|
||||
|
||||
// putCompressedTxOut compresses the passed amount and script according to their
|
||||
// domain specific compression algorithms and encodes them directly into the
|
||||
// passed target byte slice with the format described above. The target byte
|
||||
// slice must be at least large enough to handle the number of bytes returned by
|
||||
// the compressedTxOutSize function or it will panic.
|
||||
func putCompressedTxOut(target []byte, amount uint64, scriptPubKey []byte) int {
|
||||
offset := putVLQ(target, compressTxOutAmount(amount))
|
||||
offset += putCompressedScript(target[offset:], scriptPubKey)
|
||||
return offset
|
||||
}
|
||||
|
||||
// decodeCompressedTxOut decodes the passed compressed txout, possibly followed
|
||||
// by other data, into its uncompressed amount and script and returns them along
|
||||
// with the number of bytes they occupied prior to decompression.
|
||||
func decodeCompressedTxOut(serialized []byte) (uint64, []byte, int, error) {
|
||||
// Deserialize the compressed amount and ensure there are bytes
|
||||
// remaining for the compressed script.
|
||||
compressedAmount, bytesRead := deserializeVLQ(serialized)
|
||||
if bytesRead >= len(serialized) {
|
||||
return 0, nil, bytesRead, errDeserialize("unexpected end of " +
|
||||
"data after compressed amount")
|
||||
}
|
||||
|
||||
// Decode the compressed script size and ensure there are enough bytes
|
||||
// left in the slice for it.
|
||||
scriptSize := decodeCompressedScriptSize(serialized[bytesRead:])
|
||||
if len(serialized[bytesRead:]) < scriptSize {
|
||||
return 0, nil, bytesRead, errDeserialize("unexpected end of " +
|
||||
"data after script size")
|
||||
}
|
||||
|
||||
// Decompress and return the amount and script.
|
||||
amount := decompressTxOutAmount(compressedAmount)
|
||||
script := decompressScript(serialized[bytesRead : bytesRead+scriptSize])
|
||||
return amount, script, bytesRead + scriptSize, nil
|
||||
}
|
@ -1,436 +0,0 @@
|
||||
// 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"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// TestVLQ ensures the variable length quantity serialization, deserialization,
|
||||
// and size calculation works as expected.
|
||||
func TestVLQ(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
val uint64
|
||||
serialized []byte
|
||||
}{
|
||||
{0, hexToBytes("00")},
|
||||
{1, hexToBytes("01")},
|
||||
{127, hexToBytes("7f")},
|
||||
{128, hexToBytes("8000")},
|
||||
{129, hexToBytes("8001")},
|
||||
{255, hexToBytes("807f")},
|
||||
{256, hexToBytes("8100")},
|
||||
{16383, hexToBytes("fe7f")},
|
||||
{16384, hexToBytes("ff00")},
|
||||
{16511, hexToBytes("ff7f")}, // Max 2-byte value
|
||||
{16512, hexToBytes("808000")},
|
||||
{16513, hexToBytes("808001")},
|
||||
{16639, hexToBytes("80807f")},
|
||||
{32895, hexToBytes("80ff7f")},
|
||||
{2113663, hexToBytes("ffff7f")}, // Max 3-byte value
|
||||
{2113664, hexToBytes("80808000")},
|
||||
{270549119, hexToBytes("ffffff7f")}, // Max 4-byte value
|
||||
{270549120, hexToBytes("8080808000")},
|
||||
{2147483647, hexToBytes("86fefefe7f")},
|
||||
{2147483648, hexToBytes("86fefeff00")},
|
||||
{4294967295, hexToBytes("8efefefe7f")}, // Max uint32, 5 bytes
|
||||
// Max uint64, 10 bytes
|
||||
{18446744073709551615, hexToBytes("80fefefefefefefefe7f")},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the function to calculate the serialized size without
|
||||
// actually serializing the value is calculated properly.
|
||||
gotSize := serializeSizeVLQ(test.val)
|
||||
if gotSize != len(test.serialized) {
|
||||
t.Errorf("serializeSizeVLQ: did not get expected size "+
|
||||
"for %d - got %d, want %d", test.val, gotSize,
|
||||
len(test.serialized))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the value serializes to the expected bytes.
|
||||
gotBytes := make([]byte, gotSize)
|
||||
gotBytesWritten := putVLQ(gotBytes, test.val)
|
||||
if !bytes.Equal(gotBytes, test.serialized) {
|
||||
t.Errorf("putVLQUnchecked: did not get expected bytes "+
|
||||
"for %d - got %x, want %x", test.val, gotBytes,
|
||||
test.serialized)
|
||||
continue
|
||||
}
|
||||
if gotBytesWritten != len(test.serialized) {
|
||||
t.Errorf("putVLQUnchecked: did not get expected number "+
|
||||
"of bytes written for %d - got %d, want %d",
|
||||
test.val, gotBytesWritten, len(test.serialized))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the serialized bytes deserialize to the expected
|
||||
// value.
|
||||
gotVal, gotBytesRead := deserializeVLQ(test.serialized)
|
||||
if gotVal != test.val {
|
||||
t.Errorf("deserializeVLQ: did not get expected value "+
|
||||
"for %x - got %d, want %d", test.serialized,
|
||||
gotVal, test.val)
|
||||
continue
|
||||
}
|
||||
if gotBytesRead != len(test.serialized) {
|
||||
t.Errorf("deserializeVLQ: did not get expected number "+
|
||||
"of bytes read for %d - got %d, want %d",
|
||||
test.serialized, gotBytesRead,
|
||||
len(test.serialized))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestScriptCompression ensures the domain-specific script compression and
|
||||
// decompression works as expected.
|
||||
func TestScriptCompression(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
uncompressed []byte
|
||||
compressed []byte
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
uncompressed: nil,
|
||||
compressed: hexToBytes("06"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey-hash 1",
|
||||
uncompressed: hexToBytes("76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac"),
|
||||
compressed: hexToBytes("001018853670f9f3b0582c5b9ee8ce93764ac32b93"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey-hash 2",
|
||||
uncompressed: hexToBytes("76a914e34cce70c86373273efcc54ce7d2a491bb4a0e8488ac"),
|
||||
compressed: hexToBytes("00e34cce70c86373273efcc54ce7d2a491bb4a0e84"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-script-hash 1",
|
||||
uncompressed: hexToBytes("a914da1745e9b549bd0bfa1a569971c77eba30cd5a4b87"),
|
||||
compressed: hexToBytes("01da1745e9b549bd0bfa1a569971c77eba30cd5a4b"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-script-hash 2",
|
||||
uncompressed: hexToBytes("a914f815b036d9bbbce5e9f2a00abd1bf3dc91e9551087"),
|
||||
compressed: hexToBytes("01f815b036d9bbbce5e9f2a00abd1bf3dc91e95510"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey compressed 0x02",
|
||||
uncompressed: hexToBytes("2102192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4ac"),
|
||||
compressed: hexToBytes("02192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey compressed 0x03",
|
||||
uncompressed: hexToBytes("2103b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65ac"),
|
||||
compressed: hexToBytes("03b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey uncompressed 0x04 even",
|
||||
uncompressed: hexToBytes("4104192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b40d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453eac"),
|
||||
compressed: hexToBytes("04192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey uncompressed 0x04 odd",
|
||||
uncompressed: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
|
||||
compressed: hexToBytes("0511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey invalid pubkey",
|
||||
uncompressed: hexToBytes("3302aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"),
|
||||
compressed: hexToBytes("293302aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"),
|
||||
},
|
||||
{
|
||||
name: "requires 2 size bytes - data push 200 bytes",
|
||||
uncompressed: append(hexToBytes("4cc8"), bytes.Repeat([]byte{0x00}, 200)...),
|
||||
// [0x80, 0x50] = 208 as a variable length quantity
|
||||
// [0x4c, 0xc8] = OP_PUSHDATA1 200
|
||||
compressed: append(hexToBytes("80504cc8"), bytes.Repeat([]byte{0x00}, 200)...),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the function to calculate the serialized size without
|
||||
// actually serializing the value is calculated properly.
|
||||
gotSize := compressedScriptSize(test.uncompressed)
|
||||
if gotSize != len(test.compressed) {
|
||||
t.Errorf("compressedScriptSize (%s): did not get "+
|
||||
"expected size - got %d, want %d", test.name,
|
||||
gotSize, len(test.compressed))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the script compresses to the expected bytes.
|
||||
gotCompressed := make([]byte, gotSize)
|
||||
gotBytesWritten := putCompressedScript(gotCompressed,
|
||||
test.uncompressed)
|
||||
if !bytes.Equal(gotCompressed, test.compressed) {
|
||||
t.Errorf("putCompressedScript (%s): did not get "+
|
||||
"expected bytes - got %x, want %x", test.name,
|
||||
gotCompressed, test.compressed)
|
||||
continue
|
||||
}
|
||||
if gotBytesWritten != len(test.compressed) {
|
||||
t.Errorf("putCompressedScript (%s): did not get "+
|
||||
"expected number of bytes written - got %d, "+
|
||||
"want %d", test.name, gotBytesWritten,
|
||||
len(test.compressed))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the compressed script size is properly decoded from
|
||||
// the compressed script.
|
||||
gotDecodedSize := decodeCompressedScriptSize(test.compressed)
|
||||
if gotDecodedSize != len(test.compressed) {
|
||||
t.Errorf("decodeCompressedScriptSize (%s): did not get "+
|
||||
"expected size - got %d, want %d", test.name,
|
||||
gotDecodedSize, len(test.compressed))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the script decompresses to the expected bytes.
|
||||
gotDecompressed := decompressScript(test.compressed)
|
||||
if !bytes.Equal(gotDecompressed, test.uncompressed) {
|
||||
t.Errorf("decompressScript (%s): did not get expected "+
|
||||
"bytes - got %x, want %x", test.name,
|
||||
gotDecompressed, test.uncompressed)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestScriptCompressionErrors ensures calling various functions related to
|
||||
// script compression with incorrect data returns the expected results.
|
||||
func TestScriptCompressionErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// A nil script must result in a decoded size of 0.
|
||||
if gotSize := decodeCompressedScriptSize(nil); gotSize != 0 {
|
||||
t.Fatalf("decodeCompressedScriptSize with nil script did not "+
|
||||
"return 0 - got %d", gotSize)
|
||||
}
|
||||
|
||||
// A nil script must result in a nil decompressed script.
|
||||
if gotScript := decompressScript(nil); gotScript != nil {
|
||||
t.Fatalf("decompressScript with nil script did not return nil "+
|
||||
"decompressed script - got %x", gotScript)
|
||||
}
|
||||
|
||||
// A compressed script for a pay-to-pubkey (uncompressed) that results
|
||||
// in an invalid pubkey must result in a nil decompressed script.
|
||||
compressedScript := hexToBytes("04012d74d0cb94344c9569c2e77901573d8d" +
|
||||
"7903c3ebec3a957724895dca52c6b4")
|
||||
if gotScript := decompressScript(compressedScript); gotScript != nil {
|
||||
t.Fatalf("decompressScript with compressed pay-to-"+
|
||||
"uncompressed-pubkey that is invalid did not return "+
|
||||
"nil decompressed script - got %x", gotScript)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAmountCompression ensures the domain-specific transaction output amount
|
||||
// compression and decompression works as expected.
|
||||
func TestAmountCompression(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
uncompressed uint64
|
||||
compressed uint64
|
||||
}{
|
||||
{
|
||||
name: "0 KAS",
|
||||
uncompressed: 0,
|
||||
compressed: 0,
|
||||
},
|
||||
{
|
||||
name: "546 Sompi (current network dust value)",
|
||||
uncompressed: 546,
|
||||
compressed: 4911,
|
||||
},
|
||||
{
|
||||
name: "0.00001 KAS (typical transaction fee)",
|
||||
uncompressed: 1000,
|
||||
compressed: 4,
|
||||
},
|
||||
{
|
||||
name: "0.0001 KAS (typical transaction fee)",
|
||||
uncompressed: 10000,
|
||||
compressed: 5,
|
||||
},
|
||||
{
|
||||
name: "0.12345678 KAS",
|
||||
uncompressed: 12345678,
|
||||
compressed: 111111101,
|
||||
},
|
||||
{
|
||||
name: "0.5 KAS",
|
||||
uncompressed: 50000000,
|
||||
compressed: 48,
|
||||
},
|
||||
{
|
||||
name: "1 KAS",
|
||||
uncompressed: 100000000,
|
||||
compressed: 9,
|
||||
},
|
||||
{
|
||||
name: "5 KAS",
|
||||
uncompressed: 500000000,
|
||||
compressed: 49,
|
||||
},
|
||||
{
|
||||
name: "21000000 KAS (max minted coins)",
|
||||
uncompressed: 2100000000000000,
|
||||
compressed: 21000000,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the amount compresses to the expected value.
|
||||
gotCompressed := compressTxOutAmount(test.uncompressed)
|
||||
if gotCompressed != test.compressed {
|
||||
t.Errorf("compressTxOutAmount (%s): did not get "+
|
||||
"expected value - got %d, want %d", test.name,
|
||||
gotCompressed, test.compressed)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the value decompresses to the expected value.
|
||||
gotDecompressed := decompressTxOutAmount(test.compressed)
|
||||
if gotDecompressed != test.uncompressed {
|
||||
t.Errorf("decompressTxOutAmount (%s): did not get "+
|
||||
"expected value - got %d, want %d", test.name,
|
||||
gotDecompressed, test.uncompressed)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompressedTxOut ensures the transaction output serialization and
|
||||
// deserialization works as expected.
|
||||
func TestCompressedTxOut(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
amount uint64
|
||||
scriptPubKey []byte
|
||||
compressed []byte
|
||||
}{
|
||||
{
|
||||
name: "pay-to-pubkey-hash dust",
|
||||
amount: 546,
|
||||
scriptPubKey: hexToBytes("76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac"),
|
||||
compressed: hexToBytes("a52f001018853670f9f3b0582c5b9ee8ce93764ac32b93"),
|
||||
},
|
||||
{
|
||||
name: "pay-to-pubkey uncompressed 1 KAS",
|
||||
amount: 100000000,
|
||||
scriptPubKey: hexToBytes("4104192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b40d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453eac"),
|
||||
compressed: hexToBytes("0904192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the function to calculate the serialized size without
|
||||
// actually serializing the txout is calculated properly.
|
||||
gotSize := compressedTxOutSize(test.amount, test.scriptPubKey)
|
||||
if gotSize != len(test.compressed) {
|
||||
t.Errorf("compressedTxOutSize (%s): did not get "+
|
||||
"expected size - got %d, want %d", test.name,
|
||||
gotSize, len(test.compressed))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the txout compresses to the expected value.
|
||||
gotCompressed := make([]byte, gotSize)
|
||||
gotBytesWritten := putCompressedTxOut(gotCompressed,
|
||||
test.amount, test.scriptPubKey)
|
||||
if !bytes.Equal(gotCompressed, test.compressed) {
|
||||
t.Errorf("compressTxOut (%s): did not get expected "+
|
||||
"bytes - got %x, want %x", test.name,
|
||||
gotCompressed, test.compressed)
|
||||
continue
|
||||
}
|
||||
if gotBytesWritten != len(test.compressed) {
|
||||
t.Errorf("compressTxOut (%s): did not get expected "+
|
||||
"number of bytes written - got %d, want %d",
|
||||
test.name, gotBytesWritten,
|
||||
len(test.compressed))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the serialized bytes are decoded back to the expected
|
||||
// uncompressed values.
|
||||
gotAmount, gotScript, gotBytesRead, err := decodeCompressedTxOut(
|
||||
test.compressed)
|
||||
if err != nil {
|
||||
t.Errorf("decodeCompressedTxOut (%s): unexpected "+
|
||||
"error: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
if gotAmount != test.amount {
|
||||
t.Errorf("decodeCompressedTxOut (%s): did not get "+
|
||||
"expected amount - got %d, want %d",
|
||||
test.name, gotAmount, test.amount)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(gotScript, test.scriptPubKey) {
|
||||
t.Errorf("decodeCompressedTxOut (%s): did not get "+
|
||||
"expected script - got %x, want %x",
|
||||
test.name, gotScript, test.scriptPubKey)
|
||||
continue
|
||||
}
|
||||
if gotBytesRead != len(test.compressed) {
|
||||
t.Errorf("decodeCompressedTxOut (%s): did not get "+
|
||||
"expected number of bytes read - got %d, want %d",
|
||||
test.name, gotBytesRead, len(test.compressed))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxOutCompressionErrors ensures calling various functions related to
|
||||
// txout compression with incorrect data returns the expected results.
|
||||
func TestTxOutCompressionErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// A compressed txout with missing compressed script must error.
|
||||
compressedTxOut := hexToBytes("00")
|
||||
_, _, _, err := decodeCompressedTxOut(compressedTxOut)
|
||||
if !isDeserializeErr(err) {
|
||||
t.Fatalf("decodeCompressedTxOut with missing compressed script "+
|
||||
"did not return expected error type - got %T, want "+
|
||||
"errDeserialize", err)
|
||||
}
|
||||
|
||||
// A compressed txout with short compressed script must error.
|
||||
compressedTxOut = hexToBytes("0010")
|
||||
_, _, _, err = decodeCompressedTxOut(compressedTxOut)
|
||||
if !isDeserializeErr(err) {
|
||||
t.Fatalf("decodeCompressedTxOut with short compressed script "+
|
||||
"did not return expected error type - got %T, want "+
|
||||
"errDeserialize", err)
|
||||
}
|
||||
}
|
@ -204,7 +204,7 @@ func TestIsKnownBlock(t *testing.T) {
|
||||
{hash: dagconfig.SimnetParams.GenesisHash.String(), want: true},
|
||||
|
||||
// Block 3b should be present (as a second child of Block 2).
|
||||
{hash: "264176fb6072e2362db18f92d3f4b739cff071a206736df7c407c0bf9a1d7fef", want: true},
|
||||
{hash: "216301e3fc03cf89973b9192b4ecdd732bf3b677cf1ca4f6c340a56f1533fb4f", want: true},
|
||||
|
||||
// Block 100000 should be present (as an orphan).
|
||||
{hash: "65b20b048a074793ebfd1196e49341c8d194dabfc6b44a4fd0c607406e122baf", want: true},
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/buffers"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"sync"
|
||||
@ -87,22 +88,6 @@ func isNotInDAGErr(err error) bool {
|
||||
return errors.As(err, ¬InDAGErr)
|
||||
}
|
||||
|
||||
// errDeserialize signifies that a problem was encountered when deserializing
|
||||
// data.
|
||||
type errDeserialize string
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e errDeserialize) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// isDeserializeErr returns whether or not the passed error is an errDeserialize
|
||||
// error.
|
||||
func isDeserializeErr(err error) bool {
|
||||
var deserializeErr errDeserialize
|
||||
return errors.As(err, &deserializeErr)
|
||||
}
|
||||
|
||||
// dbPutVersion uses an existing database transaction to update the provided
|
||||
// key in the metadata bucket to the given version. It is primarily used to
|
||||
// track versions on entities such as buckets.
|
||||
@ -112,115 +97,46 @@ func dbPutVersion(dbTx database.Tx, key []byte, version uint32) error {
|
||||
return dbTx.Metadata().Put(key, 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
|
||||
// specific compression algorithms.
|
||||
//
|
||||
// Each entry is keyed by an outpoint as specified below. It is important to
|
||||
// note that the key encoding uses a VLQ, which employs an MSB encoding so
|
||||
// iteration of UTXOs when doing byte-wise comparisons will produce them in
|
||||
// order.
|
||||
//
|
||||
// The serialized key format is:
|
||||
// <hash><output index>
|
||||
//
|
||||
// Field Type Size
|
||||
// hash daghash.Hash daghash.HashSize
|
||||
// output index VLQ variable
|
||||
//
|
||||
// The serialized value format is:
|
||||
//
|
||||
// <header code><compressed txout>
|
||||
//
|
||||
// Field Type Size
|
||||
// header code VLQ variable
|
||||
// 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 unspent txout
|
||||
//
|
||||
// Example 1:
|
||||
// b7c3332bc138e2c9429818f5fed500bcc1746544218772389054dc8047d7cd3f:0
|
||||
//
|
||||
// 03320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52
|
||||
// <><------------------------------------------------------------------>
|
||||
// | |
|
||||
// header code compressed txout
|
||||
//
|
||||
// - header code: 0x03 (coinbase, height 1)
|
||||
// - compressed txout:
|
||||
// - 0x32: VLQ-encoded compressed amount for 5000000000 (50 KAS)
|
||||
// - 0x04: special script type pay-to-pubkey
|
||||
// - 0x96...52: x-coordinate of the pubkey
|
||||
//
|
||||
// Example 2:
|
||||
// 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f:2
|
||||
//
|
||||
// 8cf316800900b8025be1b3efc63b0ad48e7f9f10e87544528d58
|
||||
// <----><------------------------------------------>
|
||||
// | |
|
||||
// header code compressed txout
|
||||
//
|
||||
// - header code: 0x8cf316 (not coinbase, height 113931)
|
||||
// - compressed txout:
|
||||
// - 0x8009: VLQ-encoded compressed amount for 15000000 (0.15 KAS)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0xb8...58: pubkey hash
|
||||
//
|
||||
// Example 3:
|
||||
// 1b02d1c8cfef60a189017b9a420c682cf4a0028175f2f563209e4ff61c8c3620:22
|
||||
//
|
||||
// a8a2588ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6
|
||||
// <----><-------------------------------------------------->
|
||||
// | |
|
||||
// header code compressed txout
|
||||
//
|
||||
// - header code: 0xa8a258 (not coinbase, height 338156)
|
||||
// - compressed txout:
|
||||
// - 0x8ba5b9e763: VLQ-encoded compressed amount for 366875659 (3.66875659 KAS)
|
||||
// - 0x01: special script type pay-to-script-hash
|
||||
// - 0x1d...e6: script hash
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// maxUint32VLQSerializeSize is the maximum number of bytes a max uint32 takes
|
||||
// to serialize as a VLQ.
|
||||
var maxUint32VLQSerializeSize = serializeSizeVLQ(1<<32 - 1)
|
||||
|
||||
// outpointKeyPool defines a concurrent safe free list of byte slices used to
|
||||
// outpointKeyPool defines a concurrent safe free list of byte buffers used to
|
||||
// provide temporary buffers for outpoint database keys.
|
||||
var outpointKeyPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
b := make([]byte, daghash.HashSize+maxUint32VLQSerializeSize)
|
||||
return &b // Pointer to slice to avoid boxing alloc.
|
||||
return &bytes.Buffer{} // Pointer to a buffer to avoid boxing alloc.
|
||||
},
|
||||
}
|
||||
|
||||
// outpointKey returns a key suitable for use as a database key in the UTXO set
|
||||
// while making use of a free list. A new buffer is allocated if there are not
|
||||
// already any available on the free list. The returned byte slice should be
|
||||
// returned to the free list by using the recycleOutpointKey function when the
|
||||
// caller is done with it _unless_ the slice will need to live for longer than
|
||||
// the caller can calculate such as when used to write to the database.
|
||||
func outpointKey(outpoint wire.Outpoint) *[]byte {
|
||||
// A VLQ employs an MSB encoding, so they are useful not only to reduce
|
||||
// the amount of storage space, but also so iteration of UTXOs when
|
||||
// doing byte-wise comparisons will produce them in order.
|
||||
key := outpointKeyPool.Get().(*[]byte)
|
||||
idx := uint64(outpoint.Index)
|
||||
*key = (*key)[:daghash.HashSize+serializeSizeVLQ(idx)]
|
||||
copy(*key, outpoint.TxID[:])
|
||||
putVLQ((*key)[daghash.HashSize:], idx)
|
||||
return key
|
||||
// outpointIndexByteOrder is the byte order for serializing the outpoint index.
|
||||
// It uses big endian to ensure that when outpoint is used as database key, the
|
||||
// keys will be iterated in an ascending order by the outpoint index.
|
||||
var outpointIndexByteOrder = binary.BigEndian
|
||||
|
||||
func serializeOutpoint(w io.Writer, outpoint *wire.Outpoint) error {
|
||||
_, err := w.Write(outpoint.TxID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binaryserializer.PutUint32(w, outpointIndexByteOrder, outpoint.Index)
|
||||
}
|
||||
|
||||
// recycleOutpointKey puts the provided byte slice, which should have been
|
||||
// obtained via the outpointKey function, back on the free list.
|
||||
func recycleOutpointKey(key *[]byte) {
|
||||
outpointKeyPool.Put(key)
|
||||
var outpointSerializeSize = daghash.TxIDSize + 4
|
||||
|
||||
// deserializeOutpoint decodes an outpoint from the passed serialized byte
|
||||
// slice into a new wire.Outpoint using a format that is suitable for long-
|
||||
// term storage. This format is described in detail above.
|
||||
func deserializeOutpoint(r io.Reader) (*wire.Outpoint, error) {
|
||||
outpoint := &wire.Outpoint{}
|
||||
_, err := r.Read(outpoint.TxID[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outpoint.Index, err = binaryserializer.Uint32(r, outpointIndexByteOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return outpoint, nil
|
||||
}
|
||||
|
||||
// dbPutUTXODiff uses an existing database transaction to update the UTXO set
|
||||
@ -230,20 +146,42 @@ func recycleOutpointKey(key *[]byte) {
|
||||
func dbPutUTXODiff(dbTx database.Tx, diff *UTXODiff) error {
|
||||
utxoBucket := dbTx.Metadata().Bucket(utxoSetBucketName)
|
||||
for outpoint := range diff.toRemove {
|
||||
key := outpointKey(outpoint)
|
||||
err := utxoBucket.Delete(*key)
|
||||
recycleOutpointKey(key)
|
||||
w := outpointKeyPool.Get().(*bytes.Buffer)
|
||||
w.Reset()
|
||||
err := serializeOutpoint(w, &outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := w.Bytes()
|
||||
err = utxoBucket.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outpointKeyPool.Put(w)
|
||||
}
|
||||
|
||||
// We are preallocating for P2PKH entries because they are the most common ones.
|
||||
// If we have entries with a compressed script bigger than P2PKH's, the buffer will grow.
|
||||
bytesToPreallocate := (p2pkhUTXOEntrySerializeSize + outpointSerializeSize) * len(diff.toAdd)
|
||||
buff := bytes.NewBuffer(make([]byte, bytesToPreallocate))
|
||||
for outpoint, entry := range diff.toAdd {
|
||||
// Serialize and store the UTXO entry.
|
||||
serialized := serializeUTXOEntry(entry)
|
||||
sBuff := buffers.NewSubBuffer(buff)
|
||||
err := serializeUTXOEntry(sBuff, entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedEntry := sBuff.Bytes()
|
||||
|
||||
key := outpointKey(outpoint)
|
||||
err := utxoBucket.Put(*key, serialized)
|
||||
sBuff = buffers.NewSubBuffer(buff)
|
||||
err = serializeOutpoint(sBuff, &outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := sBuff.Bytes()
|
||||
err = utxoBucket.Put(key, serializedEntry)
|
||||
// NOTE: The key is intentionally not recycled here since the
|
||||
// database interface contract prohibits modifications. It will
|
||||
// be garbage collected normally when the database is done with
|
||||
@ -532,32 +470,14 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
fullUTXOCollection := make(utxoCollection, utxoEntryCount)
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() {
|
||||
// Deserialize the outpoint
|
||||
outpoint, err := deserializeOutpoint(cursor.Key())
|
||||
outpoint, err := deserializeOutpoint(bytes.NewReader(cursor.Key()))
|
||||
if err != nil {
|
||||
// Ensure any deserialization errors are returned as database
|
||||
// corruption errors.
|
||||
if isDeserializeErr(err) {
|
||||
return database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("corrupt outpoint: %s", err),
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Deserialize the utxo entry
|
||||
entry, err := deserializeUTXOEntry(cursor.Value())
|
||||
entry, err := deserializeUTXOEntry(bytes.NewReader(cursor.Value()))
|
||||
if err != nil {
|
||||
// Ensure any deserialization errors are returned as database
|
||||
// corruption errors.
|
||||
if isDeserializeErr(err) {
|
||||
return database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("corrupt utxo entry: %s", err),
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ package blockdag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"github.com/pkg/errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -36,9 +37,21 @@ func TestErrNotInDAG(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestUtxoSerialization ensures serializing and deserializing unspent
|
||||
// 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) {
|
||||
func TestUTXOSerialization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
@ -54,7 +67,7 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
blockBlueScore: 1,
|
||||
packedFlags: tfCoinbase,
|
||||
},
|
||||
serialized: hexToBytes("03320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"),
|
||||
serialized: hexToBytes("030000000000000000f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
|
||||
},
|
||||
{
|
||||
name: "blue score 100001, not coinbase",
|
||||
@ -64,13 +77,21 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
blockBlueScore: 100001,
|
||||
packedFlags: 0,
|
||||
},
|
||||
serialized: hexToBytes("8b99420700ee8bd501094a7d5ca318da2506de35e1cb025ddc"),
|
||||
serialized: hexToBytes("420d03000000000040420f00000000001976a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
// Ensure the utxo entry serializes to the expected value.
|
||||
gotBytes := serializeUTXOEntry(test.entry)
|
||||
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,
|
||||
@ -78,8 +99,8 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize to a utxo entry.
|
||||
utxoEntry, err := deserializeUTXOEntry(test.serialized)
|
||||
// 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)
|
||||
@ -124,28 +145,24 @@ func TestUtxoEntryDeserializeErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
serialized []byte
|
||||
errType error
|
||||
}{
|
||||
{
|
||||
name: "no data after header code",
|
||||
serialized: hexToBytes("02"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
{
|
||||
name: "incomplete compressed txout",
|
||||
serialized: hexToBytes("0232"),
|
||||
errType: errDeserialize(""),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Ensure the expected error type is returned and the returned
|
||||
// entry is nil.
|
||||
entry, err := deserializeUTXOEntry(test.serialized)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||
t.Errorf("deserializeUTXOEntry (%s): expected error "+
|
||||
"type does not match - got %T, want %T",
|
||||
test.name, err, test.errType)
|
||||
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 {
|
||||
|
@ -33,7 +33,7 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
k: 3,
|
||||
expectedReds: []string{"F", "G", "H", "I", "O", "P"},
|
||||
expectedReds: []string{"F", "G", "H", "I", "N", "Q"},
|
||||
dagData: []*testBlockData{
|
||||
{
|
||||
parents: []string{"A"},
|
||||
@ -166,7 +166,7 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
id: "T",
|
||||
expectedScore: 13,
|
||||
expectedSelectedParent: "S",
|
||||
expectedBlues: []string{"S", "N", "Q"},
|
||||
expectedBlues: []string{"S", "O", "P"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
BIN
blockdag/testdata/blk_0_to_4.dat
vendored
BIN
blockdag/testdata/blk_0_to_4.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3A.dat
vendored
BIN
blockdag/testdata/blk_3A.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3B.dat
vendored
BIN
blockdag/testdata/blk_3B.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3C.dat
vendored
BIN
blockdag/testdata/blk_3C.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3D.dat
vendored
BIN
blockdag/testdata/blk_3D.dat
vendored
Binary file not shown.
@ -3,7 +3,7 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math/big"
|
||||
@ -54,40 +54,26 @@ func utxoEntryHeaderCode(entry *UTXOEntry) uint64 {
|
||||
return headerCode
|
||||
}
|
||||
|
||||
func (diffStore *utxoDiffStore) deserializeBlockUTXODiffData(serializedDiffDataBytes []byte) (*blockUTXODiffData, error) {
|
||||
func (diffStore *utxoDiffStore) deserializeBlockUTXODiffData(serializedDiffData []byte) (*blockUTXODiffData, error) {
|
||||
diffData := &blockUTXODiffData{}
|
||||
serializedDiffData := bytes.NewBuffer(serializedDiffDataBytes)
|
||||
r := bytes.NewBuffer(serializedDiffData)
|
||||
|
||||
var hasDiffChild bool
|
||||
err := wire.ReadElement(serializedDiffData, &hasDiffChild)
|
||||
err := wire.ReadElement(r, &hasDiffChild)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hasDiffChild {
|
||||
hash := &daghash.Hash{}
|
||||
err := wire.ReadElement(serializedDiffData, hash)
|
||||
err := wire.ReadElement(r, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diffData.diffChild = diffStore.dag.index.LookupNode(hash)
|
||||
}
|
||||
|
||||
diffData.diff = &UTXODiff{
|
||||
useMultiset: true,
|
||||
}
|
||||
|
||||
diffData.diff.toAdd, err = deserializeDiffEntries(serializedDiffData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diffData.diff.toRemove, err = deserializeDiffEntries(serializedDiffData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diffData.diff.diffMultiset, err = deserializeMultiset(serializedDiffData)
|
||||
diffData.diff, err = deserializeUTXODiff(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -95,38 +81,38 @@ func (diffStore *utxoDiffStore) deserializeBlockUTXODiffData(serializedDiffDataB
|
||||
return diffData, nil
|
||||
}
|
||||
|
||||
func deserializeDiffEntries(r io.Reader) (utxoCollection, error) {
|
||||
func deserializeUTXODiff(r io.Reader) (*UTXODiff, error) {
|
||||
diff := &UTXODiff{
|
||||
useMultiset: true,
|
||||
}
|
||||
|
||||
var err error
|
||||
diff.toAdd, err = deserializeUTXOCollection(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diff.toRemove, err = deserializeUTXOCollection(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diff.diffMultiset, err = deserializeMultiset(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
func deserializeUTXOCollection(r io.Reader) (utxoCollection, error) {
|
||||
count, err := wire.ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
collection := utxoCollection{}
|
||||
for i := uint64(0); i < count; i++ {
|
||||
outpointSize, err := wire.ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializedOutpoint := make([]byte, outpointSize)
|
||||
err = binary.Read(r, byteOrder, serializedOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outpoint, err := deserializeOutpoint(serializedOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
utxoEntrySize, err := wire.ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serializedEntry := make([]byte, utxoEntrySize)
|
||||
err = binary.Read(r, byteOrder, serializedEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
utxoEntry, err := deserializeUTXOEntry(serializedEntry)
|
||||
utxoEntry, outpoint, err := deserializeUTXO(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -135,6 +121,19 @@ func deserializeDiffEntries(r io.Reader) (utxoCollection, error) {
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
func deserializeUTXO(r io.Reader) (*UTXOEntry, *wire.Outpoint, error) {
|
||||
outpoint, err := deserializeOutpoint(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
utxoEntry, err := deserializeUTXOEntry(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return utxoEntry, outpoint, nil
|
||||
}
|
||||
|
||||
// deserializeMultiset deserializes an EMCH multiset.
|
||||
// See serializeMultiset for more details.
|
||||
func deserializeMultiset(r io.Reader) (*ecc.Multiset, error) {
|
||||
@ -218,96 +217,93 @@ func serializeMultiset(w io.Writer, ms *ecc.Multiset) error {
|
||||
|
||||
// serializeUTXO serializes a utxo entry-outpoint pair
|
||||
func serializeUTXO(w io.Writer, entry *UTXOEntry, outpoint *wire.Outpoint) error {
|
||||
serializedOutpoint := *outpointKey(*outpoint)
|
||||
err := wire.WriteVarInt(w, uint64(len(serializedOutpoint)))
|
||||
err := serializeOutpoint(w, outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = binary.Write(w, byteOrder, serializedOutpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serializedUTXOEntry := serializeUTXOEntry(entry)
|
||||
err = wire.WriteVarInt(w, uint64(len(serializedUTXOEntry)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(w, byteOrder, serializedUTXOEntry)
|
||||
err = serializeUTXOEntry(w, entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// serializeUTXOEntry returns the entry serialized to a format that is suitable
|
||||
// for long-term storage. The format is described in detail above.
|
||||
func serializeUTXOEntry(entry *UTXOEntry) []byte {
|
||||
// p2pkhUTXOEntrySerializeSize is the serialized size for a P2PKH UTXO entry.
|
||||
// 8 bytes (header code) + 8 bytes (amount) + varint for script pub key length of 25 (for P2PKH) + 25 bytes for P2PKH script.
|
||||
var p2pkhUTXOEntrySerializeSize = 8 + 8 + wire.VarIntSerializeSize(25) + 25
|
||||
|
||||
// serializeUTXOEntry encodes the entry to the given io.Writer and use compression if useCompression is true.
|
||||
// The compression format is described in detail above.
|
||||
func serializeUTXOEntry(w io.Writer, entry *UTXOEntry) error {
|
||||
// Encode the header code.
|
||||
headerCode := utxoEntryHeaderCode(entry)
|
||||
|
||||
// Calculate the size needed to serialize the entry.
|
||||
size := serializeSizeVLQ(headerCode) +
|
||||
compressedTxOutSize(uint64(entry.Amount()), entry.ScriptPubKey())
|
||||
|
||||
// Serialize the header code followed by the compressed unspent
|
||||
// transaction output.
|
||||
serialized := make([]byte, size)
|
||||
offset := putVLQ(serialized, headerCode)
|
||||
offset += putCompressedTxOut(serialized[offset:], uint64(entry.Amount()),
|
||||
entry.ScriptPubKey())
|
||||
|
||||
return serialized
|
||||
}
|
||||
|
||||
// deserializeOutpoint decodes an outpoint from the passed serialized byte
|
||||
// slice into a new wire.Outpoint using a format that is suitable for long-
|
||||
// term storage. this format is described in detail above.
|
||||
func deserializeOutpoint(serialized []byte) (*wire.Outpoint, error) {
|
||||
if len(serialized) <= daghash.HashSize {
|
||||
return nil, errDeserialize("unexpected end of data")
|
||||
err := binaryserializer.PutUint64(w, byteOrder, headerCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txID := daghash.TxID{}
|
||||
txID.SetBytes(serialized[:daghash.HashSize])
|
||||
index, _ := deserializeVLQ(serialized[daghash.HashSize:])
|
||||
return wire.NewOutpoint(&txID, uint32(index)), nil
|
||||
err = binaryserializer.PutUint64(w, byteOrder, entry.Amount())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = wire.WriteVarInt(w, uint64(len(entry.ScriptPubKey())))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(entry.ScriptPubKey())
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deserializeUTXOEntry decodes a UTXO entry from the passed serialized byte
|
||||
// slice into a new UTXOEntry using a format that is suitable for long-term
|
||||
// storage. The format is described in detail above.
|
||||
func deserializeUTXOEntry(serialized []byte) (*UTXOEntry, error) {
|
||||
// deserializeUTXOEntry decodes a UTXO entry from the passed reader
|
||||
// into a new UTXOEntry. If isCompressed is used it will decompress
|
||||
// the entry according to the format that is described in detail
|
||||
// above.
|
||||
func deserializeUTXOEntry(r io.Reader) (*UTXOEntry, error) {
|
||||
// Deserialize the header code.
|
||||
code, offset := deserializeVLQ(serialized)
|
||||
if offset >= len(serialized) {
|
||||
return nil, errDeserialize("unexpected end of data after header")
|
||||
headerCode, err := binaryserializer.Uint64(r, byteOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the header code.
|
||||
//
|
||||
// Bit 0 indicates whether the containing transaction is a coinbase.
|
||||
// Bits 1-x encode blue score of the containing transaction.
|
||||
isCoinbase := code&0x01 != 0
|
||||
blockBlueScore := code >> 1
|
||||
|
||||
// Decode the compressed unspent transaction output.
|
||||
amount, scriptPubKey, _, err := decodeCompressedTxOut(serialized[offset:])
|
||||
if err != nil {
|
||||
return nil, errDeserialize(fmt.Sprintf("unable to decode "+
|
||||
"UTXO: %s", err))
|
||||
}
|
||||
isCoinbase := headerCode&0x01 != 0
|
||||
blockBlueScore := headerCode >> 1
|
||||
|
||||
entry := &UTXOEntry{
|
||||
amount: amount,
|
||||
scriptPubKey: scriptPubKey,
|
||||
blockBlueScore: blockBlueScore,
|
||||
packedFlags: 0,
|
||||
}
|
||||
|
||||
if isCoinbase {
|
||||
entry.packedFlags |= tfCoinbase
|
||||
}
|
||||
|
||||
entry.amount, err = binaryserializer.Uint64(r, byteOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scriptPubKeyLen, err := wire.ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry.scriptPubKey = make([]byte, scriptPubKeyLen)
|
||||
_, err = r.Read(entry.scriptPubKey)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ func TestUTXODiff(t *testing.T) {
|
||||
// Test utxoDiff string representation
|
||||
expectedDiffString := "toAdd: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ]; toRemove: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20, blueScore: 1 ]"
|
||||
if withMultiset {
|
||||
expectedDiffString = "toAdd: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ]; toRemove: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20, blueScore: 1 ], Multiset-Hash: 7cb61e48005b0c817211d04589d719bff87d86a6a6ce2454515f57265382ded7"
|
||||
expectedDiffString = "toAdd: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ]; toRemove: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20, blueScore: 1 ], Multiset-Hash: 75cb5bbca4e52a9e478dd3c5af3c856ed0f61b848088014c8c52e70432233a57"
|
||||
}
|
||||
diffString := clonedDiff.String()
|
||||
if diffString != expectedDiffString {
|
||||
@ -847,7 +847,7 @@ func TestDiffUTXOSet(t *testing.T) {
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
expectedString: "{Base: [ ], To Add: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], To Remove: [ ], Multiset-Hash:da4768bd0359c3426268d6707c1fc17a68c45ef1ea734331b07568418234487f}",
|
||||
expectedString: "{Base: [ ], To Add: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], To Remove: [ ], Multiset-Hash:2103b44fd413f5c28b5ad9afe7c3bebf19afae01cc202ea63b2f29d26252948d}",
|
||||
expectedCollection: utxoCollection{outpoint0: utxoEntry0},
|
||||
},
|
||||
{
|
||||
@ -860,7 +860,7 @@ func TestDiffUTXOSet(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expectedMeldSet: nil,
|
||||
expectedString: "{Base: [ ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], Multiset-Hash:046242cb1bb1e6d3fd91d0f181e1b2d4a597ac57fa2584fc3c2eb0e0f46c9369}",
|
||||
expectedString: "{Base: [ ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], Multiset-Hash:bae77d5370880c238265d07b80bdadbc6085990b2d2543a5f8beb2e3bd99b25b}",
|
||||
expectedCollection: utxoCollection{},
|
||||
expectedMeldToBaseError: "Couldn't remove outpoint 0000000000000000000000000000000000000000000000000000000000000000:0 because it doesn't exist in the DiffUTXOSet base",
|
||||
},
|
||||
@ -885,7 +885,7 @@ func TestDiffUTXOSet(t *testing.T) {
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], To Add: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20, blueScore: 1 ], To Remove: [ ], Multiset-Hash:556cc61fd4d7e74d7807ca2298c5320375a6a20310a18920e54667220924baff}",
|
||||
expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], To Add: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20, blueScore: 1 ], To Remove: [ ], Multiset-Hash:c9932064f2f4ba1940d63b747e4a18053d0b022d66894a2ec1bbf88f74570e93}",
|
||||
expectedCollection: utxoCollection{
|
||||
outpoint0: utxoEntry0,
|
||||
outpoint1: utxoEntry1,
|
||||
|
@ -2,6 +2,7 @@ package binaryserializer
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
@ -37,7 +38,7 @@ func Uint8(r io.Reader) (uint8, error) {
|
||||
buf := Borrow()[:1]
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
Return(buf)
|
||||
return 0, err
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
rv := buf[0]
|
||||
Return(buf)
|
||||
@ -51,7 +52,7 @@ func Uint16(r io.Reader, byteOrder binary.ByteOrder) (uint16, error) {
|
||||
buf := Borrow()[:2]
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
Return(buf)
|
||||
return 0, err
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
rv := byteOrder.Uint16(buf)
|
||||
Return(buf)
|
||||
@ -65,7 +66,7 @@ func Uint32(r io.Reader, byteOrder binary.ByteOrder) (uint32, error) {
|
||||
buf := Borrow()[:4]
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
Return(buf)
|
||||
return 0, err
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
rv := byteOrder.Uint32(buf)
|
||||
Return(buf)
|
||||
@ -79,7 +80,7 @@ func Uint64(r io.Reader, byteOrder binary.ByteOrder) (uint64, error) {
|
||||
buf := Borrow()[:8]
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
Return(buf)
|
||||
return 0, err
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
rv := byteOrder.Uint64(buf)
|
||||
Return(buf)
|
||||
@ -93,7 +94,7 @@ func PutUint8(w io.Writer, val uint8) error {
|
||||
buf[0] = val
|
||||
_, err := w.Write(buf)
|
||||
Return(buf)
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// PutUint16 serializes the provided uint16 using the given byte order into a
|
||||
@ -104,7 +105,7 @@ func PutUint16(w io.Writer, byteOrder binary.ByteOrder, val uint16) error {
|
||||
byteOrder.PutUint16(buf, val)
|
||||
_, err := w.Write(buf)
|
||||
Return(buf)
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// PutUint32 serializes the provided uint32 using the given byte order into a
|
||||
@ -115,7 +116,7 @@ func PutUint32(w io.Writer, byteOrder binary.ByteOrder, val uint32) error {
|
||||
byteOrder.PutUint32(buf, val)
|
||||
_, err := w.Write(buf)
|
||||
Return(buf)
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// PutUint64 serializes the provided uint64 using the given byte order into a
|
||||
@ -126,7 +127,7 @@ func PutUint64(w io.Writer, byteOrder binary.ByteOrder, val uint64) error {
|
||||
byteOrder.PutUint64(buf, val)
|
||||
_, err := w.Write(buf)
|
||||
Return(buf)
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// binaryFreeList provides a free list of buffers to use for serializing and
|
||||
|
@ -254,7 +254,7 @@ func TestBlockErrors(t *testing.T) {
|
||||
// Truncate the block byte buffer to force errors.
|
||||
shortBytes := block100000Bytes[:186]
|
||||
_, err = util.NewBlockFromBytes(shortBytes)
|
||||
if err != io.EOF {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Errorf("NewBlockFromBytes: did not get expected error - "+
|
||||
"got %v, want %v", err, io.EOF)
|
||||
}
|
||||
@ -289,7 +289,7 @@ func TestBlockErrors(t *testing.T) {
|
||||
// inject a short byte buffer.
|
||||
b.SetBlockBytes(shortBytes)
|
||||
_, err = b.TxLoc()
|
||||
if err != io.EOF {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Errorf("TxLoc: did not get expected error - "+
|
||||
"got %v, want %v", err, io.EOF)
|
||||
}
|
||||
|
48
util/buffers/sub_buffer.go
Normal file
48
util/buffers/sub_buffer.go
Normal file
@ -0,0 +1,48 @@
|
||||
package buffers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SubBuffer lets you write to an existing buffer
|
||||
// and let you check with the `Bytes()` method what
|
||||
// has been written to the underlying buffer using
|
||||
// the sub buffer.
|
||||
type SubBuffer struct {
|
||||
buff *bytes.Buffer
|
||||
start, end int
|
||||
}
|
||||
|
||||
// Bytes returns all the bytes that were written to the sub buffer.
|
||||
func (s *SubBuffer) Bytes() []byte {
|
||||
return s.buff.Bytes()[s.start:s.end]
|
||||
}
|
||||
|
||||
// Write writes to the sub buffer's underlying buffer
|
||||
// and increases s.end by the number of bytes written
|
||||
// so s.Bytes() will be able to return the written bytes.
|
||||
func (s *SubBuffer) Write(p []byte) (int, error) {
|
||||
if s.buff.Len() > s.end || s.buff.Len() < s.start {
|
||||
return 0, errors.New("a sub buffer cannot be written after another entity wrote or read from its " +
|
||||
"underlying buffer")
|
||||
}
|
||||
|
||||
n, err := s.buff.Write(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
s.end += n
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// NewSubBuffer returns a new sub buffer.
|
||||
func NewSubBuffer(buff *bytes.Buffer) *SubBuffer {
|
||||
return &SubBuffer{
|
||||
buff: buff,
|
||||
start: buff.Len(),
|
||||
end: buff.Len(),
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package random
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
@ -62,7 +63,7 @@ func TestRandomUint64Errors(t *testing.T) {
|
||||
// Test short reads.
|
||||
fr := &fakeRandReader{n: 2, err: io.EOF}
|
||||
nonce, err := randomUint64(fr)
|
||||
if err != io.ErrUnexpectedEOF {
|
||||
if !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
t.Errorf("Error not expected value of %v [%v]",
|
||||
io.ErrUnexpectedEOF, err)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package util_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -107,7 +108,7 @@ func TestTxErrors(t *testing.T) {
|
||||
// Truncate the transaction byte buffer to force errors.
|
||||
shortBytes := testTxBytes[:4]
|
||||
_, err = util.NewTxFromBytes(shortBytes)
|
||||
if err != io.EOF {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Errorf("NewTxFromBytes: did not get expected error - "+
|
||||
"got %v, want %v", err, io.EOF)
|
||||
}
|
||||
|
@ -350,7 +350,7 @@ func ReadVarInt(r io.Reader) (uint64, error) {
|
||||
// encoded using fewer bytes.
|
||||
min := uint64(0x100000000)
|
||||
if rv < min {
|
||||
return 0, messageError("ReadVarInt", fmt.Sprintf(
|
||||
return 0, messageError("readVarInt", fmt.Sprintf(
|
||||
errNonCanonicalVarInt, rv, discriminant, min))
|
||||
}
|
||||
|
||||
@ -365,7 +365,7 @@ func ReadVarInt(r io.Reader) (uint64, error) {
|
||||
// encoded using fewer bytes.
|
||||
min := uint64(0x10000)
|
||||
if rv < min {
|
||||
return 0, messageError("ReadVarInt", fmt.Sprintf(
|
||||
return 0, messageError("readVarInt", fmt.Sprintf(
|
||||
errNonCanonicalVarInt, rv, discriminant, min))
|
||||
}
|
||||
|
||||
@ -380,7 +380,7 @@ func ReadVarInt(r io.Reader) (uint64, error) {
|
||||
// encoded using fewer bytes.
|
||||
min := uint64(0xfd)
|
||||
if rv < min {
|
||||
return 0, messageError("ReadVarInt", fmt.Sprintf(
|
||||
return 0, messageError("readVarInt", fmt.Sprintf(
|
||||
errNonCanonicalVarInt, rv, discriminant, min))
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ func TestElementWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := WriteElement(w, test.in)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("writeElement #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -240,7 +240,7 @@ func TestElementWireErrors(t *testing.T) {
|
||||
val = reflect.New(reflect.TypeOf(test.in)).Interface()
|
||||
}
|
||||
err = ReadElement(r, val)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("readElement #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
@ -251,31 +251,30 @@ func TestElementWireErrors(t *testing.T) {
|
||||
// TestVarIntWire tests wire encode and decode for variable length integers.
|
||||
func TestVarIntWire(t *testing.T) {
|
||||
tests := []struct {
|
||||
in uint64 // Value to encode
|
||||
out uint64 // Expected decoded value
|
||||
value uint64 // Value to encode
|
||||
buf []byte // Wire encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
// Single byte
|
||||
{0, 0, []byte{0x00}},
|
||||
{0, []byte{0x00}},
|
||||
// Max single byte
|
||||
{0xfc, 0xfc, []byte{0xfc}},
|
||||
{0xfc, []byte{0xfc}},
|
||||
// Min 2-byte
|
||||
{0xfd, 0xfd, []byte{0xfd, 0x0fd, 0x00}},
|
||||
{0xfd, []byte{0xfd, 0x0fd, 0x00}},
|
||||
// Max 2-byte
|
||||
{0xffff, 0xffff, []byte{0xfd, 0xff, 0xff}},
|
||||
{0xffff, []byte{0xfd, 0xff, 0xff}},
|
||||
// Min 4-byte
|
||||
{0x10000, 0x10000, []byte{0xfe, 0x00, 0x00, 0x01, 0x00}},
|
||||
{0x10000, []byte{0xfe, 0x00, 0x00, 0x01, 0x00}},
|
||||
// Max 4-byte
|
||||
{0xffffffff, 0xffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff}},
|
||||
{0xffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff}},
|
||||
// Min 8-byte
|
||||
{
|
||||
0x100000000, 0x100000000,
|
||||
0x100000000,
|
||||
[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||
},
|
||||
// Max 8-byte
|
||||
{
|
||||
0xffffffffffffffff, 0xffffffffffffffff,
|
||||
0xffffffffffffffff,
|
||||
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
}
|
||||
@ -283,8 +282,8 @@ func TestVarIntWire(t *testing.T) {
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to wire format.
|
||||
var buf bytes.Buffer
|
||||
err := WriteVarInt(&buf, test.in)
|
||||
buf := &bytes.Buffer{}
|
||||
err := WriteVarInt(buf, test.value)
|
||||
if err != nil {
|
||||
t.Errorf("WriteVarInt #%d error %v", i, err)
|
||||
continue
|
||||
@ -302,9 +301,9 @@ func TestVarIntWire(t *testing.T) {
|
||||
t.Errorf("ReadVarInt #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if val != test.out {
|
||||
t.Errorf("ReadVarInt #%d\n got: %d want: %d", i,
|
||||
val, test.out)
|
||||
if val != test.value {
|
||||
t.Errorf("ReadVarInt #%d\n got: %x want: %x", i,
|
||||
val, test.value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -338,7 +337,7 @@ func TestVarIntWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := WriteVarInt(w, test.in)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("WriteVarInt #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -347,7 +346,7 @@ func TestVarIntWireErrors(t *testing.T) {
|
||||
// Decode from wire format.
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
_, err = ReadVarInt(r)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("ReadVarInt #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
@ -528,7 +527,7 @@ func TestVarStringWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := WriteVarString(w, test.in)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("WriteVarString #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -537,7 +536,7 @@ func TestVarStringWireErrors(t *testing.T) {
|
||||
// Decode from wire format.
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
_, err = ReadVarString(r, test.pver)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("ReadVarString #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
@ -659,7 +658,7 @@ func TestVarBytesWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := WriteVarBytes(w, test.pver, test.in)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("WriteVarBytes #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -669,7 +668,7 @@ func TestVarBytesWireErrors(t *testing.T) {
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
_, err = ReadVarBytes(r, test.pver, MaxMessagePayload,
|
||||
"test payload")
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("ReadVarBytes #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
|
@ -341,11 +341,6 @@ func TestReadMessageWireErrors(t *testing.T) {
|
||||
// Decode from wire format.
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
nr, _, _, err := ReadMessageN(r, test.pver, test.kaspaNet)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+
|
||||
"want: %T", i, err, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the number of bytes written match the expected value.
|
||||
if nr != test.bytes {
|
||||
@ -354,14 +349,19 @@ func TestReadMessageWireErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+
|
||||
"want: %v <%T>", i, err, err,
|
||||
test.readErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -432,7 +432,8 @@ func TestWriteMessageWireErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.err {
|
||||
t.Errorf("ReadMessage #%d wrong error got: %v <%T>, "+
|
||||
|
@ -274,40 +274,40 @@ func TestAddrWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgAddr
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
@ -239,7 +240,7 @@ func TestBlockWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -249,7 +250,7 @@ func TestBlockWireErrors(t *testing.T) {
|
||||
var msg MsgBlock
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
@ -365,7 +366,7 @@ func TestBlockSerializeErrors(t *testing.T) {
|
||||
// Serialize the block.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.Serialize(w)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("Serialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -375,7 +376,7 @@ func TestBlockSerializeErrors(t *testing.T) {
|
||||
var block MsgBlock
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = block.Deserialize(r)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
@ -384,7 +385,7 @@ func TestBlockSerializeErrors(t *testing.T) {
|
||||
var txLocBlock MsgBlock
|
||||
br := bytes.NewBuffer(test.buf[0:test.max])
|
||||
_, err = txLocBlock.DeserializeTxLoc(br)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("DeserializeTxLoc #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
|
@ -225,40 +225,40 @@ func TestBlockLocatorWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgBlockLocator
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,40 +139,40 @@ func TestFeeFilterWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgFeeFilter
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -119,40 +119,39 @@ func TestFilterAddWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgFilterAdd
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -165,40 +165,40 @@ func TestFilterLoadWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgFilterLoad
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -207,7 +207,8 @@ func TestGetBlockInvsWireErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
@ -227,7 +228,8 @@ func TestGetBlockInvsWireErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
|
@ -190,7 +190,8 @@ func TestGetBlockLocatorWireErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
@ -210,7 +211,8 @@ func TestGetBlockLocatorWireErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
|
@ -231,40 +231,40 @@ func TestGetDataWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgGetData
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,40 +231,40 @@ func TestInvWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgInv
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -230,40 +230,40 @@ func TestMerkleBlockWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgMerkleBlock
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,40 +221,39 @@ func TestNotFoundWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgNotFound
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -145,7 +146,7 @@ func TestPingWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -155,7 +156,7 @@ func TestPingWireErrors(t *testing.T) {
|
||||
var msg MsgPing
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
|
@ -162,40 +162,40 @@ func TestPongWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgPong
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -258,40 +258,40 @@ func TestRejectWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgReject
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ package wire
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
@ -365,7 +366,7 @@ func TestTxWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -375,7 +376,7 @@ func TestTxWireErrors(t *testing.T) {
|
||||
var msg MsgTx
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
@ -568,7 +569,7 @@ func TestTxSerializeErrors(t *testing.T) {
|
||||
// Serialize the transaction.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.Serialize(w)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("Serialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -578,7 +579,7 @@ func TestTxSerializeErrors(t *testing.T) {
|
||||
var tx MsgTx
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = tx.Deserialize(r)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
|
@ -267,40 +267,40 @@ func TestVersionWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.writeErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from wire format.
|
||||
var msg MsgVersion
|
||||
buf := bytes.NewBuffer(test.buf[0:test.max])
|
||||
err = msg.KaspaDecode(buf, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// For errors which are not of type MessageError, check them for
|
||||
// equality.
|
||||
// equality. If the error is a MessageError, check only if it's
|
||||
// the expected type.
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
} else if reflect.TypeOf(msgErr) != reflect.TypeOf(test.readErr) {
|
||||
t.Errorf("ReadMessage #%d wrong error type got: %T, "+
|
||||
"want: %T", i, msgErr, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
@ -198,7 +199,7 @@ func TestNetAddressWireErrors(t *testing.T) {
|
||||
// Encode to wire format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := writeNetAddress(w, test.pver, test.in, test.ts)
|
||||
if err != test.writeErr {
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("writeNetAddress #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
@ -208,7 +209,7 @@ func TestNetAddressWireErrors(t *testing.T) {
|
||||
var na NetAddress
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = readNetAddress(r, test.pver, &na, test.ts)
|
||||
if err != test.readErr {
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("readNetAddress #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
|
Loading…
x
Reference in New Issue
Block a user