Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configs/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ chains:
nodes:
- url: "https://api.devnet.solana.com"
- url: "https://solana-devnet.api.onfinality.io/public"
- url: "https://solana-devnet.gateway.tatum.io/"
client:
timeout: "12s"
max_retries: 2
Expand Down
2 changes: 1 addition & 1 deletion internal/indexer/solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ func (s *SolanaIndexer) extractSolanaTransfers(networkID string, slot uint64, ts
continue
}
txHash := tx.Transaction.Signatures[0]
fee := decimal.NewFromInt(int64(tx.Meta.Fee))
fee := solana.FeeLamportsToSOL(tx.Meta.Fee)
accountKeys := tx.Transaction.Message.AccountKeys

// Build token-account -> (owner, mint) lookup from token balance metadata.
Expand Down
1 change: 1 addition & 0 deletions internal/indexer/solana_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func TestSolanaBlockHashAndTransferIndex(t *testing.T) {
for _, tx := range transfers {
assert.Equal(t, blockHash, tx.BlockHash, "BlockHash should be propagated")
assert.NotEmpty(t, tx.TransferIndex, "TransferIndex should be set")
assert.Equal(t, "0.000005", tx.TxFee.String(), "TxFee should be denominated in SOL")
}

// TransferIndexes should be unique
Expand Down
10 changes: 10 additions & 0 deletions internal/rpc/solana/fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package solana

import "github.com/shopspring/decimal"

const LamportsPerSOL = 1_000_000_000

func FeeLamportsToSOL(lamports uint64) decimal.Decimal {
return decimal.NewFromInt(int64(lamports)).
Div(decimal.NewFromInt(LamportsPerSOL))
}
7 changes: 7 additions & 0 deletions internal/worker/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/fystack/multichain-indexer/internal/indexer"
"github.com/fystack/multichain-indexer/internal/status"
"github.com/fystack/multichain-indexer/pkg/common/config"
"github.com/fystack/multichain-indexer/pkg/common/enum"
"github.com/fystack/multichain-indexer/pkg/common/logger"
"github.com/fystack/multichain-indexer/pkg/common/types"
"github.com/fystack/multichain-indexer/pkg/events"
Expand Down Expand Up @@ -141,6 +142,12 @@ func (bw *BaseWorker) notifyObserver(blockNumber uint64, status BlockStatus) {
}
}

func (bw *BaseWorker) isSkippableNotFound(result indexer.BlockResult) bool {
return result.Error != nil &&
result.Error.ErrorType == indexer.ErrorTypeBlockNotFound &&
bw.chain.GetNetworkType() == enum.NetworkTypeSol
}

// handleBlockResult processes a block result and persists/forwards errors if needed.
func (bw *BaseWorker) handleBlockResult(result indexer.BlockResult) bool {
registry := status.EnsureStatusRegistry(bw.statusRegistry)
Expand Down
3 changes: 1 addition & 2 deletions internal/worker/catchup.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/fystack/multichain-indexer/internal/indexer"
"github.com/fystack/multichain-indexer/internal/status"
"github.com/fystack/multichain-indexer/pkg/common/config"
"github.com/fystack/multichain-indexer/pkg/common/enum"
"github.com/fystack/multichain-indexer/pkg/events"
"github.com/fystack/multichain-indexer/pkg/infra"
"github.com/fystack/multichain-indexer/pkg/store/blockstore"
Expand Down Expand Up @@ -287,7 +286,7 @@ func (cw *CatchupWorker) processRange(r blockstore.CatchupRange, workerID int) e
// Process results
batchSuccess := current - 1
for _, res := range results {
if res.Error != nil && res.Error.ErrorType == indexer.ErrorTypeBlockNotFound && cw.chain.GetNetworkType() == enum.NetworkTypeSol {
if cw.isSkippableNotFound(res) {
// Solana skipped slots are normal — the validator didn't produce a block
// for this slot. Skip without retry.
cw.logger.Debug("Solana skipped slot, no retry needed", "slot", res.Number)
Expand Down
6 changes: 6 additions & 0 deletions internal/worker/regular.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ func (rw *RegularWorker) processRegularBlocks() error {
stopTick, processErr = rw.processReorgCheckedBatch(results, end, &lastSuccess, &lastSuccessHash)
} else {
for _, res := range results {
if rw.isSkippableNotFound(res) {
rw.logger.Debug("Solana skipped slot, no retry needed", "slot", res.Number)
rw.notifyObserver(res.Number, BlockStatusNotFound)
lastSuccess = res.Number
continue
}
if rw.handleBlockResult(res) {
lastSuccess = res.Number
lastSuccessHash = res.Block.Hash
Expand Down
45 changes: 45 additions & 0 deletions internal/worker/regular_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,51 @@ func TestRegularWorkerProcessRegularBlocksMarksUnresolvedGapFailed(t *testing.T)
require.Equal(t, []uint64{100, 100}, chain.getBlockCalls)
}

func TestRegularWorkerProcessRegularBlocksSkipsSolanaSkippedSlot(t *testing.T) {
t.Parallel()

chain := &stubIndexer{
name: "solana",
internalCode: "SOL_MAINNET",
networkType: enum.NetworkTypeSol,
latest: 102,
getBlocksFunc: func(context.Context, uint64, uint64, bool) ([]indexer.BlockResult, error) {
return []indexer.BlockResult{
{
Number: 100,
Block: &types.Block{
Number: 100,
Hash: "sol100",
},
},
{
Number: 101,
Error: &indexer.Error{
ErrorType: indexer.ErrorTypeBlockNotFound,
Message: "block not found (skipped slot?)",
},
},
{
Number: 102,
Block: &types.Block{
Number: 102,
Hash: "sol102",
},
},
}, nil
},
}
store := &stubBlockStore{}
rw := newTestRegularWorker(chain, store, 100, 3)

err := rw.processRegularBlocks()
require.NoError(t, err)
require.Equal(t, uint64(103), rw.currentBlock)
require.Equal(t, []uint64{102}, store.savedLatest)
require.Empty(t, store.failedBlocks)
require.Empty(t, chain.getBlockCalls)
}

func TestCheckContinuityReturnsFalseForNilBlocks(t *testing.T) {
t.Parallel()

Expand Down
3 changes: 1 addition & 2 deletions internal/worker/rescanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/fystack/multichain-indexer/internal/indexer"
"github.com/fystack/multichain-indexer/internal/status"
"github.com/fystack/multichain-indexer/pkg/common/config"
"github.com/fystack/multichain-indexer/pkg/common/enum"
"github.com/fystack/multichain-indexer/pkg/events"
"github.com/fystack/multichain-indexer/pkg/infra"
"github.com/fystack/multichain-indexer/pkg/store/blockstore"
Expand Down Expand Up @@ -203,7 +202,7 @@ func (rw *RescannerWorker) processBatch(blocks []uint64) error {
success := 0

for _, res := range results {
if res.Error != nil && res.Error.ErrorType == indexer.ErrorTypeBlockNotFound && rw.chain.GetNetworkType() == enum.NetworkTypeSol {
if rw.isSkippableNotFound(res) {
// Solana skipped slots are normal. Do not retry them.
toRemove = append(toRemove, res.Number)
continue
Expand Down
Loading