mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-23 03:48:20 +00:00
Compare commits
14 Commits
v0.1.2-dev
...
v0.2.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bca7342d3 | ||
|
|
f80908fb4e | ||
|
|
e000e10738 | ||
|
|
d83862f36c | ||
|
|
1020402b34 | ||
|
|
bc6ce6ed53 | ||
|
|
d3b1953deb | ||
|
|
3c67215e76 | ||
|
|
586624c836 | ||
|
|
49855e6333 | ||
|
|
624249c0f3 | ||
|
|
1cf443a63b | ||
|
|
8909679f44 | ||
|
|
e58efbf0ea |
@@ -110,7 +110,7 @@ func (dag *BlockDAG) newBlockNode(blockHeader *wire.BlockHeader, parents blockSe
|
||||
parents: parents,
|
||||
children: make(blockSet),
|
||||
blueScore: math.MaxUint64, // Initialized to the max value to avoid collisions with the genesis block
|
||||
timestamp: dag.AdjustedTime().Unix(),
|
||||
timestamp: dag.Now().Unix(),
|
||||
bluesAnticoneSizes: make(map[*blockNode]dagconfig.KType),
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -106,7 +99,7 @@ func newTestDAG(params *dagconfig.Params) *BlockDAG {
|
||||
targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second)
|
||||
dag := &BlockDAG{
|
||||
dagParams: params,
|
||||
timeSource: NewMedianTime(),
|
||||
timeSource: NewTimeSource(),
|
||||
targetTimePerBlock: targetTimePerBlock,
|
||||
difficultyAdjustmentWindowSize: params.DifficultyAdjustmentWindowSize,
|
||||
TimestampDeviationTolerance: params.TimestampDeviationTolerance,
|
||||
@@ -211,3 +204,15 @@ func nodeByMsgBlock(t *testing.T, dag *BlockDAG, block *wire.MsgBlock) *blockNod
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
type fakeTimeSource struct {
|
||||
time time.Time
|
||||
}
|
||||
|
||||
func (fts *fakeTimeSource) Now() time.Time {
|
||||
return time.Unix(fts.time.Unix(), 0)
|
||||
}
|
||||
|
||||
func newFakeTimeSource(fakeTime time.Time) TimeSource {
|
||||
return &fakeTimeSource{time: fakeTime}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
268
blockdag/dag.go
268
blockdag/dag.go
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
@@ -61,7 +62,7 @@ type BlockDAG struct {
|
||||
// separate mutex.
|
||||
db database.DB
|
||||
dagParams *dagconfig.Params
|
||||
timeSource MedianTimeSource
|
||||
timeSource TimeSource
|
||||
sigCache *txscript.SigCache
|
||||
indexManager IndexManager
|
||||
genesis *blockNode
|
||||
@@ -153,6 +154,7 @@ type BlockDAG struct {
|
||||
SubnetworkStore *SubnetworkStore
|
||||
utxoDiffStore *utxoDiffStore
|
||||
reachabilityStore *reachabilityStore
|
||||
multisetStore *multisetStore
|
||||
}
|
||||
|
||||
// IsKnownBlock returns whether or not the DAG instance has the block represented
|
||||
@@ -486,25 +488,14 @@ func (dag *BlockDAG) addBlock(node *blockNode,
|
||||
if err != nil {
|
||||
if errors.As(err, &RuleError{}) {
|
||||
dag.index.SetStatusFlags(node, statusValidateFailed)
|
||||
} else {
|
||||
return nil, err
|
||||
err := dag.index.flushToDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dag.blockCount++
|
||||
}
|
||||
|
||||
// Intentionally ignore errors writing updated node status to DB. If
|
||||
// it fails to write, it's not the end of the world. If the block is
|
||||
// invalid, the worst that can happen is we revalidate the block
|
||||
// after a restart.
|
||||
if writeErr := dag.index.flushToDB(); writeErr != nil {
|
||||
log.Warnf("Error flushing block index changes to disk: %s",
|
||||
writeErr)
|
||||
}
|
||||
// If dag.connectBlock returned a rule error, return it here after updating DB
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dag.blockCount++
|
||||
return chainUpdates, nil
|
||||
}
|
||||
|
||||
@@ -571,14 +562,13 @@ func (dag *BlockDAG) connectBlock(node *blockNode,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newBlockUTXO, txsAcceptanceData, newBlockFeeData, err := node.verifyAndBuildUTXO(dag, block.Transactions(), fastAdd)
|
||||
newBlockUTXO, txsAcceptanceData, newBlockFeeData, newBlockMultiSet, err := node.verifyAndBuildUTXO(dag, block.Transactions(), fastAdd)
|
||||
if err != nil {
|
||||
newErrString := fmt.Sprintf("error verifying UTXO for %s: %s", node, err)
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); ok {
|
||||
return nil, ruleError(ruleErr.ErrorCode, newErrString)
|
||||
return nil, ruleError(ruleErr.ErrorCode, fmt.Sprintf("error verifying UTXO for %s: %s", node, err))
|
||||
}
|
||||
return nil, errors.New(newErrString)
|
||||
return nil, errors.Wrapf(err, "error verifying UTXO for %s", node)
|
||||
}
|
||||
|
||||
err = node.validateCoinbaseTransaction(dag, block, txsAcceptanceData)
|
||||
@@ -587,7 +577,7 @@ func (dag *BlockDAG) connectBlock(node *blockNode,
|
||||
}
|
||||
|
||||
// Apply all changes to the DAG.
|
||||
virtualUTXODiff, virtualTxsAcceptanceData, chainUpdates, err := dag.applyDAGChanges(node, newBlockUTXO, selectedParentAnticone)
|
||||
virtualUTXODiff, virtualTxsAcceptanceData, chainUpdates, err := dag.applyDAGChanges(node, newBlockUTXO, newBlockMultiSet, selectedParentAnticone)
|
||||
if err != nil {
|
||||
// Since all validation logic has already ran, if applyDAGChanges errors out,
|
||||
// this means we have a problem in the internal structure of the DAG - a problem which is
|
||||
@@ -604,19 +594,138 @@ func (dag *BlockDAG) connectBlock(node *blockNode,
|
||||
return chainUpdates, nil
|
||||
}
|
||||
|
||||
// calcMultiset returns the multiset of the UTXO of the given block with the given transactions.
|
||||
func (node *blockNode) calcMultiset(dag *BlockDAG, transactions []*util.Tx, acceptanceData MultiBlockTxsAcceptanceData, selectedParentUTXO, pastUTXO UTXOSet) (*secp256k1.MultiSet, error) {
|
||||
ms, err := node.pastUTXOMultiSet(dag, acceptanceData, selectedParentUTXO)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tx := range transactions {
|
||||
ms, err = addTxToMultiset(ms, tx.MsgTx(), pastUTXO, UnacceptedBlueScore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
// acceptedSelectedParentMultiset takes the multiset of the selected
|
||||
// parent, replaces all the selected parent outputs' blue score with
|
||||
// the block blue score and returns the result.
|
||||
func (node *blockNode) acceptedSelectedParentMultiset(dag *BlockDAG,
|
||||
acceptanceData MultiBlockTxsAcceptanceData) (*secp256k1.MultiSet, error) {
|
||||
|
||||
if node.isGenesis() {
|
||||
return secp256k1.NewMultiset(), nil
|
||||
}
|
||||
|
||||
ms, err := dag.multisetStore.multisetByBlockNode(node.selectedParent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selectedParentAcceptanceData, exists := acceptanceData.FindAcceptanceData(node.selectedParent.hash)
|
||||
if !exists {
|
||||
return nil, errors.Errorf("couldn't find selected parent acceptance data for block %s", node)
|
||||
}
|
||||
for _, txAcceptanceData := range selectedParentAcceptanceData.TxAcceptanceData {
|
||||
tx := txAcceptanceData.Tx
|
||||
msgTx := tx.MsgTx()
|
||||
isCoinbase := tx.IsCoinBase()
|
||||
for i, txOut := range msgTx.TxOut {
|
||||
outpoint := *wire.NewOutpoint(tx.ID(), uint32(i))
|
||||
|
||||
unacceptedEntry := NewUTXOEntry(txOut, isCoinbase, UnacceptedBlueScore)
|
||||
acceptedEntry := NewUTXOEntry(txOut, isCoinbase, node.blueScore)
|
||||
|
||||
var err error
|
||||
ms, err = removeUTXOFromMultiset(ms, unacceptedEntry, &outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ms, err = addUTXOToMultiset(ms, acceptedEntry, &outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func (node *blockNode) pastUTXOMultiSet(dag *BlockDAG, acceptanceData MultiBlockTxsAcceptanceData, selectedParentUTXO UTXOSet) (*secp256k1.MultiSet, error) {
|
||||
ms, err := node.acceptedSelectedParentMultiset(dag, acceptanceData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, blockAcceptanceData := range acceptanceData {
|
||||
if blockAcceptanceData.BlockHash.IsEqual(node.selectedParent.hash) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, txAcceptanceData := range blockAcceptanceData.TxAcceptanceData {
|
||||
if !txAcceptanceData.IsAccepted {
|
||||
continue
|
||||
}
|
||||
|
||||
tx := txAcceptanceData.Tx.MsgTx()
|
||||
|
||||
var err error
|
||||
ms, err = addTxToMultiset(ms, tx, selectedParentUTXO, node.blueScore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func addTxToMultiset(ms *secp256k1.MultiSet, tx *wire.MsgTx, pastUTXO UTXOSet, blockBlueScore uint64) (*secp256k1.MultiSet, error) {
|
||||
isCoinbase := tx.IsCoinBase()
|
||||
if !isCoinbase {
|
||||
for _, txIn := range tx.TxIn {
|
||||
entry, ok := pastUTXO.Get(txIn.PreviousOutpoint)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Couldn't find entry for outpoint %s", txIn.PreviousOutpoint)
|
||||
}
|
||||
|
||||
var err error
|
||||
ms, err = removeUTXOFromMultiset(ms, entry, &txIn.PreviousOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, txOut := range tx.TxOut {
|
||||
outpoint := *wire.NewOutpoint(tx.TxID(), uint32(i))
|
||||
entry := NewUTXOEntry(txOut, isCoinbase, blockBlueScore)
|
||||
|
||||
var err error
|
||||
ms, err = addUTXOToMultiset(ms, entry, &outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) saveChangesFromBlock(block *util.Block, virtualUTXODiff *UTXODiff,
|
||||
txsAcceptanceData MultiBlockTxsAcceptanceData, virtualTxsAcceptanceData MultiBlockTxsAcceptanceData,
|
||||
feeData compactFeeData) error {
|
||||
|
||||
// Write any block status changes to DB before updating the DAG state.
|
||||
err := dag.index.flushToDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Atomically insert info into the database.
|
||||
err = dag.db.Update(func(dbTx database.Tx) error {
|
||||
err := dag.utxoDiffStore.flushToDB(dbTx)
|
||||
err := dag.db.Update(func(dbTx database.Tx) error {
|
||||
err := dag.index.flushToDBWithTx(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dag.utxoDiffStore.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -626,6 +735,11 @@ func (dag *BlockDAG) saveChangesFromBlock(block *util.Block, virtualUTXODiff *UT
|
||||
return err
|
||||
}
|
||||
|
||||
err = dag.multisetStore.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update best block state.
|
||||
state := &dagState{
|
||||
TipHashes: dag.TipHashes(),
|
||||
@@ -675,6 +789,7 @@ func (dag *BlockDAG) saveChangesFromBlock(block *util.Block, virtualUTXODiff *UT
|
||||
dag.index.clearDirtyEntries()
|
||||
dag.utxoDiffStore.clearDirtyEntries()
|
||||
dag.reachabilityStore.clearDirtyEntries()
|
||||
dag.multisetStore.clearNewEntries()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -851,7 +966,7 @@ func (dag *BlockDAG) NextAcceptedIDMerkleRootNoLock() (*daghash.Hash, error) {
|
||||
//
|
||||
// This function MUST be called with the DAG read-lock held
|
||||
func (dag *BlockDAG) TxsAcceptedByVirtual() (MultiBlockTxsAcceptanceData, error) {
|
||||
_, txsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode)
|
||||
_, _, txsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode)
|
||||
return txsAcceptanceData, err
|
||||
}
|
||||
|
||||
@@ -863,7 +978,7 @@ func (dag *BlockDAG) TxsAcceptedByBlockHash(blockHash *daghash.Hash) (MultiBlock
|
||||
if node == nil {
|
||||
return nil, errors.Errorf("Couldn't find block %s", blockHash)
|
||||
}
|
||||
_, txsAcceptanceData, err := dag.pastUTXO(node)
|
||||
_, _, txsAcceptanceData, err := dag.pastUTXO(node)
|
||||
return txsAcceptanceData, err
|
||||
}
|
||||
|
||||
@@ -874,12 +989,13 @@ func (dag *BlockDAG) TxsAcceptedByBlockHash(blockHash *daghash.Hash) (MultiBlock
|
||||
// 4. Updates each of the tips' utxoDiff.
|
||||
// 5. Applies the new virtual's blue score to all the unaccepted UTXOs
|
||||
// 6. Adds the block to the reachability structures
|
||||
// 7. Updates the finality point of the DAG (if required).
|
||||
// 7. Adds the multiset of the block to the multiset store.
|
||||
// 8. Updates the finality point of the DAG (if required).
|
||||
//
|
||||
// It returns the diff in the virtual block's UTXO set.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockUTXO UTXOSet, selectedParentAnticone []*blockNode) (
|
||||
func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockUTXO UTXOSet, newBlockMultiset *secp256k1.MultiSet, selectedParentAnticone []*blockNode) (
|
||||
virtualUTXODiff *UTXODiff, virtualTxsAcceptanceData MultiBlockTxsAcceptanceData,
|
||||
chainUpdates *chainUpdates, err error) {
|
||||
|
||||
@@ -889,6 +1005,8 @@ func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockUTXO UTXOSet, sele
|
||||
return nil, nil, nil, errors.Wrap(err, "failed updating reachability")
|
||||
}
|
||||
|
||||
dag.multisetStore.setMultiset(node, newBlockMultiset)
|
||||
|
||||
if err = node.updateParents(dag, newBlockUTXO); err != nil {
|
||||
return nil, nil, nil, errors.Wrapf(err, "failed updating parents of %s", node)
|
||||
}
|
||||
@@ -897,7 +1015,7 @@ func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockUTXO UTXOSet, sele
|
||||
chainUpdates = dag.virtual.AddTip(node)
|
||||
|
||||
// Build a UTXO set for the new virtual block
|
||||
newVirtualUTXO, virtualTxsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode)
|
||||
newVirtualUTXO, _, virtualTxsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(err, "could not restore past UTXO for virtual")
|
||||
}
|
||||
@@ -948,42 +1066,49 @@ func (node *blockNode) diffFromTxs(pastUTXO UTXOSet, transactions []*util.Tx) (*
|
||||
}
|
||||
|
||||
// verifyAndBuildUTXO verifies all transactions in the given block and builds its UTXO
|
||||
// to save extra traversals it returns the transactions acceptance data and the compactFeeData for the new block
|
||||
// to save extra traversals it returns the transactions acceptance data, the compactFeeData
|
||||
// for the new block and its multiset.
|
||||
func (node *blockNode) verifyAndBuildUTXO(dag *BlockDAG, transactions []*util.Tx, fastAdd bool) (
|
||||
newBlockUTXO UTXOSet, txsAcceptanceData MultiBlockTxsAcceptanceData, newBlockFeeData compactFeeData, err error) {
|
||||
newBlockUTXO UTXOSet, txsAcceptanceData MultiBlockTxsAcceptanceData, newBlockFeeData compactFeeData, multiset *secp256k1.MultiSet, err error) {
|
||||
|
||||
pastUTXO, txsAcceptanceData, err := dag.pastUTXO(node)
|
||||
pastUTXO, selectedParentUTXO, txsAcceptanceData, err := dag.pastUTXO(node)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
err = node.validateAcceptedIDMerkleRoot(dag, txsAcceptanceData)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
feeData, err := dag.checkConnectToPastUTXO(node, pastUTXO, transactions, fastAdd)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
diffFromTxs, err := node.diffFromTxs(pastUTXO, transactions)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
utxo, err := pastUTXO.WithDiff(diffFromTxs)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
calculatedMultisetHash := utxo.Multiset().Hash()
|
||||
multiset, err = node.calcMultiset(dag, transactions, txsAcceptanceData, selectedParentUTXO, pastUTXO)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
calculatedMultisetHash := daghash.Hash(*multiset.Finalize())
|
||||
if !calculatedMultisetHash.IsEqual(node.utxoCommitment) {
|
||||
str := fmt.Sprintf("block %s UTXO commitment is invalid - block "+
|
||||
"header indicates %s, but calculated value is %s", node.hash,
|
||||
node.utxoCommitment, calculatedMultisetHash)
|
||||
return nil, nil, nil, ruleError(ErrBadUTXOCommitment, str)
|
||||
return nil, nil, nil, nil, ruleError(ErrBadUTXOCommitment, str)
|
||||
}
|
||||
return utxo, txsAcceptanceData, feeData, nil
|
||||
|
||||
return utxo, txsAcceptanceData, feeData, multiset, nil
|
||||
}
|
||||
|
||||
// TxAcceptanceData stores a transaction together with an indication
|
||||
@@ -1137,28 +1262,33 @@ func (node *blockNode) updateParentsDiffs(dag *BlockDAG, newBlockUTXO UTXOSet) e
|
||||
// To save traversals over the blue blocks, it also returns the transaction acceptance data for
|
||||
// all blue blocks
|
||||
func (dag *BlockDAG) pastUTXO(node *blockNode) (
|
||||
pastUTXO UTXOSet, bluesTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) {
|
||||
pastUTXO, selectedParentUTXO UTXOSet, bluesTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) {
|
||||
|
||||
if node.isGenesis() {
|
||||
return genesisPastUTXO(dag.virtual), MultiBlockTxsAcceptanceData{}, nil
|
||||
return genesisPastUTXO(dag.virtual), NewFullUTXOSet(), MultiBlockTxsAcceptanceData{}, nil
|
||||
}
|
||||
selectedParentUTXO, err := dag.restoreUTXO(node.selectedParent)
|
||||
selectedParentUTXO, err = dag.restoreUTXO(node.selectedParent)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
blueBlocks, err := node.fetchBlueBlocks(dag.db)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
selectedParent := blueBlocks[0]
|
||||
acceptedSelectedParentUTXO, selectedParentAcceptanceData, err := node.acceptSelectedParentTransactions(selectedParent, selectedParentUTXO)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return node.applyBlueBlocks(acceptedSelectedParentUTXO, selectedParentAcceptanceData, blueBlocks)
|
||||
pastUTXO, bluesTxsAcceptanceData, err = node.applyBlueBlocks(acceptedSelectedParentUTXO, selectedParentAcceptanceData, blueBlocks)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return pastUTXO, selectedParentUTXO, bluesTxsAcceptanceData, nil
|
||||
}
|
||||
|
||||
func (node *blockNode) acceptSelectedParentTransactions(selectedParent *util.Block, selectedParentUTXO UTXOSet) (acceptedSelectedParentUTXO UTXOSet, txAcceptanceData []TxAcceptanceData, err error) {
|
||||
@@ -1214,8 +1344,8 @@ func (dag *BlockDAG) restoreUTXO(node *blockNode) (UTXOSet, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use WithDiffInPlace, otherwise copying the diffs again and again create a polynomial overhead
|
||||
err = accumulatedDiff.WithDiffInPlace(diff)
|
||||
// Use withDiffInPlace, otherwise copying the diffs again and again create a polynomial overhead
|
||||
err = accumulatedDiff.withDiffInPlace(diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1265,14 +1395,14 @@ func (dag *BlockDAG) isCurrent() bool {
|
||||
dagTimestamp = selectedTip.timestamp
|
||||
}
|
||||
dagTime := time.Unix(dagTimestamp, 0)
|
||||
return dag.AdjustedTime().Sub(dagTime) <= isDAGCurrentMaxDiff
|
||||
return dag.Now().Sub(dagTime) <= isDAGCurrentMaxDiff
|
||||
}
|
||||
|
||||
// AdjustedTime returns the adjusted time according to
|
||||
// dag.timeSource. See MedianTimeSource.AdjustedTime for
|
||||
// Now returns the adjusted time according to
|
||||
// dag.timeSource. See TimeSource.Now for
|
||||
// more details.
|
||||
func (dag *BlockDAG) AdjustedTime() time.Time {
|
||||
return dag.timeSource.AdjustedTime()
|
||||
func (dag *BlockDAG) Now() time.Time {
|
||||
return dag.timeSource.Now()
|
||||
}
|
||||
|
||||
// IsCurrent returns whether or not the DAG believes it is current. Several
|
||||
@@ -1393,11 +1523,6 @@ func (dag *BlockDAG) UTXOConfirmations(outpoint *wire.Outpoint) (uint64, bool) {
|
||||
return confirmations, true
|
||||
}
|
||||
|
||||
// UTXOCommitment returns a commitment to the dag's current UTXOSet
|
||||
func (dag *BlockDAG) UTXOCommitment() string {
|
||||
return dag.UTXOSet().UTXOMultiset.Hash().String()
|
||||
}
|
||||
|
||||
// blockConfirmations returns the current confirmations number of the given node
|
||||
// The confirmations number is defined as follows:
|
||||
// * If the node is in the selected tip red set -> 0
|
||||
@@ -1811,7 +1936,7 @@ func (dag *BlockDAG) SubnetworkID() *subnetworkid.SubnetworkID {
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) addDelayedBlock(block *util.Block, delay time.Duration) error {
|
||||
processTime := dag.AdjustedTime().Add(delay)
|
||||
processTime := dag.Now().Add(delay)
|
||||
log.Debugf("Adding block to delayed blocks queue (block hash: %s, process time: %s)", block.Hash().String(), processTime)
|
||||
delayedBlock := &delayedBlock{
|
||||
block: block,
|
||||
@@ -1829,7 +1954,7 @@ func (dag *BlockDAG) processDelayedBlocks() error {
|
||||
// Check if the delayed block with the earliest process time should be processed
|
||||
for dag.delayedBlocksQueue.Len() > 0 {
|
||||
earliestDelayedBlockProcessTime := dag.peekDelayedBlock().processTime
|
||||
if earliestDelayedBlockProcessTime.After(dag.AdjustedTime()) {
|
||||
if earliestDelayedBlockProcessTime.After(dag.Now()) {
|
||||
break
|
||||
}
|
||||
delayedBlock := dag.popDelayedBlock()
|
||||
@@ -1895,13 +2020,9 @@ type Config struct {
|
||||
// This field is required.
|
||||
DAGParams *dagconfig.Params
|
||||
|
||||
// TimeSource defines the median time source to use for things such as
|
||||
// TimeSource defines the time source to use for things such as
|
||||
// block processing and determining whether or not the DAG is current.
|
||||
//
|
||||
// The caller is expected to keep a reference to the time source as well
|
||||
// and add time samples from other peers on the network so the local
|
||||
// time is adjusted to be in agreement with other peers.
|
||||
TimeSource MedianTimeSource
|
||||
TimeSource TimeSource
|
||||
|
||||
// SigCache defines a signature cache to use when when validating
|
||||
// signatures. This is typically most useful when individual
|
||||
@@ -1968,6 +2089,7 @@ func New(config *Config) (*BlockDAG, error) {
|
||||
dag.virtual = newVirtualBlock(dag, nil)
|
||||
dag.utxoDiffStore = newUTXODiffStore(dag)
|
||||
dag.reachabilityStore = newReachabilityStore(dag)
|
||||
dag.multisetStore = newMultisetStore(dag)
|
||||
|
||||
// Initialize the DAG state from the passed database. When the db
|
||||
// does not yet contain any DAG state, both it and the DAG state
|
||||
|
||||
@@ -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},
|
||||
@@ -561,7 +561,7 @@ func TestNew(t *testing.T) {
|
||||
config := &Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
DB: db,
|
||||
TimeSource: NewMedianTime(),
|
||||
TimeSource: NewTimeSource(),
|
||||
SigCache: txscript.NewSigCache(1000),
|
||||
}
|
||||
_, err = New(config)
|
||||
@@ -603,7 +603,7 @@ func TestAcceptingInInit(t *testing.T) {
|
||||
config := &Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
DB: db,
|
||||
TimeSource: NewMedianTime(),
|
||||
TimeSource: NewTimeSource(),
|
||||
SigCache: txscript.NewSigCache(1000),
|
||||
}
|
||||
dag, err := New(config)
|
||||
|
||||
@@ -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"
|
||||
@@ -58,6 +59,10 @@ var (
|
||||
// reachability tree nodes and future covering sets of blocks.
|
||||
reachabilityDataBucketName = []byte("reachability")
|
||||
|
||||
// multisetBucketName is the name of the database bucket used to house the
|
||||
// ECMH multisets of blocks.
|
||||
multisetBucketName = []byte("multiset")
|
||||
|
||||
// subnetworksBucketName is the name of the database bucket used to store the
|
||||
// subnetwork registry.
|
||||
subnetworksBucketName = []byte("subnetworks")
|
||||
@@ -87,22 +92,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 +101,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 +150,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
|
||||
@@ -327,6 +269,11 @@ func (dag *BlockDAG) createDAGState() error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = meta.CreateBucket(multisetBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dbPutVersion(dbTx, utxoSetVersionKeyName,
|
||||
latestUTXOSetBucketVersion)
|
||||
if err != nil {
|
||||
@@ -382,6 +329,11 @@ func (dag *BlockDAG) removeDAGState() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = meta.DeleteBucket(multisetBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dbTx.Metadata().Delete(utxoSetVersionKeyName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -532,32 +484,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
|
||||
}
|
||||
|
||||
@@ -565,11 +499,19 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
}
|
||||
|
||||
// Initialize the reachability store
|
||||
log.Infof("Loading reachability data...")
|
||||
err = dag.reachabilityStore.init(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the multiset store
|
||||
log.Infof("Loading multiset data...")
|
||||
err = dag.multisetStore.init(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply the loaded utxoCollection to the virtual block.
|
||||
dag.virtual.utxoSet, err = newFullUTXOSetFromUTXOCollection(fullUTXOCollection)
|
||||
if err != nil {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -94,7 +94,8 @@ func TestDifficulty(t *testing.T) {
|
||||
addNode := func(parents blockSet, blockTime time.Time) *blockNode {
|
||||
bluestParent := parents.bluest()
|
||||
if blockTime == zeroTime {
|
||||
blockTime = time.Unix(bluestParent.timestamp+1, 0)
|
||||
blockTime = time.Unix(bluestParent.timestamp, 0)
|
||||
blockTime = blockTime.Add(params.TargetTimePerBlock)
|
||||
}
|
||||
block, err := PrepareBlockForTest(dag, parents.hashes(), nil)
|
||||
if err != nil {
|
||||
@@ -119,7 +120,8 @@ func TestDifficulty(t *testing.T) {
|
||||
for i := uint64(0); i < dag.difficultyAdjustmentWindowSize; i++ {
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if tip.bits != dag.genesis.bits {
|
||||
t.Fatalf("As long as the bluest parent's blue score is less then the difficulty adjustment window size, the difficulty should be the same as genesis'")
|
||||
t.Fatalf("As long as the bluest parent's blue score is less then the difficulty adjustment " +
|
||||
"window size, the difficulty should be the same as genesis'")
|
||||
}
|
||||
}
|
||||
for i := uint64(0); i < dag.difficultyAdjustmentWindowSize+100; i++ {
|
||||
@@ -140,7 +142,8 @@ func TestDifficulty(t *testing.T) {
|
||||
}
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if compareBits(tip.bits, nodeInThePast.bits) >= 0 {
|
||||
t.Fatalf("tip.bits should be smaller than nodeInThePast.bits because nodeInThePast increased the block rate, so the difficulty should increase as well")
|
||||
t.Fatalf("tip.bits should be smaller than nodeInThePast.bits because nodeInThePast increased the " +
|
||||
"block rate, so the difficulty should increase as well")
|
||||
}
|
||||
expectedBits := uint32(0x207f83df)
|
||||
if tip.bits != expectedBits {
|
||||
@@ -167,7 +170,9 @@ func TestDifficulty(t *testing.T) {
|
||||
sameBitsCount = 0
|
||||
}
|
||||
}
|
||||
slowNode := addNode(blockSetFromSlice(tip), time.Unix(tip.timestamp+2, 0))
|
||||
slowBlockTime := time.Unix(tip.timestamp, 0)
|
||||
slowBlockTime = slowBlockTime.Add(params.TargetTimePerBlock + time.Second)
|
||||
slowNode := addNode(blockSetFromSlice(tip), slowBlockTime)
|
||||
if slowNode.bits != tip.bits {
|
||||
t.Fatalf("The difficulty should only change when slowNode is in the past of a block bluest parent")
|
||||
}
|
||||
@@ -180,7 +185,8 @@ func TestDifficulty(t *testing.T) {
|
||||
}
|
||||
tip = addNode(blockSetFromSlice(tip), zeroTime)
|
||||
if compareBits(tip.bits, slowNode.bits) <= 0 {
|
||||
t.Fatalf("tip.bits should be smaller than slowNode.bits because slowNode decreased the block rate, so the difficulty should decrease as well")
|
||||
t.Fatalf("tip.bits should be smaller than slowNode.bits because slowNode decreased the block" +
|
||||
" rate, so the difficulty should decrease as well")
|
||||
}
|
||||
|
||||
splitNode := addNode(blockSetFromSlice(tip), zeroTime)
|
||||
@@ -197,7 +203,8 @@ func TestDifficulty(t *testing.T) {
|
||||
tipWithRedPast := addNode(blockSetFromSlice(redChainTip, blueTip), zeroTime)
|
||||
tipWithoutRedPast := addNode(blockSetFromSlice(blueTip), zeroTime)
|
||||
if tipWithoutRedPast.bits != tipWithRedPast.bits {
|
||||
t.Fatalf("tipWithoutRedPast.bits should be the same as tipWithRedPast.bits because red blocks shouldn't affect the difficulty")
|
||||
t.Fatalf("tipWithoutRedPast.bits should be the same as tipWithRedPast.bits because red blocks" +
|
||||
" shouldn't affect the difficulty")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,11 +267,19 @@ func TestChainedTransactions(t *testing.T) {
|
||||
}
|
||||
chainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{chainedTxIn}, []*wire.TxOut{chainedTxOut})
|
||||
|
||||
block2, err := mining.PrepareBlockForTest(dag, ¶ms, []*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{tx, chainedTx}, true)
|
||||
block2, err := mining.PrepareBlockForTest(dag, ¶ms, []*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{tx}, false)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
|
||||
// Manually add a chained transaction to block2
|
||||
block2.Transactions = append(block2.Transactions, chainedTx)
|
||||
block2UtilTxs := make([]*util.Tx, len(block2.Transactions))
|
||||
for i, tx := range block2.Transactions {
|
||||
block2UtilTxs[i] = util.NewTx(tx)
|
||||
}
|
||||
block2.Header.HashMerkleRoot = blockdag.BuildHashMerkleTreeStore(block2UtilTxs).Root()
|
||||
|
||||
//Checks that dag.ProcessBlock fails because we don't allow a transaction to spend another transaction from the same block
|
||||
isOrphan, isDelayed, err = dag.ProcessBlock(util.NewBlock(block2), blockdag.BFNoPoWCheck)
|
||||
if err == 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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
// Copyright (c) 2013-2014 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 (
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxAllowedOffsetSeconds is the maximum number of seconds in either
|
||||
// direction that local clock will be adjusted. When the median time
|
||||
// of the network is outside of this range, no offset will be applied.
|
||||
maxAllowedOffsetSecs = 70 * 60 // 1 hour 10 minutes
|
||||
|
||||
// similarTimeSecs is the number of seconds in either direction from the
|
||||
// local clock that is used to determine that it is likley wrong and
|
||||
// hence to show a warning.
|
||||
similarTimeSecs = 5 * 60 // 5 minutes
|
||||
)
|
||||
|
||||
var (
|
||||
// maxMedianTimeEntries is the maximum number of entries allowed in the
|
||||
// median time data. This is a variable as opposed to a constant so the
|
||||
// test code can modify it.
|
||||
maxMedianTimeEntries = 200
|
||||
)
|
||||
|
||||
// MedianTimeSource provides a mechanism to add several time samples which are
|
||||
// used to determine a median time which is then used as an offset to the local
|
||||
// clock.
|
||||
type MedianTimeSource interface {
|
||||
// AdjustedTime returns the current time adjusted by the median time
|
||||
// offset as calculated from the time samples added by AddTimeSample.
|
||||
AdjustedTime() time.Time
|
||||
|
||||
// AddTimeSample adds a time sample that is used when determining the
|
||||
// median time of the added samples.
|
||||
AddTimeSample(id string, timeVal time.Time)
|
||||
|
||||
// Offset returns the number of seconds to adjust the local clock based
|
||||
// upon the median of the time samples added by AddTimeData.
|
||||
Offset() time.Duration
|
||||
}
|
||||
|
||||
// int64Sorter implements sort.Interface to allow a slice of 64-bit integers to
|
||||
// be sorted.
|
||||
type int64Sorter []int64
|
||||
|
||||
// Len returns the number of 64-bit integers in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s int64Sorter) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap swaps the 64-bit integers at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s int64Sorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less returns whether the 64-bit integer with index i should sort before the
|
||||
// 64-bit integer with index j. It is part of the sort.Interface
|
||||
// implementation.
|
||||
func (s int64Sorter) Less(i, j int) bool {
|
||||
return s[i] < s[j]
|
||||
}
|
||||
|
||||
// medianTime provides an implementation of the MedianTimeSource interface.
|
||||
type medianTime struct {
|
||||
mtx sync.Mutex
|
||||
knownIDs map[string]struct{}
|
||||
offsets []int64
|
||||
offsetSecs int64
|
||||
invalidTimeChecked bool
|
||||
}
|
||||
|
||||
// Ensure the medianTime type implements the MedianTimeSource interface.
|
||||
var _ MedianTimeSource = (*medianTime)(nil)
|
||||
|
||||
// AdjustedTime returns the current time adjusted by the median time offset as
|
||||
// calculated from the time samples added by AddTimeSample.
|
||||
//
|
||||
// This function is safe for concurrent access and is part of the
|
||||
// MedianTimeSource interface implementation.
|
||||
func (m *medianTime) AdjustedTime() time.Time {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// Limit the adjusted time to 1 second precision.
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
return now.Add(time.Duration(m.offsetSecs) * time.Second)
|
||||
}
|
||||
|
||||
// AddTimeSample adds a time sample that is used when determining the median
|
||||
// time of the added samples.
|
||||
//
|
||||
// This function is safe for concurrent access and is part of the
|
||||
// MedianTimeSource interface implementation.
|
||||
func (m *medianTime) AddTimeSample(sourceID string, timeVal time.Time) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// Don't add time data from the same source.
|
||||
if _, exists := m.knownIDs[sourceID]; exists {
|
||||
return
|
||||
}
|
||||
m.knownIDs[sourceID] = struct{}{}
|
||||
|
||||
// Truncate the provided offset to seconds and append it to the slice
|
||||
// of offsets while respecting the maximum number of allowed entries by
|
||||
// replacing the oldest entry with the new entry once the maximum number
|
||||
// of entries is reached.
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
offsetSecs := int64(timeVal.Sub(now).Seconds())
|
||||
numOffsets := len(m.offsets)
|
||||
if numOffsets == maxMedianTimeEntries && maxMedianTimeEntries > 0 {
|
||||
m.offsets = m.offsets[1:]
|
||||
numOffsets--
|
||||
}
|
||||
m.offsets = append(m.offsets, offsetSecs)
|
||||
numOffsets++
|
||||
|
||||
// Sort the offsets so the median can be obtained as needed later.
|
||||
sortedOffsets := make([]int64, numOffsets)
|
||||
copy(sortedOffsets, m.offsets)
|
||||
sort.Sort(int64Sorter(sortedOffsets))
|
||||
|
||||
offsetDuration := time.Duration(offsetSecs) * time.Second
|
||||
log.Debugf("Added time sample of %s (total: %d)", offsetDuration,
|
||||
numOffsets)
|
||||
|
||||
// The median offset is only updated when there are enough offsets and
|
||||
// the number of offsets is odd so the middle value is the true median.
|
||||
// Thus, there is nothing to do when those conditions are not met.
|
||||
if numOffsets < 5 || numOffsets&0x01 != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// At this point the number of offsets in the list is odd, so the
|
||||
// middle value of the sorted offsets is the median.
|
||||
median := sortedOffsets[numOffsets/2]
|
||||
|
||||
// Set the new offset when the median offset is within the allowed
|
||||
// offset range.
|
||||
if math.Abs(float64(median)) < maxAllowedOffsetSecs {
|
||||
m.offsetSecs = median
|
||||
} else {
|
||||
// The median offset of all added time data is larger than the
|
||||
// maximum allowed offset, so don't use an offset. This
|
||||
// effectively limits how far the local clock can be skewed.
|
||||
m.offsetSecs = 0
|
||||
|
||||
if !m.invalidTimeChecked {
|
||||
m.invalidTimeChecked = true
|
||||
|
||||
// Find if any time samples have a time that is close
|
||||
// to the local time.
|
||||
var remoteHasCloseTime bool
|
||||
for _, offset := range sortedOffsets {
|
||||
if math.Abs(float64(offset)) < similarTimeSecs {
|
||||
remoteHasCloseTime = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Warn if none of the time samples are close.
|
||||
if !remoteHasCloseTime {
|
||||
log.Warnf("Please check your date and time " +
|
||||
"are correct! kaspad will not work " +
|
||||
"properly with an invalid time")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
medianDuration := time.Duration(m.offsetSecs) * time.Second
|
||||
log.Debugf("New time offset: %d", medianDuration)
|
||||
}
|
||||
|
||||
// Offset returns the number of seconds to adjust the local clock based upon the
|
||||
// median of the time samples added by AddTimeData.
|
||||
//
|
||||
// This function is safe for concurrent access and is part of the
|
||||
// MedianTimeSource interface implementation.
|
||||
func (m *medianTime) Offset() time.Duration {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
return time.Duration(m.offsetSecs) * time.Second
|
||||
}
|
||||
|
||||
// NewMedianTime returns a new instance of concurrency-safe implementation of
|
||||
// the MedianTimeSource interface. The returned implementation contains the
|
||||
// rules necessary for proper time handling in the DAG consensus rules and
|
||||
// expects the time samples to be added from the timestamp field of the version
|
||||
// message received from remote peers that successfully connect and negotiate.
|
||||
func NewMedianTime() MedianTimeSource {
|
||||
return &medianTime{
|
||||
knownIDs: make(map[string]struct{}),
|
||||
offsets: make([]int64, 0, maxMedianTimeEntries),
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// Copyright (c) 2013-2017 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 (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestMedianTime tests the medianTime implementation.
|
||||
func TestMedianTime(t *testing.T) {
|
||||
tests := []struct {
|
||||
in []int64
|
||||
wantOffset int64
|
||||
useDupID bool
|
||||
}{
|
||||
// Not enough samples must result in an offset of 0.
|
||||
{in: []int64{1}, wantOffset: 0},
|
||||
{in: []int64{1, 2}, wantOffset: 0},
|
||||
{in: []int64{1, 2, 3}, wantOffset: 0},
|
||||
{in: []int64{1, 2, 3, 4}, wantOffset: 0},
|
||||
|
||||
// Various number of entries. The expected offset is only
|
||||
// updated on odd number of elements.
|
||||
{in: []int64{-13, 57, -4, -23, -12}, wantOffset: -12},
|
||||
{in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: 39},
|
||||
{in: []int64{-62, -58, -30, -62, 51, -30, 15}, wantOffset: -30},
|
||||
{in: []int64{29, -47, 39, 54, 42, 41, 8, -33}, wantOffset: 39},
|
||||
{in: []int64{37, 54, 9, -21, -56, -36, 5, -11, -39}, wantOffset: -11},
|
||||
{in: []int64{57, -28, 25, -39, 9, 63, -16, 19, -60, 25}, wantOffset: 9},
|
||||
{in: []int64{-5, -4, -3, -2, -1}, wantOffset: -3, useDupID: true},
|
||||
|
||||
// The offset stops being updated once the max number of entries
|
||||
// has been reached.
|
||||
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52}, wantOffset: 17},
|
||||
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45}, wantOffset: 17},
|
||||
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45, 4}, wantOffset: 17},
|
||||
|
||||
// Offsets that are too far away from the local time should
|
||||
// be ignored.
|
||||
{in: []int64{-4201, 4202, -4203, 4204, -4205}, wantOffset: 0},
|
||||
|
||||
// Exercise the condition where the median offset is greater
|
||||
// than the max allowed adjustment, but there is at least one
|
||||
// sample that is close enough to the current time to avoid
|
||||
// triggering a warning about an invalid local clock.
|
||||
{in: []int64{4201, 4202, 4203, 4204, -299}, wantOffset: 0},
|
||||
}
|
||||
|
||||
// Modify the max number of allowed median time entries for these tests.
|
||||
maxMedianTimeEntries = 10
|
||||
defer func() { maxMedianTimeEntries = 200 }()
|
||||
|
||||
for i, test := range tests {
|
||||
filter := NewMedianTime()
|
||||
for j, offset := range test.in {
|
||||
id := strconv.Itoa(j)
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
tOffset := now.Add(time.Duration(offset) * time.Second)
|
||||
filter.AddTimeSample(id, tOffset)
|
||||
|
||||
// Ensure the duplicate IDs are ignored.
|
||||
if test.useDupID {
|
||||
// Modify the offsets to ensure the final median
|
||||
// would be different if the duplicate is added.
|
||||
tOffset = tOffset.Add(time.Duration(offset) *
|
||||
time.Second)
|
||||
filter.AddTimeSample(id, tOffset)
|
||||
}
|
||||
}
|
||||
|
||||
// Since it is possible that the time.Now call in AddTimeSample
|
||||
// and the time.Now calls here in the tests will be off by one
|
||||
// second, allow a fudge factor to compensate.
|
||||
gotOffset := filter.Offset()
|
||||
wantOffset := time.Duration(test.wantOffset) * time.Second
|
||||
wantOffset2 := time.Duration(test.wantOffset-1) * time.Second
|
||||
if gotOffset != wantOffset && gotOffset != wantOffset2 {
|
||||
t.Errorf("Offset #%d: unexpected offset -- got %v, "+
|
||||
"want %v or %v", i, gotOffset, wantOffset,
|
||||
wantOffset2)
|
||||
continue
|
||||
}
|
||||
|
||||
// Since it is possible that the time.Now call in AdjustedTime
|
||||
// and the time.Now call here in the tests will be off by one
|
||||
// second, allow a fudge factor to compensate.
|
||||
adjustedTime := filter.AdjustedTime()
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
wantTime := now.Add(filter.Offset())
|
||||
wantTime2 := now.Add(filter.Offset() - time.Second)
|
||||
if !adjustedTime.Equal(wantTime) && !adjustedTime.Equal(wantTime2) {
|
||||
t.Errorf("AdjustedTime #%d: unexpected result -- got %v, "+
|
||||
"want %v or %v", i, adjustedTime, wantTime,
|
||||
wantTime2)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"time"
|
||||
)
|
||||
@@ -12,6 +14,8 @@ import (
|
||||
// BlockForMining returns a block with the given transactions
|
||||
// that points to the current DAG tips, that is valid from
|
||||
// all aspects except proof of work.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, error) {
|
||||
blockTimestamp := dag.NextBlockTime()
|
||||
requiredDifficulty := dag.NextRequiredDifficulty(blockTimestamp)
|
||||
@@ -34,18 +38,17 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, er
|
||||
msgBlock.AddTransaction(tx.MsgTx())
|
||||
}
|
||||
|
||||
utxoWithTransactions, err := dag.UTXOSet().WithTransactions(msgBlock.Transactions, UnacceptedBlueScore, false)
|
||||
multiset, err := dag.NextBlockMultiset(transactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
utxoCommitment := utxoWithTransactions.Multiset().Hash()
|
||||
|
||||
msgBlock.Header = wire.BlockHeader{
|
||||
Version: nextBlockVersion,
|
||||
ParentHashes: dag.TipHashes(),
|
||||
HashMerkleRoot: hashMerkleTree.Root(),
|
||||
AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
|
||||
UTXOCommitment: utxoCommitment,
|
||||
UTXOCommitment: (*daghash.Hash)(multiset.Finalize()),
|
||||
Timestamp: blockTimestamp,
|
||||
Bits: requiredDifficulty,
|
||||
}
|
||||
@@ -53,6 +56,19 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, er
|
||||
return &msgBlock, nil
|
||||
}
|
||||
|
||||
// NextBlockMultiset returns the multiset of an assumed next block
|
||||
// built on top of the current tips, with the given transactions.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) NextBlockMultiset(transactions []*util.Tx) (*secp256k1.MultiSet, error) {
|
||||
pastUTXO, selectedParentUTXO, txsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dag.virtual.blockNode.calcMultiset(dag, transactions, txsAcceptanceData, selectedParentUTXO, pastUTXO)
|
||||
}
|
||||
|
||||
// CoinbasePayloadExtraData returns coinbase payload extra data parameter
|
||||
// which is built from extra nonce and coinbase flags.
|
||||
func CoinbasePayloadExtraData(extraNonce uint64, coinbaseFlags string) ([]byte, error) {
|
||||
@@ -101,7 +117,7 @@ func (dag *BlockDAG) NextBlockTime() time.Time {
|
||||
// timestamp is truncated to a second boundary before comparison since a
|
||||
// block timestamp does not supported a precision greater than one
|
||||
// second.
|
||||
newTimestamp := dag.AdjustedTime()
|
||||
newTimestamp := dag.Now()
|
||||
minTimestamp := dag.NextBlockMinimumTime()
|
||||
if newTimestamp.Before(minTimestamp) {
|
||||
newTimestamp = minTimestamp
|
||||
|
||||
29
blockdag/multisetio.go
Normal file
29
blockdag/multisetio.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"io"
|
||||
)
|
||||
|
||||
const multisetPointSize = 32
|
||||
|
||||
// serializeMultiset serializes an ECMH multiset.
|
||||
func serializeMultiset(w io.Writer, ms *secp256k1.MultiSet) error {
|
||||
serialized := ms.Serialize()
|
||||
err := binary.Write(w, byteOrder, serialized)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deserializeMultiset deserializes an EMCH multiset.
|
||||
func deserializeMultiset(r io.Reader) (*secp256k1.MultiSet, error) {
|
||||
serialized := &secp256k1.SerializedMultiSet{}
|
||||
err := binary.Read(r, byteOrder, serialized[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secp256k1.DeserializeMultiSet(serialized)
|
||||
}
|
||||
112
blockdag/multisetstore.go
Normal file
112
blockdag/multisetstore.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/locks"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type multisetStore struct {
|
||||
dag *BlockDAG
|
||||
new map[daghash.Hash]struct{}
|
||||
loaded map[daghash.Hash]secp256k1.MultiSet
|
||||
mtx *locks.PriorityMutex
|
||||
}
|
||||
|
||||
func newMultisetStore(dag *BlockDAG) *multisetStore {
|
||||
return &multisetStore{
|
||||
dag: dag,
|
||||
new: make(map[daghash.Hash]struct{}),
|
||||
loaded: make(map[daghash.Hash]secp256k1.MultiSet),
|
||||
}
|
||||
}
|
||||
|
||||
func (store *multisetStore) setMultiset(node *blockNode, ms *secp256k1.MultiSet) {
|
||||
store.loaded[*node.hash] = *ms
|
||||
store.addToNewBlocks(node.hash)
|
||||
}
|
||||
|
||||
func (store *multisetStore) addToNewBlocks(blockHash *daghash.Hash) {
|
||||
store.new[*blockHash] = struct{}{}
|
||||
}
|
||||
|
||||
func multisetNotFoundError(blockHash *daghash.Hash) error {
|
||||
return errors.Errorf("Couldn't find multiset data for block %s", blockHash)
|
||||
}
|
||||
|
||||
func (store *multisetStore) multisetByBlockNode(node *blockNode) (*secp256k1.MultiSet, error) {
|
||||
ms, exists := store.multisetByBlockHash(node.hash)
|
||||
if !exists {
|
||||
return nil, multisetNotFoundError(node.hash)
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func (store *multisetStore) multisetByBlockHash(hash *daghash.Hash) (*secp256k1.MultiSet, bool) {
|
||||
ms, ok := store.loaded[*hash]
|
||||
return &ms, ok
|
||||
}
|
||||
|
||||
// flushToDB writes all new multiset data to the database.
|
||||
func (store *multisetStore) flushToDB(dbTx database.Tx) error {
|
||||
if len(store.new) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := &bytes.Buffer{}
|
||||
for hash := range store.new {
|
||||
hash := hash // Copy hash to a new variable to avoid passing the same pointer
|
||||
|
||||
w.Reset()
|
||||
ms, exists := store.loaded[hash]
|
||||
if !exists {
|
||||
return multisetNotFoundError(&hash)
|
||||
}
|
||||
|
||||
err := serializeMultiset(w, &ms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.dbStoreMultiset(dbTx, &hash, w.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *multisetStore) clearNewEntries() {
|
||||
store.new = make(map[daghash.Hash]struct{})
|
||||
}
|
||||
|
||||
func (store *multisetStore) init(dbTx database.Tx) error {
|
||||
bucket := dbTx.Metadata().Bucket(multisetBucketName)
|
||||
cursor := bucket.Cursor()
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() {
|
||||
hash, err := daghash.NewHash(cursor.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ms, err := deserializeMultiset(bytes.NewReader(cursor.Value()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
store.loaded[*hash] = *ms
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dbStoreMultiset stores the multiset data to the database.
|
||||
func (store *multisetStore) dbStoreMultiset(dbTx database.Tx, blockHash *daghash.Hash, serializedMS []byte) error {
|
||||
bucket := dbTx.Metadata().Bucket(multisetBucketName)
|
||||
if bucket.Get(blockHash[:]) != nil {
|
||||
return errors.Errorf("Can't override an existing multiset database entry for block %s", blockHash)
|
||||
}
|
||||
return bucket.Put(blockHash[:], serializedMS)
|
||||
}
|
||||
@@ -264,7 +264,7 @@ func (dag *BlockDAG) maxDelayOfParents(parentHashes []*daghash.Hash) (delay time
|
||||
for _, parentHash := range parentHashes {
|
||||
if delayedParent, exists := dag.delayedBlocks[*parentHash]; exists {
|
||||
isDelayed = true
|
||||
parentDelay := delayedParent.processTime.Sub(dag.AdjustedTime())
|
||||
parentDelay := delayedParent.processTime.Sub(dag.Now())
|
||||
if parentDelay > delay {
|
||||
delay = parentDelay
|
||||
}
|
||||
|
||||
@@ -72,21 +72,6 @@ func TestProcessOrphans(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type fakeTimeSource struct {
|
||||
time time.Time
|
||||
}
|
||||
|
||||
func (fts *fakeTimeSource) AdjustedTime() time.Time {
|
||||
return fts.time
|
||||
}
|
||||
|
||||
func (fts *fakeTimeSource) AddTimeSample(_ string, _ time.Time) {
|
||||
}
|
||||
|
||||
func (fts *fakeTimeSource) Offset() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestProcessDelayedBlocks(t *testing.T) {
|
||||
// We use dag1 so we can build the test blocks with the proper
|
||||
// block header (UTXO commitment, acceptedIDMerkleroot, etc), and
|
||||
@@ -103,14 +88,14 @@ func TestProcessDelayedBlocks(t *testing.T) {
|
||||
// Here we use a fake time source that returns a timestamp
|
||||
// one hour into the future to make delayedBlock artificially
|
||||
// valid.
|
||||
dag1.timeSource = &fakeTimeSource{initialTime.Add(time.Hour)}
|
||||
dag1.timeSource = newFakeTimeSource(initialTime.Add(time.Hour))
|
||||
|
||||
delayedBlock, err := PrepareBlockForTest(dag1, []*daghash.Hash{dag1.dagParams.GenesisBlock.BlockHash()}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
||||
}
|
||||
|
||||
blockDelay := time.Duration(dag1.dagParams.TimestampDeviationTolerance+5) * time.Second
|
||||
blockDelay := time.Duration(dag1.dagParams.TimestampDeviationTolerance*uint64(dag1.targetTimePerBlock)+5) * time.Second
|
||||
delayedBlock.Header.Timestamp = initialTime.Add(blockDelay)
|
||||
|
||||
isOrphan, isDelayed, err := dag1.ProcessBlock(util.NewBlock(delayedBlock), BFNoPoWCheck)
|
||||
@@ -142,7 +127,7 @@ func TestProcessDelayedBlocks(t *testing.T) {
|
||||
t.Fatalf("Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc2()
|
||||
dag2.timeSource = &fakeTimeSource{initialTime}
|
||||
dag2.timeSource = newFakeTimeSource(initialTime)
|
||||
|
||||
isOrphan, isDelayed, err = dag2.ProcessBlock(util.NewBlock(delayedBlock), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
@@ -209,10 +194,13 @@ func TestProcessDelayedBlocks(t *testing.T) {
|
||||
}
|
||||
|
||||
// We advance the clock to the point where delayedBlock timestamp is valid.
|
||||
secondsUntilDelayedBlockIsValid := delayedBlock.Header.Timestamp.Unix() - int64(dag2.TimestampDeviationTolerance) - dag2.AdjustedTime().Unix() + 1
|
||||
dag2.timeSource = &fakeTimeSource{initialTime.Add(time.Duration(secondsUntilDelayedBlockIsValid) * time.Second)}
|
||||
deviationTolerance := int64(dag2.TimestampDeviationTolerance) * dag2.targetTimePerBlock
|
||||
secondsUntilDelayedBlockIsValid := delayedBlock.Header.Timestamp.Unix() - deviationTolerance - dag2.Now().Unix() + 1
|
||||
dag2.timeSource = newFakeTimeSource(initialTime.Add(time.Duration(secondsUntilDelayedBlockIsValid) * time.Second))
|
||||
|
||||
blockAfterDelay, err := PrepareBlockForTest(dag2, []*daghash.Hash{dag2.dagParams.GenesisBlock.BlockHash()}, nil)
|
||||
blockAfterDelay, err := PrepareBlockForTest(dag2,
|
||||
[]*daghash.Hash{dag2.dagParams.GenesisBlock.BlockHash()},
|
||||
nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func DAGSetup(dbName string, config Config) (*BlockDAG, func(), error) {
|
||||
}
|
||||
}
|
||||
|
||||
config.TimeSource = NewMedianTime()
|
||||
config.TimeSource = NewTimeSource()
|
||||
config.SigCache = txscript.NewSigCache(1000)
|
||||
|
||||
// Create the DAG instance.
|
||||
@@ -173,7 +173,7 @@ func GetVirtualFromParentsForTest(dag *BlockDAG, parentHashes []*daghash.Hash) (
|
||||
}
|
||||
virtual := newVirtualBlock(dag, parents)
|
||||
|
||||
pastUTXO, _, err := dag.pastUTXO(&virtual.blockNode)
|
||||
pastUTXO, _, _, err := dag.pastUTXO(&virtual.blockNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.
25
blockdag/timesource.go
Normal file
25
blockdag/timesource.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeSource is the interface to access time.
|
||||
type TimeSource interface {
|
||||
// Now returns the current time.
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// timeSource provides an implementation of the TimeSource interface
|
||||
// that simply returns the current local time.
|
||||
type timeSource struct{}
|
||||
|
||||
// Now returns the current local time, with one second precision.
|
||||
func (m *timeSource) Now() time.Time {
|
||||
return time.Unix(time.Now().Unix(), 0)
|
||||
}
|
||||
|
||||
// NewTimeSource returns a new instance of a TimeSource
|
||||
func NewTimeSource() TimeSource {
|
||||
return &timeSource{}
|
||||
}
|
||||
@@ -2,31 +2,26 @@ package blockdag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/golang/groupcache/lru"
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
const ecmhCacheSize = 4_000_000
|
||||
|
||||
var (
|
||||
utxoToECMHCache = lru.New(ecmhCacheSize)
|
||||
)
|
||||
|
||||
func utxoMultiset(entry *UTXOEntry, outpoint *wire.Outpoint) (*ecc.Multiset, error) {
|
||||
func addUTXOToMultiset(ms *secp256k1.MultiSet, entry *UTXOEntry, outpoint *wire.Outpoint) (*secp256k1.MultiSet, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := serializeUTXO(w, entry, outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serializedUTXO := w.Bytes()
|
||||
utxoHash := daghash.DoubleHashH(serializedUTXO)
|
||||
|
||||
if cachedMSPoint, ok := utxoToECMHCache.Get(utxoHash); ok {
|
||||
return cachedMSPoint.(*ecc.Multiset), nil
|
||||
}
|
||||
msPoint := ecc.NewMultiset(ecc.S256()).Add(serializedUTXO)
|
||||
utxoToECMHCache.Add(utxoHash, msPoint)
|
||||
return msPoint, nil
|
||||
ms.Add(w.Bytes())
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func removeUTXOFromMultiset(ms *secp256k1.MultiSet, entry *UTXOEntry, outpoint *wire.Outpoint) (*secp256k1.MultiSet, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := serializeUTXO(w, entry, outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms.Remove(w.Bytes())
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var multisetPointSize = 32
|
||||
|
||||
type blockUTXODiffData struct {
|
||||
diff *UTXODiff
|
||||
diffChild *blockNode
|
||||
|
||||
@@ -2,15 +2,11 @@ package blockdag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// serializeBlockUTXODiffData serializes diff data in the following format:
|
||||
@@ -54,40 +50,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 +77,31 @@ func (diffStore *utxoDiffStore) deserializeBlockUTXODiffData(serializedDiffDataB
|
||||
return diffData, nil
|
||||
}
|
||||
|
||||
func deserializeDiffEntries(r io.Reader) (utxoCollection, error) {
|
||||
func deserializeUTXODiff(r io.Reader) (*UTXODiff, error) {
|
||||
diff := &UTXODiff{}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,31 +110,22 @@ func deserializeDiffEntries(r io.Reader) (utxoCollection, error) {
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
// deserializeMultiset deserializes an EMCH multiset.
|
||||
// See serializeMultiset for more details.
|
||||
func deserializeMultiset(r io.Reader) (*ecc.Multiset, error) {
|
||||
xBytes := make([]byte, multisetPointSize)
|
||||
yBytes := make([]byte, multisetPointSize)
|
||||
err := binary.Read(r, byteOrder, xBytes)
|
||||
func deserializeUTXO(r io.Reader) (*UTXOEntry, *wire.Outpoint, error) {
|
||||
outpoint, err := deserializeOutpoint(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
err = binary.Read(r, byteOrder, yBytes)
|
||||
|
||||
utxoEntry, err := deserializeUTXOEntry(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
var x, y big.Int
|
||||
x.SetBytes(xBytes)
|
||||
y.SetBytes(yBytes)
|
||||
return ecc.NewMultisetFromPoint(ecc.S256(), &x, &y), nil
|
||||
return utxoEntry, outpoint, nil
|
||||
}
|
||||
|
||||
// serializeUTXODiff serializes UTXODiff by serializing
|
||||
// UTXODiff.toAdd, UTXODiff.toRemove and UTXODiff.Multiset one after the other.
|
||||
func serializeUTXODiff(w io.Writer, diff *UTXODiff) error {
|
||||
if !diff.useMultiset {
|
||||
return errors.New("Cannot serialize a UTXO diff without a multiset")
|
||||
}
|
||||
err := serializeUTXOCollection(w, diff.toAdd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -169,10 +135,7 @@ func serializeUTXODiff(w io.Writer, diff *UTXODiff) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = serializeMultiset(w, diff.diffMultiset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -193,121 +156,95 @@ func serializeUTXOCollection(w io.Writer, collection utxoCollection) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// serializeMultiset serializes an ECMH multiset. The serialization
|
||||
// is done by taking the (x,y) coordinnates of the multiset point and
|
||||
// padding each one of them with 32 byte (it'll be 32 byte in most
|
||||
// cases anyway except one of the coordinates is zero) and writing
|
||||
// them one after the other.
|
||||
func serializeMultiset(w io.Writer, ms *ecc.Multiset) error {
|
||||
x, y := ms.Point()
|
||||
xBytes := make([]byte, multisetPointSize)
|
||||
copy(xBytes, x.Bytes())
|
||||
yBytes := make([]byte, multisetPointSize)
|
||||
copy(yBytes, y.Bytes())
|
||||
|
||||
err := binary.Write(w, byteOrder, xBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(w, byteOrder, yBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
@@ -153,29 +153,16 @@ func (uc utxoCollection) clone() utxoCollection {
|
||||
|
||||
// UTXODiff represents a diff between two UTXO Sets.
|
||||
type UTXODiff struct {
|
||||
toAdd utxoCollection
|
||||
toRemove utxoCollection
|
||||
diffMultiset *ecc.Multiset
|
||||
useMultiset bool
|
||||
toAdd utxoCollection
|
||||
toRemove utxoCollection
|
||||
}
|
||||
|
||||
// NewUTXODiffWithoutMultiset creates a new, empty utxoDiff
|
||||
// NewUTXODiff creates a new, empty utxoDiff
|
||||
// without a multiset.
|
||||
func NewUTXODiffWithoutMultiset() *UTXODiff {
|
||||
return &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
useMultiset: false,
|
||||
}
|
||||
}
|
||||
|
||||
// NewUTXODiff creates a new, empty utxoDiff.
|
||||
func NewUTXODiff() *UTXODiff {
|
||||
return &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
useMultiset: true,
|
||||
diffMultiset: ecc.NewMultiset(ecc.S256()),
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,9 +196,8 @@ func NewUTXODiff() *UTXODiff {
|
||||
// diffFrom results in the UTXO being added to toAdd
|
||||
func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) {
|
||||
result := UTXODiff{
|
||||
toAdd: make(utxoCollection, len(d.toRemove)+len(other.toAdd)),
|
||||
toRemove: make(utxoCollection, len(d.toAdd)+len(other.toRemove)),
|
||||
useMultiset: d.useMultiset,
|
||||
toAdd: make(utxoCollection, len(d.toRemove)+len(other.toAdd)),
|
||||
toRemove: make(utxoCollection, len(d.toAdd)+len(other.toRemove)),
|
||||
}
|
||||
|
||||
// Note that the following cases are not accounted for, as they are impossible
|
||||
@@ -293,17 +279,12 @@ func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if d.useMultiset {
|
||||
// Create a new diffMultiset as the subtraction of the two diffs.
|
||||
result.diffMultiset = other.diffMultiset.Subtract(d.diffMultiset)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// WithDiffInPlace applies provided diff to this diff in-place, that would be the result if
|
||||
// withDiffInPlace applies provided diff to this diff in-place, that would be the result if
|
||||
// first d, and than diff were applied to the same base
|
||||
func (d *UTXODiff) WithDiffInPlace(diff *UTXODiff) error {
|
||||
func (d *UTXODiff) withDiffInPlace(diff *UTXODiff) error {
|
||||
for outpoint, entryToRemove := range diff.toRemove {
|
||||
if d.toAdd.containsWithBlueScore(outpoint, entryToRemove.blockBlueScore) {
|
||||
// If already exists in toAdd with the same blueScore - remove from toAdd
|
||||
@@ -313,7 +294,7 @@ func (d *UTXODiff) WithDiffInPlace(diff *UTXODiff) error {
|
||||
if d.toRemove.contains(outpoint) {
|
||||
// If already exists - this is an error
|
||||
return ruleError(ErrWithDiff, fmt.Sprintf(
|
||||
"WithDiffInPlace: outpoint %s both in d.toRemove and in diff.toRemove", outpoint))
|
||||
"withDiffInPlace: outpoint %s both in d.toRemove and in diff.toRemove", outpoint))
|
||||
}
|
||||
|
||||
// If not exists neither in toAdd nor in toRemove - add to toRemove
|
||||
@@ -325,7 +306,7 @@ func (d *UTXODiff) WithDiffInPlace(diff *UTXODiff) error {
|
||||
// If already exists in toRemove with the same blueScore - remove from toRemove
|
||||
if d.toAdd.contains(outpoint) && !diff.toRemove.contains(outpoint) {
|
||||
return ruleError(ErrWithDiff, fmt.Sprintf(
|
||||
"WithDiffInPlace: outpoint %s both in d.toAdd and in diff.toAdd with no "+
|
||||
"withDiffInPlace: outpoint %s both in d.toAdd and in diff.toAdd with no "+
|
||||
"corresponding entry in diff.toRemove", outpoint))
|
||||
}
|
||||
d.toRemove.remove(outpoint)
|
||||
@@ -336,129 +317,34 @@ func (d *UTXODiff) WithDiffInPlace(diff *UTXODiff) error {
|
||||
!diff.toRemove.containsWithBlueScore(outpoint, existingEntry.blockBlueScore)) {
|
||||
// If already exists - this is an error
|
||||
return ruleError(ErrWithDiff, fmt.Sprintf(
|
||||
"WithDiffInPlace: outpoint %s both in d.toAdd and in diff.toAdd", outpoint))
|
||||
"withDiffInPlace: outpoint %s both in d.toAdd and in diff.toAdd", outpoint))
|
||||
}
|
||||
|
||||
// If not exists neither in toAdd nor in toRemove, or exists in toRemove with different blueScore - add to toAdd
|
||||
d.toAdd.add(outpoint, entryToAdd)
|
||||
}
|
||||
|
||||
// Apply diff.diffMultiset to d.diffMultiset
|
||||
if d.useMultiset {
|
||||
d.diffMultiset = d.diffMultiset.Union(diff.diffMultiset)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithDiff applies provided diff to this diff, creating a new utxoDiff, that would be the result if
|
||||
// first d, and than diff were applied to the same base
|
||||
//
|
||||
// WithDiff follows a set of rules represented by the following 3 by 3 table:
|
||||
//
|
||||
// | | this | |
|
||||
// ---------+-----------+-----------+-----------+-----------
|
||||
// | | toAdd | toRemove | None
|
||||
// ---------+-----------+-----------+-----------+-----------
|
||||
// other | toAdd | X | - | toAdd
|
||||
// ---------+-----------+-----------+-----------+-----------
|
||||
// | toRemove | - | X | toRemove
|
||||
// ---------+-----------+-----------+-----------+-----------
|
||||
// | None | toAdd | toRemove | -
|
||||
//
|
||||
// Key:
|
||||
// - Don't add anything to the result
|
||||
// X Return an error
|
||||
// toAdd Add the UTXO into the toAdd collection of the result
|
||||
// toRemove Add the UTXO into the toRemove collection of the result
|
||||
//
|
||||
// Examples:
|
||||
// 1. This diff contains a UTXO in toAdd, and the other diff contains it in toRemove
|
||||
// WithDiff results in nothing being added
|
||||
// 2. This diff contains a UTXO in toRemove, and the other diff does not contain it
|
||||
// WithDiff results in the UTXO being added to toRemove
|
||||
// first d, and than diff were applied to some base
|
||||
func (d *UTXODiff) WithDiff(diff *UTXODiff) (*UTXODiff, error) {
|
||||
result := UTXODiff{
|
||||
toAdd: make(utxoCollection, len(d.toAdd)+len(diff.toAdd)),
|
||||
toRemove: make(utxoCollection, len(d.toRemove)+len(diff.toRemove)),
|
||||
useMultiset: d.useMultiset,
|
||||
clone := d.clone()
|
||||
|
||||
err := clone.withDiffInPlace(diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// All transactions in d.toAdd:
|
||||
// If they are not in diff.toRemove - should be added in result.toAdd
|
||||
// If they are in diff.toAdd - should throw an error
|
||||
// Otherwise - should be ignored
|
||||
for outpoint, utxoEntry := range d.toAdd {
|
||||
if !diff.toRemove.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore) {
|
||||
result.toAdd.add(outpoint, utxoEntry)
|
||||
}
|
||||
if diffEntry, ok := diff.toAdd.get(outpoint); ok {
|
||||
// An exception is made for entries with unequal blue scores
|
||||
// as long as the appropriate entry exists in either d.toRemove
|
||||
// or diff.toRemove.
|
||||
// These are just "updates" to accepted blue score
|
||||
if diffEntry.blockBlueScore != utxoEntry.blockBlueScore &&
|
||||
diff.toRemove.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore) {
|
||||
continue
|
||||
}
|
||||
return nil, ruleError(ErrWithDiff, fmt.Sprintf("WithDiff: outpoint %s both in d.toAdd and in other.toAdd", outpoint))
|
||||
}
|
||||
}
|
||||
|
||||
// All transactions in d.toRemove:
|
||||
// If they are not in diff.toAdd - should be added in result.toRemove
|
||||
// If they are in diff.toRemove - should throw an error
|
||||
// Otherwise - should be ignored
|
||||
for outpoint, utxoEntry := range d.toRemove {
|
||||
if !diff.toAdd.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore) {
|
||||
result.toRemove.add(outpoint, utxoEntry)
|
||||
}
|
||||
if diffEntry, ok := diff.toRemove.get(outpoint); ok {
|
||||
// An exception is made for entries with unequal blue scores
|
||||
// as long as the appropriate entry exists in either d.toAdd
|
||||
// or diff.toAdd.
|
||||
// These are just "updates" to accepted blue score
|
||||
if diffEntry.blockBlueScore != utxoEntry.blockBlueScore &&
|
||||
d.toAdd.containsWithBlueScore(outpoint, diffEntry.blockBlueScore) {
|
||||
continue
|
||||
}
|
||||
return nil, ruleError(ErrWithDiff, "WithDiff: outpoint both in d.toRemove and in other.toRemove")
|
||||
}
|
||||
}
|
||||
|
||||
// All transactions in diff.toAdd:
|
||||
// If they are not in d.toRemove - should be added in result.toAdd
|
||||
for outpoint, utxoEntry := range diff.toAdd {
|
||||
if !d.toRemove.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore) {
|
||||
result.toAdd.add(outpoint, utxoEntry)
|
||||
}
|
||||
}
|
||||
|
||||
// All transactions in diff.toRemove:
|
||||
// If they are not in d.toAdd - should be added in result.toRemove
|
||||
for outpoint, utxoEntry := range diff.toRemove {
|
||||
if !d.toAdd.containsWithBlueScore(outpoint, utxoEntry.blockBlueScore) {
|
||||
result.toRemove.add(outpoint, utxoEntry)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply diff.diffMultiset to d.diffMultiset
|
||||
if d.useMultiset {
|
||||
result.diffMultiset = d.diffMultiset.Union(diff.diffMultiset)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
return clone, nil
|
||||
}
|
||||
|
||||
// clone returns a clone of this utxoDiff
|
||||
func (d *UTXODiff) clone() *UTXODiff {
|
||||
clone := &UTXODiff{
|
||||
toAdd: d.toAdd.clone(),
|
||||
toRemove: d.toRemove.clone(),
|
||||
useMultiset: d.useMultiset,
|
||||
}
|
||||
if d.useMultiset {
|
||||
clone.diffMultiset = d.diffMultiset.Clone()
|
||||
toAdd: d.toAdd.clone(),
|
||||
toRemove: d.toRemove.clone(),
|
||||
}
|
||||
return clone
|
||||
}
|
||||
@@ -475,14 +361,6 @@ func (d *UTXODiff) AddEntry(outpoint wire.Outpoint, entry *UTXOEntry) error {
|
||||
} else {
|
||||
d.toAdd.add(outpoint, entry)
|
||||
}
|
||||
|
||||
if d.useMultiset {
|
||||
newMs, err := addUTXOToMultiset(d.diffMultiset, entry, &outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.diffMultiset = newMs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -498,21 +376,10 @@ func (d *UTXODiff) RemoveEntry(outpoint wire.Outpoint, entry *UTXOEntry) error {
|
||||
} else {
|
||||
d.toRemove.add(outpoint, entry)
|
||||
}
|
||||
|
||||
if d.useMultiset {
|
||||
newMs, err := removeUTXOFromMultiset(d.diffMultiset, entry, &outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.diffMultiset = newMs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d UTXODiff) String() string {
|
||||
if d.useMultiset {
|
||||
return fmt.Sprintf("toAdd: %s; toRemove: %s, Multiset-Hash: %s", d.toAdd, d.toRemove, d.diffMultiset.Hash())
|
||||
}
|
||||
return fmt.Sprintf("toAdd: %s; toRemove: %s", d.toAdd, d.toRemove)
|
||||
}
|
||||
|
||||
@@ -537,8 +404,6 @@ type UTXOSet interface {
|
||||
AddTx(tx *wire.MsgTx, blockBlueScore uint64) (ok bool, err error)
|
||||
clone() UTXOSet
|
||||
Get(outpoint wire.Outpoint) (*UTXOEntry, bool)
|
||||
Multiset() *ecc.Multiset
|
||||
WithTransactions(transactions []*wire.MsgTx, blockBlueScore uint64, ignoreDoubleSpends bool) (UTXOSet, error)
|
||||
}
|
||||
|
||||
// diffFromTx is a common implementation for diffFromTx, that works
|
||||
@@ -608,21 +473,19 @@ func diffFromAcceptedTx(u UTXOSet, tx *wire.MsgTx, acceptingBlueScore uint64) (*
|
||||
// FullUTXOSet represents a full list of transaction outputs and their values
|
||||
type FullUTXOSet struct {
|
||||
utxoCollection
|
||||
UTXOMultiset *ecc.Multiset
|
||||
}
|
||||
|
||||
// NewFullUTXOSet creates a new utxoSet with full list of transaction outputs and their values
|
||||
func NewFullUTXOSet() *FullUTXOSet {
|
||||
return &FullUTXOSet{
|
||||
utxoCollection: utxoCollection{},
|
||||
UTXOMultiset: ecc.NewMultiset(ecc.S256()),
|
||||
}
|
||||
}
|
||||
|
||||
// newFullUTXOSetFromUTXOCollection converts a utxoCollection to a FullUTXOSet
|
||||
func newFullUTXOSetFromUTXOCollection(collection utxoCollection) (*FullUTXOSet, error) {
|
||||
var err error
|
||||
multiset := ecc.NewMultiset(ecc.S256())
|
||||
multiset := secp256k1.NewMultiset()
|
||||
for outpoint, utxoEntry := range collection {
|
||||
multiset, err = addUTXOToMultiset(multiset, utxoEntry, &outpoint)
|
||||
if err != nil {
|
||||
@@ -631,7 +494,6 @@ func newFullUTXOSetFromUTXOCollection(collection utxoCollection) (*FullUTXOSet,
|
||||
}
|
||||
return &FullUTXOSet{
|
||||
utxoCollection: collection,
|
||||
UTXOMultiset: multiset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -668,22 +530,14 @@ func (fus *FullUTXOSet) AddTx(tx *wire.MsgTx, blueScore uint64) (isAccepted bool
|
||||
}
|
||||
|
||||
for _, txIn := range tx.TxIn {
|
||||
outpoint := *wire.NewOutpoint(&txIn.PreviousOutpoint.TxID, txIn.PreviousOutpoint.Index)
|
||||
err := fus.removeAndUpdateMultiset(outpoint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
fus.remove(txIn.PreviousOutpoint)
|
||||
}
|
||||
}
|
||||
|
||||
for i, txOut := range tx.TxOut {
|
||||
outpoint := *wire.NewOutpoint(tx.TxID(), uint32(i))
|
||||
entry := NewUTXOEntry(txOut, isCoinbase, blueScore)
|
||||
|
||||
err := fus.addAndUpdateMultiset(outpoint, entry)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
fus.add(outpoint, entry)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -712,7 +566,7 @@ func (fus *FullUTXOSet) diffFromAcceptedTx(tx *wire.MsgTx, acceptingBlueScore ui
|
||||
|
||||
// clone returns a clone of this utxoSet
|
||||
func (fus *FullUTXOSet) clone() UTXOSet {
|
||||
return &FullUTXOSet{utxoCollection: fus.utxoCollection.clone(), UTXOMultiset: fus.UTXOMultiset.Clone()}
|
||||
return &FullUTXOSet{utxoCollection: fus.utxoCollection.clone()}
|
||||
}
|
||||
|
||||
// Get returns the UTXOEntry associated with the given Outpoint, and a boolean indicating if such entry was found
|
||||
@@ -721,55 +575,6 @@ func (fus *FullUTXOSet) Get(outpoint wire.Outpoint) (*UTXOEntry, bool) {
|
||||
return utxoEntry, ok
|
||||
}
|
||||
|
||||
// Multiset returns the ecmh-Multiset of this utxoSet
|
||||
func (fus *FullUTXOSet) Multiset() *ecc.Multiset {
|
||||
return fus.UTXOMultiset
|
||||
}
|
||||
|
||||
// addAndUpdateMultiset adds a UTXOEntry to this utxoSet and updates its multiset accordingly
|
||||
func (fus *FullUTXOSet) addAndUpdateMultiset(outpoint wire.Outpoint, entry *UTXOEntry) error {
|
||||
fus.add(outpoint, entry)
|
||||
newMs, err := addUTXOToMultiset(fus.UTXOMultiset, entry, &outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fus.UTXOMultiset = newMs
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeAndUpdateMultiset removes a UTXOEntry from this utxoSet and updates its multiset accordingly
|
||||
func (fus *FullUTXOSet) removeAndUpdateMultiset(outpoint wire.Outpoint) error {
|
||||
entry, ok := fus.Get(outpoint)
|
||||
if !ok {
|
||||
return errors.Errorf("Couldn't find outpoint %s", outpoint)
|
||||
}
|
||||
fus.remove(outpoint)
|
||||
var err error
|
||||
newMs, err := removeUTXOFromMultiset(fus.UTXOMultiset, entry, &outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fus.UTXOMultiset = newMs
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithTransactions returns a new UTXO Set with the added transactions.
|
||||
//
|
||||
// This function MUST be called with the DAG lock held.
|
||||
func (fus *FullUTXOSet) WithTransactions(transactions []*wire.MsgTx, blockBlueScore uint64, ignoreDoubleSpends bool) (UTXOSet, error) {
|
||||
diffSet := NewDiffUTXOSet(fus, NewUTXODiff())
|
||||
for _, tx := range transactions {
|
||||
isAccepted, err := diffSet.AddTx(tx, blockBlueScore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ignoreDoubleSpends && !isAccepted {
|
||||
return nil, errors.Errorf("Transaction %s is not valid with the current UTXO set", tx.TxID())
|
||||
}
|
||||
}
|
||||
return UTXOSet(diffSet), nil
|
||||
}
|
||||
|
||||
// DiffUTXOSet represents a utxoSet with a base fullUTXOSet and a UTXODiff
|
||||
type DiffUTXOSet struct {
|
||||
base *FullUTXOSet
|
||||
@@ -830,12 +635,11 @@ func (dus *DiffUTXOSet) AddTx(tx *wire.MsgTx, blockBlueScore uint64) (bool, erro
|
||||
func (dus *DiffUTXOSet) appendTx(tx *wire.MsgTx, blockBlueScore uint64, isCoinbase bool) error {
|
||||
if !isCoinbase {
|
||||
for _, txIn := range tx.TxIn {
|
||||
outpoint := *wire.NewOutpoint(&txIn.PreviousOutpoint.TxID, txIn.PreviousOutpoint.Index)
|
||||
entry, ok := dus.Get(outpoint)
|
||||
entry, ok := dus.Get(txIn.PreviousOutpoint)
|
||||
if !ok {
|
||||
return errors.Errorf("Couldn't find entry for outpoint %s", outpoint)
|
||||
return errors.Errorf("Couldn't find entry for outpoint %s", txIn.PreviousOutpoint)
|
||||
}
|
||||
err := dus.UTXODiff.RemoveEntry(outpoint, entry)
|
||||
err := dus.UTXODiff.RemoveEntry(txIn.PreviousOutpoint, entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -881,16 +685,7 @@ func (dus *DiffUTXOSet) meldToBase() error {
|
||||
for outpoint, utxoEntry := range dus.UTXODiff.toAdd {
|
||||
dus.base.add(outpoint, utxoEntry)
|
||||
}
|
||||
|
||||
if dus.UTXODiff.useMultiset {
|
||||
dus.base.UTXOMultiset = dus.base.UTXOMultiset.Union(dus.UTXODiff.diffMultiset)
|
||||
}
|
||||
|
||||
if dus.UTXODiff.useMultiset {
|
||||
dus.UTXODiff = NewUTXODiff()
|
||||
} else {
|
||||
dus.UTXODiff = NewUTXODiffWithoutMultiset()
|
||||
}
|
||||
dus.UTXODiff = NewUTXODiff()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -905,7 +700,7 @@ func (dus *DiffUTXOSet) diffFromAcceptedTx(tx *wire.MsgTx, acceptingBlueScore ui
|
||||
}
|
||||
|
||||
func (dus *DiffUTXOSet) String() string {
|
||||
return fmt.Sprintf("{Base: %s, To Add: %s, To Remove: %s, Multiset-Hash:%s}", dus.base, dus.UTXODiff.toAdd, dus.UTXODiff.toRemove, dus.Multiset().Hash())
|
||||
return fmt.Sprintf("{Base: %s, To Add: %s, To Remove: %s}", dus.base, dus.UTXODiff.toAdd, dus.UTXODiff.toRemove)
|
||||
}
|
||||
|
||||
// clone returns a clone of this UTXO Set
|
||||
@@ -930,42 +725,3 @@ func (dus *DiffUTXOSet) Get(outpoint wire.Outpoint) (*UTXOEntry, bool) {
|
||||
txOut, ok := dus.UTXODiff.toAdd.get(outpoint)
|
||||
return txOut, ok
|
||||
}
|
||||
|
||||
// Multiset returns the ecmh-Multiset of this utxoSet
|
||||
func (dus *DiffUTXOSet) Multiset() *ecc.Multiset {
|
||||
return dus.base.UTXOMultiset.Union(dus.UTXODiff.diffMultiset)
|
||||
}
|
||||
|
||||
// WithTransactions returns a new UTXO Set with the added transactions.
|
||||
//
|
||||
// If dus.UTXODiff.useMultiset is true, this function MUST be
|
||||
// called with the DAG lock held.
|
||||
func (dus *DiffUTXOSet) WithTransactions(transactions []*wire.MsgTx, blockBlueScore uint64, ignoreDoubleSpends bool) (UTXOSet, error) {
|
||||
diffSet := NewDiffUTXOSet(dus.base, dus.UTXODiff.clone())
|
||||
for _, tx := range transactions {
|
||||
isAccepted, err := diffSet.AddTx(tx, blockBlueScore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ignoreDoubleSpends && !isAccepted {
|
||||
return nil, errors.Errorf("Transaction %s is not valid with the current UTXO set", tx.TxID())
|
||||
}
|
||||
}
|
||||
return UTXOSet(diffSet), nil
|
||||
}
|
||||
|
||||
func addUTXOToMultiset(ms *ecc.Multiset, entry *UTXOEntry, outpoint *wire.Outpoint) (*ecc.Multiset, error) {
|
||||
utxoMS, err := utxoMultiset(entry, outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ms.Union(utxoMS), nil
|
||||
}
|
||||
|
||||
func removeUTXOFromMultiset(ms *ecc.Multiset, entry *UTXOEntry, outpoint *wire.Outpoint) (*ecc.Multiset, error) {
|
||||
utxoMS, err := utxoMultiset(entry, outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ms.Subtract(utxoMS), nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
@@ -80,49 +79,40 @@ func TestUTXODiff(t *testing.T) {
|
||||
utxoEntry0 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 10}, true, 0)
|
||||
utxoEntry1 := NewUTXOEntry(&wire.TxOut{ScriptPubKey: []byte{}, Value: 20}, false, 1)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
withMultiset := i == 0
|
||||
// Test utxoDiff creation
|
||||
var diff *UTXODiff
|
||||
if withMultiset {
|
||||
diff = NewUTXODiff()
|
||||
} else {
|
||||
diff = NewUTXODiffWithoutMultiset()
|
||||
}
|
||||
if len(diff.toAdd) != 0 || len(diff.toRemove) != 0 {
|
||||
t.Errorf("new diff is not empty")
|
||||
}
|
||||
// Test utxoDiff creation
|
||||
|
||||
err := diff.AddEntry(outpoint0, utxoEntry0)
|
||||
if err != nil {
|
||||
t.Fatalf("error adding entry to utxo diff: %s", err)
|
||||
}
|
||||
diff := NewUTXODiff()
|
||||
|
||||
err = diff.RemoveEntry(outpoint1, utxoEntry1)
|
||||
if err != nil {
|
||||
t.Fatalf("error adding entry to utxo diff: %s", err)
|
||||
}
|
||||
if len(diff.toAdd) != 0 || len(diff.toRemove) != 0 {
|
||||
t.Errorf("new diff is not empty")
|
||||
}
|
||||
|
||||
// Test utxoDiff cloning
|
||||
clonedDiff := diff.clone()
|
||||
if clonedDiff == diff {
|
||||
t.Errorf("cloned diff is reference-equal to the original")
|
||||
}
|
||||
if !reflect.DeepEqual(clonedDiff, diff) {
|
||||
t.Errorf("cloned diff not equal to the original"+
|
||||
"Original: \"%v\", cloned: \"%v\".", diff, clonedDiff)
|
||||
}
|
||||
err := diff.AddEntry(outpoint0, utxoEntry0)
|
||||
if err != nil {
|
||||
t.Fatalf("error adding entry to utxo diff: %s", err)
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
diffString := clonedDiff.String()
|
||||
if diffString != expectedDiffString {
|
||||
t.Errorf("unexpected diff string. "+
|
||||
"Expected: \"%s\", got: \"%s\".", expectedDiffString, diffString)
|
||||
}
|
||||
err = diff.RemoveEntry(outpoint1, utxoEntry1)
|
||||
if err != nil {
|
||||
t.Fatalf("error adding entry to utxo diff: %s", err)
|
||||
}
|
||||
|
||||
// Test utxoDiff cloning
|
||||
clonedDiff := diff.clone()
|
||||
if clonedDiff == diff {
|
||||
t.Errorf("cloned diff is reference-equal to the original")
|
||||
}
|
||||
if !reflect.DeepEqual(clonedDiff, diff) {
|
||||
t.Errorf("cloned diff not equal to the original"+
|
||||
"Original: \"%v\", cloned: \"%v\".", diff, clonedDiff)
|
||||
}
|
||||
|
||||
// Test utxoDiff string representation
|
||||
expectedDiffString := "toAdd: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ]; toRemove: [ (1111111111111111111111111111111111111111111111111111111111111111, 0) => 20, blueScore: 1 ]"
|
||||
diffString := clonedDiff.String()
|
||||
if diffString != expectedDiffString {
|
||||
t.Errorf("unexpected diff string. "+
|
||||
"Expected: \"%s\", got: \"%s\".", expectedDiffString, diffString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +127,7 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
// For each of the following test cases, we will:
|
||||
// this.diffFrom(other) and compare it to expectedDiffFromResult
|
||||
// this.WithDiff(other) and compare it to expectedWithDiffResult
|
||||
// this.WithDiffInPlace(other) and compare it to expectedWithDiffResult
|
||||
// this.withDiffInPlace(other) and compare it to expectedWithDiffResult
|
||||
//
|
||||
// Note: an expected nil result means that we expect the respective operation to fail
|
||||
// See the following spreadsheet for a summary of all test-cases:
|
||||
@@ -542,157 +532,101 @@ func TestUTXODiffRules(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
this := addMultisetToDiff(t, test.this)
|
||||
other := addMultisetToDiff(t, test.other)
|
||||
expectedDiffFromResult := addMultisetToDiff(t, test.expectedDiffFromResult)
|
||||
expectedWithDiffResult := addMultisetToDiff(t, test.expectedWithDiffResult)
|
||||
|
||||
// diffFrom from this to other
|
||||
diffResult, err := this.diffFrom(other)
|
||||
// diffFrom from test.this to test.other
|
||||
diffResult, err := test.this.diffFrom(test.other)
|
||||
|
||||
// Test whether diffFrom returned an error
|
||||
isDiffFromOk := err == nil
|
||||
expectedIsDiffFromOk := expectedDiffFromResult != nil
|
||||
expectedIsDiffFromOk := test.expectedDiffFromResult != nil
|
||||
if isDiffFromOk != expectedIsDiffFromOk {
|
||||
t.Errorf("unexpected diffFrom error in test \"%s\". "+
|
||||
"Expected: \"%t\", got: \"%t\".", test.name, expectedIsDiffFromOk, isDiffFromOk)
|
||||
}
|
||||
|
||||
// If not error, test the diffFrom result
|
||||
if isDiffFromOk && !expectedDiffFromResult.equal(diffResult) {
|
||||
if isDiffFromOk && !test.expectedDiffFromResult.equal(diffResult) {
|
||||
t.Errorf("unexpected diffFrom result in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, expectedDiffFromResult, diffResult)
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedDiffFromResult, diffResult)
|
||||
}
|
||||
|
||||
// Make sure that WithDiff after diffFrom results in the original other
|
||||
// Make sure that WithDiff after diffFrom results in the original test.other
|
||||
if isDiffFromOk {
|
||||
otherResult, err := this.WithDiff(diffResult)
|
||||
otherResult, err := test.this.WithDiff(diffResult)
|
||||
if err != nil {
|
||||
t.Errorf("WithDiff unexpectedly failed in test \"%s\": %s", test.name, err)
|
||||
}
|
||||
if !other.equal(otherResult) {
|
||||
if !test.other.equal(otherResult) {
|
||||
t.Errorf("unexpected WithDiff result in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, other, otherResult)
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.other, otherResult)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDiff from this to other
|
||||
withDiffResult, err := this.WithDiff(other)
|
||||
// WithDiff from test.this to test.other
|
||||
withDiffResult, err := test.this.WithDiff(test.other)
|
||||
|
||||
// Test whether WithDiff returned an error
|
||||
isWithDiffOk := err == nil
|
||||
expectedIsWithDiffOk := expectedWithDiffResult != nil
|
||||
expectedIsWithDiffOk := test.expectedWithDiffResult != nil
|
||||
if isWithDiffOk != expectedIsWithDiffOk {
|
||||
t.Errorf("unexpected WithDiff error in test \"%s\". "+
|
||||
"Expected: \"%t\", got: \"%t\".", test.name, expectedIsWithDiffOk, isWithDiffOk)
|
||||
}
|
||||
|
||||
// If not error, test the WithDiff result
|
||||
if isWithDiffOk && !withDiffResult.equal(expectedWithDiffResult) {
|
||||
if isWithDiffOk && !withDiffResult.equal(test.expectedWithDiffResult) {
|
||||
t.Errorf("unexpected WithDiff result in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, expectedWithDiffResult, withDiffResult)
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedWithDiffResult, withDiffResult)
|
||||
}
|
||||
|
||||
// Repeat WithDiff check this time using WithDiffInPlace
|
||||
thisClone := this.clone()
|
||||
err = thisClone.WithDiffInPlace(other)
|
||||
// Repeat WithDiff check test.this time using withDiffInPlace
|
||||
thisClone := test.this.clone()
|
||||
err = thisClone.withDiffInPlace(test.other)
|
||||
|
||||
// Test whether WithDiffInPlace returned an error
|
||||
// Test whether withDiffInPlace returned an error
|
||||
isWithDiffInPlaceOk := err == nil
|
||||
expectedIsWithDiffInPlaceOk := expectedWithDiffResult != nil
|
||||
expectedIsWithDiffInPlaceOk := test.expectedWithDiffResult != nil
|
||||
if isWithDiffInPlaceOk != expectedIsWithDiffInPlaceOk {
|
||||
t.Errorf("unexpected WithDiffInPlace error in test \"%s\". "+
|
||||
t.Errorf("unexpected withDiffInPlace error in test \"%s\". "+
|
||||
"Expected: \"%t\", got: \"%t\".", test.name, expectedIsWithDiffInPlaceOk, isWithDiffInPlaceOk)
|
||||
}
|
||||
|
||||
// If not error, test the WithDiffInPlace result
|
||||
if isWithDiffInPlaceOk && !thisClone.equal(expectedWithDiffResult) {
|
||||
t.Errorf("unexpected WithDiffInPlace result in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, expectedWithDiffResult, thisClone)
|
||||
// If not error, test the withDiffInPlace result
|
||||
if isWithDiffInPlaceOk && !thisClone.equal(test.expectedWithDiffResult) {
|
||||
t.Errorf("unexpected withDiffInPlace result in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedWithDiffResult, thisClone)
|
||||
}
|
||||
|
||||
// Make sure that diffFrom after WithDiff results in the original other
|
||||
// Make sure that diffFrom after WithDiff results in the original test.other
|
||||
if isWithDiffOk {
|
||||
otherResult, err := this.diffFrom(withDiffResult)
|
||||
otherResult, err := test.this.diffFrom(withDiffResult)
|
||||
if err != nil {
|
||||
t.Errorf("diffFrom unexpectedly failed in test \"%s\": %s", test.name, err)
|
||||
}
|
||||
if !other.equal(otherResult) {
|
||||
if !test.other.equal(otherResult) {
|
||||
t.Errorf("unexpected diffFrom result in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, other, otherResult)
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.other, otherResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func areMultisetsEqual(a *ecc.Multiset, b *ecc.Multiset) bool {
|
||||
aX, aY := a.Point()
|
||||
bX, bY := b.Point()
|
||||
return aX.Cmp(bX) == 0 && aY.Cmp(bY) == 0
|
||||
}
|
||||
|
||||
func (d *UTXODiff) equal(other *UTXODiff) bool {
|
||||
if d == nil || other == nil {
|
||||
return d == other
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(d.toAdd, other.toAdd) &&
|
||||
reflect.DeepEqual(d.toRemove, other.toRemove) &&
|
||||
areMultisetsEqual(d.diffMultiset, other.diffMultiset)
|
||||
reflect.DeepEqual(d.toRemove, other.toRemove)
|
||||
}
|
||||
|
||||
func (fus *FullUTXOSet) equal(other *FullUTXOSet) bool {
|
||||
return reflect.DeepEqual(fus.utxoCollection, other.utxoCollection) &&
|
||||
areMultisetsEqual(fus.UTXOMultiset, other.UTXOMultiset)
|
||||
return reflect.DeepEqual(fus.utxoCollection, other.utxoCollection)
|
||||
}
|
||||
|
||||
func (dus *DiffUTXOSet) equal(other *DiffUTXOSet) bool {
|
||||
return dus.base.equal(other.base) && dus.UTXODiff.equal(other.UTXODiff)
|
||||
}
|
||||
|
||||
func addMultisetToDiff(t *testing.T, diff *UTXODiff) *UTXODiff {
|
||||
if diff == nil {
|
||||
return nil
|
||||
}
|
||||
diffWithMs := NewUTXODiff()
|
||||
for outpoint, entry := range diff.toAdd {
|
||||
err := diffWithMs.AddEntry(outpoint, entry)
|
||||
if err != nil {
|
||||
t.Fatalf("Error with diffWithMs.AddEntry: %s", err)
|
||||
}
|
||||
}
|
||||
for outpoint, entry := range diff.toRemove {
|
||||
err := diffWithMs.RemoveEntry(outpoint, entry)
|
||||
if err != nil {
|
||||
t.Fatalf("Error with diffWithMs.removeEntry: %s", err)
|
||||
}
|
||||
}
|
||||
return diffWithMs
|
||||
}
|
||||
|
||||
func addMultisetToFullUTXOSet(t *testing.T, fus *FullUTXOSet) *FullUTXOSet {
|
||||
if fus == nil {
|
||||
return nil
|
||||
}
|
||||
fusWithMs := NewFullUTXOSet()
|
||||
for outpoint, entry := range fus.utxoCollection {
|
||||
err := fusWithMs.addAndUpdateMultiset(outpoint, entry)
|
||||
if err != nil {
|
||||
t.Fatalf("Error with diffWithMs.AddEntry: %s", err)
|
||||
}
|
||||
}
|
||||
return fusWithMs
|
||||
}
|
||||
|
||||
func addMultisetToDiffUTXOSet(t *testing.T, diffSet *DiffUTXOSet) *DiffUTXOSet {
|
||||
if diffSet == nil {
|
||||
return nil
|
||||
}
|
||||
diffWithMs := addMultisetToDiff(t, diffSet.UTXODiff)
|
||||
baseWithMs := addMultisetToFullUTXOSet(t, diffSet.base)
|
||||
return NewDiffUTXOSet(baseWithMs, diffWithMs)
|
||||
}
|
||||
|
||||
// TestFullUTXOSet makes sure that fullUTXOSet is working as expected.
|
||||
func TestFullUTXOSet(t *testing.T) {
|
||||
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
@@ -703,10 +637,10 @@ func TestFullUTXOSet(t *testing.T) {
|
||||
txOut1 := &wire.TxOut{ScriptPubKey: []byte{}, Value: 20}
|
||||
utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
|
||||
utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
|
||||
diff := addMultisetToDiff(t, &UTXODiff{
|
||||
diff := &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry0},
|
||||
toRemove: utxoCollection{outpoint1: utxoEntry1},
|
||||
})
|
||||
}
|
||||
|
||||
// Test fullUTXOSet creation
|
||||
emptySet := NewFullUTXOSet()
|
||||
@@ -735,7 +669,7 @@ func TestFullUTXOSet(t *testing.T) {
|
||||
} else if isAccepted {
|
||||
t.Errorf("addTx unexpectedly succeeded")
|
||||
}
|
||||
emptySet = addMultisetToFullUTXOSet(t, &FullUTXOSet{utxoCollection: utxoCollection{outpoint0: utxoEntry0}})
|
||||
emptySet = &FullUTXOSet{utxoCollection: utxoCollection{outpoint0: utxoEntry0}}
|
||||
if isAccepted, err := emptySet.AddTx(transaction0, 0); err != nil {
|
||||
t.Errorf("addTx unexpectedly failed. Error: %s", err)
|
||||
} else if !isAccepted {
|
||||
@@ -767,10 +701,10 @@ func TestDiffUTXOSet(t *testing.T) {
|
||||
txOut1 := &wire.TxOut{ScriptPubKey: []byte{}, Value: 20}
|
||||
utxoEntry0 := NewUTXOEntry(txOut0, true, 0)
|
||||
utxoEntry1 := NewUTXOEntry(txOut1, false, 1)
|
||||
diff := addMultisetToDiff(t, &UTXODiff{
|
||||
diff := &UTXODiff{
|
||||
toAdd: utxoCollection{outpoint0: utxoEntry0},
|
||||
toRemove: utxoCollection{outpoint1: utxoEntry1},
|
||||
})
|
||||
}
|
||||
|
||||
// Test diffUTXOSet creation
|
||||
emptySet := NewDiffUTXOSet(NewFullUTXOSet(), NewUTXODiff())
|
||||
@@ -828,7 +762,7 @@ func TestDiffUTXOSet(t *testing.T) {
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
expectedString: "{Base: [ ], To Add: [ ], To Remove: [ ], Multiset-Hash:0000000000000000000000000000000000000000000000000000000000000000}",
|
||||
expectedString: "{Base: [ ], To Add: [ ], To Remove: [ ]}",
|
||||
expectedCollection: utxoCollection{},
|
||||
},
|
||||
{
|
||||
@@ -847,7 +781,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: [ ]}",
|
||||
expectedCollection: utxoCollection{outpoint0: utxoEntry0},
|
||||
},
|
||||
{
|
||||
@@ -860,7 +794,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 ]}",
|
||||
expectedCollection: utxoCollection{},
|
||||
expectedMeldToBaseError: "Couldn't remove outpoint 0000000000000000000000000000000000000000000000000000000000000000:0 because it doesn't exist in the DiffUTXOSet base",
|
||||
},
|
||||
@@ -885,7 +819,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: [ ]}",
|
||||
expectedCollection: utxoCollection{
|
||||
outpoint0: utxoEntry0,
|
||||
outpoint1: utxoEntry1,
|
||||
@@ -909,24 +843,21 @@ func TestDiffUTXOSet(t *testing.T) {
|
||||
toRemove: utxoCollection{},
|
||||
},
|
||||
},
|
||||
expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], Multiset-Hash:0000000000000000000000000000000000000000000000000000000000000000}",
|
||||
expectedString: "{Base: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ], To Add: [ ], To Remove: [ (0000000000000000000000000000000000000000000000000000000000000000, 0) => 10, blueScore: 0 ]}",
|
||||
expectedCollection: utxoCollection{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
diffSet := addMultisetToDiffUTXOSet(t, test.diffSet)
|
||||
expectedMeldSet := addMultisetToDiffUTXOSet(t, test.expectedMeldSet)
|
||||
|
||||
// Test string representation
|
||||
setString := diffSet.String()
|
||||
setString := test.diffSet.String()
|
||||
if setString != test.expectedString {
|
||||
t.Errorf("unexpected string in test \"%s\". "+
|
||||
"Expected: \"%s\", got: \"%s\".", test.name, test.expectedString, setString)
|
||||
}
|
||||
|
||||
// Test meldToBase
|
||||
meldSet := diffSet.clone().(*DiffUTXOSet)
|
||||
meldSet := test.diffSet.clone().(*DiffUTXOSet)
|
||||
err := meldSet.meldToBase()
|
||||
errString := ""
|
||||
if err != nil {
|
||||
@@ -938,27 +869,27 @@ func TestDiffUTXOSet(t *testing.T) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !meldSet.equal(expectedMeldSet) {
|
||||
if !meldSet.equal(test.expectedMeldSet) {
|
||||
t.Errorf("unexpected melded set in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, expectedMeldSet, meldSet)
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedMeldSet, meldSet)
|
||||
}
|
||||
|
||||
// Test collection
|
||||
setCollection, err := diffSet.collection()
|
||||
setCollection, err := test.diffSet.collection()
|
||||
if err != nil {
|
||||
t.Errorf("Error getting diffSet collection: %s", err)
|
||||
t.Errorf("Error getting test.diffSet collection: %s", err)
|
||||
} else if !reflect.DeepEqual(setCollection, test.expectedCollection) {
|
||||
t.Errorf("unexpected set collection in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedCollection, setCollection)
|
||||
}
|
||||
|
||||
// Test cloning
|
||||
clonedSet := diffSet.clone().(*DiffUTXOSet)
|
||||
if !reflect.DeepEqual(clonedSet, diffSet) {
|
||||
clonedSet := test.diffSet.clone().(*DiffUTXOSet)
|
||||
if !reflect.DeepEqual(clonedSet, test.diffSet) {
|
||||
t.Errorf("unexpected set clone in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, diffSet, clonedSet)
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.diffSet, clonedSet)
|
||||
}
|
||||
if clonedSet == diffSet {
|
||||
if clonedSet == test.diffSet {
|
||||
t.Errorf("cloned set is reference-equal to the original")
|
||||
}
|
||||
}
|
||||
@@ -1159,10 +1090,7 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
||||
|
||||
testLoop:
|
||||
for _, test := range tests {
|
||||
startSet := addMultisetToDiffUTXOSet(t, test.startSet)
|
||||
expectedSet := addMultisetToDiffUTXOSet(t, test.expectedSet)
|
||||
|
||||
diffSet := startSet.clone()
|
||||
diffSet := test.startSet.clone()
|
||||
|
||||
// Apply all transactions to diffSet, in order, with the initial block height startHeight
|
||||
for i, transaction := range test.toAdd {
|
||||
@@ -1174,18 +1102,18 @@ testLoop:
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that the result diffSet equals to the expectedSet
|
||||
if !diffSet.(*DiffUTXOSet).equal(expectedSet) {
|
||||
// Make sure that the result diffSet equals to test.expectedSet
|
||||
if !diffSet.(*DiffUTXOSet).equal(test.expectedSet) {
|
||||
t.Errorf("unexpected diffSet in test \"%s\". "+
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, expectedSet, diffSet)
|
||||
"Expected: \"%v\", got: \"%v\".", test.name, test.expectedSet, diffSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffFromTx(t *testing.T) {
|
||||
fus := addMultisetToFullUTXOSet(t, &FullUTXOSet{
|
||||
fus := &FullUTXOSet{
|
||||
utxoCollection: utxoCollection{},
|
||||
})
|
||||
}
|
||||
|
||||
txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutpoint: wire.Outpoint{TxID: *txID0, Index: math.MaxUint32}, Sequence: 0}
|
||||
@@ -1241,10 +1169,10 @@ func TestDiffFromTx(t *testing.T) {
|
||||
}
|
||||
|
||||
//Test that we get an error if the outpoint is inside diffUTXOSet's toRemove
|
||||
diff2 := addMultisetToDiff(t, &UTXODiff{
|
||||
diff2 := &UTXODiff{
|
||||
toAdd: utxoCollection{},
|
||||
toRemove: utxoCollection{},
|
||||
})
|
||||
}
|
||||
dus := NewDiffUTXOSet(fus, diff2)
|
||||
if isAccepted, err := dus.AddTx(tx, 2); err != nil {
|
||||
t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
|
||||
@@ -1321,7 +1249,6 @@ func TestUTXOSetAddEntry(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
expectedUTXODiff := addMultisetToDiff(t, test.expectedUTXODiff)
|
||||
err := utxoDiff.AddEntry(*test.outpointToAdd, test.utxoEntryToAdd)
|
||||
errString := ""
|
||||
if err != nil {
|
||||
@@ -1330,9 +1257,9 @@ func TestUTXOSetAddEntry(t *testing.T) {
|
||||
if errString != test.expectedError {
|
||||
t.Fatalf("utxoDiff.AddEntry: unexpected err in test \"%s\". Expected: %s but got: %s", test.name, test.expectedError, err)
|
||||
}
|
||||
if err == nil && !utxoDiff.equal(expectedUTXODiff) {
|
||||
if err == nil && !utxoDiff.equal(test.expectedUTXODiff) {
|
||||
t.Fatalf("utxoDiff.AddEntry: unexpected utxoDiff in test \"%s\". "+
|
||||
"Expected: %v, got: %v", test.name, expectedUTXODiff, utxoDiff)
|
||||
"Expected: %v, got: %v", test.name, test.expectedUTXODiff, utxoDiff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +435,7 @@ func (dag *BlockDAG) checkBlockHeaderSanity(header *wire.BlockHeader, flags Beha
|
||||
// the duration of time that should be waited before the block becomes valid.
|
||||
// This check needs to be last as it does not return an error but rather marks the
|
||||
// header as delayed (and valid).
|
||||
maxTimestamp := dag.AdjustedTime().Add(time.Second *
|
||||
maxTimestamp := dag.Now().Add(time.Second *
|
||||
time.Duration(int64(dag.TimestampDeviationTolerance)*dag.targetTimePerBlock))
|
||||
if header.Timestamp.After(maxTimestamp) {
|
||||
return header.Timestamp.Sub(maxTimestamp), nil
|
||||
|
||||
@@ -169,6 +169,7 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer teardownFunc()
|
||||
dag.timeSource = newFakeTimeSource(time.Now())
|
||||
|
||||
block := util.NewBlock(&Block100000)
|
||||
if len(block.Transactions()) < 3 {
|
||||
@@ -191,7 +192,8 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
if !errors.As(err, &ruleErr) {
|
||||
t.Errorf("CheckBlockSanity: wrong error returned, expect RuleError, got %T", err)
|
||||
} else if ruleErr.ErrorCode != ErrTransactionsNotSorted {
|
||||
t.Errorf("CheckBlockSanity: wrong error returned, expect ErrTransactionsNotSorted, got %v, err %s", ruleErr.ErrorCode, err)
|
||||
t.Errorf("CheckBlockSanity: wrong error returned, expect ErrTransactionsNotSorted, got"+
|
||||
" %v, err %s", ruleErr.ErrorCode, err)
|
||||
}
|
||||
if delay != 0 {
|
||||
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
|
||||
@@ -492,8 +494,8 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
|
||||
blockInTheFuture := Block100000
|
||||
expectedDelay := 10 * time.Second
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
blockInTheFuture.Header.Timestamp = now.Add(time.Duration(dag.TimestampDeviationTolerance)*time.Second + expectedDelay)
|
||||
deviationTolerance := time.Duration(dag.TimestampDeviationTolerance*uint64(dag.targetTimePerBlock)) * time.Second
|
||||
blockInTheFuture.Header.Timestamp = dag.Now().Add(deviationTolerance + expectedDelay)
|
||||
delay, err = dag.checkBlockSanity(util.NewBlock(&blockInTheFuture), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Errorf("CheckBlockSanity: %v", err)
|
||||
|
||||
@@ -304,7 +304,7 @@ func newBlockImporter(db database.DB, r io.ReadSeeker) (*blockImporter, error) {
|
||||
dag, err := blockdag.New(&blockdag.Config{
|
||||
DB: db,
|
||||
DAGParams: ActiveConfig().NetParams(),
|
||||
TimeSource: blockdag.NewMedianTime(),
|
||||
TimeSource: blockdag.NewTimeSource(),
|
||||
IndexManager: indexManager,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -2,11 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -37,6 +39,7 @@ type configFlags struct {
|
||||
Verbose bool `long:"verbose" short:"v" description:"Enable logging of RPC requests"`
|
||||
NumberOfBlocks uint64 `short:"n" long:"numblocks" description:"Number of blocks to mine. If omitted, will mine until the process is interrupted."`
|
||||
BlockDelay uint64 `long:"block-delay" description:"Delay for block submission (in milliseconds). This is used only for testing purposes."`
|
||||
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
@@ -78,6 +81,13 @@ func parseConfig() (*configFlags, error) {
|
||||
return nil, errors.New("--rpccert should be omitted if --notls is used")
|
||||
}
|
||||
|
||||
if cfg.Profile != "" {
|
||||
profilePort, err := strconv.Atoi(cfg.Profile)
|
||||
if err != nil || profilePort < 1024 || profilePort > 65535 {
|
||||
return nil, errors.New("The profile port must be between 1024 and 65535")
|
||||
}
|
||||
}
|
||||
|
||||
initLog(defaultLogFile, defaultErrLogFile)
|
||||
|
||||
return cfg, nil
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -- multistage docker build: stage #1: build stage
|
||||
FROM golang:1.13-alpine AS build
|
||||
FROM golang:1.14-alpine AS build
|
||||
|
||||
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
|
||||
|
||||
@@ -20,7 +20,7 @@ WORKDIR /go/src/github.com/kaspanet/kaspad/cmd/kaspaminer
|
||||
RUN GOFMT_RESULT=`go fmt ./...`; echo $GOFMT_RESULT; test -z "$GOFMT_RESULT"
|
||||
RUN go vet ./...
|
||||
RUN golint -set_exit_status ./...
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o kaspaminer .
|
||||
RUN GOOS=linux go build -a -installsuffix cgo -o kaspaminer .
|
||||
|
||||
# --- multistage docker build: stage #2: runtime image
|
||||
FROM alpine
|
||||
|
||||
@@ -2,11 +2,16 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/kaspanet/kaspad/signal"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
@@ -28,6 +33,17 @@ func main() {
|
||||
enableRPCLogging()
|
||||
}
|
||||
|
||||
// Enable http profiling server if requested.
|
||||
if cfg.Profile != "" {
|
||||
spawn(func() {
|
||||
listenAddr := net.JoinHostPort("", cfg.Profile)
|
||||
log.Infof("Profile server listening on %s", listenAddr)
|
||||
profileRedirect := http.RedirectHandler("/debug/pprof", http.StatusSeeOther)
|
||||
http.Handle("/", profileRedirect)
|
||||
log.Errorf("%s", http.ListenAndServe(listenAddr, nil))
|
||||
})
|
||||
}
|
||||
|
||||
client, err := connectToServer(cfg)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "Error connecting to the RPC server"))
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
@@ -28,7 +28,11 @@ func main() {
|
||||
printErrorAndExit(err, "Failed to decode transaction")
|
||||
}
|
||||
|
||||
scriptPubKey, err := createScriptPubKey(privateKey.PubKey())
|
||||
pubkey, err := privateKey.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
printErrorAndExit(err, "Failed to generate a public key")
|
||||
}
|
||||
scriptPubKey, err := createScriptPubKey(pubkey)
|
||||
if err != nil {
|
||||
printErrorAndExit(err, "Failed to create scriptPubKey")
|
||||
}
|
||||
@@ -46,10 +50,12 @@ func main() {
|
||||
fmt.Printf("Signed Transaction (hex): %s\n\n", serializedTransaction)
|
||||
}
|
||||
|
||||
func parsePrivateKey(privateKeyHex string) (*ecc.PrivateKey, error) {
|
||||
func parsePrivateKey(privateKeyHex string) (*secp256k1.PrivateKey, error) {
|
||||
privateKeyBytes, err := hex.DecodeString(privateKeyHex)
|
||||
privateKey, _ := ecc.PrivKeyFromBytes(ecc.S256(), privateKeyBytes)
|
||||
return privateKey, err
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("'%s' isn't a valid hex. err: '%s' ", privateKeyHex, err)
|
||||
}
|
||||
return secp256k1.DeserializePrivateKeyFromSlice(privateKeyBytes)
|
||||
}
|
||||
|
||||
func parseTransaction(transactionHex string) (*wire.MsgTx, error) {
|
||||
@@ -62,8 +68,12 @@ func parseTransaction(transactionHex string) (*wire.MsgTx, error) {
|
||||
return &transaction, err
|
||||
}
|
||||
|
||||
func createScriptPubKey(publicKey *ecc.PublicKey) ([]byte, error) {
|
||||
p2pkhAddress, err := util.NewAddressPubKeyHashFromPublicKey(publicKey.SerializeCompressed(), ActiveConfig().NetParams().Prefix)
|
||||
func createScriptPubKey(publicKey *secp256k1.SchnorrPublicKey) ([]byte, error) {
|
||||
serializedKey, err := publicKey.SerializeCompressed()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p2pkhAddress, err := util.NewAddressPubKeyHashFromPublicKey(serializedKey, ActiveConfig().NetParams().Prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -71,7 +81,7 @@ func createScriptPubKey(publicKey *ecc.PublicKey) ([]byte, error) {
|
||||
return scriptPubKey, err
|
||||
}
|
||||
|
||||
func signTransaction(transaction *wire.MsgTx, privateKey *ecc.PrivateKey, scriptPubKey []byte) error {
|
||||
func signTransaction(transaction *wire.MsgTx, privateKey *secp256k1.PrivateKey, scriptPubKey []byte) error {
|
||||
for i, transactionInput := range transaction.TxIn {
|
||||
signatureScript, err := txscript.SignatureScript(transaction, i, scriptPubKey, txscript.SigHashAll, privateKey, true)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -- multistage docker build: stage #1: build stage
|
||||
FROM golang:1.13-alpine AS build
|
||||
FROM golang:1.14-alpine AS build
|
||||
|
||||
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
|
||||
|
||||
@@ -25,7 +25,7 @@ RUN golint -set_exit_status ./...
|
||||
# RUN aligncheck ./...
|
||||
# RUN structcheck -e ./...
|
||||
# RUN varcheck -e ./...
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o kaspad .
|
||||
RUN GOOS=linux go build -a -installsuffix cgo -o kaspad .
|
||||
|
||||
# Remove the line below and uncomment the line after it for testing with coverage
|
||||
RUN go test ./...
|
||||
|
||||
8
go.mod
8
go.mod
@@ -1,21 +1,21 @@
|
||||
module github.com/kaspanet/kaspad
|
||||
|
||||
go 1.13
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792
|
||||
github.com/btcsuite/winsvc v1.0.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/jrick/logrotate v1.0.0
|
||||
github.com/kaspanet/go-secp256k1 v0.0.2
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
|
||||
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
|
||||
12
go.sum
12
go.sum
@@ -8,19 +8,21 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc h1:55rEp52jU6bkyslZ1+C/7NGfpQsEc6pxGLAGDOctqbw=
|
||||
github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kaspanet/go-secp256k1 v0.0.2 h1:KZGXddYHxzS02rx6EPPQYYe2tZ/rREj4P6XxgQQwQIw=
|
||||
github.com/kaspanet/go-secp256k1 v0.0.2/go.mod h1:W9OcWBKzH8P/PN2WAUn9k2YmZG/Uc660WAL1NTS3G3M=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -39,11 +41,15 @@ github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpP
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -53,6 +59,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20200228224639-71482053b885/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -454,7 +454,7 @@ func (mp *TxPool) HaveTransaction(hash *daghash.TxID) bool {
|
||||
//
|
||||
// This function MUST be called with the mempool lock held (for writes).
|
||||
func (mp *TxPool) removeTransactions(txs []*util.Tx) error {
|
||||
diff := blockdag.NewUTXODiffWithoutMultiset()
|
||||
diff := blockdag.NewUTXODiff()
|
||||
|
||||
for _, tx := range txs {
|
||||
txID := tx.ID()
|
||||
@@ -502,7 +502,7 @@ func (mp *TxPool) removeTransaction(tx *util.Tx, removeDependants bool, restoreI
|
||||
return nil
|
||||
}
|
||||
|
||||
diff := blockdag.NewUTXODiffWithoutMultiset()
|
||||
diff := blockdag.NewUTXODiff()
|
||||
err := mp.removeTransactionWithDiff(tx, diff, restoreInputs)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1353,7 +1353,7 @@ func (mp *TxPool) HandleNewBlock(block *util.Block, txChan chan NewBlockMsg) err
|
||||
// transactions until they are mined into a block.
|
||||
func New(cfg *Config) *TxPool {
|
||||
virtualUTXO := cfg.DAG.UTXOSet()
|
||||
mpUTXO := blockdag.NewDiffUTXOSet(virtualUTXO, blockdag.NewUTXODiffWithoutMultiset())
|
||||
mpUTXO := blockdag.NewDiffUTXOSet(virtualUTXO, blockdag.NewUTXODiff())
|
||||
return &TxPool{
|
||||
cfg: *cfg,
|
||||
pool: make(map[daghash.TxID]*TxDesc),
|
||||
|
||||
@@ -254,7 +254,7 @@ func (tc *testContext) mineTransactions(transactions []*util.Tx, numberOfBlocks
|
||||
if i == 0 {
|
||||
blockTxs = msgTxs
|
||||
}
|
||||
block, err := mining.PrepareBlockForTest(tc.harness.txPool.cfg.DAG, tc.harness.txPool.cfg.DAGParams, tc.harness.txPool.cfg.DAG.TipHashes(), blockTxs, true)
|
||||
block, err := mining.PrepareBlockForTest(tc.harness.txPool.cfg.DAG, tc.harness.txPool.cfg.DAGParams, tc.harness.txPool.cfg.DAG.TipHashes(), blockTxs, false)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("PrepareBlockForTest: %s", err)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ type BlkTmplGenerator struct {
|
||||
dagParams *dagconfig.Params
|
||||
txSource TxSource
|
||||
dag *blockdag.BlockDAG
|
||||
timeSource blockdag.MedianTimeSource
|
||||
timeSource blockdag.TimeSource
|
||||
sigCache *txscript.SigCache
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ type BlkTmplGenerator struct {
|
||||
// consensus rules.
|
||||
func NewBlkTmplGenerator(policy *Policy, params *dagconfig.Params,
|
||||
txSource TxSource, dag *blockdag.BlockDAG,
|
||||
timeSource blockdag.MedianTimeSource,
|
||||
timeSource blockdag.TimeSource,
|
||||
sigCache *txscript.SigCache) *BlkTmplGenerator {
|
||||
|
||||
return &BlkTmplGenerator{
|
||||
@@ -183,11 +183,11 @@ func NewBlkTmplGenerator(policy *Policy, params *dagconfig.Params,
|
||||
// | transactions (while block size | |
|
||||
// | <= policy.BlockMinSize) | |
|
||||
// ----------------------------------- --
|
||||
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTemplate, error) {
|
||||
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address, extraNonce uint64) (*BlockTemplate, error) {
|
||||
g.dag.Lock()
|
||||
defer g.dag.Unlock()
|
||||
|
||||
txsForBlockTemplate, err := g.selectTxs(payToAddress)
|
||||
txsForBlockTemplate, err := g.selectTxs(payToAddress, extraNonce)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to select transactions: %s", err)
|
||||
}
|
||||
@@ -219,15 +219,6 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *BlkTmplGenerator) buildUTXOCommitment(transactions []*wire.MsgTx) (*daghash.Hash, error) {
|
||||
utxoWithTransactions, err := g.dag.UTXOSet().WithTransactions(transactions, blockdag.UnacceptedBlueScore, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return utxoWithTransactions.Multiset().Hash(), nil
|
||||
}
|
||||
|
||||
// UpdateBlockTime updates the timestamp in the header of the passed block to
|
||||
// the current time while taking into account the median time of the last
|
||||
// several blocks to ensure the new time is after that time per the DAG
|
||||
@@ -244,59 +235,6 @@ func (g *BlkTmplGenerator) UpdateBlockTime(msgBlock *wire.MsgBlock) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateExtraNonce updates the extra nonce in the coinbase script of the passed
|
||||
// block by regenerating the coinbase script with the passed value and block
|
||||
// height. It also recalculates and updates the new merkle root that results
|
||||
// from changing the coinbase script.
|
||||
func (g *BlkTmplGenerator) UpdateExtraNonce(msgBlock *wire.MsgBlock, extraNonce uint64) error {
|
||||
coinbasePayloadScriptPubKey, _, err := blockdag.DeserializeCoinbasePayload(msgBlock.Transactions[util.CoinbaseTransactionIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
coinbasePayloadExtraData, err := blockdag.CoinbasePayloadExtraData(extraNonce, CoinbaseFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
coinbasePayload, err := blockdag.SerializeCoinbasePayload(coinbasePayloadScriptPubKey, coinbasePayloadExtraData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(coinbasePayload) > blockdag.MaxCoinbasePayloadLen {
|
||||
return errors.Errorf("coinbase transaction script length "+
|
||||
"of %d is out of range (max: %d)",
|
||||
len(coinbasePayload),
|
||||
blockdag.MaxCoinbasePayloadLen)
|
||||
}
|
||||
oldCoinbaseTx := msgBlock.Transactions[util.CoinbaseTransactionIndex]
|
||||
msgBlock.Transactions[util.CoinbaseTransactionIndex] = wire.NewSubnetworkMsgTx(oldCoinbaseTx.Version, oldCoinbaseTx.TxIn, oldCoinbaseTx.TxOut, &oldCoinbaseTx.SubnetworkID, oldCoinbaseTx.Gas, coinbasePayload)
|
||||
|
||||
// TODO(davec): A util.Block should use saved in the state to avoid
|
||||
// recalculating all of the other transaction hashes.
|
||||
// block.Transactions[util.CoinbaseTransactionIndex].InvalidateCache()
|
||||
|
||||
// Recalculate the merkle roots with the updated extra nonce.
|
||||
block := util.NewBlock(msgBlock)
|
||||
hashMerkleTree := blockdag.BuildHashMerkleTreeStore(block.Transactions())
|
||||
msgBlock.Header.HashMerkleRoot = hashMerkleTree.Root()
|
||||
|
||||
// buildUTXOCommitment is the only function in UpdateExtraNonce that
|
||||
// requires the dagLock, and as such we lock and unlock it locally.
|
||||
utxoCommitment, err := func() (*daghash.Hash, error) {
|
||||
g.dag.Lock()
|
||||
defer g.dag.Unlock()
|
||||
|
||||
return g.buildUTXOCommitment(msgBlock.Transactions)
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgBlock.Header.UTXOCommitment = utxoCommitment
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TxSource returns the associated transaction source.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
|
||||
@@ -61,20 +61,18 @@ func PrepareBlockForTest(dag *blockdag.BlockDAG, params *dagconfig.Params, paren
|
||||
}
|
||||
|
||||
blockTemplateGenerator := NewBlkTmplGenerator(&policy,
|
||||
params, txSource, dag, blockdag.NewMedianTime(), txscript.NewSigCache(100000))
|
||||
params, txSource, dag, blockdag.NewTimeSource(), txscript.NewSigCache(100000))
|
||||
|
||||
OpTrueAddr, err := OpTrueAddress(params.Prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template, err := blockTemplateGenerator.NewBlockTemplate(OpTrueAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We create a deterministic extra nonce in order of
|
||||
// creating deterministic coinbase tx ids.
|
||||
extraNonce := GenerateDeterministicExtraNonceForTest()
|
||||
|
||||
// In order of creating deterministic coinbase tx ids.
|
||||
err = blockTemplateGenerator.UpdateExtraNonce(template.Block, GenerateDeterministicExtraNonceForTest())
|
||||
template, err := blockTemplateGenerator.NewBlockTemplate(OpTrueAddr, extraNonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -106,10 +104,12 @@ func PrepareBlockForTest(dag *blockdag.BlockDAG, params *dagconfig.Params, paren
|
||||
}
|
||||
template.Block.Header.HashMerkleRoot = blockdag.BuildHashMerkleTreeStore(utilTxs).Root()
|
||||
|
||||
template.Block.Header.UTXOCommitment, err = blockTemplateGenerator.buildUTXOCommitment(template.Block.Transactions)
|
||||
ms, err := dag.NextBlockMultiset(utilTxs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template.Block.Header.UTXOCommitment = (*daghash.Hash)(ms.Finalize())
|
||||
}
|
||||
return template.Block, nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package mining
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"math"
|
||||
"math/rand"
|
||||
@@ -65,13 +64,13 @@ type txsForBlockTemplate struct {
|
||||
// Once the sum of probabilities of marked transactions is greater than
|
||||
// rebalanceThreshold percent of the sum of probabilities of all transactions,
|
||||
// rebalance.
|
||||
func (g *BlkTmplGenerator) selectTxs(payToAddress util.Address) (*txsForBlockTemplate, error) {
|
||||
func (g *BlkTmplGenerator) selectTxs(payToAddress util.Address, extraNonce uint64) (*txsForBlockTemplate, error) {
|
||||
// Fetch the source transactions.
|
||||
sourceTxs := g.txSource.MiningDescs()
|
||||
|
||||
// Create a new txsForBlockTemplate struct, onto which all selectedTxs
|
||||
// will be appended.
|
||||
txsForBlockTemplate, err := g.newTxsForBlockTemplate(payToAddress, sourceTxs)
|
||||
txsForBlockTemplate, err := g.newTxsForBlockTemplate(payToAddress, extraNonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,7 +90,7 @@ func (g *BlkTmplGenerator) selectTxs(payToAddress util.Address) (*txsForBlockTem
|
||||
|
||||
// newTxsForBlockTemplate creates a txsForBlockTemplate and initializes it
|
||||
// with a coinbase transaction.
|
||||
func (g *BlkTmplGenerator) newTxsForBlockTemplate(payToAddress util.Address, sourceTxs []*TxDesc) (*txsForBlockTemplate, error) {
|
||||
func (g *BlkTmplGenerator) newTxsForBlockTemplate(payToAddress util.Address, extraNonce uint64) (*txsForBlockTemplate, error) {
|
||||
// Create a new txsForBlockTemplate struct. The struct holds the mass,
|
||||
// the fees, and number of signature operations for each of the selected
|
||||
// transactions and adds an entry for the coinbase. This allows the code
|
||||
@@ -103,10 +102,6 @@ func (g *BlkTmplGenerator) newTxsForBlockTemplate(payToAddress util.Address, sou
|
||||
txFees: make([]uint64, 0),
|
||||
}
|
||||
|
||||
extraNonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coinbasePayloadExtraData, err := blockdag.CoinbasePayloadExtraData(extraNonce, CoinbaseFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -141,7 +136,7 @@ func (g *BlkTmplGenerator) collectCandidatesTxs(sourceTxs []*TxDesc) []*candidat
|
||||
|
||||
// A block can't contain non-finalized transactions.
|
||||
if !blockdag.IsFinalizedTransaction(tx, nextBlockBlueScore,
|
||||
g.timeSource.AdjustedTime()) {
|
||||
g.timeSource.Now()) {
|
||||
log.Debugf("Skipping non-finalized tx %s", tx.ID())
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/mempool"
|
||||
peerpkg "github.com/kaspanet/kaspad/peer"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
@@ -228,7 +227,7 @@ func (sm *SyncManager) startSync() {
|
||||
}
|
||||
|
||||
func (sm *SyncManager) shouldQueryPeerSelectedTips() bool {
|
||||
return sm.dag.AdjustedTime().Sub(sm.dag.CalcPastMedianTime()) > minDAGTimeDelay
|
||||
return sm.dag.Now().Sub(sm.dag.CalcPastMedianTime()) > minDAGTimeDelay
|
||||
}
|
||||
|
||||
func queueMsgGetSelectedTip(peer *peerpkg.Peer, state *peerSyncState) {
|
||||
@@ -495,18 +494,12 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) {
|
||||
// rejected as opposed to something actually going wrong, so log
|
||||
// it as such. Otherwise, something really did go wrong, so log
|
||||
// it as an actual error.
|
||||
if errors.As(err, &blockdag.RuleError{}) {
|
||||
log.Infof("Rejected block %s from %s: %s", blockHash,
|
||||
peer, err)
|
||||
} else {
|
||||
log.Errorf("Failed to process block %s: %s",
|
||||
blockHash, err)
|
||||
}
|
||||
var dbErr database.Error
|
||||
if ok := errors.As(err, &dbErr); ok && dbErr.ErrorCode ==
|
||||
database.ErrCorruption {
|
||||
panic(dbErr)
|
||||
if !errors.As(err, &blockdag.RuleError{}) {
|
||||
panic(errors.Wrapf(err, "Failed to process block %s",
|
||||
blockHash))
|
||||
}
|
||||
log.Infof("Rejected block %s from %s: %s", blockHash,
|
||||
peer, err)
|
||||
|
||||
// Convert the error into an appropriate reject message and
|
||||
// send it.
|
||||
|
||||
@@ -108,7 +108,6 @@ type GetBlockDAGInfoResult struct {
|
||||
TipHashes []string `json:"tipHashes"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
MedianTime int64 `json:"medianTime"`
|
||||
UTXOCommitment string `json:"utxoCommitment"`
|
||||
VerificationProgress float64 `json:"verificationProgress,omitempty"`
|
||||
Pruned bool `json:"pruned"`
|
||||
PruneHeight uint64 `json:"pruneHeight,omitempty"`
|
||||
@@ -387,7 +386,6 @@ type InfoDAGResult struct {
|
||||
Version string `json:"version"`
|
||||
ProtocolVersion int32 `json:"protocolVersion"`
|
||||
Blocks uint64 `json:"blocks"`
|
||||
TimeOffset int64 `json:"timeOffset"`
|
||||
Connections int32 `json:"connections"`
|
||||
Proxy string `json:"proxy"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
|
||||
@@ -11,10 +11,6 @@ import (
|
||||
// and is used to negotiate the protocol version details as well as kick start
|
||||
// the communications.
|
||||
func (sp *Peer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) {
|
||||
// Add the remote peer time as a sample for creating an offset against
|
||||
// the local clock to keep the network time in sync.
|
||||
sp.server.TimeSource.AddTimeSample(sp.Addr(), msg.Timestamp)
|
||||
|
||||
// Signal the sync manager this peer is a new sync candidate.
|
||||
sp.server.SyncManager.NewPeer(sp.Peer)
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ type Server struct {
|
||||
wg sync.WaitGroup
|
||||
nat serverutils.NAT
|
||||
db database.DB
|
||||
TimeSource blockdag.MedianTimeSource
|
||||
TimeSource blockdag.TimeSource
|
||||
services wire.ServiceFlag
|
||||
|
||||
// We add to quitWaitGroup before every instance in which we wait for
|
||||
@@ -1550,7 +1550,7 @@ func NewServer(listenAddrs []string, db database.DB, dagParams *dagconfig.Params
|
||||
newOutboundConnection: make(chan *outboundPeerConnectedMsg, config.ActiveConfig().TargetOutboundPeers),
|
||||
nat: nat,
|
||||
db: db,
|
||||
TimeSource: blockdag.NewMedianTime(),
|
||||
TimeSource: blockdag.NewTimeSource(),
|
||||
services: services,
|
||||
SigCache: txscript.NewSigCache(config.ActiveConfig().SigCacheMaxSize),
|
||||
notifyNewTransactions: notifyNewTransactions,
|
||||
|
||||
@@ -18,15 +18,14 @@ func handleGetBlockDAGInfo(s *Server, cmd interface{}, closeChan <-chan struct{}
|
||||
dag := s.cfg.DAG
|
||||
|
||||
dagInfo := &rpcmodel.GetBlockDAGInfoResult{
|
||||
DAG: params.Name,
|
||||
Blocks: dag.BlockCount(),
|
||||
Headers: dag.BlockCount(),
|
||||
TipHashes: daghash.Strings(dag.TipHashes()),
|
||||
Difficulty: getDifficultyRatio(dag.CurrentBits(), params),
|
||||
MedianTime: dag.CalcPastMedianTime().Unix(),
|
||||
UTXOCommitment: dag.UTXOCommitment(),
|
||||
Pruned: false,
|
||||
Bip9SoftForks: make(map[string]*rpcmodel.Bip9SoftForkDescription),
|
||||
DAG: params.Name,
|
||||
Blocks: dag.BlockCount(),
|
||||
Headers: dag.BlockCount(),
|
||||
TipHashes: daghash.Strings(dag.TipHashes()),
|
||||
Difficulty: getDifficultyRatio(dag.CurrentBits(), params),
|
||||
MedianTime: dag.CalcPastMedianTime().Unix(),
|
||||
Pruned: false,
|
||||
Bip9SoftForks: make(map[string]*rpcmodel.Bip9SoftForkDescription),
|
||||
}
|
||||
|
||||
// Finally, query the BIP0009 version bits state for all currently
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
"math/rand"
|
||||
@@ -69,12 +70,12 @@ type gbtWorkState struct {
|
||||
minTimestamp time.Time
|
||||
template *mining.BlockTemplate
|
||||
notifyMap map[string]map[int64]chan struct{}
|
||||
timeSource blockdag.MedianTimeSource
|
||||
timeSource blockdag.TimeSource
|
||||
}
|
||||
|
||||
// newGbtWorkState returns a new instance of a gbtWorkState with all internal
|
||||
// fields initialized and ready to use.
|
||||
func newGbtWorkState(timeSource blockdag.MedianTimeSource) *gbtWorkState {
|
||||
func newGbtWorkState(timeSource blockdag.TimeSource) *gbtWorkState {
|
||||
return &gbtWorkState{
|
||||
notifyMap: make(map[string]map[int64]chan struct{}),
|
||||
timeSource: timeSource,
|
||||
@@ -616,10 +617,17 @@ func (state *gbtWorkState) updateBlockTemplate(s *Server, useCoinbaseValue bool)
|
||||
// block template doesn't include the coinbase, so the caller
|
||||
// will ultimately create their own coinbase which pays to the
|
||||
// appropriate address(es).
|
||||
blkTemplate, err := generator.NewBlockTemplate(payAddr)
|
||||
|
||||
extraNonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
return internalRPCError("Failed to create new block "+
|
||||
"template: "+err.Error(), "")
|
||||
return internalRPCError(fmt.Sprintf("Failed to randomize "+
|
||||
"extra nonce: %s", err.Error()), "")
|
||||
}
|
||||
|
||||
blkTemplate, err := generator.NewBlockTemplate(payAddr, extraNonce)
|
||||
if err != nil {
|
||||
return internalRPCError(fmt.Sprintf("Failed to create new block "+
|
||||
"template: %s", err.Error()), "")
|
||||
}
|
||||
template = blkTemplate
|
||||
msgBlock = template.Block
|
||||
@@ -712,7 +720,7 @@ func (state *gbtWorkState) blockTemplateResult(dag *blockdag.BlockDAG, useCoinba
|
||||
template := state.template
|
||||
msgBlock := template.Block
|
||||
header := &msgBlock.Header
|
||||
adjustedTime := state.timeSource.AdjustedTime()
|
||||
adjustedTime := state.timeSource.Now()
|
||||
maxTime := adjustedTime.Add(time.Second * time.Duration(dag.TimestampDeviationTolerance))
|
||||
if header.Timestamp.After(maxTime) {
|
||||
return nil, &rpcmodel.RPCError{
|
||||
|
||||
@@ -13,7 +13,6 @@ func handleGetInfo(s *Server, cmd interface{}, closeChan <-chan struct{}) (inter
|
||||
Version: version.Version(),
|
||||
ProtocolVersion: int32(maxProtocolVersion),
|
||||
Blocks: s.cfg.DAG.BlockCount(),
|
||||
TimeOffset: int64(s.cfg.TimeSource.Offset().Seconds()),
|
||||
Connections: s.cfg.ConnMgr.ConnectedCount(),
|
||||
Proxy: config.ActiveConfig().Proxy,
|
||||
Difficulty: getDifficultyRatio(s.cfg.DAG.CurrentBits(), s.cfg.DAGParams),
|
||||
|
||||
@@ -768,7 +768,7 @@ type rpcserverConfig struct {
|
||||
|
||||
// These fields allow the RPC server to interface with the local block
|
||||
// DAG data and state.
|
||||
TimeSource blockdag.MedianTimeSource
|
||||
TimeSource blockdag.TimeSource
|
||||
DAG *blockdag.BlockDAG
|
||||
DAGParams *dagconfig.Params
|
||||
DB database.DB
|
||||
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"hash"
|
||||
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
@@ -2033,36 +2033,34 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||
script := vm.currentScript()
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
hash, err := calcSignatureHash(script, hashType, &vm.tx, vm.txIdx)
|
||||
sigHash, err := calcSignatureHash(script, hashType, &vm.tx, vm.txIdx)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
pubKey, err := ecc.ParsePubKey(pkBytes, ecc.S256())
|
||||
pubKey, err := secp256k1.DeserializeSchnorrPubKey(pkBytes)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
signature, err := ecc.ParseSignature(sigBytes)
|
||||
signature, err := secp256k1.DeserializeSchnorrSignatureFromSlice(sigBytes)
|
||||
if err != nil {
|
||||
vm.dstack.PushBool(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
var valid bool
|
||||
secpHash := secp256k1.Hash(*sigHash)
|
||||
if vm.sigCache != nil {
|
||||
var sigHash daghash.Hash
|
||||
copy(sigHash[:], hash)
|
||||
|
||||
valid = vm.sigCache.Exists(sigHash, signature, pubKey)
|
||||
if !valid && signature.Verify(hash, pubKey) {
|
||||
vm.sigCache.Add(sigHash, signature, pubKey)
|
||||
valid = vm.sigCache.Exists(secpHash, signature, pubKey)
|
||||
if !valid && pubKey.SchnorrVerify(&secpHash, signature) {
|
||||
vm.sigCache.Add(secpHash, signature, pubKey)
|
||||
valid = true
|
||||
}
|
||||
} else {
|
||||
valid = signature.Verify(hash, pubKey)
|
||||
valid = pubKey.SchnorrVerify(&secpHash, signature)
|
||||
}
|
||||
|
||||
if !valid && len(sigBytes) > 0 {
|
||||
@@ -2092,7 +2090,7 @@ func opcodeCheckSigVerify(op *parsedOpcode, vm *Engine) error {
|
||||
// the same signature multiple times when verifying a multisig.
|
||||
type parsedSigInfo struct {
|
||||
signature []byte
|
||||
parsedSignature *ecc.Signature
|
||||
parsedSignature *secp256k1.SchnorrSignature
|
||||
parsed bool
|
||||
}
|
||||
|
||||
@@ -2204,7 +2202,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||
signature := rawSig[:len(rawSig)-1]
|
||||
|
||||
// Only parse and check the signature encoding once.
|
||||
var parsedSig *ecc.Signature
|
||||
var parsedSig *secp256k1.SchnorrSignature
|
||||
if !sigInfo.parsed {
|
||||
if err := vm.checkHashTypeEncoding(hashType); err != nil {
|
||||
return err
|
||||
@@ -2214,13 +2212,12 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||
}
|
||||
|
||||
// Parse the signature.
|
||||
var err error
|
||||
parsedSig, err = ecc.ParseSignature(signature)
|
||||
|
||||
parsedSig, err = secp256k1.DeserializeSchnorrSignatureFromSlice(signature)
|
||||
sigInfo.parsed = true
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sigInfo.parsedSignature = parsedSig
|
||||
} else {
|
||||
// Skip to the next pubkey if the signature is invalid.
|
||||
@@ -2237,29 +2234,28 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||
}
|
||||
|
||||
// Parse the pubkey.
|
||||
parsedPubKey, err := ecc.ParsePubKey(pubKey, ecc.S256())
|
||||
parsedPubKey, err := secp256k1.DeserializeSchnorrPubKey(pubKey)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate the signature hash based on the signature hash type.
|
||||
hash, err := calcSignatureHash(script, hashType, &vm.tx, vm.txIdx)
|
||||
sigHash, err := calcSignatureHash(script, hashType, &vm.tx, vm.txIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secpHash := secp256k1.Hash(*sigHash)
|
||||
var valid bool
|
||||
if vm.sigCache != nil {
|
||||
var sigHash daghash.Hash
|
||||
copy(sigHash[:], hash)
|
||||
|
||||
valid = vm.sigCache.Exists(sigHash, parsedSig, parsedPubKey)
|
||||
if !valid && parsedSig.Verify(hash, parsedPubKey) {
|
||||
vm.sigCache.Add(sigHash, parsedSig, parsedPubKey)
|
||||
valid = vm.sigCache.Exists(secpHash, parsedSig, parsedPubKey)
|
||||
if !valid && parsedPubKey.SchnorrVerify(&secpHash, parsedSig) {
|
||||
vm.sigCache.Add(secpHash, parsedSig, parsedPubKey)
|
||||
valid = true
|
||||
}
|
||||
} else {
|
||||
valid = parsedSig.Verify(hash, parsedPubKey)
|
||||
valid = parsedPubKey.SchnorrVerify(&secpHash, parsedSig)
|
||||
}
|
||||
|
||||
if valid {
|
||||
|
||||
@@ -285,7 +285,7 @@ func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx {
|
||||
// CalcSignatureHash will, given a script and hash type for the current script
|
||||
// engine instance, calculate the signature hash to be used for signing and
|
||||
// verification.
|
||||
func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) {
|
||||
func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx int) (*daghash.Hash, error) {
|
||||
parsedScript, err := parseScript(script)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("cannot parse output script: %s", err)
|
||||
@@ -296,7 +296,7 @@ func CalcSignatureHash(script []byte, hashType SigHashType, tx *wire.MsgTx, idx
|
||||
// calcSignatureHash will, given a script and hash type for the current script
|
||||
// engine instance, calculate the signature hash to be used for signing and
|
||||
// verification.
|
||||
func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.MsgTx, idx int) ([]byte, error) {
|
||||
func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.MsgTx, idx int) (*daghash.Hash, error) {
|
||||
// The SigHashSingle signature type signs only the corresponding input
|
||||
// and output (the output with the same index number as the input).
|
||||
//
|
||||
@@ -367,7 +367,8 @@ func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.Msg
|
||||
wbuf := bytes.NewBuffer(make([]byte, 0, txCopy.SerializeSize()+4))
|
||||
txCopy.Serialize(wbuf)
|
||||
binary.Write(wbuf, binary.LittleEndian, hashType)
|
||||
return daghash.DoubleHashB(wbuf.Bytes()), nil
|
||||
hash := daghash.DoubleHashH(wbuf.Bytes())
|
||||
return &hash, nil
|
||||
}
|
||||
|
||||
// asSmallInt returns the passed opcode, which must be true according to
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
package txscript
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// sigCacheEntry represents an entry in the SigCache. Entries within the
|
||||
@@ -18,8 +16,8 @@ import (
|
||||
// match. In the occasion that two sigHashes collide, the newer sigHash will
|
||||
// simply overwrite the existing entry.
|
||||
type sigCacheEntry struct {
|
||||
sig *ecc.Signature
|
||||
pubKey *ecc.PublicKey
|
||||
sig *secp256k1.SchnorrSignature
|
||||
pubKey *secp256k1.SchnorrPublicKey
|
||||
}
|
||||
|
||||
// SigCache implements an ECDSA signature verification cache with a randomized
|
||||
@@ -34,7 +32,7 @@ type sigCacheEntry struct {
|
||||
// if they've already been seen and verified within the mempool.
|
||||
type SigCache struct {
|
||||
sync.RWMutex
|
||||
validSigs map[daghash.Hash]sigCacheEntry
|
||||
validSigs map[secp256k1.Hash]sigCacheEntry
|
||||
maxEntries uint
|
||||
}
|
||||
|
||||
@@ -45,7 +43,7 @@ type SigCache struct {
|
||||
// cache to exceed the max.
|
||||
func NewSigCache(maxEntries uint) *SigCache {
|
||||
return &SigCache{
|
||||
validSigs: make(map[daghash.Hash]sigCacheEntry, maxEntries),
|
||||
validSigs: make(map[secp256k1.Hash]sigCacheEntry, maxEntries),
|
||||
maxEntries: maxEntries,
|
||||
}
|
||||
}
|
||||
@@ -55,7 +53,7 @@ func NewSigCache(maxEntries uint) *SigCache {
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access. Readers won't be blocked
|
||||
// unless there exists a writer, adding an entry to the SigCache.
|
||||
func (s *SigCache) Exists(sigHash daghash.Hash, sig *ecc.Signature, pubKey *ecc.PublicKey) bool {
|
||||
func (s *SigCache) Exists(sigHash secp256k1.Hash, sig *secp256k1.SchnorrSignature, pubKey *secp256k1.SchnorrPublicKey) bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
entry, ok := s.validSigs[sigHash]
|
||||
@@ -70,7 +68,7 @@ func (s *SigCache) Exists(sigHash daghash.Hash, sig *ecc.Signature, pubKey *ecc.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access. Writers will block
|
||||
// simultaneous readers until function execution has concluded.
|
||||
func (s *SigCache) Add(sigHash daghash.Hash, sig *ecc.Signature, pubKey *ecc.PublicKey) {
|
||||
func (s *SigCache) Add(sigHash secp256k1.Hash, sig *secp256k1.SchnorrSignature, pubKey *secp256k1.SchnorrPublicKey) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
|
||||
@@ -6,32 +6,35 @@ package txscript
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// genRandomSig returns a random message, a signature of the message under the
|
||||
// public key and the public key. This function is used to generate randomized
|
||||
// test data.
|
||||
func genRandomSig() (*daghash.Hash, *ecc.Signature, *ecc.PublicKey, error) {
|
||||
privKey, err := ecc.NewPrivateKey(ecc.S256())
|
||||
func genRandomSig() (*secp256k1.Hash, *secp256k1.SchnorrSignature, *secp256k1.SchnorrPublicKey, error) {
|
||||
privKey, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
var msgHash daghash.Hash
|
||||
msgHash := &secp256k1.Hash{}
|
||||
if _, err := rand.Read(msgHash[:]); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
sig, err := privKey.Sign(msgHash[:])
|
||||
sig, err := privKey.SchnorrSign(msgHash)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return &msgHash, sig, privKey.PubKey(), nil
|
||||
pubkey, err := privKey.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return msgHash, sig, pubkey, nil
|
||||
}
|
||||
|
||||
// TestSigCacheAddExists tests the ability to add, and later check the
|
||||
@@ -42,15 +45,16 @@ func TestSigCacheAddExists(t *testing.T) {
|
||||
// Generate a random sigCache entry triplet.
|
||||
msg1, sig1, key1, err := genRandomSig()
|
||||
if err != nil {
|
||||
t.Errorf("unable to generate random signature test data")
|
||||
t.Fatalf("unable to generate random signature test data")
|
||||
}
|
||||
|
||||
// Add the triplet to the signature cache.
|
||||
sigCache.Add(*msg1, sig1, key1)
|
||||
|
||||
// The previously added triplet should now be found within the sigcache.
|
||||
sig1Copy, _ := ecc.ParseSignature(sig1.Serialize())
|
||||
key1Copy, _ := ecc.ParsePubKey(key1.SerializeCompressed(), ecc.S256())
|
||||
sig1Copy := secp256k1.DeserializeSchnorrSignature(sig1.Serialize())
|
||||
key1Serialized, _ := key1.SerializeCompressed()
|
||||
key1Copy, _ := secp256k1.DeserializeSchnorrPubKey(key1Serialized)
|
||||
if !sigCache.Exists(*msg1, sig1Copy, key1Copy) {
|
||||
t.Errorf("previously added item not found in signature cache")
|
||||
}
|
||||
@@ -73,8 +77,9 @@ func TestSigCacheAddEvictEntry(t *testing.T) {
|
||||
|
||||
sigCache.Add(*msg, sig, key)
|
||||
|
||||
sigCopy, _ := ecc.ParseSignature(sig.Serialize())
|
||||
keyCopy, _ := ecc.ParsePubKey(key.SerializeCompressed(), ecc.S256())
|
||||
sigCopy := secp256k1.DeserializeSchnorrSignature(sig.Serialize())
|
||||
keySerialized, _ := key.SerializeCompressed()
|
||||
keyCopy, _ := secp256k1.DeserializeSchnorrPubKey(keySerialized)
|
||||
if !sigCache.Exists(*msg, sigCopy, keyCopy) {
|
||||
t.Errorf("previously added item not found in signature" +
|
||||
"cache")
|
||||
@@ -102,8 +107,9 @@ func TestSigCacheAddEvictEntry(t *testing.T) {
|
||||
}
|
||||
|
||||
// The entry added above should be found within the sigcache.
|
||||
sigNewCopy, _ := ecc.ParseSignature(sigNew.Serialize())
|
||||
keyNewCopy, _ := ecc.ParsePubKey(keyNew.SerializeCompressed(), ecc.S256())
|
||||
sigNewCopy := secp256k1.DeserializeSchnorrSignature(sigNew.Serialize())
|
||||
keyNewSerialized, _ := keyNew.SerializeCompressed()
|
||||
keyNewCopy, _ := secp256k1.DeserializeSchnorrPubKey(keyNewSerialized)
|
||||
if !sigCache.Exists(*msgNew, sigNewCopy, keyNewCopy) {
|
||||
t.Fatalf("previously added item not found in signature cache")
|
||||
}
|
||||
@@ -118,15 +124,16 @@ func TestSigCacheAddMaxEntriesZeroOrNegative(t *testing.T) {
|
||||
// Generate a random sigCache entry triplet.
|
||||
msg1, sig1, key1, err := genRandomSig()
|
||||
if err != nil {
|
||||
t.Errorf("unable to generate random signature test data")
|
||||
t.Fatalf("unable to generate random signature test data")
|
||||
}
|
||||
|
||||
// Add the triplet to the signature cache.
|
||||
sigCache.Add(*msg1, sig1, key1)
|
||||
|
||||
// The generated triplet should not be found.
|
||||
sig1Copy, _ := ecc.ParseSignature(sig1.Serialize())
|
||||
key1Copy, _ := ecc.ParsePubKey(key1.SerializeCompressed(), ecc.S256())
|
||||
sig1Copy := secp256k1.DeserializeSchnorrSignature(sig1.Serialize())
|
||||
key1Serialized, _ := key1.SerializeCompressed()
|
||||
key1Copy, _ := secp256k1.DeserializeSchnorrPubKey(key1Serialized)
|
||||
if sigCache.Exists(*msg1, sig1Copy, key1Copy) {
|
||||
t.Errorf("previously added signature found in sigcache, but" +
|
||||
"shouldn't have been")
|
||||
|
||||
@@ -5,29 +5,30 @@
|
||||
package txscript
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
// RawTxInSignature returns the serialized ECDSA signature for the input idx of
|
||||
// RawTxInSignature returns the serialized Schnorr signature for the input idx of
|
||||
// the given transaction, with hashType appended to it.
|
||||
func RawTxInSignature(tx *wire.MsgTx, idx int, script []byte,
|
||||
hashType SigHashType, key *ecc.PrivateKey) ([]byte, error) {
|
||||
hashType SigHashType, key *secp256k1.PrivateKey) ([]byte, error) {
|
||||
|
||||
hash, err := CalcSignatureHash(script, hashType, tx, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signature, err := key.Sign(hash)
|
||||
secpHash := secp256k1.Hash(*hash)
|
||||
signature, err := key.SchnorrSign(&secpHash)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("cannot sign tx input: %s", err)
|
||||
}
|
||||
|
||||
return append(signature.Serialize(), byte(hashType)), nil
|
||||
return append(signature.Serialize()[:], byte(hashType)), nil
|
||||
}
|
||||
|
||||
// SignatureScript creates an input signature script for tx to spend KAS sent
|
||||
@@ -38,18 +39,24 @@ func RawTxInSignature(tx *wire.MsgTx, idx int, script []byte,
|
||||
// as the idx'th input. privKey is serialized in either a compressed or
|
||||
// uncompressed format based on compress. This format must match the same format
|
||||
// used to generate the payment address, or the script validation will fail.
|
||||
func SignatureScript(tx *wire.MsgTx, idx int, script []byte, hashType SigHashType, privKey *ecc.PrivateKey, compress bool) ([]byte, error) {
|
||||
func SignatureScript(tx *wire.MsgTx, idx int, script []byte, hashType SigHashType, privKey *secp256k1.PrivateKey, compress bool) ([]byte, error) {
|
||||
sig, err := RawTxInSignature(tx, idx, script, hashType, privKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&privKey.PublicKey)
|
||||
pk, err := privKey.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pkData []byte
|
||||
if compress {
|
||||
pkData = pk.SerializeCompressed()
|
||||
pkData, err = pk.SerializeCompressed()
|
||||
} else {
|
||||
pkData = pk.SerializeUncompressed()
|
||||
pkData, err = pk.SerializeUncompressed()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewScriptBuilder().AddData(sig).AddData(pkData).Script()
|
||||
@@ -159,14 +166,14 @@ func mergeScripts(dagParams *dagconfig.Params, tx *wire.MsgTx, idx int,
|
||||
// KeyDB is an interface type provided to SignTxOutput, it encapsulates
|
||||
// any user state required to get the private keys for an address.
|
||||
type KeyDB interface {
|
||||
GetKey(util.Address) (*ecc.PrivateKey, bool, error)
|
||||
GetKey(util.Address) (*secp256k1.PrivateKey, bool, error)
|
||||
}
|
||||
|
||||
// KeyClosure implements KeyDB with a closure.
|
||||
type KeyClosure func(util.Address) (*ecc.PrivateKey, bool, error)
|
||||
type KeyClosure func(util.Address) (*secp256k1.PrivateKey, bool, error)
|
||||
|
||||
// GetKey implements KeyDB by returning the result of calling the closure.
|
||||
func (kc KeyClosure) GetKey(address util.Address) (*ecc.PrivateKey,
|
||||
func (kc KeyClosure) GetKey(address util.Address) (*secp256k1.PrivateKey,
|
||||
bool, error) {
|
||||
return kc(address)
|
||||
}
|
||||
|
||||
@@ -6,29 +6,29 @@ package txscript
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/pkg/errors"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
type addressToKey struct {
|
||||
key *ecc.PrivateKey
|
||||
key *secp256k1.PrivateKey
|
||||
compressed bool
|
||||
}
|
||||
|
||||
func mkGetKey(keys map[string]addressToKey) KeyDB {
|
||||
if keys == nil {
|
||||
return KeyClosure(func(addr util.Address) (*ecc.PrivateKey,
|
||||
return KeyClosure(func(addr util.Address) (*secp256k1.PrivateKey,
|
||||
bool, error) {
|
||||
return nil, false, errors.New("nope")
|
||||
})
|
||||
}
|
||||
return KeyClosure(func(addr util.Address) (*ecc.PrivateKey,
|
||||
return KeyClosure(func(addr util.Address) (*secp256k1.PrivateKey,
|
||||
bool, error) {
|
||||
a2k, ok := keys[addr.EncodeAddress()]
|
||||
if !ok {
|
||||
@@ -139,17 +139,29 @@ func TestSignTxOutput(t *testing.T) {
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.TxIn {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
key, err := ecc.NewPrivateKey(ecc.S256())
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %v",
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
break
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&key.PublicKey).
|
||||
SerializeUncompressed()
|
||||
pubKey, err := key.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a publickey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
uncompressedPubKey, err := pubKey.SerializeUncompressed()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a pubkey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.Hash160(pk), util.Bech32PrefixKaspaTest)
|
||||
util.Hash160(uncompressedPubKey), util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -176,17 +188,28 @@ func TestSignTxOutput(t *testing.T) {
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.TxIn {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
key, err := ecc.NewPrivateKey(ecc.S256())
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %v",
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
break
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&key.PublicKey).
|
||||
SerializeUncompressed()
|
||||
pubKey, err := key.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a publickey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
uncompressedPubKey, err := pubKey.SerializeUncompressed()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a pubkey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.Hash160(pk), util.Bech32PrefixKaspaTest)
|
||||
util.Hash160(uncompressedPubKey), util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -237,17 +260,29 @@ func TestSignTxOutput(t *testing.T) {
|
||||
for i := range tx.TxIn {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
|
||||
key, err := ecc.NewPrivateKey(ecc.S256())
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %v",
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
break
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&key.PublicKey).
|
||||
SerializeCompressed()
|
||||
pubKey, err := key.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a publickey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
compressedPubKey, err := pubKey.SerializeCompressed()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a pubkey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.Hash160(pk), util.Bech32PrefixKaspaTest)
|
||||
util.Hash160(compressedPubKey), util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -275,17 +310,29 @@ func TestSignTxOutput(t *testing.T) {
|
||||
for i := range tx.TxIn {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
|
||||
key, err := ecc.NewPrivateKey(ecc.S256())
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %v",
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
break
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&key.PublicKey).
|
||||
SerializeCompressed()
|
||||
pubKey, err := key.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a publickey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
compressedPubKey, err := pubKey.SerializeCompressed()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a pubkey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.Hash160(pk), util.Bech32PrefixKaspaTest)
|
||||
util.Hash160(compressedPubKey), util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -336,17 +383,29 @@ func TestSignTxOutput(t *testing.T) {
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.TxIn {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
key, err := ecc.NewPrivateKey(ecc.S256())
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %v",
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
break
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&key.PublicKey).
|
||||
SerializeUncompressed()
|
||||
pubKey, err := key.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a publickey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
uncompressedPubKey, err := pubKey.SerializeUncompressed()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a pubkey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.Hash160(pk), util.Bech32PrefixKaspaTest)
|
||||
util.Hash160(uncompressedPubKey), util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -392,17 +451,29 @@ func TestSignTxOutput(t *testing.T) {
|
||||
for _, hashType := range hashTypes {
|
||||
for i := range tx.TxIn {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
key, err := ecc.NewPrivateKey(ecc.S256())
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %v",
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
break
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&key.PublicKey).
|
||||
SerializeUncompressed()
|
||||
pubKey, err := key.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a publickey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
uncompressedPubKey, err := pubKey.SerializeUncompressed()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a pubkey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.Hash160(pk), util.Bech32PrefixKaspaTest)
|
||||
util.Hash160(uncompressedPubKey), util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -474,17 +545,29 @@ func TestSignTxOutput(t *testing.T) {
|
||||
for i := range tx.TxIn {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
|
||||
key, err := ecc.NewPrivateKey(ecc.S256())
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %v",
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
break
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&key.PublicKey).
|
||||
SerializeCompressed()
|
||||
pubKey, err := key.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a publickey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
compressedPubKey, err := pubKey.SerializeCompressed()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a pubkey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.Hash160(pk), util.Bech32PrefixKaspaTest)
|
||||
util.Hash160(compressedPubKey), util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -530,17 +613,29 @@ func TestSignTxOutput(t *testing.T) {
|
||||
for i := range tx.TxIn {
|
||||
msg := fmt.Sprintf("%d:%d", hashType, i)
|
||||
|
||||
key, err := ecc.NewPrivateKey(ecc.S256())
|
||||
key, err := secp256k1.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make privKey for %s: %v",
|
||||
t.Errorf("failed to make privKey for %s: %s",
|
||||
msg, err)
|
||||
break
|
||||
}
|
||||
|
||||
pk := (*ecc.PublicKey)(&key.PublicKey).
|
||||
SerializeCompressed()
|
||||
pubKey, err := key.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a publickey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
compressedPubKey, err := pubKey.SerializeCompressed()
|
||||
if err != nil {
|
||||
t.Errorf("failed to make a pubkey for %s: %s",
|
||||
key, err)
|
||||
break
|
||||
}
|
||||
|
||||
address, err := util.NewAddressPubKeyHash(
|
||||
util.Hash160(pk), util.Bech32PrefixKaspaTest)
|
||||
util.Hash160(compressedPubKey), util.Bech32PrefixKaspaTest)
|
||||
if err != nil {
|
||||
t.Errorf("failed to make address for %s: %v",
|
||||
msg, err)
|
||||
@@ -629,7 +724,7 @@ var coinbaseOutpoint = &wire.Outpoint{
|
||||
// Pregenerated private key, with associated public key and scriptPubKeys
|
||||
// for the uncompressed and compressed hash160.
|
||||
var (
|
||||
privKeyD = []byte{0x6b, 0x0f, 0xd8, 0xda, 0x54, 0x22, 0xd0, 0xb7,
|
||||
privKeyD = secp256k1.SerializedPrivateKey{0x6b, 0x0f, 0xd8, 0xda, 0x54, 0x22, 0xd0, 0xb7,
|
||||
0xb4, 0xfc, 0x4e, 0x55, 0xd4, 0x88, 0x42, 0xb3, 0xa1, 0x65,
|
||||
0xac, 0x70, 0x7f, 0x3d, 0xa4, 0x39, 0x5e, 0xcb, 0x3b, 0xb0,
|
||||
0xd6, 0x0e, 0x06, 0x92}
|
||||
@@ -864,7 +959,7 @@ var sigScriptTests = []tstSigScript{
|
||||
func TestSignatureScript(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
privKey, _ := ecc.PrivKeyFromBytes(ecc.S256(), privKeyD)
|
||||
privKey, _ := secp256k1.DeserializePrivateKey(&privKeyD)
|
||||
|
||||
nexttest:
|
||||
for i := range sigScriptTests {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -221,8 +221,13 @@ func TestFilterInsertKey(t *testing.T) {
|
||||
}
|
||||
|
||||
f := bloom.NewFilter(2, 0, 0.001, wire.BloomUpdateAll)
|
||||
f.Add(wif.SerializePubKey())
|
||||
f.Add(util.Hash160(wif.SerializePubKey()))
|
||||
serializedPubKey, err := wif.SerializePubKey()
|
||||
if err != nil {
|
||||
t.Errorf("TestFilterInsertKey SerializePubKey failed: %v", err)
|
||||
return
|
||||
}
|
||||
f.Add(serializedPubKey)
|
||||
f.Add(util.Hash160(serializedPubKey))
|
||||
|
||||
want, err := hex.DecodeString("038fc16b080000000000000001")
|
||||
if err != nil {
|
||||
|
||||
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,10 +2,11 @@ package panics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/logs"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/logs"
|
||||
)
|
||||
|
||||
// HandlePanic recovers panics, log them, runs an optional panicHandler,
|
||||
@@ -24,15 +25,16 @@ func HandlePanic(log *logs.Logger, goroutineStackTrace []byte) {
|
||||
}
|
||||
log.Criticalf("Stack trace: %s", debug.Stack())
|
||||
log.Backend().Close()
|
||||
panicHandlerDone <- struct{}{}
|
||||
close(panicHandlerDone)
|
||||
}()
|
||||
|
||||
const panicHandlerTimeout = 5 * time.Second
|
||||
select {
|
||||
case <-time.Tick(panicHandlerTimeout):
|
||||
case <-time.After(panicHandlerTimeout):
|
||||
fmt.Fprintln(os.Stderr, "Couldn't handle a fatal error. Exiting...")
|
||||
case <-panicHandlerDone:
|
||||
}
|
||||
log.Criticalf("Exiting")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
32
util/wif.go
32
util/wif.go
@@ -6,7 +6,7 @@ package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/util/base58"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
@@ -30,7 +30,7 @@ const compressMagic byte = 0x01
|
||||
// by calling NewWIF.
|
||||
type WIF struct {
|
||||
// PrivKey is the private key being imported or exported.
|
||||
PrivKey *ecc.PrivateKey
|
||||
PrivKey *secp256k1.PrivateKey
|
||||
|
||||
// CompressPubKey specifies whether the address controlled by the
|
||||
// imported or exported private key was created by hashing a
|
||||
@@ -47,7 +47,7 @@ type WIF struct {
|
||||
// as a string encoded in the Wallet Import Format. The compress argument
|
||||
// specifies whether the address intended to be imported or exported was created
|
||||
// by serializing the public key compressed rather than uncompressed.
|
||||
func NewWIF(privKey *ecc.PrivateKey, privateKeyID byte, compress bool) (*WIF, error) {
|
||||
func NewWIF(privKey *secp256k1.PrivateKey, privateKeyID byte, compress bool) (*WIF, error) {
|
||||
return &WIF{privKey, compress, privateKeyID}, nil
|
||||
}
|
||||
|
||||
@@ -85,12 +85,12 @@ func DecodeWIF(wif string) (*WIF, error) {
|
||||
// Length of base58 decoded WIF must be 32 bytes + an optional 1 byte
|
||||
// (0x01) if compressed, plus 1 byte for netID + 4 bytes of checksum.
|
||||
switch decodedLen {
|
||||
case 1 + ecc.PrivKeyBytesLen + 1 + 4:
|
||||
case 1 + secp256k1.SerializedPrivateKeySize + 1 + 4:
|
||||
if decoded[33] != compressMagic {
|
||||
return nil, ErrMalformedPrivateKey
|
||||
}
|
||||
compress = true
|
||||
case 1 + ecc.PrivKeyBytesLen + 4:
|
||||
case 1 + secp256k1.SerializedPrivateKeySize + 4:
|
||||
compress = false
|
||||
default:
|
||||
return nil, ErrMalformedPrivateKey
|
||||
@@ -101,9 +101,9 @@ func DecodeWIF(wif string) (*WIF, error) {
|
||||
// private key.
|
||||
var tosum []byte
|
||||
if compress {
|
||||
tosum = decoded[:1+ecc.PrivKeyBytesLen+1]
|
||||
tosum = decoded[:1+secp256k1.SerializedPrivateKeySize+1]
|
||||
} else {
|
||||
tosum = decoded[:1+ecc.PrivKeyBytesLen]
|
||||
tosum = decoded[:1+secp256k1.SerializedPrivateKeySize]
|
||||
}
|
||||
cksum := daghash.DoubleHashB(tosum)[:4]
|
||||
if !bytes.Equal(cksum, decoded[decodedLen-4:]) {
|
||||
@@ -111,8 +111,11 @@ func DecodeWIF(wif string) (*WIF, error) {
|
||||
}
|
||||
|
||||
netID := decoded[0]
|
||||
privKeyBytes := decoded[1 : 1+ecc.PrivKeyBytesLen]
|
||||
privKey, _ := ecc.PrivKeyFromBytes(ecc.S256(), privKeyBytes)
|
||||
privKeyBytes := decoded[1 : 1+secp256k1.SerializedPrivateKeySize]
|
||||
privKey, err := secp256k1.DeserializePrivateKeyFromSlice(privKeyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &WIF{privKey, compress, netID}, nil
|
||||
}
|
||||
|
||||
@@ -124,7 +127,7 @@ func (w *WIF) String() string {
|
||||
// is one byte for the network, 32 bytes of private key, possibly one
|
||||
// extra byte if the pubkey is to be compressed, and finally four
|
||||
// bytes of checksum.
|
||||
encodeLen := 1 + ecc.PrivKeyBytesLen + 4
|
||||
encodeLen := 1 + secp256k1.SerializedPrivateKeySize + 4
|
||||
if w.CompressPubKey {
|
||||
encodeLen++
|
||||
}
|
||||
@@ -133,7 +136,7 @@ func (w *WIF) String() string {
|
||||
a = append(a, w.netID)
|
||||
// Pad and append bytes manually, instead of using Serialize, to
|
||||
// avoid another call to make.
|
||||
a = paddedAppend(ecc.PrivKeyBytesLen, a, w.PrivKey.D.Bytes())
|
||||
a = paddedAppend(secp256k1.SerializedPrivateKeySize, a, w.PrivKey.Serialize()[:])
|
||||
if w.CompressPubKey {
|
||||
a = append(a, compressMagic)
|
||||
}
|
||||
@@ -145,8 +148,11 @@ func (w *WIF) String() string {
|
||||
// SerializePubKey serializes the associated public key of the imported or
|
||||
// exported private key in either a compressed or uncompressed format. The
|
||||
// serialization format chosen depends on the value of w.CompressPubKey.
|
||||
func (w *WIF) SerializePubKey() []byte {
|
||||
pk := (*ecc.PublicKey)(&w.PrivKey.PublicKey)
|
||||
func (w *WIF) SerializePubKey() ([]byte, error) {
|
||||
pk, err := w.PrivKey.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if w.CompressPubKey {
|
||||
return pk.SerializeCompressed()
|
||||
}
|
||||
|
||||
@@ -7,24 +7,32 @@ package util_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/ecc"
|
||||
. "github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeWIF(t *testing.T) {
|
||||
priv1, _ := ecc.PrivKeyFromBytes(ecc.S256(), []byte{
|
||||
priv1, err := secp256k1.DeserializePrivateKey(&secp256k1.SerializedPrivateKey{
|
||||
0x0c, 0x28, 0xfc, 0xa3, 0x86, 0xc7, 0xa2, 0x27,
|
||||
0x60, 0x0b, 0x2f, 0xe5, 0x0b, 0x7c, 0xae, 0x11,
|
||||
0xec, 0x86, 0xd3, 0xbf, 0x1f, 0xbe, 0x47, 0x1b,
|
||||
0xe8, 0x98, 0x27, 0xe1, 0x9d, 0x72, 0xaa, 0x1d})
|
||||
|
||||
priv2, _ := ecc.PrivKeyFromBytes(ecc.S256(), []byte{
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
priv2, err := secp256k1.DeserializePrivateKey(&secp256k1.SerializedPrivateKey{
|
||||
0xdd, 0xa3, 0x5a, 0x14, 0x88, 0xfb, 0x97, 0xb6,
|
||||
0xeb, 0x3f, 0xe6, 0xe9, 0xef, 0x2a, 0x25, 0x81,
|
||||
0x4e, 0x39, 0x6f, 0xb5, 0xdc, 0x29, 0x5f, 0xe9,
|
||||
0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wif1, err := NewWIF(priv1, dagconfig.MainnetParams.PrivateKeyID, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -10,8 +10,8 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
|
||||
|
||||
const (
|
||||
appMajor uint = 0
|
||||
appMinor uint = 1
|
||||
appPatch uint = 2
|
||||
appMinor uint = 2
|
||||
appPatch uint = 0
|
||||
)
|
||||
|
||||
// appBuild is defined as a variable so it can be overridden during the build
|
||||
|
||||
@@ -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
|
||||
buf []byte // Wire encoding
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user