From c182d40dae1029ea079b3354e9a77ac03b000e0c Mon Sep 17 00:00:00 2001 From: Roniel Valdez Date: Wed, 21 Jan 2026 17:08:31 +0100 Subject: [PATCH 1/8] feat: transaction batch support --- cmd/rpc/admin.go | 5 +++-- cmd/rpc/client.go | 14 ++++++++++++++ cmd/rpc/eth.go | 19 ++++++++++--------- cmd/rpc/query.go | 19 ++++++++++++++++++- cmd/rpc/routes.go | 26 +++++++++++++++----------- cmd/rpc/server.go | 38 +++++++++++++++++++++++++------------- controller/tx.go | 6 +++--- 7 files changed, 88 insertions(+), 39 deletions(-) diff --git a/cmd/rpc/admin.go b/cmd/rpc/admin.go index b9b73eb49..2c4ca5f4d 100644 --- a/cmd/rpc/admin.go +++ b/cmd/rpc/admin.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/hex" "fmt" - "github.com/canopy-network/canopy/fsm" "net/http" "os" "os/exec" @@ -12,6 +11,8 @@ import ( "strings" "time" + "github.com/canopy-network/canopy/fsm" + "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" "github.com/julienschmidt/httprouter" @@ -567,7 +568,7 @@ func (s *Server) txHandler(w http.ResponseWriter, r *http.Request, callback func // Check if the transaction should be submitted to the network. if ptr.Submit { // Submit the transaction for processing. - s.submitTx(w, p) + s.submitTxs(w, []lib.TransactionI{p}) } else { // Marshal the transaction into JSON and write it to the response bz, e := lib.MarshalJSONIndent(p) diff --git a/cmd/rpc/client.go b/cmd/rpc/client.go index 42238762a..7341164fd 100644 --- a/cmd/rpc/client.go +++ b/cmd/rpc/client.go @@ -399,6 +399,20 @@ func (c *Client) Transaction(tx lib.TransactionI) (hash *string, err lib.ErrorI) return } +func (c *Client) Transactions(txs []lib.TransactionI) (hashes []*string, err lib.ErrorI) { + bz, err := lib.MarshalJSON(txs) + if err != nil { + return nil, err + } + // a single transaction returns a single string hash + if len(txs) == 1 { + hash := new(string) + err = c.post(TxsRouteName, bz, hash) + return []*string{hash}, err + } + return hashes, c.post(TxsRouteName, bz, &hashes) +} + func (c *Client) Keystore() (keystore *crypto.Keystore, err lib.ErrorI) { keystore = new(crypto.Keystore) err = c.get(KeystoreRouteName, "", keystore, true) diff --git a/cmd/rpc/eth.go b/cmd/rpc/eth.go index f102db9be..3b64cc409 100644 --- a/cmd/rpc/eth.go +++ b/cmd/rpc/eth.go @@ -6,6 +6,15 @@ import ( "encoding/json" "errors" "fmt" + "math" + "math/big" + "net/http" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + "github.com/canopy-network/canopy/fsm" "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" @@ -17,14 +26,6 @@ import ( ethCrypto "github.com/ethereum/go-ethereum/crypto" "github.com/julienschmidt/httprouter" "google.golang.org/protobuf/types/known/anypb" - "math" - "math/big" - "net/http" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" ) /* This file wraps Canopy with the Ethereum JSON-RPC interface as specified here: https://ethereum.org/en/developers/docs/apis/json-rpc */ @@ -367,7 +368,7 @@ func (s *Server) EthSendRawTransaction(args []any) (any, error) { return nil, err } // send transaction to controller - if err = s.controller.SendTxMsg(bz); err != nil { + if err = s.controller.SendTxMsgs([][]byte{bz}); err != nil { return nil, err } // get the tx hash string diff --git a/cmd/rpc/query.go b/cmd/rpc/query.go index f2dcb7d3b..79878843f 100644 --- a/cmd/rpc/query.go +++ b/cmd/rpc/query.go @@ -32,7 +32,24 @@ func (s *Server) Transaction(w http.ResponseWriter, r *http.Request, _ httproute return } // Submit transaction to RPC server - s.submitTx(w, tx) + s.submitTxs(w, []lib.TransactionI{tx}) +} + +// Transactions handles multiple transactions in a single request +func (s *Server) Transactions(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + // create a slice to hold the incoming transactions + var txs []lib.Transaction + // unmarshal the HTTP request body into the transactions slice + if ok := unmarshal(w, r, &txs); !ok { + return + } + // cast txs to lib.TransactionI + txsI := make([]lib.TransactionI, len(txs)) + for i := range txs { + txsI[i] = &txs[i] + } + // submit transactions to RPC server + s.submitTxs(w, txsI) } // Height responds with the next block version diff --git a/cmd/rpc/routes.go b/cmd/rpc/routes.go index c5486c9f2..dd09d2a07 100644 --- a/cmd/rpc/routes.go +++ b/cmd/rpc/routes.go @@ -10,6 +10,7 @@ import ( const ( VersionRoutePath = "/v1/" TxRoutePath = "/v1/tx" + TxsRoutePath = "/v1/txs" HeightRoutePath = "/v1/query/height" IndexerBlobsRoutePath = "/v1/query/indexer-blobs" AccountRoutePath = "/v1/query/account" @@ -62,7 +63,7 @@ const ( RootChainInfoRoutePath = "/v1/query/root-chain-info" ValidatorSetRoutePath = "/v1/query/validator-set" CheckpointRoutePath = "/v1/query/checkpoint" - SubscribeRCInfoPath = "/v1/subscribe-rc-info" + SubscribeRCInfoPath = "/v1/subscribe-rc-info" // debug DebugBlockedRoutePath = "/debug/blocked" DebugHeapRoutePath = "/debug/heap" @@ -109,6 +110,7 @@ const ( const ( VersionRouteName = "version" TxRouteName = "tx" + TxsRouteName = "txs" HeightRouteName = "height" IndexerBlobsRouteName = "indexer-blobs" AccountRouteName = "account" @@ -200,9 +202,9 @@ const ( PeerBookRouteName = "peer-book" ConfigRouteName = "config" LogsRouteName = "logs" - AddVoteRouteName = "add-vote" - DelVoteRouteName = "del-vote" - SubscribeRCInfoName = "subscribe-rc-info" + AddVoteRouteName = "add-vote" + DelVoteRouteName = "del-vote" + SubscribeRCInfoName = "subscribe-rc-info" ) // routes contains the method and path for a canopy command @@ -215,6 +217,7 @@ type routes map[string]struct { var routePaths = routes{ VersionRouteName: {Method: http.MethodGet, Path: VersionRoutePath}, TxRouteName: {Method: http.MethodPost, Path: TxRoutePath}, + TxsRouteName: {Method: http.MethodPost, Path: TxsRoutePath}, HeightRouteName: {Method: http.MethodPost, Path: HeightRoutePath}, IndexerBlobsRouteName: {Method: http.MethodPost, Path: IndexerBlobsRoutePath}, AccountRouteName: {Method: http.MethodPost, Path: AccountRoutePath}, @@ -306,9 +309,9 @@ var routePaths = routes{ PeerBookRouteName: {Method: http.MethodGet, Path: PeerBookRoutePath}, ConfigRouteName: {Method: http.MethodGet, Path: ConfigRoutePath}, LogsRouteName: {Method: http.MethodGet, Path: LogsRoutePath}, - AddVoteRouteName: {Method: http.MethodPost, Path: AddVoteRoutePath}, - DelVoteRouteName: {Method: http.MethodPost, Path: DelVoteRoutePath}, - SubscribeRCInfoName: {Method: http.MethodGet, Path: SubscribeRCInfoPath}, + AddVoteRouteName: {Method: http.MethodPost, Path: AddVoteRoutePath}, + DelVoteRouteName: {Method: http.MethodPost, Path: DelVoteRoutePath}, + SubscribeRCInfoName: {Method: http.MethodGet, Path: SubscribeRCInfoPath}, } // httpRouteHandlers is a custom type that maps strings to httprouter handle functions @@ -319,6 +322,7 @@ func createRouter(s *Server) *httprouter.Router { var r = httpRouteHandlers{ VersionRouteName: s.Version, TxRouteName: s.Transaction, + TxsRouteName: s.Transactions, HeightRouteName: s.Height, IndexerBlobsRouteName: s.IndexerBlobs, AccountRouteName: s.Account, @@ -369,10 +373,10 @@ func createRouter(s *Server) *httprouter.Router { FailedTxRouteName: s.FailedTxs, ProposalsRouteName: s.Proposals, PollRouteName: s.Poll, - RootChainInfoRouteName: s.RootChainInfo, - CheckpointRouteName: s.Checkpoint, - EthereumRouteName: s.EthereumHandler, - SubscribeRCInfoName: s.WebSocket, + RootChainInfoRouteName: s.RootChainInfo, + CheckpointRouteName: s.Checkpoint, + EthereumRouteName: s.EthereumHandler, + SubscribeRCInfoName: s.WebSocket, } // Initialize a new router using the httprouter package. diff --git a/cmd/rpc/server.go b/cmd/rpc/server.go index 320d98a16..fc455ec65 100644 --- a/cmd/rpc/server.go +++ b/cmd/rpc/server.go @@ -179,24 +179,36 @@ func (s *Server) startStaticFileServers() { s.runStaticFileServer(explorerFS, explorerStaticDir, s.config.ExplorerPort, s.config) } -// submitTx submits a transaction to the controller and writes http response -func (s *Server) submitTx(w http.ResponseWriter, tx any) (ok bool) { - - // Marshal the transaction - bz, err := lib.Marshal(tx) - if err != nil { +// submitTx submits transactions to the controller and writes http response +func (s *Server) submitTxs(w http.ResponseWriter, txs []lib.TransactionI) (ok bool) { + // marshal each transaction to bytes + var txBytes [][]byte + for _, tx := range txs { + bz, err := lib.Marshal(tx) + if err != nil { + write(w, err, http.StatusBadRequest) + return + } + txBytes = append(txBytes, bz) + } + // send transactions to controller + if err := s.controller.SendTxMsgs(txBytes); err != nil { write(w, err, http.StatusBadRequest) return } - - // Send transaction to controller - if err = s.controller.SendTxMsg(bz); err != nil { - write(w, err, http.StatusBadRequest) + // return hashes of all submitted transactions + var hashes []*string + for _, bz := range txBytes { + hash := crypto.HashString(bz) + hashes = append(hashes, &hash) + } + // if only one transaction was submitted, return the hash as a string + if len(hashes) == 1 { + write(w, hashes[0], http.StatusOK) return } - - // Write transaction to http response - write(w, crypto.HashString(bz), http.StatusOK) + // if multiple transactions were submitted, return the hashes as an array + write(w, hashes, http.StatusOK) return true } diff --git a/controller/tx.go b/controller/tx.go index f7f25b8a2..c500a09c5 100644 --- a/controller/tx.go +++ b/controller/tx.go @@ -19,10 +19,10 @@ import ( /* This file implements logic for transaction sending and handling as well as memory pooling */ -// SendTxMsg() routes a locally generated transaction message to the listener for processing + gossiping -func (c *Controller) SendTxMsg(tx []byte) lib.ErrorI { +// SendTxMsgs() routes generated transaction messages to the listener for processing + gossiping +func (c *Controller) SendTxMsgs(txs [][]byte) lib.ErrorI { // create a transaction message object using the tx bytes and the chain id - msg := &lib.TxMessage{ChainId: c.Config.ChainId, Txs: [][]byte{tx}} + msg := &lib.TxMessage{ChainId: c.Config.ChainId, Txs: txs} // send the transaction message to the listener using internal routing return c.P2P.SelfSend(c.PublicKey, Tx, msg) } From 6dbf86631f6cda3c5bf184778d5a9291809c297b Mon Sep 17 00:00:00 2001 From: Roniel Valdez Date: Wed, 21 Jan 2026 23:39:23 +0100 Subject: [PATCH 2/8] feat: allow for parallel transaction listen --- controller/tx.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/controller/tx.go b/controller/tx.go index c500a09c5..d2fcb6437 100644 --- a/controller/tx.go +++ b/controller/tx.go @@ -38,13 +38,13 @@ func (c *Controller) ListenForTx() { // exit continue } - func() { - // check and add the message to the cache to prevent duplicates - if ok := cache.Add(msg); !ok { - // if duplicate, exit - return - } - c.log.Debug("Handling transaction") + // check and add the message to the cache to prevent duplicates + if ok := cache.Add(msg); !ok { + // if duplicate, exit + continue + } + go func() { + // c.log.Debug("Handling transaction async") // create a convenience variable for the identity of the sender senderID := msg.Sender.Address.PublicKey // try to unmarshal the p2p message as a tx message @@ -244,6 +244,7 @@ func (m *Mempool) CheckMempool() { if ownRoot { rcBuildHeight = m.FSM.Height() } + fmt.Printf("finished checking mempool, txs: %d\n", len(result.Results)) // cache the proposal m.cachedProposal.Store(&CachedProposal{ Block: block, From da81236377ab061d73c246d35328982c6c4c6d55 Mon Sep 17 00:00:00 2001 From: andrewnguyen22 Date: Wed, 21 Jan 2026 18:08:16 -0400 Subject: [PATCH 3/8] Revert "fix for root chain info being the 'next height' and not the 'committed height'" This reverts commit 930913e38b638d69bbef7fd577dc161369f08cc2. --- fsm/state.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/fsm/state.go b/fsm/state.go index b2936a7a5..0ce2205a4 100644 --- a/fsm/state.go +++ b/fsm/state.go @@ -435,13 +435,9 @@ func (s *StateMachine) LoadRootChainInfo(id, height uint64) (*lib.RootChainInfo, lastHeight := uint64(1) // update the metrics once complete defer s.Metrics.UpdateGetRootChainInfo(time.Now()) - // if height is 0; use the latest committed height + // if height is 0; use the latest height if height == 0 { - if s.height > 1 { - height = s.height - 1 - } else { - height = 1 - } + height = s.height } // ensure lastHeight is not < 0 if height != 1 { From a90a992b57c2b0172acf3d17520103316c3c0d27 Mon Sep 17 00:00:00 2001 From: Roniel Valdez Date: Thu, 22 Jan 2026 19:22:12 +0100 Subject: [PATCH 4/8] fix: incomplete check for empty root chain order --- fsm/swap.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fsm/swap.go b/fsm/swap.go index 2ed270203..c8e09638c 100644 --- a/fsm/swap.go +++ b/fsm/swap.go @@ -3,9 +3,10 @@ package fsm import ( "bytes" "encoding/json" + "sort" + "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" - "sort" ) /* This file contains state machine changes related to 'token swapping' */ @@ -77,7 +78,7 @@ func (s *StateMachine) ParseCloseOrder(tx *lib.Transaction) (co *lib.CloseOrder, // ProcessRootChainOrderBook() processes the order book from the root-chain and cross-references blocks on this chain to determine // actions that warrant committee level changes to the root-chain order book like: LockOrder, ResetOrder and CloseOrder func (s *StateMachine) ProcessRootChainOrderBook(book *lib.OrderBook, proposalBlock *lib.BlockResult) (lockOrders []*lib.LockOrder, closedOrders, resetOrders [][]byte) { - if book == nil { + if book == nil || len(book.Orders) == 0 { return } blocks := []*lib.BlockResult{proposalBlock} From bf34ad26453c53828d482fa2720cbea9104bf408 Mon Sep 17 00:00:00 2001 From: Roniel Valdez Date: Thu, 22 Jan 2026 19:37:58 +0100 Subject: [PATCH 5/8] chore: comment fix --- bft/bft.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bft/bft.go b/bft/bft.go index 9f5c49841..fd01f2194 100644 --- a/bft/bft.go +++ b/bft/bft.go @@ -943,7 +943,7 @@ type ( CommitCertificate(qc *lib.QuorumCertificate, block *lib.Block, blockResult *lib.BlockResult, ts uint64) (err lib.ErrorI) // GossipBlock() is a P2P call to gossip a completed Quorum Certificate with a Proposal GossipBlock(certificate *lib.QuorumCertificate, sender []byte, timestamp uint64) - // GossipConsensus() is a P2P call to gossip a completed Quorum Certificate with a Proposal + // GossipConsensus() is a P2P call to gossip a consensus message GossipConsensus(message *Message, senderPubExclude []byte) // SendToSelf() is a P2P call to directly send a completed Quorum Certificate to self SelfSendBlock(qc *lib.QuorumCertificate, timestamp uint64) From b1cd53ee211f9cde5d5374bcf43ad7ef78a97eae Mon Sep 17 00:00:00 2001 From: Roniel Valdez Date: Thu, 22 Jan 2026 21:55:16 +0100 Subject: [PATCH 6/8] feat: add both CommitCertificateParallel and smt.CommitParallel --- controller/block.go | 2 +- store/store.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/block.go b/controller/block.go index 4768621c4..3f62e62f0 100644 --- a/controller/block.go +++ b/controller/block.go @@ -573,7 +573,7 @@ func (c *Controller) HandlePeerBlock(msg *lib.BlockMessage, syncing bool) (*lib. result = nil } // attempts to commit the QC to persistence of chain by playing it against the state machine - if err = c.CommitCertificate(qc, block, result, msg.Time); err != nil { + if err = c.CommitCertificateParallel(qc, block, result, msg.Time); err != nil { // exit with error return nil, err } diff --git a/store/store.go b/store/store.go index fb3e68d63..88e8707be 100644 --- a/store/store.go +++ b/store/store.go @@ -353,7 +353,7 @@ func (s *Store) Root() (root []byte, err lib.ErrorI) { // set up the state commit store s.sc = NewDefaultSMT(NewTxn(s.ss.reader, s.ss.writer, stateCommitIDPrefix, false, false, true, nextVersion)) // commit the SMT directly using the txn ops - if err = s.sc.Commit(s.ss.txn.ops); err != nil { + if err = s.sc.CommitParallel(s.ss.txn.ops); err != nil { return nil, err } } From 44efc8e25563ca1b142aa41484607d0c32c9f0bb Mon Sep 17 00:00:00 2001 From: Roniel Valdez Date: Thu, 22 Jan 2026 22:00:40 +0100 Subject: [PATCH 7/8] feat: lru cache for public keys --- fsm/gov.go | 14 ++++++++++---- fsm/state.go | 15 ++++++++++----- fsm/state_test.go | 5 ++++- fsm/transaction.go | 18 +++++++++++++----- lib/mempool.go | 3 ++- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/fsm/gov.go b/fsm/gov.go index 444c3c8da..9880436fc 100644 --- a/fsm/gov.go +++ b/fsm/gov.go @@ -382,10 +382,16 @@ func (s *StateMachine) ParsePollTransactions(b *lib.BlockResult) { } // for each transaction in the block for _, tx := range b.Transactions { - // get the public key object - pub, e := crypto.NewPublicKeyFromBytes(tx.Transaction.Signature.PublicKey) - if e != nil { - return + // get the public key object from the cache + pub, ok := s.cache.publicKey.Get(string(tx.Transaction.Signature.PublicKey)) + if !ok { + var e error + pub, e = crypto.NewPublicKeyFromBytes(tx.Transaction.Signature.PublicKey) + if e != nil { + return + } + // add the public key to the cache + s.cache.publicKey.Add(string(tx.Transaction.Signature.PublicKey), pub) } // check for a poll transaction if err := ap.CheckForPollTransaction(pub.Address(), tx.Transaction.Memo, s.Height()); err != nil { diff --git a/fsm/state.go b/fsm/state.go index 0ce2205a4..25db11a2e 100644 --- a/fsm/state.go +++ b/fsm/state.go @@ -8,6 +8,7 @@ import ( "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" + lru "github.com/hashicorp/golang-lru/v2" ) const ( @@ -37,14 +38,16 @@ type StateMachine struct { // cache is the set of items to be cached used by the state machine type cache struct { - accounts map[uint64]*Account // cache of accounts accessed - feeParams *FeeParams // fee params for the current block - valParams *ValidatorParams // validator params for the current block + accounts map[uint64]*Account // cache of accounts accessed + feeParams *FeeParams // fee params for the current block + valParams *ValidatorParams // validator params for the current block + publicKey *lru.Cache[string, crypto.PublicKeyI] // public keys for block processing } // New() creates a new instance of a StateMachine func New(c lib.Config, store lib.StoreI, metrics *lib.Metrics, log lib.LoggerI) (*StateMachine, lib.ErrorI) { // create the state machine object reference + publicKeyCache, _ := lru.New[string, crypto.PublicKeyI](10_000) sm := &StateMachine{ store: nil, ProtocolVersion: CurrentProtocolVersion, @@ -56,7 +59,8 @@ func New(c lib.Config, store lib.StoreI, metrics *lib.Metrics, log lib.LoggerI) log: log, events: new(lib.EventsTracker), cache: &cache{ - accounts: make(map[uint64]*Account), + accounts: make(map[uint64]*Account), + publicKey: publicKeyCache, }, } // initialize the state machine @@ -516,7 +520,8 @@ func (s *StateMachine) Copy() (*StateMachine, lib.ErrorI) { Config: s.Config, log: s.log, cache: &cache{ - accounts: make(map[uint64]*Account), + accounts: make(map[uint64]*Account), + publicKey: s.cache.publicKey, }, LastValidatorSet: s.LastValidatorSet, }, nil diff --git a/fsm/state_test.go b/fsm/state_test.go index 7b296ddb6..689860240 100644 --- a/fsm/state_test.go +++ b/fsm/state_test.go @@ -10,6 +10,7 @@ import ( "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" "github.com/canopy-network/canopy/store" + lru "github.com/hashicorp/golang-lru/v2" "github.com/stretchr/testify/require" ) @@ -335,6 +336,7 @@ func newTestStateMachine(t *testing.T) StateMachine { log := lib.NewDefaultLogger() db, err := store.NewStoreInMemory(log) require.NoError(t, err) + publicKeyCache, _ := lru.New[string, crypto.PublicKeyI](10_000) sm := StateMachine{ store: db, ProtocolVersion: 0, @@ -350,7 +352,8 @@ func newTestStateMachine(t *testing.T) StateMachine { events: new(lib.EventsTracker), log: log, cache: &cache{ - accounts: make(map[uint64]*Account), + accounts: make(map[uint64]*Account), + publicKey: publicKeyCache, }, } require.NoError(t, sm.SetParams(DefaultParams())) diff --git a/fsm/transaction.go b/fsm/transaction.go index 9e2d1529d..9e5c84c72 100644 --- a/fsm/transaction.go +++ b/fsm/transaction.go @@ -1,10 +1,11 @@ package fsm import ( + "time" + "github.com/canopy-network/canopy/lib" "github.com/canopy-network/canopy/lib/crypto" "google.golang.org/protobuf/types/known/anypb" - "time" ) /* This file contains transaction handling logic - for the payload handling check message.go */ @@ -96,10 +97,17 @@ func (s *StateMachine) CheckSignature(msg lib.MessageI, tx *lib.Transaction, bat if err != nil { return nil, ErrTxSignBytes(err) } - // convert signature bytes to public key object - publicKey, e := crypto.NewPublicKeyFromBytes(tx.Signature.PublicKey) - if e != nil { - return nil, ErrInvalidPublicKey(e) + // try to obtain the public key from the cache + var e error + publicKey, ok := s.cache.publicKey.Get(string(tx.Signature.PublicKey)) + if !ok { + // convert signature bytes to public key object + publicKey, e = crypto.NewPublicKeyFromBytes(tx.Signature.PublicKey) + if e != nil { + return nil, ErrInvalidPublicKey(e) + } + // add the public key to the cache + s.cache.publicKey.Add(string(tx.Signature.PublicKey), publicKey) } // special case: check for a special RLP transaction if _, hasEthPubKey := publicKey.(*crypto.ETHSECP256K1PublicKey); hasEthPubKey && tx.Memo == RLPIndicator { diff --git a/lib/mempool.go b/lib/mempool.go index 387f979da..c22a2b267 100644 --- a/lib/mempool.go +++ b/lib/mempool.go @@ -1,12 +1,13 @@ package lib import ( - "github.com/canopy-network/canopy/lib/crypto" "maps" "math" "sort" "sync" "time" + + "github.com/canopy-network/canopy/lib/crypto" ) /* This file defines and implements a mempool that maintains an ordered list of 'valid, pending to be included' transactions in memory */ From 8b22fcb771aa18e963cefaa7f5c49ac07a3f5132 Mon Sep 17 00:00:00 2001 From: Roniel Valdez Date: Thu, 22 Jan 2026 22:38:55 +0100 Subject: [PATCH 8/8] chore: remove print --- controller/tx.go | 1 - 1 file changed, 1 deletion(-) diff --git a/controller/tx.go b/controller/tx.go index d2fcb6437..c1907ed3d 100644 --- a/controller/tx.go +++ b/controller/tx.go @@ -244,7 +244,6 @@ func (m *Mempool) CheckMempool() { if ownRoot { rcBuildHeight = m.FSM.Height() } - fmt.Printf("finished checking mempool, txs: %d\n", len(result.Results)) // cache the proposal m.cachedProposal.Store(&CachedProposal{ Block: block,