Skip to content

Commit

Permalink
feat: Cache Tx Decoder (#528)
Browse files Browse the repository at this point in the history
* init

* nit

* nits

* nit

* more nits

* go version bump

* image bump

* nit
  • Loading branch information
davidterpay authored Jun 20, 2024
1 parent 154f47d commit bfdd584
Show file tree
Hide file tree
Showing 13 changed files with 532 additions and 136 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
steps:
- uses: actions/setup-go@v5
with:
go-version: 1.21.5
go-version: 1.22.4
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
Expand All @@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.21.5
go-version: 1.22.4
cache: true
cache-dependency-path: go.sum
- uses: technote-space/[email protected]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.21.5
go-version: 1.22.4
- name: Unshallow
run: git fetch --prune --unshallow
- name: Create release
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.21.5
go-version: 1.22.4
cache: true
cache-dependency-path: go.sum
- uses: technote-space/[email protected]
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.21.5
go-version: 1.22.4
cache: true
cache-dependency-path: go.sum
- uses: technote-space/[email protected]
Expand Down Expand Up @@ -80,7 +80,7 @@ jobs:
- uses: actions/setup-go@v5
if: env.GIT_DIFF
with:
go-version: 1.21.5
go-version: 1.22.4
cache: true

# In this step, this action saves a list of existing images, the cache is
Expand Down
30 changes: 23 additions & 7 deletions abci/checktx/check_tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/skip-mev/block-sdk/v2/abci/checktx"
"github.com/skip-mev/block-sdk/v2/block"
"github.com/skip-mev/block-sdk/v2/block/utils"
"github.com/skip-mev/block-sdk/v2/lanes/mev"
mevlanetestutils "github.com/skip-mev/block-sdk/v2/lanes/mev/testutils"
"github.com/skip-mev/block-sdk/v2/testutils"
Expand Down Expand Up @@ -66,12 +67,15 @@ func (s *CheckTxTestSuite) TestCheckTxMempoolParity() {
})
s.Require().NoError(err)

cacheDecoder, err := utils.NewDefaultCacheTxDecoder(s.EncCfg.TxConfig.TxDecoder())
s.Require().NoError(err)

ba := &baseApp{
s.Ctx,
}
mevLaneHandler := checktx.NewMEVCheckTxHandler(
ba,
s.EncCfg.TxConfig.TxDecoder(),
cacheDecoder.TxDecoder(),
mevLane,
s.SetUpAnteHandler(txs),
ba.CheckTx,
Expand All @@ -80,7 +84,7 @@ func (s *CheckTxTestSuite) TestCheckTxMempoolParity() {
handler := checktx.NewMempoolParityCheckTx(
s.Ctx.Logger(),
mempool,
s.EncCfg.TxConfig.TxDecoder(),
cacheDecoder.TxDecoder(),
mevLaneHandler,
).CheckTx()

Expand Down Expand Up @@ -134,10 +138,13 @@ func (s *CheckTxTestSuite) TestRemovalOnRecheckTx() {
})
s.Require().NoError(err)

cacheDecoder, err := utils.NewDefaultCacheTxDecoder(s.EncCfg.TxConfig.TxDecoder())
s.Require().NoError(err)

handler := checktx.NewMempoolParityCheckTx(
s.Ctx.Logger(),
mempool,
s.EncCfg.TxConfig.TxDecoder(),
cacheDecoder.TxDecoder(),
func(*cometabci.RequestCheckTx) (*cometabci.ResponseCheckTx, error) {
// always fail
return &cometabci.ResponseCheckTx{Code: 1}, nil
Expand Down Expand Up @@ -165,10 +172,13 @@ func (s *CheckTxTestSuite) TestRemovalOnRecheckTx() {

func (s *CheckTxTestSuite) TestMempoolParityCheckTx() {
s.Run("tx fails tx-decoding", func() {
cacheDecoder, err := utils.NewDefaultCacheTxDecoder(s.EncCfg.TxConfig.TxDecoder())
s.Require().NoError(err)

handler := checktx.NewMempoolParityCheckTx(
s.Ctx.Logger(),
nil,
s.EncCfg.TxConfig.TxDecoder(),
cacheDecoder.TxDecoder(),
nil,
)

Expand Down Expand Up @@ -197,10 +207,13 @@ func (s *CheckTxTestSuite) TestMEVCheckTxHandler() {
normalTx, err := testutils.CreateRandomTxBz(s.EncCfg.TxConfig, acc, 0, 1, 0, 0)
s.Require().NoError(err)

cacheDecoder, err := utils.NewDefaultCacheTxDecoder(s.EncCfg.TxConfig.TxDecoder())
s.Require().NoError(err)

var gotTx []byte
mevLaneHandler := checktx.NewMEVCheckTxHandler(
ba,
s.EncCfg.TxConfig.TxDecoder(),
cacheDecoder.TxDecoder(),
mevLane,
s.SetUpAnteHandler(txs),
func(req *cometabci.RequestCheckTx) (*cometabci.ResponseCheckTx, error) {
Expand All @@ -215,7 +228,7 @@ func (s *CheckTxTestSuite) TestMEVCheckTxHandler() {
handler := checktx.NewMempoolParityCheckTx(
s.Ctx.Logger(),
mempool,
s.EncCfg.TxConfig.TxDecoder(),
cacheDecoder.TxDecoder(),
mevLaneHandler,
).CheckTx()

Expand Down Expand Up @@ -284,12 +297,15 @@ func (s *CheckTxTestSuite) TestValidateBidTx() {

mevLane := s.InitLane(math.LegacyOneDec(), txs)

cacheDecoder, err := utils.NewDefaultCacheTxDecoder(s.EncCfg.TxConfig.TxDecoder())
s.Require().NoError(err)

ba := &baseApp{
s.Ctx,
}
mevLaneHandler := checktx.NewMEVCheckTxHandler(
ba,
s.EncCfg.TxConfig.TxDecoder(),
cacheDecoder.TxDecoder(),
mevLane,
s.SetUpAnteHandler(txs),
ba.CheckTx,
Expand Down
7 changes: 6 additions & 1 deletion abci/checktx/mempool_parity_check_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ type MempoolParityCheckTx struct {
}

// NewMempoolParityCheckTx returns a new MempoolParityCheckTx handler.
func NewMempoolParityCheckTx(logger log.Logger, mempl block.Mempool, txDecoder sdk.TxDecoder, checkTxHandler CheckTx) MempoolParityCheckTx {
func NewMempoolParityCheckTx(
logger log.Logger,
mempl block.Mempool,
txDecoder sdk.TxDecoder,
checkTxHandler CheckTx,
) MempoolParityCheckTx {
return MempoolParityCheckTx{
logger: logger,
mempl: mempl,
Expand Down
121 changes: 121 additions & 0 deletions block/utils/decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package utils

import (
"fmt"
"sync"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// DefaultMaxSize is the default maximum size of the cache.
var DefaultMaxSize uint64 = 500

// CacheTxDecoder wraps the sdk.TxDecoder and caches the decoded transactions with
// an LRU'esque cache. Each transaction is cached using the transaction's hash
// as the key. The cache is purged when the number of transactions in the cache
// exceeds the maximum size. The oldest transactions are removed first.
type CacheTxDecoder struct {
mut sync.Mutex

decoder sdk.TxDecoder
cache map[string]sdk.Tx
window []string
insertIndex int
oldestIndex int
maxSize uint64
}

// NewDefaultCacheTxDecoder returns a new CacheTxDecoder.
func NewDefaultCacheTxDecoder(
decoder sdk.TxDecoder,
) (*CacheTxDecoder, error) {
if decoder == nil {
return nil, fmt.Errorf("decoder cannot be nil")
}

return &CacheTxDecoder{
decoder: decoder,
cache: make(map[string]sdk.Tx),
window: make([]string, DefaultMaxSize),
insertIndex: 0,
oldestIndex: 0,
maxSize: DefaultMaxSize,
}, nil
}

// NewCacheTxDecoder returns a new CacheTxDecoder with the given cache interval.
func NewCacheTxDecoder(
decoder sdk.TxDecoder,
maxSize uint64,
) (*CacheTxDecoder, error) {
if decoder == nil {
return nil, fmt.Errorf("decoder cannot be nil")
}

return &CacheTxDecoder{
decoder: decoder,
cache: make(map[string]sdk.Tx),
window: make([]string, maxSize),
insertIndex: 0,
oldestIndex: 0,
maxSize: maxSize,
}, nil
}

// Decode decodes the transaction bytes into a sdk.Tx. It caches the decoded
// transaction using the transaction's hash as the key.
func (ctd *CacheTxDecoder) TxDecoder() sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, error) {
ctd.mut.Lock()
defer ctd.mut.Unlock()

hash := TxHash(txBytes)
if tx, ok := ctd.cache[hash]; ok {
return tx, nil
}

tx, err := ctd.decoder(txBytes)
if err != nil {
return nil, err
}

// Purge the cache if necessary
if uint64(len(ctd.cache)) >= ctd.maxSize {
// Purge the oldest transaction
entry := ctd.window[ctd.oldestIndex]
delete(ctd.cache, entry)

// Increment the oldest index
ctd.oldestIndex++
ctd.oldestIndex %= int(ctd.maxSize)
}

// Update the cache and window
ctd.cache[hash] = tx
ctd.window[ctd.insertIndex] = hash

// Increment the insert index
ctd.insertIndex++
ctd.insertIndex %= int(ctd.maxSize)

return tx, nil
}
}

// Len returns the number of transactions in the cache.
func (ctd *CacheTxDecoder) Len() int {
ctd.mut.Lock()
defer ctd.mut.Unlock()

return len(ctd.cache)
}

// Contains returns true if the cache contains the transaction with the given hash.
func (ctd *CacheTxDecoder) Contains(txBytes []byte) bool {
ctd.mut.Lock()
defer ctd.mut.Unlock()

hash := TxHash(txBytes)
_, ok := ctd.cache[hash]
return ok
}
Loading

0 comments on commit bfdd584

Please sign in to comment.