feat(wire): add asset_descriptor_operation tag 40 reader
Reads the CHAIN_TRANSITION_VER structure for asset deploy/emit/update/burn operations. Stores as opaque bytes for bit-identical round-tripping. Required for HF5 block deserialisation. Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
9631efa5a8
commit
3e79f34a65
2 changed files with 298 additions and 0 deletions
|
|
@ -536,6 +536,9 @@ const (
|
|||
tagExtraAliasEntry = 33 // extra_alias_entry — complex
|
||||
tagZarcanumTxDataV1 = 39 // zarcanum_tx_data_v1 — varint (fee)
|
||||
|
||||
// Asset descriptor operation (HF5).
|
||||
tagAssetDescriptorOperation = 40 // asset_descriptor_operation
|
||||
|
||||
// Signature variant tags (signature_v).
|
||||
tagNLSAGSig = 42 // NLSAG_sig — vector<signature>
|
||||
tagZCSig = 43 // ZC_sig — 2 public_keys + CLSAG_GGX
|
||||
|
|
@ -604,6 +607,10 @@ func readVariantElementData(dec *Decoder, tag uint8) []byte {
|
|||
case tagZarcanumTxDataV1: // fee — FIELD(fee) → serialize_int → 8-byte LE
|
||||
return dec.ReadBytes(8)
|
||||
|
||||
// Asset descriptor operation (HF5)
|
||||
case tagAssetDescriptorOperation:
|
||||
return readAssetDescriptorOperation(dec)
|
||||
|
||||
// Signature variants
|
||||
case tagNLSAGSig: // vector<signature> (64 bytes each)
|
||||
return readVariantVectorFixed(dec, 64)
|
||||
|
|
@ -784,6 +791,158 @@ func readSignedParts(dec *Decoder) []byte {
|
|||
return raw
|
||||
}
|
||||
|
||||
// --- asset operation readers (HF5) ---
|
||||
|
||||
// readAssetDescriptorOperation reads asset_descriptor_operation (tag 40).
|
||||
// Structure (CHAIN_TRANSITION_VER, version 0 and 1):
|
||||
//
|
||||
// ver (uint8) + operation_type (uint8)
|
||||
// + opt_asset_id (uint8 marker + 32 bytes if present)
|
||||
// + opt_descriptor (uint8 marker + AssetDescriptorBase if present)
|
||||
// + amount_to_emit (uint64 LE) + amount_to_burn (uint64 LE)
|
||||
// + etc (vector<uint8>)
|
||||
//
|
||||
// AssetDescriptorBase:
|
||||
//
|
||||
// ticker (string) + full_name (string) + total_max_supply (uint64 LE)
|
||||
// + current_supply (uint64 LE) + decimal_point (uint8) + meta_info (string)
|
||||
// + owner_key (32 bytes) + etc (vector<uint8>)
|
||||
func readAssetDescriptorOperation(dec *Decoder) []byte {
|
||||
var raw []byte
|
||||
|
||||
// ver: uint8 (CHAIN_TRANSITION_VER version byte)
|
||||
ver := dec.ReadUint8()
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, ver)
|
||||
|
||||
// operation_type: uint8
|
||||
opType := dec.ReadUint8()
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, opType)
|
||||
|
||||
// opt_asset_id: optional<hash> — uint8 marker, then 32 bytes if present
|
||||
marker := dec.ReadUint8()
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, marker)
|
||||
if marker != 0 {
|
||||
b := dec.ReadBytes(32)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, b...)
|
||||
}
|
||||
|
||||
// opt_descriptor: optional<AssetDescriptorBase>
|
||||
marker = dec.ReadUint8()
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, marker)
|
||||
if marker != 0 {
|
||||
b := readAssetDescriptorBase(dec)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, b...)
|
||||
}
|
||||
|
||||
// amount_to_emit: uint64 LE
|
||||
b := dec.ReadBytes(8)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, b...)
|
||||
|
||||
// amount_to_burn: uint64 LE
|
||||
b = dec.ReadBytes(8)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, b...)
|
||||
|
||||
// etc: vector<uint8>
|
||||
v := readVariantVectorFixed(dec, 1)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, v...)
|
||||
|
||||
return raw
|
||||
}
|
||||
|
||||
// readAssetDescriptorBase reads the AssetDescriptorBase structure.
|
||||
// Wire: ticker (string) + full_name (string) + total_max_supply (uint64 LE)
|
||||
//
|
||||
// + current_supply (uint64 LE) + decimal_point (uint8) + meta_info (string)
|
||||
// + owner_key (32 bytes) + etc (vector<uint8>).
|
||||
func readAssetDescriptorBase(dec *Decoder) []byte {
|
||||
var raw []byte
|
||||
|
||||
// ticker: string
|
||||
s := readStringBlob(dec)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, s...)
|
||||
|
||||
// full_name: string
|
||||
s = readStringBlob(dec)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, s...)
|
||||
|
||||
// total_max_supply: uint64 LE
|
||||
b := dec.ReadBytes(8)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, b...)
|
||||
|
||||
// current_supply: uint64 LE
|
||||
b = dec.ReadBytes(8)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, b...)
|
||||
|
||||
// decimal_point: uint8
|
||||
dp := dec.ReadUint8()
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, dp)
|
||||
|
||||
// meta_info: string
|
||||
s = readStringBlob(dec)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, s...)
|
||||
|
||||
// owner_key: 32 bytes (crypto::public_key)
|
||||
b = dec.ReadBytes(32)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, b...)
|
||||
|
||||
// etc: vector<uint8>
|
||||
v := readVariantVectorFixed(dec, 1)
|
||||
if dec.err != nil {
|
||||
return nil
|
||||
}
|
||||
raw = append(raw, v...)
|
||||
|
||||
return raw
|
||||
}
|
||||
|
||||
// --- crypto blob readers ---
|
||||
// These read variable-length serialised crypto structures and return raw bytes.
|
||||
// All vectors are varint(count) + 32*count bytes (scalars or points).
|
||||
|
|
|
|||
139
wire/transaction_v3_test.go
Normal file
139
wire/transaction_v3_test.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// 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"
|
||||
)
|
||||
|
||||
// buildAssetDescriptorOpBlob constructs a minimal asset_descriptor_operation
|
||||
// binary blob (version 1, operation_type=register, with descriptor, no asset_id).
|
||||
func buildAssetDescriptorOpBlob() []byte {
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
|
||||
// ver: uint8 = 1
|
||||
enc.WriteUint8(1)
|
||||
// operation_type: uint8 = 0 (register)
|
||||
enc.WriteUint8(0)
|
||||
// opt_asset_id: absent (marker = 0)
|
||||
enc.WriteUint8(0)
|
||||
// opt_descriptor: present (marker = 1)
|
||||
enc.WriteUint8(1)
|
||||
// -- AssetDescriptorBase --
|
||||
// ticker: string "LTHN" (varint len + bytes)
|
||||
enc.WriteVarint(4)
|
||||
enc.WriteBytes([]byte("LTHN"))
|
||||
// full_name: string "Lethean" (varint len + bytes)
|
||||
enc.WriteVarint(7)
|
||||
enc.WriteBytes([]byte("Lethean"))
|
||||
// total_max_supply: uint64 LE
|
||||
enc.WriteUint64LE(1000000)
|
||||
// current_supply: uint64 LE
|
||||
enc.WriteUint64LE(0)
|
||||
// decimal_point: uint8
|
||||
enc.WriteUint8(12)
|
||||
// meta_info: string "" (empty)
|
||||
enc.WriteVarint(0)
|
||||
// owner_key: 32 bytes
|
||||
enc.WriteBytes(make([]byte, 32))
|
||||
// etc: vector<uint8> (empty)
|
||||
enc.WriteVarint(0)
|
||||
// -- end AssetDescriptorBase --
|
||||
// amount_to_emit: uint64 LE
|
||||
enc.WriteUint64LE(0)
|
||||
// amount_to_burn: uint64 LE
|
||||
enc.WriteUint64LE(0)
|
||||
// etc: vector<uint8> (empty)
|
||||
enc.WriteVarint(0)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func TestReadAssetDescriptorOperation_Good(t *testing.T) {
|
||||
blob := buildAssetDescriptorOpBlob()
|
||||
dec := NewDecoder(bytes.NewReader(blob))
|
||||
got := readAssetDescriptorOperation(dec)
|
||||
if dec.Err() != nil {
|
||||
t.Fatalf("readAssetDescriptorOperation failed: %v", dec.Err())
|
||||
}
|
||||
if !bytes.Equal(got, blob) {
|
||||
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(blob))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadAssetDescriptorOperation_Bad(t *testing.T) {
|
||||
// Truncated blob — should error.
|
||||
dec := NewDecoder(bytes.NewReader([]byte{1, 0}))
|
||||
_ = readAssetDescriptorOperation(dec)
|
||||
if dec.Err() == nil {
|
||||
t.Fatal("expected error for truncated asset descriptor operation")
|
||||
}
|
||||
}
|
||||
|
||||
// buildAssetDescriptorOpEmitBlob constructs an emit operation (version 1,
|
||||
// operation_type=1, with asset_id, no descriptor).
|
||||
func buildAssetDescriptorOpEmitBlob() []byte {
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
|
||||
// ver: uint8 = 1
|
||||
enc.WriteUint8(1)
|
||||
// operation_type: uint8 = 1 (emit)
|
||||
enc.WriteUint8(1)
|
||||
// opt_asset_id: present (marker = 1) + 32-byte hash
|
||||
enc.WriteUint8(1)
|
||||
enc.WriteBytes(bytes.Repeat([]byte{0xAB}, 32))
|
||||
// opt_descriptor: absent (marker = 0)
|
||||
enc.WriteUint8(0)
|
||||
// amount_to_emit: uint64 LE = 500000
|
||||
enc.WriteUint64LE(500000)
|
||||
// amount_to_burn: uint64 LE = 0
|
||||
enc.WriteUint64LE(0)
|
||||
// etc: vector<uint8> (empty)
|
||||
enc.WriteVarint(0)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func TestReadAssetDescriptorOperationEmit_Good(t *testing.T) {
|
||||
blob := buildAssetDescriptorOpEmitBlob()
|
||||
dec := NewDecoder(bytes.NewReader(blob))
|
||||
got := readAssetDescriptorOperation(dec)
|
||||
if dec.Err() != nil {
|
||||
t.Fatalf("readAssetDescriptorOperation (emit) failed: %v", dec.Err())
|
||||
}
|
||||
if !bytes.Equal(got, blob) {
|
||||
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(blob))
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariantVectorWithTag40_Good(t *testing.T) {
|
||||
// Build a variant vector containing one element: tag 40 (asset_descriptor_operation).
|
||||
innerBlob := buildAssetDescriptorOpEmitBlob()
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
// count = 1
|
||||
enc.WriteVarint(1)
|
||||
// tag
|
||||
enc.WriteUint8(tagAssetDescriptorOperation)
|
||||
// data
|
||||
enc.WriteBytes(innerBlob)
|
||||
|
||||
raw := buf.Bytes()
|
||||
|
||||
// Decode as raw variant vector.
|
||||
dec := NewDecoder(bytes.NewReader(raw))
|
||||
got := decodeRawVariantVector(dec)
|
||||
if dec.Err() != nil {
|
||||
t.Fatalf("decodeRawVariantVector with tag 40 failed: %v", dec.Err())
|
||||
}
|
||||
if !bytes.Equal(got, raw) {
|
||||
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(raw))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue