@@ -28,22 +28,49 @@ import (
2828// (for block ingestion) and core.Store (via the embedded *storage.Store, for API queries).
2929type Chain struct {
3030 * storage.Store
31- db * sql.DB
32- ts * trie.Store
33- prevHash common.Hash // Ethereum hash of last committed block; seeded from DB on startup
31+ db * sql.DB
32+ ts * trie.Store
33+ prevHash common.Hash // Ethereum hash of last committed block; seeded from DB on startup
34+ namespace string // Fabric namespace to filter; empty means accept all
35+ }
36+
37+ // ChainOpts is the parameter bundle for NewChain.
38+ //
39+ // DBConnString uses the modernc SQLite DSN format.
40+ // TriePath is the directory for the PebbleDB trie; empty = in-memory (and ignored when WithTrie is false).
41+ // Namespace filters delivered blocks to only those containing transactions for that Fabric
42+ // namespace; empty accepts all namespaces.
43+ // BlockRetention and BlockTruncationInterval are forwarded to the underlying Store; zero values
44+ // use the storage package defaults (storage.DefaultBlockRetention / storage.DefaultBlockTruncationInterval).
45+ type ChainOpts struct {
46+ DBConnString string
47+ TriePath string
48+ Namespace string
49+ WithTrie bool
50+ BlockRetention int
51+ BlockTruncationInterval uint64
3452}
3553
3654// NewChain opens the SQLite database and trie store, seeds state from the latest committed
37- // block, and returns a ready Chain. dbConnStr uses the modernc SQLite DSN format;
38- // triePath is the directory for the PebbleDB trie (empty string = in-memory).
55+ // block, and returns a ready Chain.
3956// The caller must register the SQLite driver (e.g. _ "modernc.org/sqlite") before calling.
40- func NewChain (dbConnStr , triePath string , withTrie bool ) (* Chain , error ) {
41- db , err := sqlite .Open (dbConnStr )
57+ func NewChain (opts ChainOpts ) (* Chain , error ) {
58+ db , err := sqlite .Open (opts . DBConnString )
4259 if err != nil {
4360 return nil , fmt .Errorf ("open db: %w" , err )
4461 }
62+ // Store SQLite temp objects in memory. The release image is built from
63+ // scratch and has no /tmp, so file-based temp storage would fail with
64+ // SQLITE_IOERR_GETTEMPPATH (6410) during WAL operations like DELETE.
65+ if _ , err := db .Exec ("PRAGMA temp_store=MEMORY" ); err != nil {
66+ db .Close ()
67+ return nil , fmt .Errorf ("set temp_store pragma: %w" , err )
68+ }
4569
46- blockStore := storage .NewStore (db )
70+ blockStore := storage .NewStore (db , storage.StoreOpts {
71+ BlockRetention : opts .BlockRetention ,
72+ BlockTruncationInterval : opts .BlockTruncationInterval ,
73+ })
4774 if err := blockStore .Init (); err != nil {
4875 db .Close ()
4976 return nil , fmt .Errorf ("init block store: %w" , err )
@@ -58,21 +85,39 @@ func NewChain(dbConnStr, triePath string, withTrie bool) (*Chain, error) {
5885 }
5986
6087 var ts * trie.Store
61- if withTrie {
62- ts , err = trie .New (triePath , initialRoot )
88+ if opts . WithTrie {
89+ ts , err = trie .New (opts . TriePath , initialRoot )
6390 if err != nil {
6491 db .Close ()
6592 return nil , fmt .Errorf ("open trie store: %w" , err )
6693 }
6794 }
6895
69- return & Chain {Store : blockStore , db : db , ts : ts , prevHash : prevHash }, nil
96+ return & Chain {Store : blockStore , db : db , ts : ts , prevHash : prevHash , namespace : opts .Namespace }, nil
97+ }
98+
99+ // filterAndConvert applies namespace filtering then delegates to the package-level ConvertToDomain.
100+ func (c * Chain ) filterAndConvert (b blocks.Block ) domain.Block {
101+ if c .namespace == "" {
102+ return ConvertToDomain (b )
103+ }
104+ filtered := b
105+ filtered .Transactions = filtered .Transactions [:0 :0 ]
106+ for _ , tx := range b .Transactions {
107+ for _ , nrws := range tx .NsRWS {
108+ if nrws .Namespace == c .namespace {
109+ filtered .Transactions = append (filtered .Transactions , tx )
110+ break
111+ }
112+ }
113+ }
114+ return ConvertToDomain (filtered )
70115}
71116
72117// Handle implements blocks.BlockHandler. It commits the block's write sets to the trie,
73118// then persists the block and its transactions to the database.
74119func (c * Chain ) Handle (ctx context.Context , b blocks.Block ) error {
75- ebl := ConvertToDomain (b )
120+ ebl := c . filterAndConvert (b )
76121
77122 ebl .ParentHash = c .prevHash .Bytes ()
78123 if c .ts != nil {
@@ -90,6 +135,12 @@ func (c *Chain) Handle(ctx context.Context, b blocks.Block) error {
90135 return err
91136 }
92137
138+ if c .Store .BlockRetention > 0 && c .Store .BlockTruncationInterval > 0 && ebl .BlockNumber % c .Store .BlockTruncationInterval == 0 {
139+ if err := c .Store .TruncateBlocks (ctx , c .Store .BlockRetention ); err != nil {
140+ logger .Warnf ("block DB truncation failed at block %d: %v" , ebl .BlockNumber , err )
141+ }
142+ }
143+
93144 return nil
94145}
95146
@@ -115,8 +166,6 @@ func ConvertToDomain(b blocks.Block) domain.Block {
115166
116167 logIndex := int64 (0 ) // logIndex is the index of the log in the block
117168 for _ , tx := range b .Transactions {
118- // TODO: filter on namespace?
119-
120169 // retrieve the Ethereum transaction from the chaincode invocation
121170 if len (tx .InputArgs ) < 2 || ! bytes .Equal (tx .InputArgs [0 ], []byte {byte (fc .ProposalTypeEVMTx )}) {
122171 // skip non-eth tx
0 commit comments