Problem
The open-aea-ledger-solana plugin has accumulated correctness and maintenance debt that should be addressed in a dedicated refactor. The dep bump in #(this PR) re-enables the plugin for downstream consumers (see valory-xyz/open-autonomy#2479) but intentionally preserves pre-existing behaviour to keep that change minimal and review-able. This issue tracks the items that should be cleaned up next.
Correctness: blockhash handling
The plugin caches a recent blockhash with a wall-clock TTL (10 seconds, both before and after #(this PR) — the upstream solana.blockhash.BlockhashCache being replaced was itself TTL-based). Solana blockhash validity is tied to block height, not time:
- a blockhash expires when
current_block_height > last_valid_block_height (~150 blocks, ~60–90s but variable with cluster load)
context.slot from get_latest_blockhash is the slot the RPC responded at, not an expiration bound
- under load / async delays this yields intermittent
Blockhash not found at submit time
Needed:
Correctness: other pre-existing issues
Naming / API clarity
Dependency hygiene
Mypy / pylint baseline
Not blocking this follow-up, but for reference the plugin currently carries 51 mypy errors and ~20 pylint warnings that long predate any recent change. Worth a tracked clean-up pass.
Context
Problem
The
open-aea-ledger-solanaplugin has accumulated correctness and maintenance debt that should be addressed in a dedicated refactor. The dep bump in #(this PR) re-enables the plugin for downstream consumers (see valory-xyz/open-autonomy#2479) but intentionally preserves pre-existing behaviour to keep that change minimal and review-able. This issue tracks the items that should be cleaned up next.Correctness: blockhash handling
The plugin caches a recent blockhash with a wall-clock TTL (10 seconds, both before and after #(this PR) — the upstream
solana.blockhash.BlockhashCachebeing replaced was itself TTL-based). Solana blockhash validity is tied to block height, not time:current_block_height > last_valid_block_height(~150 blocks, ~60–90s but variable with cluster load)context.slotfromget_latest_blockhashis the slot the RPC responded at, not an expiration boundBlockhash not foundat submit timeNeeded:
value.blockhashandvalue.last_valid_block_heightfromget_latest_blockhash()get_block_height()and compare againstlast_valid_block_height— expire if exceededto_json()→json.loads(...)roundtrip)SolanaApi.latest_hashalready does an unconditional RPC call on every access today, so the cache is mostly ornamental.slotparameter fromBlockhashCache.set()— it is stored but never read back.Correctness: other pre-existing issues
_generate_tx_noncecallsself._api.BlockhashCache.get(), butBlockhashCacheis an attribute ofSolanaApi, not._api(aSolanaApiClient— confirmed:hasattr(SolanaApiClient(...), 'BlockhashCache')isFalse). Every call raisesAttributeError, is caught by the broadexcept Exception, and falls through to a fresh RPC. The cache has been ornamental in practice — removing it entirely in a follow-up changes nothing behaviourally. Correct reference:self.BlockhashCache.get().except Exceptionin_generate_tx_nonceswallows RPC failures, serialization errors, and logic bugs alike. Narrow toLookupError(cache miss) so real failures surface.get_latest_blockhash().value— crashes withAttributeErrorif the RPC returns no value rather than raising.SolanaHelper.load_contract_interface:open(bytecode_path, "rb")without awithblock. Add context manager + explicit encoding._get_latest_hashand_generate_tx_nonce(result.value.to_json()→json.loads(...)) —result.value.blockhashis already a typed string. Remove the roundtrip.Naming / API clarity
Helper._generate_tx_nonceis chain-agnostic ("nonce" = replay-defeater). For Solana readers this is confusing — blockhashes are an anti-replay mechanism, distinct from Solana's actual durable-nonce accounts. Add a docstring clarifying what "nonce" means for this plugin, or introduce a Solana-specific name alongside the overriding method.generate_tx_nonce(seller, client)(the@staticmethodsha256 variant onSolanaHelper) is literally unrelated to Solana — pure deterministic hash. Document or remove.Dependency hygiene
solana>=0.33.0,<0.34.0in #(this PR).solana>=0.36removed thesolana.transactionmodule (replaced bysolders.transaction) — adopting it requires refactoring allTransactionimports + call sites. Worth doing so we can track solana-py's current line.anchorpy>=0.20.0,<0.21.0—anchorpy0.21+ requiressolders>=0.21+solana>=0.36(the two bumps above). Group these.api.devnet.solana.comfaucet) and are failing today due to airdrop rate-limit exhaustion. Either gate them behind a@pytest.mark.integrationmarker excluded from default runs, or stub the RPC boundary so unit tests remain deterministic.Mypy / pylint baseline
Not blocking this follow-up, but for reference the plugin currently carries 51 mypy errors and ~20 pylint warnings that long predate any recent change. Worth a tracked clean-up pass.
Context
BlockhashCachesubstitute, which flagged that faithfully preserving upstream behaviour preserved upstream's pre-existing flaws.