Add readExtraAliasEntryOld (tag 20) and readExtraAliasEntry (tag 33) wire readers so the node can deserialise blocks containing alias registrations. Without these readers, mainnet sync would fail on any block with an alias transaction. Three round-trip tests validate the new readers. Also apply AX-2 (comments as usage examples) across 12 files: add concrete usage-example comments to exported functions in config/, types/, wire/, chain/, difficulty/, and consensus/. Fix stale doc in consensus/doc.go that incorrectly referenced *config.ChainConfig. Co-Authored-By: Virgil <virgil@lethean.io>
1211 lines
32 KiB
Go
1211 lines
32 KiB
Go
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
|
//
|
|
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package wire
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"dappco.re/go/core/blockchain/types"
|
|
)
|
|
|
|
func TestCoinbaseTxEncodeDecode_Good(t *testing.T) {
|
|
// Build a minimal v1 coinbase transaction.
|
|
tx := types.Transaction{
|
|
Version: 1,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 42}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 1000000,
|
|
Target: types.TxOutToKey{
|
|
Key: types.PublicKey{0xDE, 0xAD},
|
|
MixAttr: 0,
|
|
},
|
|
}},
|
|
Extra: EncodeVarint(0), // empty extra (count=0)
|
|
}
|
|
|
|
// Encode prefix.
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
// Decode prefix.
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransactionPrefix(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
if got.Version != tx.Version {
|
|
t.Errorf("version: got %d, want %d", got.Version, tx.Version)
|
|
}
|
|
if len(got.Vin) != 1 {
|
|
t.Fatalf("vin count: got %d, want 1", len(got.Vin))
|
|
}
|
|
gen, ok := got.Vin[0].(types.TxInputGenesis)
|
|
if !ok {
|
|
t.Fatalf("vin[0] type: got %T, want TxInputGenesis", got.Vin[0])
|
|
}
|
|
if gen.Height != 42 {
|
|
t.Errorf("height: got %d, want 42", gen.Height)
|
|
}
|
|
if len(got.Vout) != 1 {
|
|
t.Fatalf("vout count: got %d, want 1", len(got.Vout))
|
|
}
|
|
bare, ok := got.Vout[0].(types.TxOutputBare)
|
|
if !ok {
|
|
t.Fatalf("vout[0] type: got %T, want TxOutputBare", got.Vout[0])
|
|
}
|
|
if bare.Amount != 1000000 {
|
|
t.Errorf("amount: got %d, want 1000000", bare.Amount)
|
|
}
|
|
toKey, ok := bare.Target.(types.TxOutToKey)
|
|
if !ok {
|
|
t.Fatalf("target type: got %T, want TxOutToKey", bare.Target)
|
|
}
|
|
if toKey.Key[0] != 0xDE || toKey.Key[1] != 0xAD {
|
|
t.Errorf("target key: got %x, want DE AD...", toKey.Key[:2])
|
|
}
|
|
}
|
|
|
|
func TestFullTxRoundTrip_Good(t *testing.T) {
|
|
// Build a v1 coinbase transaction with empty signatures and attachment.
|
|
tx := types.Transaction{
|
|
Version: 1,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 0}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 5000000000,
|
|
Target: types.TxOutToKey{
|
|
Key: types.PublicKey{0x01, 0x02, 0x03},
|
|
MixAttr: 0,
|
|
},
|
|
}},
|
|
Extra: EncodeVarint(0), // empty extra
|
|
Attachment: EncodeVarint(0), // empty attachment
|
|
}
|
|
|
|
// Encode full transaction.
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransaction(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
encoded := buf.Bytes()
|
|
|
|
// Decode full transaction.
|
|
dec := NewDecoder(bytes.NewReader(encoded))
|
|
got := DecodeTransaction(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
// Re-encode and compare bytes.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransaction(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
if !bytes.Equal(rtBuf.Bytes(), encoded) {
|
|
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), encoded)
|
|
}
|
|
}
|
|
|
|
func TestTransactionHash_Good(t *testing.T) {
|
|
// TransactionHash should equal TransactionPrefixHash for all versions.
|
|
// Confirmed from C++ source: get_transaction_hash delegates to
|
|
// get_transaction_prefix_hash for all transaction versions.
|
|
tx := types.Transaction{
|
|
Version: 1,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 0}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 5000000000,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{0x01, 0x02, 0x03}},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
Attachment: EncodeVarint(0),
|
|
}
|
|
|
|
txHash := TransactionHash(&tx)
|
|
prefixHash := TransactionPrefixHash(&tx)
|
|
|
|
// TransactionHash always delegates to TransactionPrefixHash.
|
|
if txHash != prefixHash {
|
|
t.Error("TransactionHash should equal TransactionPrefixHash")
|
|
}
|
|
|
|
// Verify manual consistency.
|
|
var prefBuf bytes.Buffer
|
|
enc := NewEncoder(&prefBuf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
|
|
if Keccak256(prefBuf.Bytes()) != [32]byte(prefixHash) {
|
|
t.Error("TransactionPrefixHash does not match manual prefix encoding")
|
|
}
|
|
}
|
|
|
|
func TestTxInputToKeyRoundTrip_Good(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: 1,
|
|
Vin: []types.TxInput{types.TxInputToKey{
|
|
Amount: 100,
|
|
KeyOffsets: []types.TxOutRef{
|
|
{Tag: types.RefTypeGlobalIndex, GlobalIndex: 42},
|
|
{Tag: types.RefTypeGlobalIndex, GlobalIndex: 7},
|
|
},
|
|
KeyImage: types.KeyImage{0xFF},
|
|
EtcDetails: EncodeVarint(0),
|
|
}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 50,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{0xAB}},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
Attachment: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransaction(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransaction(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
toKey, ok := got.Vin[0].(types.TxInputToKey)
|
|
if !ok {
|
|
t.Fatalf("vin[0] type: got %T, want TxInputToKey", got.Vin[0])
|
|
}
|
|
if toKey.Amount != 100 {
|
|
t.Errorf("amount: got %d, want 100", toKey.Amount)
|
|
}
|
|
if len(toKey.KeyOffsets) != 2 {
|
|
t.Fatalf("key_offsets: got %d, want 2", len(toKey.KeyOffsets))
|
|
}
|
|
if toKey.KeyOffsets[0].GlobalIndex != 42 {
|
|
t.Errorf("key_offsets[0]: got %d, want 42", toKey.KeyOffsets[0].GlobalIndex)
|
|
}
|
|
if toKey.KeyImage[0] != 0xFF {
|
|
t.Errorf("key_image[0]: got 0x%02x, want 0xFF", toKey.KeyImage[0])
|
|
}
|
|
}
|
|
|
|
func TestExtraVariantTags_Good(t *testing.T) {
|
|
// Test that various extra variant tags decode and re-encode correctly.
|
|
tests := []struct {
|
|
name string
|
|
data []byte // raw extra bytes (including varint count prefix)
|
|
}{
|
|
{
|
|
name: "public_key",
|
|
// count=1, tag=22 (crypto::public_key), 32 bytes of key
|
|
data: append([]byte{0x01, tagPublicKey}, make([]byte, 32)...),
|
|
},
|
|
{
|
|
name: "unlock_time",
|
|
// count=1, tag=14 (etc_tx_details_unlock_time), varint(100)=0x64
|
|
data: []byte{0x01, tagUnlockTime, 0x64},
|
|
},
|
|
{
|
|
name: "tx_details_flags",
|
|
// count=1, tag=16, varint(1)=0x01
|
|
data: []byte{0x01, tagTxDetailsFlags, 0x01},
|
|
},
|
|
{
|
|
name: "derivation_hint",
|
|
// count=1, tag=11, string len=3, "abc"
|
|
data: []byte{0x01, tagTxDerivationHint, 0x03, 'a', 'b', 'c'},
|
|
},
|
|
{
|
|
name: "user_data",
|
|
// count=1, tag=19, string len=2, "hi"
|
|
data: []byte{0x01, tagExtraUserData, 0x02, 'h', 'i'},
|
|
},
|
|
{
|
|
name: "extra_padding",
|
|
// count=1, tag=21, vector count=4, 4 bytes
|
|
data: []byte{0x01, tagExtraPadding, 0x04, 0x00, 0x00, 0x00, 0x00},
|
|
},
|
|
{
|
|
name: "crypto_checksum",
|
|
// count=1, tag=10, 8 bytes (two uint32 LE)
|
|
data: []byte{0x01, tagTxCryptoChecksum, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00},
|
|
},
|
|
{
|
|
name: "signed_parts",
|
|
// count=1, tag=17, two varints: n_outs=2, n_extras=1
|
|
data: []byte{0x01, tagSignedParts, 0x02, 0x01},
|
|
},
|
|
{
|
|
name: "etc_tx_flags16",
|
|
// count=1, tag=23, uint16 LE
|
|
data: []byte{0x01, tagEtcTxFlags16, 0x01, 0x00},
|
|
},
|
|
{
|
|
name: "etc_tx_time",
|
|
// count=1, tag=27, varint(42)
|
|
data: []byte{0x01, tagEtcTxTime, 0x2A},
|
|
},
|
|
{
|
|
name: "tx_comment",
|
|
// count=1, tag=7, string len=5, "hello"
|
|
data: []byte{0x01, tagTxComment, 0x05, 'h', 'e', 'l', 'l', 'o'},
|
|
},
|
|
{
|
|
name: "tx_payer_old",
|
|
// count=1, tag=8, 64 bytes (2 public keys)
|
|
data: append([]byte{0x01, tagTxPayerOld}, make([]byte, 64)...),
|
|
},
|
|
{
|
|
name: "tx_receiver_old",
|
|
// count=1, tag=29, 64 bytes
|
|
data: append([]byte{0x01, tagTxReceiverOld}, make([]byte, 64)...),
|
|
},
|
|
{
|
|
name: "tx_payer_not_auditable",
|
|
// count=1, tag=31, 64 bytes (2 keys) + marker=0 (no auditable flag)
|
|
data: append(append([]byte{0x01, tagTxPayer}, make([]byte, 64)...), 0x00),
|
|
},
|
|
{
|
|
name: "tx_payer_auditable",
|
|
// count=1, tag=31, 64 bytes (2 keys) + marker=1 + auditable_flag=1
|
|
data: append(append([]byte{0x01, tagTxPayer}, make([]byte, 64)...), 0x01, 0x01),
|
|
},
|
|
{
|
|
name: "extra_attachment_info",
|
|
// count=1, tag=18, cnt_type(string len=0) + hash(32 zeros) + sz(varint 0)
|
|
data: append([]byte{0x01, tagExtraAttachmentInfo, 0x00}, append(make([]byte, 32), 0x00)...),
|
|
},
|
|
{
|
|
name: "unlock_time2",
|
|
// count=1, tag=30, vector count=1, entry: {unlock_time=10, output_index=0}
|
|
data: []byte{0x01, tagUnlockTime2, 0x01, 0x0A, 0x00},
|
|
},
|
|
{
|
|
name: "tx_service_attachment",
|
|
// count=1, tag=12, 3 empty strings + empty key vec + flags=0
|
|
data: []byte{0x01, tagTxServiceAttachment, 0x00, 0x00, 0x00, 0x00, 0x00},
|
|
},
|
|
{
|
|
name: "multiple_elements",
|
|
// count=2: public_key + unlock_time
|
|
data: append(
|
|
append([]byte{0x02, tagPublicKey}, make([]byte, 32)...),
|
|
tagUnlockTime, 0x64,
|
|
),
|
|
},
|
|
{
|
|
name: "empty_extra",
|
|
// count=0
|
|
data: []byte{0x00},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Build a v1 tx with this extra data.
|
|
tx := types.Transaction{
|
|
Version: 1,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 0}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 1000,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{0xAA}},
|
|
}},
|
|
Extra: tt.data,
|
|
Attachment: EncodeVarint(0),
|
|
}
|
|
|
|
// Encode.
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransaction(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
// Decode.
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransaction(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
// Re-encode and compare.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransaction(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
if !bytes.Equal(rtBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), buf.Bytes())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTxWithSignaturesRoundTrip_Good(t *testing.T) {
|
|
// Test v1 transaction with non-empty signatures.
|
|
sig := types.Signature{}
|
|
sig[0] = 0xAA
|
|
sig[63] = 0xBB
|
|
|
|
tx := types.Transaction{
|
|
Version: 1,
|
|
Vin: []types.TxInput{types.TxInputToKey{
|
|
Amount: 100,
|
|
KeyOffsets: []types.TxOutRef{
|
|
{Tag: types.RefTypeGlobalIndex, GlobalIndex: 42},
|
|
},
|
|
KeyImage: types.KeyImage{0xFF},
|
|
EtcDetails: EncodeVarint(0),
|
|
}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 50,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{0xAB}},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
Signatures: [][]types.Signature{
|
|
{sig},
|
|
},
|
|
Attachment: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransaction(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransaction(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
if len(got.Signatures) != 1 {
|
|
t.Fatalf("signatures count: got %d, want 1", len(got.Signatures))
|
|
}
|
|
if len(got.Signatures[0]) != 1 {
|
|
t.Fatalf("ring[0] size: got %d, want 1", len(got.Signatures[0]))
|
|
}
|
|
if got.Signatures[0][0][0] != 0xAA || got.Signatures[0][0][63] != 0xBB {
|
|
t.Error("signature data mismatch")
|
|
}
|
|
|
|
// Round-trip byte comparison.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransaction(enc2, &got)
|
|
if !bytes.Equal(rtBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("round-trip mismatch")
|
|
}
|
|
}
|
|
|
|
func TestRefByIDRoundTrip_Good(t *testing.T) {
|
|
// Test TxOutRef with RefTypeByID tag.
|
|
tx := types.Transaction{
|
|
Version: 1,
|
|
Vin: []types.TxInput{types.TxInputToKey{
|
|
Amount: 100,
|
|
KeyOffsets: []types.TxOutRef{
|
|
{
|
|
Tag: types.RefTypeByID,
|
|
TxID: types.Hash{0xDE, 0xAD},
|
|
N: 3,
|
|
},
|
|
},
|
|
KeyImage: types.KeyImage{0xFF},
|
|
EtcDetails: EncodeVarint(0),
|
|
}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 50,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{0xAB}},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
Attachment: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransaction(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransaction(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
toKey := got.Vin[0].(types.TxInputToKey)
|
|
if len(toKey.KeyOffsets) != 1 {
|
|
t.Fatalf("key_offsets: got %d, want 1", len(toKey.KeyOffsets))
|
|
}
|
|
ref := toKey.KeyOffsets[0]
|
|
if ref.Tag != types.RefTypeByID {
|
|
t.Errorf("ref tag: got %d, want %d", ref.Tag, types.RefTypeByID)
|
|
}
|
|
if ref.TxID[0] != 0xDE || ref.TxID[1] != 0xAD {
|
|
t.Errorf("ref txid: got %x, want DEAD...", ref.TxID[:2])
|
|
}
|
|
if ref.N != 3 {
|
|
t.Errorf("ref N: got %d, want 3", ref.N)
|
|
}
|
|
}
|
|
|
|
func TestHTLCInputRoundTrip_Good(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: types.VersionPreHF4,
|
|
Vin: []types.TxInput{
|
|
types.TxInputHTLC{
|
|
HTLCOrigin: "test_origin",
|
|
Amount: 42000,
|
|
KeyOffsets: []types.TxOutRef{
|
|
{Tag: types.RefTypeGlobalIndex, GlobalIndex: 100},
|
|
},
|
|
KeyImage: types.KeyImage{0xAA},
|
|
EtcDetails: EncodeVarint(0),
|
|
},
|
|
},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 41000,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{0x01}},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransactionPrefix(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
if len(got.Vin) != 1 {
|
|
t.Fatalf("vin count: got %d, want 1", len(got.Vin))
|
|
}
|
|
htlc, ok := got.Vin[0].(types.TxInputHTLC)
|
|
if !ok {
|
|
t.Fatalf("vin[0] type: got %T, want TxInputHTLC", got.Vin[0])
|
|
}
|
|
if htlc.HTLCOrigin != "test_origin" {
|
|
t.Errorf("HTLCOrigin: got %q, want %q", htlc.HTLCOrigin, "test_origin")
|
|
}
|
|
if htlc.Amount != 42000 {
|
|
t.Errorf("Amount: got %d, want 42000", htlc.Amount)
|
|
}
|
|
if htlc.KeyImage[0] != 0xAA {
|
|
t.Errorf("KeyImage[0]: got 0x%02x, want 0xAA", htlc.KeyImage[0])
|
|
}
|
|
|
|
// Byte-level round-trip.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransactionPrefix(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
if !bytes.Equal(rtBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), buf.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestMultisigInputRoundTrip_Good(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: types.VersionPreHF4,
|
|
Vin: []types.TxInput{
|
|
types.TxInputMultisig{
|
|
Amount: 50000,
|
|
MultisigOutID: types.Hash{0xBB},
|
|
SigsCount: 3,
|
|
EtcDetails: EncodeVarint(0),
|
|
},
|
|
},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 49000,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{0x02}},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransactionPrefix(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
if len(got.Vin) != 1 {
|
|
t.Fatalf("vin count: got %d, want 1", len(got.Vin))
|
|
}
|
|
msig, ok := got.Vin[0].(types.TxInputMultisig)
|
|
if !ok {
|
|
t.Fatalf("vin[0] type: got %T, want TxInputMultisig", got.Vin[0])
|
|
}
|
|
if msig.Amount != 50000 {
|
|
t.Errorf("Amount: got %d, want 50000", msig.Amount)
|
|
}
|
|
if msig.MultisigOutID[0] != 0xBB {
|
|
t.Errorf("MultisigOutID[0]: got 0x%02x, want 0xBB", msig.MultisigOutID[0])
|
|
}
|
|
if msig.SigsCount != 3 {
|
|
t.Errorf("SigsCount: got %d, want 3", msig.SigsCount)
|
|
}
|
|
|
|
// Byte-level round-trip.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransactionPrefix(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
if !bytes.Equal(rtBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), buf.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestMultisigTargetV1RoundTrip_Good(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: types.VersionPreHF4,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 1}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 5000,
|
|
Target: types.TxOutMultisig{
|
|
MinimumSigs: 2,
|
|
Keys: []types.PublicKey{{0x01}, {0x02}, {0x03}},
|
|
},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransactionPrefix(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
bare, ok := got.Vout[0].(types.TxOutputBare)
|
|
if !ok {
|
|
t.Fatalf("vout[0] type: got %T, want TxOutputBare", got.Vout[0])
|
|
}
|
|
msig, ok := bare.Target.(types.TxOutMultisig)
|
|
if !ok {
|
|
t.Fatalf("target type: got %T, want TxOutMultisig", bare.Target)
|
|
}
|
|
if msig.MinimumSigs != 2 {
|
|
t.Errorf("MinimumSigs: got %d, want 2", msig.MinimumSigs)
|
|
}
|
|
if len(msig.Keys) != 3 {
|
|
t.Errorf("Keys count: got %d, want 3", len(msig.Keys))
|
|
}
|
|
|
|
// Byte-level round-trip.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransactionPrefix(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
if !bytes.Equal(rtBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), buf.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestHTLCTargetV1RoundTrip_Good(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: types.VersionPreHF4,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 1}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 7000,
|
|
Target: types.TxOutHTLC{
|
|
HTLCHash: types.Hash{0xCC},
|
|
Flags: 1, // RIPEMD160
|
|
Expiration: 20000,
|
|
PKRedeem: types.PublicKey{0xDD},
|
|
PKRefund: types.PublicKey{0xEE},
|
|
},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransactionPrefix(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
bare, ok := got.Vout[0].(types.TxOutputBare)
|
|
if !ok {
|
|
t.Fatalf("vout[0] type: got %T, want TxOutputBare", got.Vout[0])
|
|
}
|
|
htlc, ok := bare.Target.(types.TxOutHTLC)
|
|
if !ok {
|
|
t.Fatalf("target type: got %T, want TxOutHTLC", bare.Target)
|
|
}
|
|
if htlc.HTLCHash[0] != 0xCC {
|
|
t.Errorf("HTLCHash[0]: got 0x%02x, want 0xCC", htlc.HTLCHash[0])
|
|
}
|
|
if htlc.Flags != 1 {
|
|
t.Errorf("Flags: got %d, want 1", htlc.Flags)
|
|
}
|
|
if htlc.Expiration != 20000 {
|
|
t.Errorf("Expiration: got %d, want 20000", htlc.Expiration)
|
|
}
|
|
if htlc.PKRedeem[0] != 0xDD {
|
|
t.Errorf("PKRedeem[0]: got 0x%02x, want 0xDD", htlc.PKRedeem[0])
|
|
}
|
|
if htlc.PKRefund[0] != 0xEE {
|
|
t.Errorf("PKRefund[0]: got 0x%02x, want 0xEE", htlc.PKRefund[0])
|
|
}
|
|
|
|
// Byte-level round-trip.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransactionPrefix(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
if !bytes.Equal(rtBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), buf.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestMultisigTargetV2RoundTrip_Good(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: types.VersionPostHF4,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 1}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 5000,
|
|
Target: types.TxOutMultisig{
|
|
MinimumSigs: 2,
|
|
Keys: []types.PublicKey{{0x01}, {0x02}},
|
|
},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransactionPrefix(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
bare, ok := got.Vout[0].(types.TxOutputBare)
|
|
if !ok {
|
|
t.Fatalf("vout[0] type: got %T, want TxOutputBare", got.Vout[0])
|
|
}
|
|
msig, ok := bare.Target.(types.TxOutMultisig)
|
|
if !ok {
|
|
t.Fatalf("target type: got %T, want TxOutMultisig", bare.Target)
|
|
}
|
|
if msig.MinimumSigs != 2 {
|
|
t.Errorf("MinimumSigs: got %d, want 2", msig.MinimumSigs)
|
|
}
|
|
if len(msig.Keys) != 2 {
|
|
t.Errorf("Keys count: got %d, want 2", len(msig.Keys))
|
|
}
|
|
|
|
// Byte-level round-trip.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransactionPrefix(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
if !bytes.Equal(rtBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), buf.Bytes())
|
|
}
|
|
}
|
|
|
|
func TestHF1MixedTxRoundTrip_Good(t *testing.T) {
|
|
// Construct a v1 transaction with HTLC input, multisig input, multisig output,
|
|
// and HTLC output target -- covering all HF1 types in a single round-trip.
|
|
tx := types.Transaction{
|
|
Version: types.VersionPreHF4,
|
|
Vin: []types.TxInput{
|
|
types.TxInputToKey{
|
|
Amount: 100000,
|
|
KeyOffsets: []types.TxOutRef{
|
|
{Tag: types.RefTypeGlobalIndex, GlobalIndex: 42},
|
|
},
|
|
KeyImage: types.KeyImage{0x01},
|
|
EtcDetails: EncodeVarint(0),
|
|
},
|
|
types.TxInputHTLC{
|
|
HTLCOrigin: "htlc_preimage_data",
|
|
Amount: 50000,
|
|
KeyOffsets: []types.TxOutRef{
|
|
{Tag: types.RefTypeGlobalIndex, GlobalIndex: 99},
|
|
},
|
|
KeyImage: types.KeyImage{0x02},
|
|
EtcDetails: EncodeVarint(0),
|
|
},
|
|
types.TxInputMultisig{
|
|
Amount: 30000,
|
|
MultisigOutID: types.Hash{0xFF},
|
|
SigsCount: 2,
|
|
EtcDetails: EncodeVarint(0),
|
|
},
|
|
},
|
|
Vout: []types.TxOutput{
|
|
types.TxOutputBare{
|
|
Amount: 70000,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{0xAA}},
|
|
},
|
|
types.TxOutputBare{
|
|
Amount: 50000,
|
|
Target: types.TxOutMultisig{
|
|
MinimumSigs: 2,
|
|
Keys: []types.PublicKey{{0xBB}, {0xCC}},
|
|
},
|
|
},
|
|
types.TxOutputBare{
|
|
Amount: 40000,
|
|
Target: types.TxOutHTLC{
|
|
HTLCHash: types.Hash{0xDD},
|
|
Flags: 0,
|
|
Expiration: 15000,
|
|
PKRedeem: types.PublicKey{0xEE},
|
|
PKRefund: types.PublicKey{0xFF},
|
|
},
|
|
},
|
|
},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
// Encode.
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
encoded := buf.Bytes()
|
|
|
|
// Decode.
|
|
dec := NewDecoder(bytes.NewReader(encoded))
|
|
got := DecodeTransactionPrefix(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
// Verify inputs.
|
|
if len(got.Vin) != 3 {
|
|
t.Fatalf("vin count: got %d, want 3", len(got.Vin))
|
|
}
|
|
|
|
if _, ok := got.Vin[0].(types.TxInputToKey); !ok {
|
|
t.Errorf("vin[0]: got %T, want TxInputToKey", got.Vin[0])
|
|
}
|
|
|
|
htlcIn, ok := got.Vin[1].(types.TxInputHTLC)
|
|
if !ok {
|
|
t.Fatalf("vin[1]: got %T, want TxInputHTLC", got.Vin[1])
|
|
}
|
|
if htlcIn.HTLCOrigin != "htlc_preimage_data" {
|
|
t.Errorf("HTLCOrigin: got %q, want %q", htlcIn.HTLCOrigin, "htlc_preimage_data")
|
|
}
|
|
if htlcIn.Amount != 50000 {
|
|
t.Errorf("HTLC Amount: got %d, want 50000", htlcIn.Amount)
|
|
}
|
|
|
|
msigIn, ok := got.Vin[2].(types.TxInputMultisig)
|
|
if !ok {
|
|
t.Fatalf("vin[2]: got %T, want TxInputMultisig", got.Vin[2])
|
|
}
|
|
if msigIn.Amount != 30000 {
|
|
t.Errorf("Multisig Amount: got %d, want 30000", msigIn.Amount)
|
|
}
|
|
if msigIn.SigsCount != 2 {
|
|
t.Errorf("SigsCount: got %d, want 2", msigIn.SigsCount)
|
|
}
|
|
|
|
// Verify outputs.
|
|
if len(got.Vout) != 3 {
|
|
t.Fatalf("vout count: got %d, want 3", len(got.Vout))
|
|
}
|
|
|
|
bare0, ok := got.Vout[0].(types.TxOutputBare)
|
|
if !ok {
|
|
t.Fatalf("vout[0]: got %T, want TxOutputBare", got.Vout[0])
|
|
}
|
|
if _, ok := bare0.Target.(types.TxOutToKey); !ok {
|
|
t.Errorf("vout[0] target: got %T, want TxOutToKey", bare0.Target)
|
|
}
|
|
|
|
bare1, ok := got.Vout[1].(types.TxOutputBare)
|
|
if !ok {
|
|
t.Fatalf("vout[1]: got %T, want TxOutputBare", got.Vout[1])
|
|
}
|
|
msigTgt, ok := bare1.Target.(types.TxOutMultisig)
|
|
if !ok {
|
|
t.Fatalf("vout[1] target: got %T, want TxOutMultisig", bare1.Target)
|
|
}
|
|
if msigTgt.MinimumSigs != 2 {
|
|
t.Errorf("MinimumSigs: got %d, want 2", msigTgt.MinimumSigs)
|
|
}
|
|
if len(msigTgt.Keys) != 2 {
|
|
t.Errorf("Keys count: got %d, want 2", len(msigTgt.Keys))
|
|
}
|
|
|
|
bare2, ok := got.Vout[2].(types.TxOutputBare)
|
|
if !ok {
|
|
t.Fatalf("vout[2]: got %T, want TxOutputBare", got.Vout[2])
|
|
}
|
|
htlcTgt, ok := bare2.Target.(types.TxOutHTLC)
|
|
if !ok {
|
|
t.Fatalf("vout[2] target: got %T, want TxOutHTLC", bare2.Target)
|
|
}
|
|
if htlcTgt.Expiration != 15000 {
|
|
t.Errorf("Expiration: got %d, want 15000", htlcTgt.Expiration)
|
|
}
|
|
|
|
// Re-encode and verify bit-identical.
|
|
var buf2 bytes.Buffer
|
|
enc2 := NewEncoder(&buf2)
|
|
EncodeTransactionPrefix(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
|
|
if !bytes.Equal(encoded, buf2.Bytes()) {
|
|
t.Errorf("round-trip not bit-identical: encoded %d bytes, re-encoded %d bytes",
|
|
len(encoded), len(buf2.Bytes()))
|
|
}
|
|
}
|
|
|
|
func TestHTLCTargetV2RoundTrip_Good(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: types.VersionPostHF4,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 1}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 7000,
|
|
Target: types.TxOutHTLC{
|
|
HTLCHash: types.Hash{0xCC},
|
|
Flags: 0, // SHA256
|
|
Expiration: 15000,
|
|
PKRedeem: types.PublicKey{0xDD},
|
|
PKRefund: types.PublicKey{0xEE},
|
|
},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() != nil {
|
|
t.Fatalf("encode error: %v", enc.Err())
|
|
}
|
|
|
|
dec := NewDecoder(bytes.NewReader(buf.Bytes()))
|
|
got := DecodeTransactionPrefix(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode error: %v", dec.Err())
|
|
}
|
|
|
|
bare, ok := got.Vout[0].(types.TxOutputBare)
|
|
if !ok {
|
|
t.Fatalf("vout[0] type: got %T, want TxOutputBare", got.Vout[0])
|
|
}
|
|
htlc, ok := bare.Target.(types.TxOutHTLC)
|
|
if !ok {
|
|
t.Fatalf("target type: got %T, want TxOutHTLC", bare.Target)
|
|
}
|
|
if htlc.HTLCHash[0] != 0xCC {
|
|
t.Errorf("HTLCHash[0]: got 0x%02x, want 0xCC", htlc.HTLCHash[0])
|
|
}
|
|
if htlc.Flags != 0 {
|
|
t.Errorf("Flags: got %d, want 0", htlc.Flags)
|
|
}
|
|
if htlc.Expiration != 15000 {
|
|
t.Errorf("Expiration: got %d, want 15000", htlc.Expiration)
|
|
}
|
|
if htlc.PKRedeem[0] != 0xDD {
|
|
t.Errorf("PKRedeem[0]: got 0x%02x, want 0xDD", htlc.PKRedeem[0])
|
|
}
|
|
if htlc.PKRefund[0] != 0xEE {
|
|
t.Errorf("PKRefund[0]: got 0x%02x, want 0xEE", htlc.PKRefund[0])
|
|
}
|
|
|
|
// Byte-level round-trip.
|
|
var rtBuf bytes.Buffer
|
|
enc2 := NewEncoder(&rtBuf)
|
|
EncodeTransactionPrefix(enc2, &got)
|
|
if enc2.Err() != nil {
|
|
t.Fatalf("re-encode error: %v", enc2.Err())
|
|
}
|
|
if !bytes.Equal(rtBuf.Bytes(), buf.Bytes()) {
|
|
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), buf.Bytes())
|
|
}
|
|
}
|
|
|
|
type unsupportedTxInput struct{}
|
|
|
|
func (unsupportedTxInput) InputType() uint8 { return 250 }
|
|
|
|
type unsupportedTxOutTarget struct{}
|
|
|
|
func (unsupportedTxOutTarget) TargetType() uint8 { return 250 }
|
|
|
|
type unsupportedTxOutput struct{}
|
|
|
|
func (unsupportedTxOutput) OutputType() uint8 { return 250 }
|
|
|
|
func TestEncodeTransaction_UnsupportedInput_Bad(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: 1,
|
|
Vin: []types.TxInput{unsupportedTxInput{}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 1,
|
|
Target: types.TxOutToKey{Key: types.PublicKey{1}},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() == nil {
|
|
t.Fatal("expected encode error for unsupported input type")
|
|
}
|
|
}
|
|
|
|
func TestEncodeTransaction_UnsupportedOutputTarget_Bad(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
version uint64
|
|
}{
|
|
{name: "v1", version: types.VersionPreHF4},
|
|
{name: "v2", version: types.VersionPostHF4},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: tt.version,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 1}},
|
|
Vout: []types.TxOutput{types.TxOutputBare{
|
|
Amount: 1,
|
|
Target: unsupportedTxOutTarget{},
|
|
}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() == nil {
|
|
t.Fatal("expected encode error for unsupported output target type")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEncodeTransaction_UnsupportedOutputType_Bad(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
version uint64
|
|
}{
|
|
{name: "v1", version: types.VersionPreHF4},
|
|
{name: "v2", version: types.VersionPostHF4},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tx := types.Transaction{
|
|
Version: tt.version,
|
|
Vin: []types.TxInput{types.TxInputGenesis{Height: 1}},
|
|
Vout: []types.TxOutput{unsupportedTxOutput{}},
|
|
Extra: EncodeVarint(0),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
enc := NewEncoder(&buf)
|
|
EncodeTransactionPrefix(enc, &tx)
|
|
if enc.Err() == nil {
|
|
t.Fatal("expected encode error for unsupported output type")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestExtraAliasEntryOldRoundTrip_Good verifies that a variant vector
|
|
// containing an extra_alias_entry_old (tag 20) round-trips through
|
|
// decodeRawVariantVector without error.
|
|
func TestExtraAliasEntryOldRoundTrip_Good(t *testing.T) {
|
|
// Build a synthetic variant vector with one extra_alias_entry_old element.
|
|
// Format: count(1) + tag(20) + alias(string) + address(64 bytes) +
|
|
// text_comment(string) + sign(vector of 64-byte sigs).
|
|
var raw []byte
|
|
raw = append(raw, EncodeVarint(1)...) // 1 element
|
|
raw = append(raw, tagExtraAliasEntryOld)
|
|
|
|
// m_alias: "test.lthn"
|
|
alias := []byte("test.lthn")
|
|
raw = append(raw, EncodeVarint(uint64(len(alias)))...)
|
|
raw = append(raw, alias...)
|
|
|
|
// m_address: spend_key(32) + view_key(32) = 64 bytes
|
|
addr := make([]byte, 64)
|
|
for i := range addr {
|
|
addr[i] = byte(i)
|
|
}
|
|
raw = append(raw, addr...)
|
|
|
|
// m_text_comment: "hello"
|
|
comment := []byte("hello")
|
|
raw = append(raw, EncodeVarint(uint64(len(comment)))...)
|
|
raw = append(raw, comment...)
|
|
|
|
// m_sign: 1 signature (generic_schnorr_sig_s = 64 bytes)
|
|
raw = append(raw, EncodeVarint(1)...) // 1 signature
|
|
sig := make([]byte, 64)
|
|
for i := range sig {
|
|
sig[i] = byte(0xAA)
|
|
}
|
|
raw = append(raw, sig...)
|
|
|
|
// Decode and round-trip.
|
|
dec := NewDecoder(bytes.NewReader(raw))
|
|
decoded := decodeRawVariantVector(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode failed: %v", dec.Err())
|
|
}
|
|
if !bytes.Equal(decoded, raw) {
|
|
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(decoded), len(raw))
|
|
}
|
|
}
|
|
|
|
// TestExtraAliasEntryRoundTrip_Good verifies that a variant vector
|
|
// containing an extra_alias_entry (tag 33) round-trips through
|
|
// decodeRawVariantVector without error.
|
|
func TestExtraAliasEntryRoundTrip_Good(t *testing.T) {
|
|
// Build a synthetic variant vector with one extra_alias_entry element.
|
|
// Format: count(1) + tag(33) + alias(string) + address(tx_payer format) +
|
|
// text_comment(string) + sign(vector) + view_key(optional).
|
|
var raw []byte
|
|
raw = append(raw, EncodeVarint(1)...) // 1 element
|
|
raw = append(raw, tagExtraAliasEntry)
|
|
|
|
// m_alias: "myalias"
|
|
alias := []byte("myalias")
|
|
raw = append(raw, EncodeVarint(uint64(len(alias)))...)
|
|
raw = append(raw, alias...)
|
|
|
|
// m_address: tx_payer format = spend_key(32) + view_key(32) + optional marker
|
|
addr := make([]byte, 64)
|
|
for i := range addr {
|
|
addr[i] = byte(i + 10)
|
|
}
|
|
raw = append(raw, addr...)
|
|
// is_auditable optional marker: 0 = not present
|
|
raw = append(raw, 0x00)
|
|
|
|
// m_text_comment: empty
|
|
raw = append(raw, EncodeVarint(0)...)
|
|
|
|
// m_sign: 0 signatures
|
|
raw = append(raw, EncodeVarint(0)...)
|
|
|
|
// m_view_key: optional, present (marker=1 + 32 bytes)
|
|
raw = append(raw, 0x01)
|
|
viewKey := make([]byte, 32)
|
|
for i := range viewKey {
|
|
viewKey[i] = byte(0xBB)
|
|
}
|
|
raw = append(raw, viewKey...)
|
|
|
|
// Decode and round-trip.
|
|
dec := NewDecoder(bytes.NewReader(raw))
|
|
decoded := decodeRawVariantVector(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode failed: %v", dec.Err())
|
|
}
|
|
if !bytes.Equal(decoded, raw) {
|
|
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(decoded), len(raw))
|
|
}
|
|
}
|
|
|
|
// TestExtraAliasEntryNoViewKey_Good verifies extra_alias_entry with
|
|
// the optional view_key marker set to 0 (not present).
|
|
func TestExtraAliasEntryNoViewKey_Good(t *testing.T) {
|
|
var raw []byte
|
|
raw = append(raw, EncodeVarint(1)...) // 1 element
|
|
raw = append(raw, tagExtraAliasEntry)
|
|
|
|
// m_alias: "short"
|
|
alias := []byte("short")
|
|
raw = append(raw, EncodeVarint(uint64(len(alias)))...)
|
|
raw = append(raw, alias...)
|
|
|
|
// m_address: keys + no auditable flag
|
|
raw = append(raw, make([]byte, 64)...)
|
|
raw = append(raw, 0x00) // not auditable
|
|
|
|
// m_text_comment: empty
|
|
raw = append(raw, EncodeVarint(0)...)
|
|
|
|
// m_sign: 0 signatures
|
|
raw = append(raw, EncodeVarint(0)...)
|
|
|
|
// m_view_key: not present (marker=0)
|
|
raw = append(raw, 0x00)
|
|
|
|
dec := NewDecoder(bytes.NewReader(raw))
|
|
decoded := decodeRawVariantVector(dec)
|
|
if dec.Err() != nil {
|
|
t.Fatalf("decode failed: %v", dec.Err())
|
|
}
|
|
if !bytes.Equal(decoded, raw) {
|
|
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(decoded), len(raw))
|
|
}
|
|
}
|