-
-
Notifications
You must be signed in to change notification settings - Fork 745
fix(tron): the receipt for unsolidified block should not be set to ni… #1583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
edd3f34
72a1191
cae749d
dd1b64d
a5da37d
f2115fc
a886dc6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -287,6 +287,16 @@ func (b *TronRPC) GetBestBlockHash() (string, error) { | |
|
|
||
| // GetBlockHash returns block hash in Tron API format (without 0x prefix). | ||
| func (b *TronRPC) GetBlockHash(height uint32) (string, error) { | ||
| if b.isBlockSolidified(uint64(height)) && b.solidityNodeHTTP != nil { | ||
| ctx, cancel := context.WithTimeout(b.requestContext(), b.Timeout) | ||
| defer cancel() | ||
| hash, err := b.requestBlockHashByNum(ctx, height) | ||
| if err == nil { | ||
| return hash, nil | ||
| } | ||
| glog.V(1).Infof("Tron native getblock hash lookup failed for height %d, falling back to JSON-RPC: %v", height, err) | ||
| } | ||
|
|
||
| hash, err := b.EthereumRPC.GetBlockHash(height) | ||
| if err != nil { | ||
| return "", err | ||
|
|
@@ -347,21 +357,6 @@ func (b *TronRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { | |
| } | ||
|
|
||
| func (b *TronRPC) getBestHeader() (bchain.EVMHeader, error) { | ||
| // During initial sync (before ZeroMQ is initialized) there is no push-based | ||
| // tip refresh, so always read the latest header from the backend. | ||
| if b.mq == nil { | ||
| _, err := b.refreshBestHeaderFromChain() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| b.bestHeaderLock.Lock() | ||
| defer b.bestHeaderLock.Unlock() | ||
| if b.bestHeader == nil || b.bestHeader.Number() == nil { | ||
| return nil, errors.New("best header is nil") | ||
| } | ||
| return b.bestHeader, nil | ||
| } | ||
|
|
||
| b.bestHeaderLock.Lock() | ||
| cachedHeader := b.bestHeader | ||
| cachedAt := b.bestHeaderTime | ||
|
|
@@ -384,22 +379,6 @@ func (b *TronRPC) getBestHeader() (bchain.EVMHeader, error) { | |
| return b.bestHeader, nil | ||
| } | ||
|
|
||
| // setBestHeader stores h as the cached tip and reports whether it changed. | ||
| // | ||
| // Unlike EthereumRPC.setBestHeader this is intentionally NON-monotonic: a lower | ||
| // height is accepted. Tron's tip is never taken from the feed header (the ZeroMQ | ||
| // notification carries none) — it is always an HTTP re-query (refreshBestHeaderFromChain), | ||
| // refreshed on every notification and on a tronBestHeaderMaxAge timer, so the cache | ||
| // is meant to track whatever the backend currently reports. Accepting a lower height | ||
| // is what lets a genuine rollback surface immediately to resyncIndex, so Tron is not | ||
| // subject to the frozen-tip masking that the EVM monotonic guard introduces (and which | ||
| // EVM has to undo with a watchdog regress). | ||
| // | ||
| // Tradeoff: with a load-balanced Tron RPC, a single lagging node answering a re-query | ||
| // could regress the tip and trip a spurious fork in resyncIndex (the case the EVM | ||
| // monotonic guard exists to prevent). That is acceptable for the common single-node | ||
| // java-tron backend; if Tron is ever fronted by a load balancer, port the EVM pattern | ||
| // here (monotonic hot path + on-advance liveness + allowRegress watchdog poll). | ||
| func (b *TronRPC) setBestHeader(h bchain.EVMHeader) bool { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This branch deleted the doc comment that explained |
||
| if h == nil || h.Number() == nil { | ||
| return false | ||
|
|
@@ -697,11 +676,11 @@ func (b *TronRPC) computeBlockConfirmations(blockNumber uint64) (uint32, error) | |
| return uint32(bestHeight - blockNumber + 1), nil | ||
| } | ||
|
|
||
| func (b *TronRPC) buildTxFromHTTPData(txByID *tronGetTransactionByIDResponse, txInfo *tronGetTransactionInfoByIDResponse, blockTime int64, confirmations uint32, internalData *bchain.EthereumInternalData, isSolidified bool) (*bchain.Tx, error) { | ||
| func (b *TronRPC) buildTxFromHTTPData(txByID *tronGetTransactionByIDResponse, txInfo *tronGetTransactionInfoByIDResponse, blockTime int64, confirmations uint32, internalData *bchain.EthereumInternalData, keepReceipt bool) (*bchain.Tx, error) { | ||
| csd := tronBuildEthereumSpecificData(txByID, txInfo) | ||
| csd.InternalData = internalData | ||
|
|
||
| if !isSolidified { | ||
| if !keepReceipt { | ||
| csd.Receipt = nil // set to nil so it can be considered as pending | ||
| } | ||
|
|
||
|
|
@@ -948,7 +927,9 @@ func (b *TronRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { | |
| txInternalData = &internalData[i] | ||
| } | ||
|
|
||
| rebuiltTx, err := b.buildTxFromHTTPData(txByID, txInfo, bbh.Time, confirmations, txInternalData, isSolidified) | ||
| // Keep receipts for block indexing even before solidity. If logs are dropped | ||
| // here, token-transfer participants never reach the address/contract indexes. | ||
| rebuiltTx, err := b.buildTxFromHTTPData(txByID, txInfo, bbh.Time, confirmations, txInternalData, true) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fixes the indexing path, but the serving path stays asymmetric: |
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # Tron Sync Tuning | ||
|
|
||
| Fast Tron initial sync needs tuning on both sides of the RPC boundary. In the | ||
| observed setup, the bottleneck was backend block-hash/block lookup latency, not | ||
| Blockbook Go GC or RocksDB writes. | ||
|
|
||
| ## java-tron | ||
|
|
||
| Keep the backend on LevelDB, keep transaction history enabled, enable the LevelDB read tuning and raise the | ||
| HTTP connection limit enough for Blockbook's worker count: | ||
|
|
||
| ```hocon | ||
| storage { | ||
| db.engine = "LEVELDB" | ||
| db.sync = false | ||
| transHistory.switch = "on" | ||
|
|
||
| # LevelDB read tuning from Tron TIP-343. Raises fd usage. | ||
| default = { | ||
| maxOpenFiles = 100 | ||
| } | ||
| defaultM = { | ||
| maxOpenFiles = 500 | ||
| } | ||
| defaultL = { | ||
| maxOpenFiles = 1000 | ||
| } | ||
| } | ||
|
|
||
| node { | ||
| # With Blockbook -workers=32, start around 200. | ||
| maxHttpConnectNumber = 200 | ||
| } | ||
| ``` | ||
|
|
||
| Run java-tron with enough heap, but leave RAM for the OS page cache. On large | ||
| hosts, `-Xms32g -Xmx32g` with G1GC is a good starting point. Validate with GC | ||
| logs and disk latency; a larger heap is not automatically faster. | ||
|
|
||
| Keep event subscription minimal for Blockbook sync: | ||
|
|
||
| ```hocon | ||
| event.subscribe { | ||
| topics = [ | ||
| { triggerName = "block", enable = true, topic = "block", solidified = false }, | ||
| { triggerName = "solidity", enable = true, topic = "solidity" } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| Do not enable `transaction`, `contractevent`, or `contractlog` topics just for | ||
| Blockbook initial sync. | ||
|
|
||
| ## Blockbook | ||
|
|
||
| Run initial sync with enough workers to keep java-tron busy: | ||
|
|
||
| ```ini | ||
| ExecStart=.../blockbook ... -sync -workers=32 ... | ||
| ``` | ||
|
|
||
| Pair this with a sufficiently large Tron HTTP connection pool in Blockbook and | ||
| `node.maxHttpConnectNumber` in java-tron. Raising workers without raising the | ||
| backend connection limit just moves the queue. | ||
|
|
||
| ## Verify | ||
|
|
||
| Useful indicators while syncing: | ||
|
|
||
| * Blockbook log `elapsed` per 1000 blocks | ||
| * `blockbook_rpc_latency{method="GetBlockHash"}` and `method="GetBlock"`, increased latency means the bottleneck is on the Java-Tron side | ||
| * java-tron GC pauses and heap after GC | ||
| * disk utilization and await, for example `iostat -xz 1` | ||
| * open file descriptors for both services | ||
| * `blockbook_index_block_not_found_retries` | ||
|
|
||
| With the tuning above and native hash lookup, observed throughput reached about | ||
| 7.5-9.5 seconds per 1000 historical blocks on the tested setup. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heads-up on the removed
if b.mq == nil { refreshBestHeaderFromChain() }block:getBestHeadernow serves a cached tip for up totronBestHeaderMaxAge(30s), and the watchdog that refreshes it only starts inInitializeMempool, which runs after the initialResyncIndex. So during initial bulk sync (and in polling-only mode before mempool init) the target tip andcomputeBlockConfirmationscan lag the chain by up to 30s where they were previously always fresh. This looks like the deliberate trade-off from the "reduce requests per block" commit and is benign for far-behind sync — just confirming it's intended and that the syncer can't briefly treat the stale tip as fully-synced.