• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperledger-labs / fabric-token-sdk / 25670448124

11 May 2026 12:35PM UTC coverage: 82.584% (-0.007%) from 82.591%
25670448124

push

github

web-flow
bug(balance): to return big.Int to avoid overflows (#1692)

Signed-off-by: Angelo De Caro <adc@zurich.ibm.com>

31 of 40 new or added lines in 8 files covered. (77.5%)

7 existing lines in 4 files now uncovered.

35468 of 42948 relevant lines covered (82.58%)

16.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.38
/token/services/storage/db/sql/postgres/driver.go
1
/*
2
Copyright IBM Corp. All Rights Reserved.
3

4
SPDX-License-Identifier: Apache-2.0
5
*/
6

7
package postgres
8

9
import (
10
        "crypto/sha256"
11
        "encoding/binary"
12
        "fmt"
13
        "strings"
14

15
        "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils"
16
        "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/lazy"
17
        driver2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/storage/driver"
18
        "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/storage/driver/common"
19
        "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/storage/driver/sql/postgres"
20
        driver3 "github.com/hyperledger-labs/fabric-token-sdk/token/services/storage/db/driver"
21
        common3 "github.com/hyperledger-labs/fabric-token-sdk/token/services/storage/db/sql/common"
22
)
23

24
// configProvider defines the interface for retrieving database configuration.
25
type configProvider interface {
26
        // GetOpts returns the Postgres configuration for the given persistence name and parameters.
27
        GetOpts(name driver2.PersistenceName, params ...string) (*postgres.Config, error)
28
}
29

30
// Driver implements the token storage driver for Postgres.
31
type Driver struct {
32
        cp configProvider
33

34
        // Lazy providers for various store types to ensure they are initialized only when needed.
35
        TokenLock lazy.Provider[postgres.Config, *TokenLockStore]
36
        Wallet    lazy.Provider[postgres.Config, *WalletStore]
37
        Identity  lazy.Provider[postgres.Config, *IdentityStore]
38
        Token     lazy.Provider[postgres.Config, *TokenStore]
39
        AuditTx   lazy.Provider[postgres.Config, *AuditTransactionStore]
40
        OwnerTx   lazy.Provider[postgres.Config, *TransactionStore]
41
        KeyStore  lazy.Provider[postgres.Config, *KeystoreStore]
42
}
43

44
// NewNamedDriver returns a NamedDriver for Postgres.
45
func NewNamedDriver(config driver3.Config, dbProvider postgres.DbProvider) driver3.NamedDriver {
39✔
46
        return driver3.NamedDriver{
39✔
47
                Name:   postgres.Persistence,
39✔
48
                Driver: NewDriverWithDbProvider(config, dbProvider),
39✔
49
        }
39✔
50
}
39✔
51

52
// NewDriver returns a new Driver for Postgres using the default database provider.
53
func NewDriver(config driver3.Config) *Driver {
2✔
54
        return NewDriverWithDbProvider(config, postgres.NewDbProvider())
2✔
55
}
2✔
56

57
// NewDriverWithDbProvider returns a new Driver for Postgres using the given database provider.
58
func NewDriverWithDbProvider(config driver3.Config, dbProvider postgres.DbProvider) *Driver {
43✔
59
        d := &Driver{
43✔
60
                cp: postgres.NewConfigProvider(common.NewConfig(config)),
43✔
61
        }
43✔
62

43✔
63
        d.TokenLock = newProviderWithKeyMapper(dbProvider, NewTokenLockStore, "tokenlock")
43✔
64
        d.Wallet = newProviderWithKeyMapper(dbProvider, NewWalletStore, "wallet")
43✔
65
        d.Identity = newIdentityStoreProvider(dbProvider)
43✔
66
        d.Token = newTokenStoreProvider(dbProvider)
43✔
67
        d.AuditTx = newProviderWithKeyMapper(dbProvider, NewAuditTransactionStore, "audittx")
43✔
68
        d.OwnerTx = newTransactionStoreProvider(dbProvider)
43✔
69
        d.KeyStore = newProviderWithKeyMapper(dbProvider, NewKeystoreStore, "keystore")
43✔
70

43✔
71
        return d
43✔
72
}
43✔
73

74
// newTokenStoreProvider returns a lazy provider for TokenStore.
75
func newTokenStoreProvider(dbProvider postgres.DbProvider) lazy.Provider[postgres.Config, *TokenStore] {
43✔
76
        return lazy.NewProviderWithKeyMapper(key, func(o postgres.Config) (*TokenStore, error) {
85✔
77
                opts := postgres.Opts{
42✔
78
                        DataSource:      o.DataSource,
42✔
79
                        MaxOpenConns:    o.MaxOpenConns,
42✔
80
                        MaxIdleConns:    *o.MaxIdleConns,
42✔
81
                        MaxIdleTime:     *o.MaxIdleTime,
42✔
82
                        TablePrefix:     o.TablePrefix,
42✔
83
                        TableNameParams: o.TableNameParams,
42✔
84
                        Tracing:         o.Tracing,
42✔
85
                }
42✔
86
                dbs, err := dbProvider.Get(opts)
42✔
87
                if err != nil {
42✔
88
                        return nil, err
×
89
                }
×
90
                tableNames, err := common3.GetTableNames(o.TablePrefix, o.TableNameParams...)
42✔
91
                if err != nil {
42✔
92
                        return nil, err
×
93
                }
×
94

95
                // notifier
96
                notifier, err := NewTokenNotifier(dbs, tableNames, o.DataSource)
42✔
97
                if err != nil {
42✔
98
                        return nil, err
×
99
                }
×
100

101
                // db
102
                p, err := NewTokenStoreWithNotifier(dbs, tableNames, notifier)
42✔
103
                if err != nil {
42✔
104
                        return nil, err
×
105
                }
×
106
                if !o.SkipCreateTable {
84✔
107
                        if err := p.CreateSchema(); err != nil {
42✔
108
                                return nil, err
×
109
                        }
×
110
                        if err := notifier.CreateSchema(); err != nil {
42✔
111
                                return nil, err
×
112
                        }
×
113
                }
114

115
                return p, nil
42✔
116
        })
117
}
118

119
// newIdentityStoreProvider returns a lazy provider for IdentityStore.
120
func newIdentityStoreProvider(dbProvider postgres.DbProvider) lazy.Provider[postgres.Config, *IdentityStore] {
43✔
121
        return lazy.NewProviderWithKeyMapper(key, func(o postgres.Config) (*IdentityStore, error) {
83✔
122
                opts := postgres.Opts{
40✔
123
                        DataSource:      o.DataSource,
40✔
124
                        MaxOpenConns:    o.MaxOpenConns,
40✔
125
                        MaxIdleConns:    *o.MaxIdleConns,
40✔
126
                        MaxIdleTime:     *o.MaxIdleTime,
40✔
127
                        TablePrefix:     o.TablePrefix,
40✔
128
                        TableNameParams: o.TableNameParams,
40✔
129
                        Tracing:         o.Tracing,
40✔
130
                }
40✔
131
                dbs, err := dbProvider.Get(opts)
40✔
132
                if err != nil {
40✔
133
                        return nil, err
×
134
                }
×
135
                tableNames, err := common3.GetTableNames(o.TablePrefix, o.TableNameParams...)
40✔
136
                if err != nil {
40✔
137
                        return nil, err
×
138
                }
×
139

140
                // Create identity store with notifier (includes advisory lock)
141
                p, err := NewIdentityStore(dbs, tableNames, o.DataSource)
40✔
142
                if err != nil {
40✔
143
                        return nil, err
×
144
                }
×
145

146
                // Get notifier for schema creation
147
                notifier, err := NewIdentityNotifier(dbs, tableNames, o.DataSource)
40✔
148
                if err != nil {
40✔
149
                        return nil, err
×
150
                }
×
151

152
                if !o.SkipCreateTable {
80✔
153
                        if err := p.CreateSchema(); err != nil {
40✔
UNCOV
154
                                return nil, err
×
UNCOV
155
                        }
×
156
                        if err := notifier.CreateSchema(); err != nil {
40✔
157
                                return nil, err
×
158
                        }
×
159
                }
160

161
                return p, nil
40✔
162
        })
163
}
164

165
// newTransactionStoreProvider returns a lazy provider for TransactionStore with notifier support.
166
func newTransactionStoreProvider(dbProvider postgres.DbProvider) lazy.Provider[postgres.Config, *TransactionStore] {
43✔
167
        return lazy.NewProviderWithKeyMapper(key, func(o postgres.Config) (*TransactionStore, error) {
83✔
168
                opts := postgres.Opts{
40✔
169
                        DataSource:      o.DataSource,
40✔
170
                        MaxOpenConns:    o.MaxOpenConns,
40✔
171
                        MaxIdleConns:    *o.MaxIdleConns,
40✔
172
                        MaxIdleTime:     *o.MaxIdleTime,
40✔
173
                        TablePrefix:     o.TablePrefix,
40✔
174
                        TableNameParams: o.TableNameParams,
40✔
175
                        Tracing:         o.Tracing,
40✔
176
                }
40✔
177
                dbs, err := dbProvider.Get(opts)
40✔
178
                if err != nil {
40✔
179
                        return nil, err
×
180
                }
×
181
                tableNames, err := common3.GetTableNames(o.TablePrefix, o.TableNameParams...)
40✔
182
                if err != nil {
40✔
183
                        return nil, err
×
184
                }
×
185

186
                // notifier
187
                notifier, err := NewTransactionNotifier(dbs, tableNames, o.DataSource)
40✔
188
                if err != nil {
40✔
189
                        return nil, err
×
190
                }
×
191

192
                // db
193
                p, err := NewTransactionStoreWithNotifier(dbs, tableNames, notifier)
40✔
194
                if err != nil {
40✔
195
                        return nil, err
×
196
                }
×
197
                if !o.SkipCreateTable {
80✔
198
                        if err := p.CreateSchema(); err != nil {
40✔
199
                                return nil, err
×
200
                        }
×
201
                        if err := notifier.CreateSchema(); err != nil {
40✔
202
                                return nil, err
×
203
                        }
×
204
                }
205

206
                return p, nil
40✔
207
        })
208
}
209

210
// NewTokenLock returns a new TokenLockStore.
211
func (d *Driver) NewTokenLock(name driver2.PersistenceName, params ...string) (driver3.TokenLockStore, error) {
36✔
212
        opts, err := d.cp.GetOpts(name, params...)
36✔
213
        if err != nil {
36✔
214
                return nil, err
×
215
        }
×
216

217
        return d.TokenLock.Get(*opts)
36✔
218
}
219

220
// NewWallet returns a new WalletStore.
221
func (d *Driver) NewWallet(name driver2.PersistenceName, params ...string) (driver3.WalletStore, error) {
40✔
222
        opts, err := d.cp.GetOpts(name, params...)
40✔
223
        if err != nil {
40✔
224
                return nil, err
×
225
        }
×
226

227
        return d.Wallet.Get(*opts)
40✔
228
}
229

230
// NewIdentity returns a new IdentityStore.
231
func (d *Driver) NewIdentity(name driver2.PersistenceName, params ...string) (driver3.IdentityStore, error) {
40✔
232
        opts, err := d.cp.GetOpts(name, params...)
40✔
233
        if err != nil {
40✔
234
                return nil, err
×
235
        }
×
236

237
        return d.Identity.Get(*opts)
40✔
238
}
239

240
// NewKeyStore returns a new KeyStoreStore.
241
func (d *Driver) NewKeyStore(name driver2.PersistenceName, params ...string) (driver3.KeyStore, error) {
40✔
242
        opts, err := d.cp.GetOpts(name, params...)
40✔
243
        if err != nil {
40✔
244
                return nil, err
×
245
        }
×
246

247
        return d.KeyStore.Get(*opts)
40✔
248
}
249

250
// NewToken returns a new TokenStore.
251
func (d *Driver) NewToken(name driver2.PersistenceName, params ...string) (driver3.TokenStore, error) {
42✔
252
        opts, err := d.cp.GetOpts(name, params...)
42✔
253
        if err != nil {
42✔
254
                return nil, err
×
255
        }
×
256

257
        return d.Token.Get(*opts)
42✔
258
}
259

260
// NewAuditTransaction returns a new AuditTransactionStore.
261
func (d *Driver) NewAuditTransaction(name driver2.PersistenceName, params ...string) (driver3.AuditTransactionStore, error) {
38✔
262
        opts, err := d.cp.GetOpts(name, append(params, "aud")...)
38✔
263
        if err != nil {
38✔
264
                return nil, err
×
265
        }
×
266

267
        return d.AuditTx.Get(*opts)
38✔
268
}
269

270
// NewOwnerTransaction returns a new TokenTransactionStore.
271
func (d *Driver) NewOwnerTransaction(name driver2.PersistenceName, params ...string) (driver3.TokenTransactionStore, error) {
40✔
272
        opts, err := d.cp.GetOpts(name, params...)
40✔
273
        if err != nil {
40✔
274
                return nil, err
×
275
        }
×
276

277
        return d.OwnerTx.Get(*opts)
40✔
278
}
279

280
// newProviderWithKeyMapper returns a lazy provider for a DB object using a common constructor.
281
func newProviderWithKeyMapper[V common.DBObject](dbProvider postgres.DbProvider, constructor common3.PersistenceConstructor[V], storeType string) lazy.Provider[postgres.Config, V] {
43✔
282
        return lazy.NewProviderWithKeyMapper(key, func(o postgres.Config) (V, error) {
85✔
283
                opts := postgres.Opts{
42✔
284
                        DataSource:      o.DataSource,
42✔
285
                        MaxOpenConns:    o.MaxOpenConns,
42✔
286
                        MaxIdleConns:    *o.MaxIdleConns,
42✔
287
                        MaxIdleTime:     *o.MaxIdleTime,
42✔
288
                        TablePrefix:     o.TablePrefix,
42✔
289
                        TableNameParams: o.TableNameParams,
42✔
290
                        Tracing:         o.Tracing,
42✔
291
                }
42✔
292
                dbs, err := dbProvider.Get(opts)
42✔
293
                if err != nil {
42✔
294
                        return utils.Zero[V](), err
×
295
                }
×
296
                tableNames, err := common3.GetTableNames(o.TablePrefix, o.TableNameParams...)
42✔
297
                if err != nil {
42✔
298
                        return utils.Zero[V](), err
×
299
                }
×
300
                p, err := constructor(dbs, tableNames)
42✔
301
                if err != nil {
42✔
302
                        return utils.Zero[V](), err
×
303
                }
×
304
                if !o.SkipCreateTable {
84✔
305
                        if err := p.CreateSchema(); err != nil {
42✔
306
                                return utils.Zero[V](), err
×
307
                        }
×
308
                }
309

310
                return p, nil
42✔
311
        })
312
}
313

314
// key returns a unique key for the given Postgres configuration.
315
func key(k postgres.Config) string {
42✔
316
        return "postgres" + k.DataSource + k.TablePrefix + strings.Join(k.TableNameParams, "_")
42✔
317
}
42✔
318

319
// createTableLockID generates a deterministic lock ID for a store type.
320
// Uses SHA256 hash of the store type name, converted to int64.
321
// This ensures the same store type always gets the same lock ID across replicas.
322
func createTableLockID(storeType string) int64 {
42✔
323
        h := sha256.Sum256([]byte(storeType))
42✔
324

42✔
325
        return int64(binary.BigEndian.Uint64(h[:])) //nolint:gosec
42✔
326
}
42✔
327

328
// prefixSchemaWithLock prefixes SQL schema with a PostgreSQL advisory lock statement.
329
// The lock is transaction-scoped (pg_advisory_xact_lock) and automatically released on commit/rollback.
330
// This prevents conflicts when multiple replicas attempt to create the same tables simultaneously.
331
func prefixSchemaWithLock(schema string, lockID int64) string {
42✔
332
        return fmt.Sprintf("SELECT pg_advisory_xact_lock(%d);\n%s", lockID, schema)
42✔
333
}
42✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc