Documentation
¶
Overview ¶
Package chain provides Namecoin blockchain functionality built on btcd.
The chain package wraps btcd's blockchain.BlockChain with Namecoin-specific name operation validation, AuxPow (Auxiliary Proof of Work) support, and name database integration. It implements composition over reimplementation, leveraging btcd's battle-tested blockchain components while adding the necessary extensions for Namecoin's naming system.
Architecture ¶
The package follows a layered design:
- BlockChain: Embeds btcd's blockchain.BlockChain with name validation hooks
- AuxPow: Merged mining support for blocks after height 19,200
- Block: Namecoin block wrapper with AuxPow header support
- Validation: Name operation and protocol compliance checking
Name Operations ¶
The chain validates three types of name operations:
- NAME_NEW: Pre-registration commitment to prevent front-running
- NAME_FIRSTUPDATE: First registration, reveals the name from NAME_NEW
- NAME_UPDATE: Updates name value and extends expiration by 36,000 blocks
Each operation must comply with Namecoin protocol rules including:
- Name length: 1-255 bytes
- Value size: ≤1023 bytes (consensus), ≤520 bytes (UI limit for user-facing APIs)
- Timing: NAME_FIRSTUPDATE within 12-36,000 blocks of NAME_NEW
- Uniqueness: No duplicate unexpired names
AuxPow (Merged Mining) ¶
After block 19,200, Namecoin requires Auxiliary Proof of Work (AuxPow) which allows miners to mine Namecoin alongside Bitcoin or other Scrypt-based chains. AuxPow validation includes:
- Version bit verification (0x100 must be set)
- Parent block header validation
- Coinbase transaction merkle proof verification
- Chain ID validation (Namecoin = 1)
The package includes an LRU cache for AuxPow data to handle the mismatch between btcd's block representation and Namecoin's extended block format.
Thread Safety ¶
BlockChain is safe for concurrent use. All shared state is protected with sync.RWMutex. Block validation and name database updates are atomic.
Example Usage ¶
Creating a new blockchain:
cfg := &chain.Config{
ChainParams: &config.NamecoinMainNetParams,
NameDBPath: "/path/to/names.db",
DataDir: "/path/to/data",
}
bc, err := chain.NewBlockChain(cfg, nil)
if err != nil {
log.Fatal(err)
}
defer bc.Close()
// Process a block
err = bc.ProcessBlock(block, blockchain.BFNone)
if err != nil {
log.Printf("Block rejected: %v", err)
}
Known Limitations ¶
The protocol constants, name operations, and AuxPow deserialization are fully compliant with Namecoin Core (22/22 checks pass, 6/6 mainnet test vectors pass). See docs/development/PROTOCOL_COMPLIANCE_AUDIT.md for the full audit.
Remaining gaps that affect production mainnet readiness:
- Full AuxPow parent-chain Proof of Work verification (pragmatic shortcut used)
- Namecoin-specific subsidy calculation edge cases
These gaps do not affect name operation correctness or block structure validation. The package is suitable for development, testing, and name resolution use cases.
Index ¶
- Constants
- func CheckMerkleBranch(leaf *chainhash.Hash, branch *MerkleBranch, root *chainhash.Hash) bool
- func ExtractChainIDFromVersion(version int32) uint32
- type AuxPow
- type Block
- type BlockChain
- func (bc *BlockChain) BestSnapshot() *blockchain.BestState
- func (bc *BlockChain) ChainParams() *chaincfg.Params
- func (bc *BlockChain) Close() error
- func (bc *BlockChain) GetBlockByHash(hash *chainhash.Hash) (*btcutil.Block, error)
- func (bc *BlockChain) GetBlockHeader(hash *chainhash.Hash) (wire.BlockHeader, error)
- func (bc *BlockChain) GetName(name string) (*namedb.NameRecord, error)
- func (bc *BlockChain) GetNameDB() *namedb.NameDatabase
- func (bc *BlockChain) GetNameHistory(name string) ([]*namedb.NameRecord, error)
- func (bc *BlockChain) GetNameUTXO(name string) (*namedb.UTXO, error)
- func (bc *BlockChain) GetUTXOsForAddress(address string) ([]*namedb.UTXO, error)
- func (bc *BlockChain) HandleBlockchainNotification(notification *blockchain.Notification)
- func (bc *BlockChain) ListNames() ([]*namedb.NameRecord, error)
- func (bc *BlockChain) ProcessBlock(block *btcutil.Block, flags blockchain.BehaviorFlags) (bool, bool, error)
- func (bc *BlockChain) ScanNames(prefix string, count int) ([]*namedb.NameRecord, error)
- func (bc *BlockChain) SetBlockAuxPowFromBytes(blockHash *chainhash.Hash, serializedBlock []byte) error
- func (bc *BlockChain) ValidateMempoolTransaction(tx *wire.MsgTx) error
- func (bc *BlockChain) VerifyBlock(block *btcutil.Block) error
- type Config
- type MainnetTestVector
- type MerkleBranch
- type NameOperationInfo
Constants ¶
const DefaultAuxPowCacheSize = 100
DefaultAuxPowCacheSize is the default maximum number of AuxPow entries to cache. This value is chosen to be large enough to handle normal operation (blocks are processed sequentially) while providing protection against adversarial scenarios. Each AuxPow entry is approximately 1KB, so 100 entries use ~100KB memory.
const NamecoinChainID = 1
Namecoin chain ID for merge mining Per Namecoin Core src/auxpow.cpp: Namecoin uses chain ID = 1
Variables ¶
This section is empty.
Functions ¶
func CheckMerkleBranch ¶
CheckMerkleBranch verifies a merkle branch proof.
Verifies that 'leaf' is included in a merkle tree with 'root' as the merkle root, using the provided merkle branch path.
Per Namecoin Core src/auxpow.cpp (CheckMerkleBranch): The algorithm walks up the merkle tree from the leaf to the root, combining the current hash with sibling hashes from the branch. The SideMask determines whether the sibling is on the left (bit=1) or right (bit=0) at each level.
Arguments:
- leaf: The hash of the leaf node (e.g., transaction hash)
- branch: The merkle branch proof (sibling hashes and side mask)
- root: The expected merkle root
Returns:
- true if the proof is valid (computed root matches expected root)
- false if the proof is invalid
func ExtractChainIDFromVersion ¶
ExtractChainIDFromVersion extracts the chain ID from a Namecoin block version.
Per Namecoin Core src/primitives/pureheader.h: The chain ID is stored in bits 16+ of the block version. Formula: chainID = (version >> 16)
For Namecoin mainnet blocks with AuxPoW: - Version 0x00010101 = chain ID 1 (0x0001) - The AuxPoW bit is 0x100 (bit 8) - The base version is in bits 0-7
This function extracts the chain ID from any Namecoin block version, allowing validation that the block is intended for the Namecoin chain.
Arguments:
- version: The block version from the Namecoin block header
Returns:
- Chain ID extracted from the version
Types ¶
type AuxPow ¶
type AuxPow struct {
// CoinbaseTx is the coinbase transaction from the parent blockchain
// that commits to this block's hash in its scriptSig or output.
CoinbaseTx wire.MsgTx
// BlockHash is the hash of the auxiliary blockchain block (this Namecoin block).
// This hash must appear in the parent chain's coinbase transaction.
BlockHash chainhash.Hash
// CoinbaseBranch is the merkle branch proving the coinbase transaction
// is included in the parent block's merkle tree.
CoinbaseBranch MerkleBranch
// ChainMerkleBranch is the merkle branch proving the auxiliary block hash
// is committed to in the coinbase transaction.
ChainMerkleBranch MerkleBranch
// ParentBlock is the header of the parent blockchain block.
// This header's merkle root must match the merkle proof from CoinbaseBranch,
// and its hash must meet the difficulty target.
ParentBlock wire.BlockHeader
}
AuxPow represents an auxiliary proof of work used in merged mining. This structure contains the proof that a block was included in a parent blockchain's (typically Bitcoin's) merkle tree, allowing Namecoin to leverage Bitcoin's proof of work.
Per Namecoin Core src/primitives/pureheader.h and src/auxpow.cpp: An AuxPow consists of: 1. Coinbase transaction from parent chain containing the merge-mined block hash 2. Block hash of the merge-mined block 3. Merkle branch proving coinbase is in parent block 4. Merkle branch proving aux block hash is in coinbase 5. Parent block header
References: - Namecoin Core: https://github.com/namecoin/namecoin-core/blob/master/src/primitives/pureheader.h - BIP: https://en.bitcoin.it/wiki/Merged_mining_specification
func DeserializeAuxPow ¶
DeserializeAuxPow reads an AuxPow structure from a reader. This implements the wire protocol deserialization for AuxPow blocks.
Per Namecoin Core wire format (src/auxpow.cpp): 1. Coinbase transaction (standard Bitcoin tx format) 2. Block hash (32 bytes) 3. Coinbase merkle branch (variable length) 4. Chain merkle branch (variable length) 5. Parent block header (80 bytes)
Returns:
- AuxPow structure if successful
- Error if deserialization fails (malformed data, EOF, etc.)
func (*AuxPow) SerializeAuxPow ¶
SerializeAuxPow writes an AuxPow structure to a writer. This implements the wire protocol serialization for AuxPow blocks.
Format matches DeserializeAuxPow: 1. Coinbase transaction 2. Block hash 3. Coinbase merkle branch 4. Chain merkle branch 5. Parent block header
func (*AuxPow) ValidateAuxPow ¶
ValidateAuxPow validates an AuxPow proof for a merged-mined block.
Per Namecoin Core src/auxpow.cpp (CheckAuxPow): Full validation includes: 1. Verify coinbase merkle branch connects coinbase to parent block merkle root 2. Verify chain merkle branch connects aux block hash to coinbase 3. Verify parent block hash meets difficulty target 4. Extract and validate chain ID matches expected chain 5. Verify aux block hash appears in correct position in coinbase
Note: Chain ID validation is the caller's responsibility. The caller should extract the chain ID from the Namecoin block's version using ExtractChainIDFromVersion() and validate it before calling this function.
Arguments:
- blockHash: The hash of the auxiliary blockchain block (this Namecoin block)
- targetDifficulty: The required proof-of-work difficulty target for the parent block
Returns:
- nil if validation succeeds
- error describing validation failure
type Block ¶
Block extends btcutil.Block with Namecoin-specific AuxPow data. This wrapper allows us to work with both standard Bitcoin blocks (pre-AuxPow) and Namecoin merged-mined blocks (post-AuxPow activation at height 19,200).
Design: Instead of forking btcd's Block type, we compose it and add AuxPow as an additional field. This maintains compatibility with btcd's blockchain package while adding Namecoin-specific functionality.
func NewBlock ¶
NewBlock creates a new Block from a wire.MsgBlock. The AuxPow field is initially nil and must be set separately via SetAuxPow() or by using NewBlockFromReader() which deserializes the full block including AuxPow.
func NewBlockFromBytes ¶
NewBlockFromBytes deserializes a block from a byte slice, including AuxPow data if present.
The wire format for Namecoin blocks differs based on the block version:
Pre-AuxPoW blocks (version bit 0x100 NOT set):
- Block header (80 bytes)
- Transaction count (varint)
- Transactions (variable length)
AuxPoW blocks (version bit 0x100 set):
- Block header (80 bytes)
- AuxPoW data (variable length) - BEFORE transactions!
- Transaction count (varint)
- Transactions (variable length)
This function automatically detects whether AuxPow is present based on the block version and deserializes it in the correct order.
Arguments:
- serializedBlock: The complete serialized block including AuxPow (if present)
Returns:
- Block with AuxPow populated (if block version indicates AuxPow)
- Error if deserialization fails
func NewBlockFromReader ¶
NewBlockFromReader deserializes a block from an io.Reader, including AuxPow data if present.
This is the primary deserialization function for Namecoin blocks. It handles both: 1. Pre-AuxPow blocks (< height 19,200): Standard Bitcoin block format 2. AuxPow blocks (>= height 19,200): Header + AuxPow + Transactions
IMPORTANT: The wire format for AuxPoW blocks places the AuxPoW data BEFORE the transaction count and transactions, NOT after. This differs from what one might expect and requires custom deserialization logic.
Reference: This ordering (AuxPoW serialized immediately after the header and before the transaction vector) matches Namecoin Core's wire format as implemented in src/primitives/block.h (CBlockHeader serialization with AuxPow) and related AuxPow handling code. Maintain this ordering to stay consensus-compatible.
Wire format for AuxPoW blocks:
- Block header (80 bytes)
- AuxPoW structure: - Coinbase TX from parent chain - Block hash (32 bytes) - Coinbase merkle branch - Chain merkle branch - Parent block header (80 bytes)
- Transaction count (varint)
- Block transactions
Arguments:
- r: Reader containing the serialized block data
Returns:
- Block with AuxPow populated (if block version indicates AuxPow)
- Error if deserialization fails (malformed block, incomplete AuxPow, etc.)
func (*Block) AuxPow ¶
AuxPow returns the AuxPow data for this block, or nil if the block is pre-AuxPow.
Returns:
- AuxPow structure for merged-mined blocks (version bit 0x100 set)
- nil for pre-AuxPow blocks or if AuxPow was not deserialized
func (*Block) Bytes ¶
Bytes returns the serialized block as a byte slice, including AuxPow if present.
Returns:
- Byte slice containing the complete serialized block
- Error if serialization fails
func (*Block) HasAuxPow ¶
HasAuxPow returns true if this block has AuxPow data. This checks both: 1. The block version has the AuxPow bit set 2. The auxPow field is non-nil
Returns:
- true if block should have and does have AuxPow
- false for pre-AuxPow blocks or if AuxPow is missing
func (*Block) Serialize ¶
Serialize writes the complete block to a writer, including AuxPow if present.
Wire format for AuxPoW blocks:
- Block header (80 bytes)
- AuxPoW data (if HasAuxPow() returns true) - BEFORE transactions!
- Transaction count (varint)
- Transactions (variable length)
Wire format for pre-AuxPoW blocks:
- Block header (80 bytes)
- Transaction count (varint)
- Transactions (variable length)
This produces a serialized block that can be sent over the Namecoin P2P network or stored in block files.
Arguments:
- w: Writer to serialize the block to
Returns:
- nil on success
- error if serialization fails
type BlockChain ¶
type BlockChain struct {
*blockchain.BlockChain
// contains filtered or unexported fields
}
BlockChain wraps btcd blockchain with name operation validation
func NewBlockChain ¶
func NewBlockChain(cfg *Config, indexManager blockchain.IndexManager) (*BlockChain, error)
NewBlockChain creates a new blockchain with name support
func (*BlockChain) BestSnapshot ¶
func (bc *BlockChain) BestSnapshot() *blockchain.BestState
BestSnapshot returns the current best chain snapshot
func (*BlockChain) ChainParams ¶
func (bc *BlockChain) ChainParams() *chaincfg.Params
ChainParams returns the chain parameters
func (*BlockChain) Close ¶
func (bc *BlockChain) Close() error
Close closes the blockchain and name database
func (*BlockChain) GetBlockByHash ¶
GetBlockByHash returns a block by hash
func (*BlockChain) GetBlockHeader ¶
func (bc *BlockChain) GetBlockHeader(hash *chainhash.Hash) (wire.BlockHeader, error)
GetBlockHeader returns a block header by hash
func (*BlockChain) GetName ¶
func (bc *BlockChain) GetName(name string) (*namedb.NameRecord, error)
GetName retrieves a name from the database
func (*BlockChain) GetNameDB ¶
func (bc *BlockChain) GetNameDB() *namedb.NameDatabase
GetNameDB returns the name database instance. This allows external packages to access the name database for read operations.
func (*BlockChain) GetNameHistory ¶
func (bc *BlockChain) GetNameHistory(name string) ([]*namedb.NameRecord, error)
GetNameHistory retrieves the historical records for a specific name. Returns all past operations on the name in chronological order.
func (*BlockChain) GetNameUTXO ¶
func (bc *BlockChain) GetNameUTXO(name string) (*namedb.UTXO, error)
GetNameUTXO retrieves the UTXO that holds a specific name
func (*BlockChain) GetUTXOsForAddress ¶
func (bc *BlockChain) GetUTXOsForAddress(address string) ([]*namedb.UTXO, error)
GetUTXOsForAddress retrieves all UTXOs for a specific address
func (*BlockChain) HandleBlockchainNotification ¶
func (bc *BlockChain) HandleBlockchainNotification(notification *blockchain.Notification)
HandleBlockchainNotification processes blockchain notifications. This method must NOT acquire bc.mu because it may be dispatched synchronously from within bc.BlockChain.ProcessBlock() (which holds bc.mu), causing a deadlock. All operations here delegate to bc.nameDB which has its own independent mutex.
func (*BlockChain) ListNames ¶
func (bc *BlockChain) ListNames() ([]*namedb.NameRecord, error)
ListNames retrieves all names from the database
func (*BlockChain) ProcessBlock ¶
func (bc *BlockChain) ProcessBlock(block *btcutil.Block, flags blockchain.BehaviorFlags) (bool, bool, error)
ProcessBlock processes a block, validates name operations, and returns its chain status.
The supplied block is treated as read-only; the caller retains ownership and may safely reuse the block after ProcessBlock returns.
Return values (isMainChain, isOrphan, err):
(true, false, nil) — block accepted onto the main chain; name database updated.
(false, false, nil) — block accepted as a side-chain block; name database NOT updated.
(false, true, nil) — block is an isolated orphan (parent unknown); name database NOT updated.
Note: (true, true, nil) is theoretically possible from the embedded
btcd BlockChain; callers should handle it as an orphan.
(_, _, err) — block rejected; no state was changed.
Callers in network/peermgr.go use `isOrphan || isMainChain` to decide whether to request the orphan's parent; a pure side-chain block (false, false, nil) does not trigger that path.
func (*BlockChain) ScanNames ¶
func (bc *BlockChain) ScanNames(prefix string, count int) ([]*namedb.NameRecord, error)
ScanNames scans names matching a prefix with pagination. Returns up to count names starting from the given prefix. This is used by the name_scan RPC to provide Namecoin Core compatibility.
func (*BlockChain) SetBlockAuxPowFromBytes ¶
func (bc *BlockChain) SetBlockAuxPowFromBytes(blockHash *chainhash.Hash, serializedBlock []byte) error
SetBlockAuxPowFromBytes extracts and caches AuxPow data from a serialized block. This is called by the network layer when blocks arrive from peers.
The function: 1. Checks if the block version has the AuxPow bit set 2. If so, deserializes the entire block including AuxPow 3. Caches the AuxPow for later validation
Arguments:
- blockHash: Hash of the block (used as cache key)
- serializedBlock: Complete serialized block bytes including AuxPow
Returns:
- nil if successful or if block doesn't have AuxPow
- error if deserialization fails for a block that should have AuxPow
func (*BlockChain) ValidateMempoolTransaction ¶
func (bc *BlockChain) ValidateMempoolTransaction(tx *wire.MsgTx) error
ValidateMempoolTransaction validates a transaction for inclusion in the mempool This performs consensus validation on name operations without requiring the transaction to be part of a block. It checks: - Basic transaction structure and format - Name operation syntax and semantics - Minimum fees for name operations - Name existence and expiration state - UTXO availability for name updates
IMPORTANT LIMITATIONS:
This method does NOT validate script signatures or verify that transactions can actually spend the UTXOs they reference. Signature validation is expensive and deferred to block validation. Invalid transactions with incorrect signatures may be accepted into the mempool and relayed to peers, but will be rejected during block validation. This is an intentional trade-off between DoS resistance and validation cost.
Fee validation requires that all input UTXOs are present in this node's UTXO database. If any input UTXO cannot be found, the transaction is rejected, even if it might be valid on the network. This means a node with incomplete UTXO data (e.g., still syncing or missing historical data) cannot accept transactions that depend on: - UTXOs created in blocks before the node started tracking UTXOs - Recent unconfirmed transactions in other nodes' mempools - Transactions this node has not yet seen This behavior differs from Bitcoin Core's mempool, which can validate and store chains of unconfirmed transactions.
This method is thread-safe and can be called concurrently.
func (*BlockChain) VerifyBlock ¶
func (bc *BlockChain) VerifyBlock(block *btcutil.Block) error
VerifyBlock verifies a block
type Config ¶
type Config struct {
ChainParams *chaincfg.Params
NameDBPath string
DataDir string
// BlockDBPath is the path to the block database directory.
// If empty, blocks.db will be created in DataDir.
BlockDBPath string
}
Config holds blockchain configuration
type MainnetTestVector ¶
type MainnetTestVector struct {
Description string `json:"description"` // Human-readable description
Network string `json:"network"` // mainnet, testnet, or regtest
Type string `json:"type"` // "block" for block vectors
Height int32 `json:"height"` // Block height
Hash string `json:"hash"` // Block hash (hex)
Hex string `json:"hex"` // Serialized block data (hex)
Valid bool `json:"valid"` // Expected validation result
Notes string `json:"notes"` // Additional context
}
MainnetTestVector represents a blockchain test case from real Namecoin mainnet data
func LoadMainnetTestVector ¶
func LoadMainnetTestVector(path string) (*MainnetTestVector, error)
LoadMainnetTestVector loads a single mainnet test vector from a JSON file
func LoadMainnetTestVectors ¶
func LoadMainnetTestVectors(dir, pattern string) ([]*MainnetTestVector, error)
LoadMainnetTestVectors loads all mainnet test vectors from a directory matching a glob pattern Example: LoadMainnetTestVectors("testdata/blocks", "block_*.json")
func (*MainnetTestVector) DecodeHex ¶
func (v *MainnetTestVector) DecodeHex() ([]byte, error)
DecodeHex decodes the hex-encoded block data
func (*MainnetTestVector) IsBlock ¶
func (v *MainnetTestVector) IsBlock() bool
IsBlock returns true if this is a block test vector
func (*MainnetTestVector) IsMainnet ¶
func (v *MainnetTestVector) IsMainnet() bool
IsMainnet returns true if this test vector is from mainnet
func (*MainnetTestVector) IsRegtest ¶
func (v *MainnetTestVector) IsRegtest() bool
IsRegtest returns true if this test vector is from regtest
func (*MainnetTestVector) IsTestnet ¶
func (v *MainnetTestVector) IsTestnet() bool
IsTestnet returns true if this test vector is from testnet
func (*MainnetTestVector) String ¶
func (v *MainnetTestVector) String() string
String returns a human-readable representation of the test vector
type MerkleBranch ¶
type MerkleBranch struct {
// Branch is the list of sibling hashes in the merkle path.
// The length of this slice is the depth of the merkle tree from leaf to root.
Branch []chainhash.Hash
// SideMask is a bit mask indicating the position of the hash at each level.
// Bit i corresponds to level i of the tree:
// - 0 = sibling is on the right, we are on the left
// - 1 = sibling is on the left, we are on the right
// This determines whether to compute Hash(us || sibling) or Hash(sibling || us)
SideMask uint32
}
MerkleBranch represents a merkle branch proof in the AuxPow structure. A merkle branch is a path from a leaf (transaction) to the merkle root, consisting of the sibling hashes at each level of the tree.
Per Namecoin Core src/auxpow.cpp (CAuxMerkleBranch): - Branch: List of sibling hashes in the merkle path - SideMask: Bit mask indicating whether the sibling is on the left (0) or right (1)
Example: To prove a transaction is in a block with 8 transactions: - 3 levels of tree (2^3 = 8 leaves) - Branch has 3 hashes (one sibling at each level) - SideMask has 3 bits (indicating left/right position at each level)
func DeserializeMerkleBranch ¶
func DeserializeMerkleBranch(r io.Reader) (*MerkleBranch, error)
DeserializeMerkleBranch reads a MerkleBranch from a reader.
Wire format: 1. Branch size (varint) - number of hashes in the branch 2. Branch hashes (32 bytes each) 3. Side mask (4 bytes, little-endian uint32)
func (*MerkleBranch) SerializeMerkleBranch ¶
func (mb *MerkleBranch) SerializeMerkleBranch(w io.Writer) error
SerializeMerkleBranch writes a MerkleBranch to a writer.
Wire format matches DeserializeMerkleBranch: 1. Branch size (varint) 2. Branch hashes (32 bytes each) 3. Side mask (4 bytes, little-endian uint32)
type NameOperationInfo ¶
type NameOperationInfo struct {
OpType namedb.NameOperation // The type of name operation (NameNew, NameFirstUpdate, NameUpdate)
Name string // The name being operated on (empty for NAME_NEW)
Value string // The value associated with the name (empty for NAME_NEW)
TxHash chainhash.Hash // The transaction hash containing this operation
OutputIndex int // The output index within the transaction
}
NameOperationInfo contains parsed name operation data extracted from a transaction output. Used by RPC methods like name_pending to provide information about pending name operations in the mempool before they are confirmed in a block.
func ParseNameOperationsFromTx ¶
func ParseNameOperationsFromTx(tx *wire.MsgTx) []NameOperationInfo
ParseNameOperationsFromTx extracts name operations from a transaction's outputs. Returns a slice of NameOperationInfo for each name operation found in the transaction. This is useful for finding pending name operations in the mempool.