Skip to content

Dragan's Builder Journey #108

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

Open
Milosevic02 opened this issue Feb 6, 2025 · 14 comments
Open

Dragan's Builder Journey #108

Milosevic02 opened this issue Feb 6, 2025 · 14 comments
Assignees

Comments

@Milosevic02
Copy link

Hey, I’m Dragan Milosevic, a final-year student of Applied Software Engineering at the Faculty of Technical Sciences in Novi Sad, Serbia, with a strong focus on blockchain and Web3 technologies.

Since attending the Web3 Camp in Petnica, I’ve been actively exploring decentralized systems, smart contract development, and blockchain infrastructure. My journey in Web3 started with building a bridge between Ethereum and gno.land, which really sparked my passion for blockchain development.

I also have experience in AI, particularly in NLP and neural networks, and I’ve competed in and won two AI hackathons (the first focused on CNN, the second on chatbots). Additionally, I won the ETH Sofia hackathon, where I secured four sponsor bounties. I’m truly passionate about contributing to the blockchain ecosystem, and I’m eager to collaborate on innovative projects and continue expanding my expertise.

@Milosevic02
Copy link
Author

Milosevic02 commented Feb 11, 2025

Hey everyone, this week I set up my Home Realm and implemented the core functionality. I also spent time styling it and making sure everything works smoothly (when it's fully ready, I'll open a PR).

To get familiar with the system, I explored the codebase and learned how things are organized. I also got more comfortable with GitHub workflow. To make development easier, I set up dual boot on my machine.

A big thanks to @leohhhn , and @Nemanya8 , who helped me get started and introduced me to the key concepts. I also went through all the journey examples, read issues and watched workshop videos to better understand what’s possible and to gather inspiration for future improvements.

I started brainstorming projects I could work on to deepen my understanding of the concepts and possibilities within Gnoland. One idea is to implement a Mastermind game, and another is an NFT auction where bids are placed anonymously, so no one knows who offered what price. I got feedback from Leon to try thinking of something even better if possible, so I’ll continue exploring new ideas.

I also reported and worked on an issue that I encountered, which you can find here.

This week was all about getting comfortable with the environment and exploring ideas for future development. Excited to keep making progress!

@Milosevic02 Milosevic02 changed the title Dragon's Builder Jurney Dragan's Builder Jurney Feb 19, 2025
@Milosevic02
Copy link
Author

Milosevic02 commented Feb 19, 2025

Hey everyone,

This week, I finished my Home Realm implementation and opened a PR for it. Along the way, I became more familiar with gnokey, Adena, gno studio, etc., by calling functions and testing how everything works under the hood.

Regarding the issue I mentioned last week, @gfanton provided a fix, and after reviewing it, we closed the issue. Big thanks to him for the quick response! You can check out the issue here.

I also refactored backend code for @Nemanya8 in gno-visualized.

For my next steps, I’d love to build Liars Lie on Gnoland. This project will help me get even closer to Gnoland while allowing me to dive deeper into topics like oracle mechanisms, protocol communication, and potentially even AI-driven liar detection in the future. Another interesting aspect could be implementing a voting system for identifying liars based on reputation and consensus. I'm currently waiting for feedback before diving into development.

Excited for what's next!

@Milosevic02
Copy link
Author

Hey everyone,

This week, I initially started working on the Liars Lie game, but during the Coworking Day in Belgrade, Milos assigned me a much more important task —improving the mempool— so I shifted my focus entirely to that. This is a significantly bigger challenge, and I’m really excited to dive deep into it.

Since I’m new to this area, I spent a lot of time watching videos and reading extensively about mempools to fully understand the concept, its purpose, and how it operates under the hood.

I also got familiar with the existing mempool implementation in Milos' code —analyzing its structure and understanding how different components interact. This deep dive helped me build a solid foundation for what comes next.

I’m super excited to continue working on this because it’s a truly fascinating topic. Big thanks to Milos for the idea and guidance!

@Milosevic02
Copy link
Author

Hey everyone,

This week, I focused on understanding the core mempool implementation, particularly the CListMempool component. I spent time analyzing how it manages transaction storage using a concurrent linked list (CList), handles transaction validation through ABCI interface, and maintains transaction order. The most interesting part was seeing how it efficiently manages transaction lifecycle from receipt to finalization, including its sophisticated caching mechanism to prevent duplicate transactions.

I learned about key concepts like transaction rechecking, Write-Ahead Logging (WAL) for persistence, and how the mempool handles transaction size limits and overflow conditions. I'm excited to continue exploring this area and potentially contribute improvements to the implementation!

I also had a great sync with @zivkovicmilos where he helped clarify some concepts I wasn't fully grasping and we discussed setting up a weekly sync to ensure I'm on the right track. Big thanks to Milos for his support and guidance!

Next week, I plan to dive deep into the reactor component to understand how it handles peer-to-peer communication and transaction broadcasting across the network. This will help me get a complete picture of how the mempool interacts with other nodes in the network.

@Milosevic02
Copy link
Author

Hey everyone,

This week, I deployed my home realm and closed the PR, shifting my full focus to the mempool work. I've decided to put aside adding unique features to the home realm for now so I can concentrate on understanding and improving the mempool implementation.

I also discovered a bug related to images not displaying on the portal loop due to CSP restrictions and opened an issue for it: #3880.

Additionally, I focused on the mempool's reactor component, analyzing how it handles peer connections and transaction propagation across the network. I learned about peer state tracking, transaction broadcasting protocols, and concurrent transaction propagation, which helped me better understand its role in network-wide transaction distribution. I'm excited to explore this further and potentially contribute improvements.

Next week, I plan to go through bench_test and cache_test, which will complete my review of all files in the project. Once that’s done, I might start working on my own tests to further explore and validate different aspects of the system.

@Milosevic02 Milosevic02 changed the title Dragan's Builder Jurney Dragan's Builder Journey Mar 13, 2025
@Milosevic02
Copy link
Author

Milosevic02 commented Mar 19, 2025

Hey everyone,

This week, I officially completed my review of the entire project by finishing the bench_test and cache_test. With this, I’ve gone through all the files and have a solid understanding of the system.

After that, I started writing my own tests to further explore and validate different aspects of the mempool:

  • TestConcurrentCheckTx – Ensures transactions are correctly written to the mempool when processed concurrently by multiple goroutines.
  • TestMempoolFullBehavior – Tests how the mempool behaves when reaching its maximum transaction capacity.
  • TestRemoveTxOnError – Verifies what happens when an update includes a transaction with an error.
  • TestPreCheckFunction – Checks if the pre-checking mechanism works as expected.

You can find my tests here: clist_mempool_test.go

I also attended a coworking day where I discussed the mempool with @zivkovicmilos . Based on our discussion, I received a new task: instead of fixing issues incrementally, I will start writing my own mempool from scratch. This shift in approach should lead to a cleaner and more efficient implementation.

Next week, I plan to focus on laying the foundation for this new mempool design, identifying key components, and setting up initial structures.

@Milosevic02
Copy link
Author

Hey everyone,

This week, I focused entirely on improving the core behavior of the mempool—how transactions are added, removed, and selected—without worrying about broader integration just yet. The goal was to make sure the mempool handles transactions efficiently before moving on to validation and committing.

And of course, I must thank @zivkovicmilos again! We had a deep discussion about different mempool implementations, and he patiently answered all my questions. His insights helped me refine my approach and solve some key issues.

Key Accomplishments:

  • Fixed Sorting Issue – Initially, transactions were only sorted by gas fee, which caused failures when higher-fee transactions were selected without processing their lower-nonce predecessors. I solved this by modifying the selection process to:

    • First, pick the transaction with the highest gas fee.
    • Then, ensure that all previous transactions from the same sender (with lower nonce) are included before processing the chosen transaction.
  • Efficient Data Structure – Switched to a map-based approach to improve lookup and sorting performance. This structure allows for faster updates and ensures transactions are selected correctly.

  • Optimized Transaction Selection – To avoid unnecessary iterations, I introduced an additional map that tracks the transaction with the highest gas fee per account. This way, the mempool doesn’t need to scan all transactions when picking the next one.

  • Implemented Basic Functionality – The core logic for transaction addition, removal, and prioritization is now in place and working as expected.

  • Test Coverage – Wrote tests to verify that transaction ordering, selection, and updates behave correctly. Everything is running smoothly so far.

Next Steps:

  • Further refine and optimize the mempool structure for better performance.
  • Improve handling of edge cases to ensure stability under load.
  • Start working on transaction validation and committing, making sure the mempool correctly interacts with real transactions.

Right now, the mempool is behaving well, and I’m excited to push it further.

@Milosevic02
Copy link
Author

Hey everyone,

As per my contract, I will be taking two weeks off from April 1st to April 14th. After that, I’ll be back and ready to continue with the project at full speed.

See you soon!

@Milosevic02
Copy link
Author

Hi everyone,

Although I only had two days to work this week due to being on break, I managed to accomplish quite a lot in that time, focusing on core improvements to the mempool. My goal was to make the system more robust, efficient, and fair, especially under conditions like transaction spam or unordered input.

Key Accomplishments

Heap-Based Prioritization

Switched the representative transaction structure from a basic map to a heap-based approach. This significantly improves selection performance when deciding which transactions to pop during the Update() phase.

Immediate Heap Insertion

As soon as a new representative (highest-fee transaction) is determined for a sender, it's inserted into the heap right away. This ensures the heap is always ready for the next pop, removing any delay or need for re-sorting.

Spam Protection via Sender Limit

Added a per-sender cap on the number of transactions that can be included in a single block. This prevents a single user from injecting one high-fee transaction followed by 10,000 micro-fee spam transactions.

Strict Nonce Enforcement

Enforced a strict rule that each transaction from a sender must have a nonce exactly one greater than the previous one. This prevents gaps and guarantees ordered processing without skipping.

Test Suite Improvements

Wrote a comprehensive set of tests to validate all of the above changes. These include:

  • Nonce order checks
  • Heap behavior correctness
  • Enforcement of sender limits
  • Accurate removal and replacement of highest-fee representatives
  • Concurrency stress testing

I currently have a small bug related to concurrency. I have a rough idea of where the issue lies and plan to fix it soon.

Next Steps

  • I have a meeting with @zivkovicmilos tomorrow to align on next steps, but my current plan is:
    • Fix the concurrency bug
    • Refactor the logic to clearly separate select and remove phases
    • Add a proper Update mechanism and introduce caching
    • Stress-test the mempool further to ensure consistent and stable behavior

The mempool is now significantly more predictable, fair, and efficient. I’m happy with the progress and excited for the next layer of robustness.

@Milosevic02
Copy link
Author

Hi everyone,

This week, I focused on a complete redesign of the mempool.
I had a coworking session and technical discussion with @zivkovicmilos, during which we finalized the direction of the design and the key structural changes.
Based on that, I restructured the entire system and implemented everything needed.

Key Improvements

Representative Redesign

Previously, each sender was represented in the heap by their highest-fee transaction. I changed this so that:

  • Each sender is now represented by their transaction with the lowest nonce.
  • Among those representatives, prioritization is based on gas fee.

This ensures fairness, prevents skipped nonces, and keeps transaction order deterministic.

Heap-Based Selection

The selection logic now uses a heap of sender representatives. Once a transaction is popped from the heap, a new representative (next-lowest nonce) is immediately pushed back in, keeping the heap ready for further selection.

Cache for Selected Transactions

When transactions are selected for a block via CollectTxsForBlock, they are stored in a local cachedTxs slice. This allows:

  • Rolling back transactions if the block is not committed
  • Committing confirmed transactions cleanly with Update()

Update Mechanism for Network Sync

The Update(committed []Transaction) function was rewritten to accept a list of externally confirmed transactions and remove them directly from the mempool. It no longer relies on local cache, which allows all nodes in the network to update their mempools independently and consistently after a block is committed.

Rollback Support

If the block fails or is not committed, Rollback() re-adds cached transactions back to the mempool via CheckTx. This guarantees safe recovery and ensures no transaction is lost during a failed commit.

Test Suite

I wrote an entirely new suite of tests to verify:

  • Correct selection order
  • Heap prioritization by gas fee
  • Update logic across multiple mempool instances
  • Proper rollback behavior
  • Concurrency via parallel CheckTx calls

Known Limitation

  • Transactions with nonce gaps are currently accepted. This is temporary and will be fixed with proper expected nonce tracking per sender.

Next Steps

  • Add expectedNonce tracking for each sender
  • Validate that only transactions with the expected nonce are selected
  • Skip senders with a nonce gap until a valid transaction arrives
  • Extend the test suite to cover nonce-gap behavior

The new design provides a clean, safe, and deterministic mempool that is ready for further integration. The core logic is now complete and stable, and I’m excited to move on to the final layer of nonce enforcement.

@Milosevic02
Copy link
Author

Hi everyone,

This week, I focused on further optimizing and simplifying the mempool implementation, with the goal of making it cleaner, faster, and more predictable in behavior.

Following a suggestion from @gfanton, I opened a draft PR to share progress and facilitate early feedback.

Key Improvements

Heap Removal and Simplified Representative Model

I removed the heap structure that was previously used to maintain sender representatives. Instead, the new model treats the transaction with the expected nonce per sender as their representative.
Transactions are kept sorted by nonce, and selection now works by iterating through all representatives and picking the one with the highest gas fee.
This results in O(n) complexity for selection.

Clean Functional API

I refactored the mempool into well-defined, functional components with a clear API:

  • Add(tx) – inserts a transaction into the mempool
  • Select() – selects the highest-priority transaction for block inclusion
  • Update(committedTxs) – removes confirmed transactions and updates internal state accordingly

This structure makes the logic easier to test, debug, and extend in future iterations.

Sequence Fetching for Expected Nonce

I implemented two approaches for retrieving the account sequence (nonce):

  • Using mocked ABCI state for test scenarios
  • Using RPC queries to testnet-6 for integration testing with live nodes

This capability will be used to enforce correct transaction order and detect nonce gaps, which is essential for safe and deterministic execution.

Weekly Coworking Practice

Had a productive coworking session with @zivkovicmilos where we aligned on goals, reviewed design tradeoffs, and planned next steps. We've decided to keep doing this weekly to maintain strong collaboration.

Next Steps

  • Implement caching of expected nonce per sender to avoid repeated sequence lookups
  • Integrate this cached value wherever necessary for selection and validation

The mempool is now much leaner and better structured, with a clear path forward for enforcing correct transaction order and handling edge cases like nonce gaps.

@Milosevic02
Copy link
Author

Hi everyone,

This week, I focused on enforcing strict nonce-based transaction ordering in the mempool by introducing expected nonce caching and replacing all remaining RPC-based logic with clean ABCI integration.

I also added an initial README to explain the design goals and how the custom mempool works.

Key Improvements

ABCI-Based Sequence Fetching

I removed all previous RPC logic and replaced it with ABCI-based querying using QuerySync, which is now fully integrated into the mempool via the proxyAppConn interface.
This allows us to fetch the current sequence number (nonce) of any account directly from the application state without relying on external queries.

Expected Nonce Caching

To avoid repeated ABCI calls, I implemented caching of the expected nonce per sender after the first query.
This value is then used during transaction validation, selection, and update, significantly improving performance and reducing state queries.

Support for Pending and Queued Transactions

To further optimize performance, I introduced a distinction between:

  • Pending transactions – those that match the current expected nonce and are immediately eligible for execution.
  • Queued transactions – future transactions stored in order but not yet executable due to nonce gaps.

This allows the mempool to select only from ready transactions, reducing complexity and avoiding unnecessary iteration over invalid candidates.
While this feature is already implemented and working, further refinement is planned to make it more elegant and efficient.

Test Coverage with ABCI Mocking

All logic is now tested with mock ABCI responses.
This includes:

  • Validation and rejection of transactions with incorrect nonces
  • Automatic promotion of queued transactions once their nonce becomes valid
  • Correct update behavior after transactions are included in a block

Started having daily coworking sessions with @zivkovicmilos to align on architecture decisions and push the quality of the mempool implementation to an even higher level.

Next Steps

  • Refactor to wrap each account’s data (nonce, pending tx, queued txs) into a proper Account struct
  • Use sync.Map for concurrency-friendly access to accounts
  • Improve the Update() method to support external gossiping of nonce/pending state if needed
  • Review edge cases (e.g., nonce reset, dropped tx) and optimize memory cleanup

@Milosevic02
Copy link
Author

Hi everyone,

This week, Milos and I collaborated on writing a full RFC that outlines the design and architecture of a new custom mempool. The implementation has been initiated and closely follows the structure proposed in the RFC.

Key Progress

RFC-Based Mempool Foundation

A new mempool structure was implemented to reflect the ideas proposed in the RFC:

  • Per-sender account isolation with separate pending and queued transaction lists
  • Strict nonce enforcement, where only transactions matching the expected nonce are immediately executable
  • Use of sync.Map and fine-grained locking for safe concurrent access

ABCI Integration

ABCI-based querying (QuerySync) is now used to fetch initial sequence numbers for new accounts.
Transactions are validated via CheckTxAsync, ensuring correctness before inclusion.

Queueing Logic and Promotion

Transactions with nonces above the expected value are added to the queued set.
Upon accepting a transaction or updating after a block commit, the mempool automatically promotes any now-valid queued transactions to pending.

The core structure defined in the RFC is now functional. However, we've encountered some design tradeoffs and implementation challenges that will be discussed in more depth with @zivkovicmilos in our upcoming session.

Next Steps

  • Integrate the insertion-queue library for efficient queued transaction ordering
  • Implement conversion between internal Tx and raw []byte / std.Tx formats
  • Finalize design decisions and resolve pending questions through discussion with Milos

@Milosevic02
Copy link
Author

Hi everyone,

This week, we hit a key limitation in the current Tendermint2 implementation — there's no way to extract the sender or sequence from a transaction during the CheckTx phase. Although we had fully implemented a more advanced mempool design that relied on this metadata for correct ordering and validation, the lack of these fields in Tendermint2 makes that solution unworkable without upstream changes.

New Direction: Simplified and Optimized Mempool

Given this constraint, @zivkovicmilos and I decided to pivot from that design and focus on creating a clean, minimal, and optimized version of the mempool. This new approach is a significant improvement over the original CList-based implementation that came with the codebase.

Key Improvements Over the CList Version

  • Replaced CList with a combination of:

    • A map[string]txEntry for fast transaction lookup and deduplication
    • A FIFO []string slice containing transaction hashes to preserve ordering
  • Simplified the architecture by removing complex and error-prone handling logic

  • Improved clarity of the Mempool interface with focused methods: AddTx, RemoveTx, Update, Flush, ReapMaxBytesMaxGas

  • Wrote basic tests to validate the implementation — all tests pass and confirm correct behavior

Current Status

The new implementation resolves the issues we observed in the CList-based version, especially around inconsistent ordering and inefficient access. It is simpler, faster, easier to maintain, and leaves room for clean extension if needed.

Even though our original nonce-based design is not viable under Tendermint2, this alternative approach has turned out to be a practical and elegant solution. We're confident that it will serve as a solid baseline for future iterations.

Next Steps

  • I’ll be working on benchmarking to evaluate performance and explore optimization opportunities
  • Adding configuration options (e.g. mempool size limits, expiration rules)
  • Milos is finalizing the reactor integration and cleaning up supporting code
  • Once both parts are done, we’ll merge and test everything end-to-end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

No branches or pull requests

2 participants