Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions stores/blockchain/sql/GetBlockExists.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
// 1. First checks the response cache using a derived cache key for boolean existence results
// 2. If not found in cache, executes a minimal SQL query that only checks existence without
// retrieving full block data
// 3. Caches the boolean existence result to optimize future queries for the same block
// 3. Caches a "found" result to optimize future queries for the same block; "not found" is
// never cached, since the cache is only cleared after a block's commit
//
// This approach significantly reduces database load and improves response times for this
// frequently called operation. The response cache is automatically invalidated whenever blocks
Expand Down Expand Up @@ -44,7 +45,9 @@ import (
// - If a boolean result was previously cached, return it immediately
// 2. If not found in cache (cache miss), executes an optimized SQL query
// that only checks for existence without retrieving block data
// 3. Caches the boolean result (true or false) to optimize future queries for the same block
// 3. Caches a true result to optimize future queries for the same block. A false result is
// not cached: StoreBlock clears the cache only after its commit, so a cached false could be
// served for a block that is already stored
//
// The SQL query is carefully designed to be as lightweight as possible, only checking
// for the presence of a block hash in the blocks table without retrieving any columns.
Expand Down Expand Up @@ -92,8 +95,9 @@ func (s *SQL) GetBlockExists(ctx context.Context, blockHash *chainhash.Hash) (bo
&height,
); err != nil {
if errors.Is(err, sql.ErrNoRows) {
// Cache the non-existence result
cacheOp.Set(false, s.cacheTTL)
// Not cached: StoreBlock clears the response cache only after its commit, so a
// cached "not found" could still be served for a block already stored, and block
// validation sends a child whose parent reads as missing to catchup.
return false, nil
}

Expand Down
32 changes: 32 additions & 0 deletions stores/blockchain/sql/GetBlockExists_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package sql

import (
"context"
"fmt"
"net/url"
"testing"

"github.com/bsv-blockchain/go-bt/v2/chainhash"
"github.com/bsv-blockchain/teranode/ulogger"
"github.com/bsv-blockchain/teranode/util/test"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -42,3 +44,33 @@ func TestSQLGetBlockExists(t *testing.T) {
assert.True(t, exists)
})
}

// TestSQLGetBlockExists_NeverCachesNotFound: StoreBlock clears the response cache only after
// its commit, so a cached "not found" can still be served for a block that is already stored.
// Block validation reads GetBlockExists to decide whether a child's parent is missing and the
// child goes to catchup, so a stale "not found" there costs a whole catchup. Only "found" is
// cached.
func TestSQLGetBlockExists_NeverCachesNotFound(t *testing.T) {
tSettings := test.CreateBaseTestSettings(t)

storeURL, err := url.Parse("sqlitememory:///")
require.NoError(t, err)

s, err := New(ulogger.TestLogger{}, storeURL, tSettings)
require.NoError(t, err)

cacheID := chainhash.HashH([]byte(fmt.Sprintf("GetBlockExists-%s", block1.Hash().String())))

exists, err := s.GetBlockExists(context.Background(), block1.Hash())
require.NoError(t, err)
require.False(t, exists)
require.Nil(t, s.responseCache.NewOp(cacheID).Get(), "a not-found result must not be cached")

_, _, err = s.StoreBlock(context.Background(), block1, "")
require.NoError(t, err)

exists, err = s.GetBlockExists(context.Background(), block1.Hash())
require.NoError(t, err)
require.True(t, exists)
require.NotNil(t, s.responseCache.NewOp(cacheID).Get(), "a found result is still cached")
}
Loading