Skip to content

Conversation

@jadepark-dev
Copy link
Collaborator

@jadepark-dev jadepark-dev commented Dec 22, 2025

Primary Changes

  • Added GetLatestMasterBlockSeqno method to retrieve the highest masterchain block sequence number from stored logs, enabling service restart resumption
  • Updated Tx model to include MasterBlockSeqno field for tracking masterchain finalization of shard blocks
  • Modified getLastProcessedBlock to check stored state before falling back to lookback window calculation

Others

  • Updated in-memory log store with cached maxMasterBlockSeqno for O(1) lookups
  • Added comprehensive test coverage for GetLatestMasterBlockSeqno functionality
  • Updated parser to populate MasterBlockSeqno in log entries from transaction data
  • Aded observability wrapper for the new database query method

@jadepark-dev jadepark-dev marked this pull request as ready for review January 5, 2026 15:42
@jadepark-dev jadepark-dev requested a review from a team as a code owner January 5, 2026 15:42
Copilot AI review requested due to automatic review settings January 5, 2026 15:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements masterchain block sequence number tracking to enable service restart resumption from the last processed state, eliminating the need to reprocess blocks after restarts.

Key Changes:

  • Added GetLatestMasterBlockSeqno method to retrieve the highest masterchain block sequence number from stored logs
  • Updated Tx model to include MasterBlockSeqno field for tracking masterchain finalization of shard blocks
  • Modified getLastProcessedBlock to prioritize stored state: in-memory → database → lookback window calculation

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/logpoller/store/postgres/logs.go Implements database query to retrieve max master_block_seqno from logs table
pkg/logpoller/store/memory/logs.go Adds cached maxMasterBlockSeqno field with O(1) lookup implementation
pkg/logpoller/store/memory/logs_test.go Comprehensive test coverage for GetLatestMasterBlockSeqno with empty store and multiple logs scenarios
pkg/logpoller/interfaces.go Defines GetLatestMasterBlockSeqno interface method with documentation
pkg/logpoller/observed_log_store.go Adds observability wrapper with metrics for the new database query method
pkg/logpoller/models/models.go Extends Tx model with MasterBlockSeqno field and updates Block field documentation
pkg/logpoller/parser.go Updates parser to accept Tx model and populate MasterBlockSeqno in log entries
pkg/logpoller/parser_test.go Updates test cases to use new Tx model structure
pkg/logpoller/block.go Implements three-tier fallback logic in getLastProcessedBlock for state resumption

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

lp.lggr.Debugw("Resuming from last processed", "seqNo", lastProcessed)
}
return lastProcessed, nil
blocksToProcess := currentBlock.SeqNo - lookbackSeqNo
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The variable blocksToProcess is computed but never used. This appears to be dead code left over from refactoring. Consider removing this line if it serves no purpose, or use it in the logging statement below if the intent is to include it.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed in following PR: #499

func (s *pgLogStore) GetLatestMasterBlockSeqno(ctx context.Context) (uint32, error) {
var result *int64

sql := `SELECT MAX(master_block_seqno) FROM ton.log_poller_logs WHERE chain_id = :chain_id`
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

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

The query scans the entire table for MAX(master_block_seqno). If this table grows large, consider adding an index on (chain_id, master_block_seqno DESC) to optimize this query, especially since it will be called on every service restart.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Requires db schema update, NONEVM-3033 will handle with other optimizations in future sprint

patricios-space
patricios-space previously approved these changes Jan 7, 2026
ogtownsend
ogtownsend previously approved these changes Jan 7, 2026
huangzhen1997
huangzhen1997 previously approved these changes Jan 8, 2026
* feat: resolve mc block seqno on load

* chore: tests

* chore: nix hash

* chore: nix hash

* chore: lint

* fix: tests
// GetLatestMCBlockSeqno returns the highest masterchain block sequence number
// from stored logs. Returns (seqno, exists, err) where exists indicates whether any
// logs are stored.
func (s *inMemoryLogs) GetLatestMCBlockSeqno(_ context.Context) (uint32, bool, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The Latest vs Max confuses me.

  1. We expect block sequence numbers to be growing, right? Then the expression
		// update cached max
		if log.MCBlockSeqno > s.maxMCBlockSeqno {
			s.maxMCBlockSeqno = log.MCBlockSeqno
		}

should always be true. Should we throw an error if this condition is not met?

  1. Do we expect sequence numbers to be incremental? In that case, we can also expect log.MCBlockSeqno == s.maxMCBlockSeqno + 1

Copy link
Collaborator

Choose a reason for hiding this comment

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

My question arises from GetLatestMCBlockSeqno returning maxMCBlockSeqno. If we don't expect seq numbers to always be greater than the previous one, the latest to be process may not be maxMCBlockSeqno.

Maybe I am missing something

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good questions, yeah the naming could be confusing.

  • "highest seqno" - what the current implementation returns
  • "chronologically last processed" - what you might think from the name

Should we throw an error if log.MCBlockSeqno > s.maxMCBlockSeqno is not met?

In our polling logic, multiple filters are processed concurrently, so logs arriving at the store may not be the "latest" in terms of masterchain block order. However, the function name was chosen from the perspective of the logpoller main loop: "what is the block number where processing was most recently completed?". I've renamed the function to GetHighestMCBlockSeqno to make this clearer.

Do we expect log.MCBlockSeqno == s.maxMCBlockSeqno + 1?

No. Sequence numbers are not strictly incremental for two reasons:

  • Multiple logs can share the same MCBlockSeqno (multiple txs in one block)
  • Blocks without matching transactions are skipped, creating gaps

Comment on lines 294 to 295
errsOut <- fmt.Errorf("failed to resolve masterchain block seqno: %w", err)
continue
Copy link
Collaborator

Choose a reason for hiding this comment

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

will this cause any txs/events to be dropped permanently?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I revisited the logic and I would go soft with storing masterchain block number. I'll add retry logic, but allowing default masterchain block number 0 to avoid data loss. In worst case we'll have a few records with mcBlock=0, but it's used only for determining the highest block number processed, and it's safer than losing logs

"err", err)
} else if exists {
if dbSeqno == 0 {
return 0, errors.New("database contains logs with master_block_seqno=0, which indicates data corruption")
Copy link
Collaborator

Choose a reason for hiding this comment

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

are there any 0 seqNos in the current DB? will this cause failures during or right after a migration when these changes are deployed for the first time?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, lowered it to warning

return 0, nil
}

// Validate logs before expensive conversion to fail fast
Copy link
Collaborator

Choose a reason for hiding this comment

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

nice nice

@jadepark-dev jadepark-dev merged commit 73550e4 into main Jan 19, 2026
37 checks passed
@jadepark-dev jadepark-dev deleted the jade/logpoller-block-fetcher branch January 19, 2026 11:06
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.

4 participants