Skip to content

Conversation

@Woft257
Copy link

@Woft257 Woft257 commented Dec 14, 2025

This pull request documents the step-by-step process of integrating a robust Cardano indexer into the existing multichain framework. The primary focus was to handle Cardano's unique EUTXO model without introducing breaking changes to other chains, while also ensuring production-readiness by implementing reorg handling.

What Was Done (The Process)

1. Handling the UTXO/EUTXO Model

To support Cardano's complex transactions (multiple inputs/outputs, multi-asset), i took the following steps:

  • A new RichTransaction struct was created within the cardano package to accurately represent a Cardano transaction.
  • To avoid modifying the shared types.Transaction struct, this RichTransaction is encoded and passed within the Payload field.
  • The BaseWorker was then updated with new logic to detect this Payload. If present, it decodes the data and processes it using a Cardano-specific path, leaving the EVM/Tron logic untouched.
  • The address of the first input of a transaction is now used as the representative FromAddress.

2. Optimizing NATS Events

A naive implementation would have created an "event storm" on NATS to use with UTXO/EUTXO. To solve this:

  • I created a new pkg/events/events.go file and defined a MultiAssetTransactionEvent.
  • This new event structure is designed to bundle all assets transferred to a single destination within one transaction into a single, efficient message.
  • The Emitter interface and its implementation were extended to support this new event type.
  • The BaseWorker now uses this new event, drastically reducing the number of messages published for multi-asset transactions.

3. Implementing Reorg Handling

A critical step for production stability was to handle blockchain reorganizations.

  • first investigated the existing RegularWorker and discovered a pre-existing, chain-agnostic reorg detection mechanism (detectAndHandleReorg).
  • This mechanism, which works by comparing the ParentHash of new blocks against the stored Hash of previous blocks, was only active for EVM.
  • Recognizing that this logic is universal to all blockchains, I activated it for Cardano by modifying the isReorgCheckRequired function.

Comment on lines 63 to 65
return e.queue.Enqueue(infra.MultiAssetTransferEventTopicQueue, eventBytes, &infra.EnqueueOptions{
IdempotententKey: event.TxHash, // Use TxHash for idempotency
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't support mult asset event, if there is multi asset transaciton. it should be broken down into multiple individual single token transaction

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already updated! Multi-asset transactions are now broken down into individual single-token events in the convertBlock() function (lines 260-282) in file internal/indexer/cardano.go. Each asset in the output creates a separate transfer event.

}
concurrency := c.config.Throttle.Concurrency
if concurrency <= 0 {
concurrency = 4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should make this a constant

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already using constant! DefaultTxFetchConcurrency = 4 is defined in internal/rpc/cardano/client.go at line 18.

// We use the payload to carry the rich data.
Payload: payload,
}
transactions = append(transactions, genericTx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it check if a transaction has status = success?
Otherwise we can index a transfer with status = failed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This is handled in the RPC client (GetTransaction function, commit 1f1fec4).

We first ensure the transaction is finalized in a block, then we explicitly reject any transaction where valid_contract is false. Only fully successful transactions are ever indexed.

Comment on lines 76 to 79
if len(t.Payload) > 0 {
builder.WriteByte('|')
builder.Write(t.Payload)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for? should make a test file

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the Payload field since we don't need it anymore

Comment on lines 7 to 25

// Encode converts an interface into a byte slice using gob encoding.
// Useful for serializing complex data structures into a generic payload.
func Encode(data interface{}) ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

// Decode converts a byte slice back into an interface using gob decoding.
// The 'out' parameter must be a pointer to the target data structure.
func Decode(data []byte, out interface{}) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
return dec.Decode(out)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purpose of those functions?
Should give examples in test cases

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These Encode() and Decode() functions were originally used for the Payload field (to serialize complex data structures using gob encoding). Since we removed the Payload field, these functions are no longer needed and have been removed as well.

Comment on lines 12 to 20
type MultiAssetTransactionEvent struct {
Chain string `json:"chain"`
TxHash string `json:"tx_hash"`
BlockHeight uint64 `json:"block_height"`
FromAddress string `json:"from_address"` // Representative from address
ToAddress string `json:"to_address"` // The address that received the assets
Assets []AssetTransfer `json:"assets"` // List of assets transferred to the ToAddress
Fee string `json:"fee"`
Timestamp uint64 `json:"timestamp"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention above, our backend doesn't support transfer:multi_asset_event , should propose a solution that is backward compatible

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-asset support removed. Each asset is now a separate transfer event. events.go and related code deleted.

@Woft257
Copy link
Author

Woft257 commented Dec 21, 2025

Hi team! Here's a summary of what's been updated since commit de3e3a3:

Recent Changes (de3e3a3 → HEAD)

Transaction Model Enhancements (a127947)

  • Added Collateral and Reference fields to Cardano transaction inputs/outputs
  • Better support for smart contract transactions and script validation

Concurrency & Performance (671bba0, 7f72270)

  • Standardized concurrency with DefaultTxFetchConcurrency = 4 constant
  • Optimized block fetching with concurrency control and error handling
  • Dynamic concurrency adjustment based on transaction count Rate Limiting & Error Handling (eb70680, dda8121, 035279d)
  • Enhanced parallel transaction fetching with rate-limit error handling
  • Improved Cardano API interactions with better rate limiting logic
  • Fixed variable assignment for transaction fees in GetTransaction method

Transaction Validation (1f1fec4)

  • Added validation for transaction finalization (skip failed smart contracts)
  • Added TTL (Time To Live) validation
  • Proper fee calculation and validation

Code Cleanup (de3e3a3)

  • Removed multi-asset event support (now split into individual transfers)
  • Removed rich transaction support and related code
  • Deleted events.go and gob encoding utilities

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants