Skip to content

Commit a4be7c0

Browse files
Sherdlock (#632)
Signed-off-by: Alexandros Filios <[email protected]>
1 parent 1eae3f3 commit a4be7c0

38 files changed

+1382
-91
lines changed

integration/nwo/token/orion/template.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ token:
3737
auditdb:
3838
persistence:
3939
type: unity
40+
tokenlockdb:
41+
persistence:
42+
type: unity
4043
tokendb:
4144
persistence:
4245
type: sql

token/common/core/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package core
8+
9+
type (
10+
TxID = string
11+
BlockNum = uint64
12+
TxNum = uint64
13+
Namespace = string
14+
)

token/sdk/sdk.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
"context"
1111
"time"
1212

13-
"github.com/hyperledger-labs/fabric-token-sdk/token/services/logging"
14-
1513
"github.com/hyperledger-labs/fabric-smart-client/platform/fabric"
1614
orion2 "github.com/hyperledger-labs/fabric-smart-client/platform/orion"
1715
view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view"
@@ -42,14 +40,19 @@ import (
4240
"github.com/hyperledger-labs/fabric-token-sdk/token/services/identitydb"
4341
_ "github.com/hyperledger-labs/fabric-token-sdk/token/services/identitydb/db/sql"
4442
"github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc"
43+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/logging"
4544
"github.com/hyperledger-labs/fabric-token-sdk/token/services/network"
4645
_ "github.com/hyperledger-labs/fabric-token-sdk/token/services/network/fabric"
4746
"github.com/hyperledger-labs/fabric-token-sdk/token/services/network/orion"
4847
"github.com/hyperledger-labs/fabric-token-sdk/token/services/selector/mailman"
48+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/selector/sherdlock"
4949
selector "github.com/hyperledger-labs/fabric-token-sdk/token/services/selector/simple"
5050
"github.com/hyperledger-labs/fabric-token-sdk/token/services/tokendb"
5151
_ "github.com/hyperledger-labs/fabric-token-sdk/token/services/tokendb/db/memory"
5252
_ "github.com/hyperledger-labs/fabric-token-sdk/token/services/tokendb/db/sql"
53+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/tokenlockdb"
54+
_ "github.com/hyperledger-labs/fabric-token-sdk/token/services/tokenlockdb/db/memory"
55+
_ "github.com/hyperledger-labs/fabric-token-sdk/token/services/tokenlockdb/db/sql"
5356
"github.com/hyperledger-labs/fabric-token-sdk/token/services/tokens"
5457
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx"
5558
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttxdb"
@@ -101,6 +104,9 @@ func (p *SDK) Install() error {
101104
fabricNSP, _ := fabric.GetNetworkServiceProvider(p.registry)
102105
orionNSP, _ := orion2.GetNetworkServiceProvider(p.registry)
103106

107+
tokenDBManager := tokendb.NewManager(configProvider, dbconfig.NewConfig(configProvider, "tokendb.persistence.type", "db.persistence.type"))
108+
assert.NoError(p.registry.RegisterService(tokenDBManager))
109+
104110
// configure selector service
105111
var selectorManagerProvider token.SelectorManagerProvider
106112
switch configProvider.GetString("token.selector.driver") {
@@ -111,11 +117,15 @@ func (p *SDK) Install() error {
111117
5*time.Second,
112118
tracing.Get(p.registry).GetTracer(),
113119
)
114-
default:
120+
case "mailman":
115121
// we use mailman as our default selector
116122
subscriber, err := events.GetSubscriber(p.registry)
117123
assert.NoError(err, "failed to get events subscriber")
118124
selectorManagerProvider = mailman.NewService(subscriber, tracing.Get(p.registry).GetTracer())
125+
default:
126+
tokenLockDBManager := tokenlockdb.NewManager(configProvider, dbconfig.NewConfig(configProvider, "tokenlockdb.persistence.type", "db.persistence.type"))
127+
assert.NoError(p.registry.RegisterService(tokenLockDBManager))
128+
selectorManagerProvider = sherdlock.NewService(tokenDBManager, tokenLockDBManager)
119129
}
120130

121131
// Register the token management service provider
@@ -132,8 +142,6 @@ func (p *SDK) Install() error {
132142
// DBs and their managers
133143
ttxdbManager := ttxdb.NewManager(configProvider, dbconfig.NewConfig(configProvider, "ttxdb.persistence.type", "db.persistence.type"))
134144
assert.NoError(p.registry.RegisterService(ttxdbManager))
135-
tokenDBManager := tokendb.NewManager(configProvider, dbconfig.NewConfig(configProvider, "tokendb.persistence.type", "db.persistence.type"))
136-
assert.NoError(p.registry.RegisterService(tokenDBManager))
137145
auditDBManager := auditdb.NewManager(configProvider, dbconfig.NewConfig(configProvider, "auditdb.persistence.type", "db.persistence.type"))
138146
assert.NoError(p.registry.RegisterService(auditDBManager))
139147
identityDBManager := identitydb.NewManager(configProvider, dbconfig.NewConfig(configProvider, "identitydb.persistence.type", "db.persistence.type"))

token/services/db/driver/token.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
token2 "github.com/hyperledger-labs/fabric-token-sdk/token"
14+
"github.com/hyperledger-labs/fabric-token-sdk/token/common/core"
1415
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
1516
"github.com/hyperledger-labs/fabric-token-sdk/token/token"
1617
)
@@ -170,6 +171,23 @@ type TokenDBDriver interface {
170171
Open(cp ConfigProvider, tmsID token2.TMSID) (TokenDB, error)
171172
}
172173

174+
// TokenLockDB enforces that a token be used only by one process
175+
// A housekeeping job can clean up expired locks (e.g. created_at is more than 5 minutes ago) in order to:
176+
// - avoid that the table grows infinitely
177+
// - unlock tokens that were locked by a process that exited unexpectedly
178+
type TokenLockDB interface {
179+
// Lock locks a specific token for the consumer TX
180+
Lock(tokenID *token.ID, consumerTxID core.TxID) error
181+
// UnlockByTxID unlocks all tokens locked by the consumer TX
182+
UnlockByTxID(consumerTxID core.TxID) error
183+
}
184+
185+
// TokenLockDBDriver is the interface for a token database driver
186+
type TokenLockDBDriver interface {
187+
// Open opens a token database
188+
Open(cp ConfigProvider, tmsID token2.TMSID) (TokenLockDB, error)
189+
}
190+
173191
var (
174192
ErrTokenDoesNotExist = errors.New("token does not exist")
175193
)

token/services/db/sql/driver/driver.go

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ SPDX-License-Identifier: Apache-2.0
77
package sql
88

99
import (
10-
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/cache/secondcache"
10+
"database/sql"
11+
12+
"github.com/hyperledger-labs/fabric-smart-client/platform/common/utils"
1113
"github.com/hyperledger-labs/fabric-token-sdk/token"
1214
"github.com/hyperledger-labs/fabric-token-sdk/token/services/auditdb"
1315
dbdriver "github.com/hyperledger-labs/fabric-token-sdk/token/services/db/driver"
1416
sqldb "github.com/hyperledger-labs/fabric-token-sdk/token/services/db/sql"
17+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/drivers"
1518
"github.com/hyperledger-labs/fabric-token-sdk/token/services/identitydb"
1619
"github.com/hyperledger-labs/fabric-token-sdk/token/services/tokendb"
20+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/tokenlockdb"
1721
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttxdb"
1822
"github.com/pkg/errors"
1923
)
@@ -35,43 +39,37 @@ func NewDriver() *Driver {
3539
}
3640

3741
func (d *Driver) OpenTokenTransactionDB(cp dbdriver.ConfigProvider, tmsID token.TMSID) (dbdriver.TokenTransactionDB, error) {
38-
sqlDB, opts, err := d.DBOpener.Open(cp, tmsID)
39-
if err != nil {
40-
return nil, errors.Wrapf(err, "failed to open db at [%s:%s]", optsKey, envVarKey)
41-
}
42-
return sqldb.NewTransactionDB(sqlDB, opts.TablePrefix, !opts.SkipCreateTable)
42+
return openDB(d.DBOpener, cp, tmsID, sqldb.NewTransactionDB)
4343
}
4444

4545
func (d *Driver) OpenTokenDB(cp dbdriver.ConfigProvider, tmsID token.TMSID) (dbdriver.TokenDB, error) {
46-
sqlDB, opts, err := d.DBOpener.Open(cp, tmsID)
47-
if err != nil {
48-
return nil, errors.Wrapf(err, "failed to open db at [%s:%s]", optsKey, envVarKey)
49-
}
50-
return sqldb.NewTokenDB(sqlDB, opts.TablePrefix, !opts.SkipCreateTable)
46+
return openDB(d.DBOpener, cp, tmsID, sqldb.NewTokenDB)
47+
}
48+
49+
func (d *Driver) OpenTokenLockDB(cp dbdriver.ConfigProvider, tmsID token.TMSID) (dbdriver.TokenLockDB, error) {
50+
return openDB(d.DBOpener, cp, tmsID, sqldb.NewTokenLockDB)
5151
}
5252

5353
func (d *Driver) OpenAuditTransactionDB(cp dbdriver.ConfigProvider, tmsID token.TMSID) (dbdriver.AuditTransactionDB, error) {
54-
sqlDB, opts, err := d.DBOpener.Open(cp, tmsID)
55-
if err != nil {
56-
return nil, errors.Wrapf(err, "failed to open db at [%s:%s]", optsKey, envVarKey)
57-
}
58-
return sqldb.NewTransactionDB(sqlDB, opts.TablePrefix+"aud_", !opts.SkipCreateTable)
54+
return openDB(d.DBOpener, cp, tmsID, func(sqlDB *sql.DB, tablePrefix string, createSchema bool) (dbdriver.AuditTransactionDB, error) {
55+
return sqldb.NewTransactionDB(sqlDB, tablePrefix+"aud_", createSchema)
56+
})
5957
}
6058

6159
func (d *Driver) OpenWalletDB(cp dbdriver.ConfigProvider, tmsID token.TMSID) (dbdriver.WalletDB, error) {
62-
sqlDB, opts, err := d.DBOpener.Open(cp, tmsID)
63-
if err != nil {
64-
return nil, errors.Wrapf(err, "failed to open db at [%s:%s]", optsKey, envVarKey)
65-
}
66-
return sqldb.NewWalletDB(sqlDB, opts.TablePrefix, !opts.SkipCreateTable)
60+
return openDB(d.DBOpener, cp, tmsID, sqldb.NewWalletDB)
6761
}
6862

6963
func (d *Driver) OpenIdentityDB(cp dbdriver.ConfigProvider, tmsID token.TMSID) (dbdriver.IdentityDB, error) {
70-
sqlDB, opts, err := d.DBOpener.Open(cp, tmsID)
64+
return openDB(d.DBOpener, cp, tmsID, sqldb.NewCachedIdentityDB)
65+
}
66+
67+
func openDB[D any](dbOpener *sqldb.DBOpener, cp dbdriver.ConfigProvider, tmsID token.TMSID, newDB drivers.NewDBFunc[D]) (D, error) {
68+
sqlDB, opts, err := dbOpener.Open(cp, tmsID)
7169
if err != nil {
72-
return nil, errors.Wrapf(err, "failed to open db at [%s:%s]", optsKey, envVarKey)
70+
return utils.Zero[D](), errors.Wrapf(err, "failed to open db at [%s:%s]", optsKey, envVarKey)
7371
}
74-
return sqldb.NewIdentityDB(sqlDB, opts.TablePrefix, !opts.SkipCreateTable, secondcache.New(1000))
72+
return newDB(sqlDB, opts.TablePrefix, !opts.SkipCreateTable)
7573
}
7674

7775
type TtxDBDriver struct {
@@ -90,6 +88,14 @@ func (t *TokenDBDriver) Open(cp dbdriver.ConfigProvider, tmsID token.TMSID) (dbd
9088
return t.OpenTokenDB(cp, tmsID)
9189
}
9290

91+
type TokenLockDBDriver struct {
92+
*Driver
93+
}
94+
95+
func (t *TokenLockDBDriver) Open(cp dbdriver.ConfigProvider, tmsID token.TMSID) (dbdriver.TokenLockDB, error) {
96+
return t.OpenTokenLockDB(cp, tmsID)
97+
}
98+
9399
type AuditDBDriver struct {
94100
*Driver
95101
}
@@ -114,6 +120,7 @@ func init() {
114120
root := NewDriver()
115121
ttxdb.Register("unity", &TtxDBDriver{Driver: root})
116122
tokendb.Register("unity", &TokenDBDriver{Driver: root})
123+
tokenlockdb.Register("unity", &TokenLockDBDriver{Driver: root})
117124
auditdb.Register("unity", &AuditDBDriver{Driver: root})
118125
identitydb.Register("unity", &IdentityDBDriver{Driver: root})
119126
}

token/services/db/sql/identity.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"fmt"
1313
"sync"
1414

15+
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/cache/secondcache"
1516
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash"
1617
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
1718
"github.com/hyperledger-labs/fabric-token-sdk/token/services/db/driver"
@@ -47,6 +48,10 @@ func newIdentityDB(db *sql.DB, tables identityTables, singerInfoCache cache) *Id
4748
}
4849
}
4950

51+
func NewCachedIdentityDB(db *sql.DB, tablePrefix string, createSchema bool) (*IdentityDB, error) {
52+
return NewIdentityDB(db, tablePrefix, createSchema, secondcache.New(1000))
53+
}
54+
5055
func NewIdentityDB(db *sql.DB, tablePrefix string, createSchema bool, signerInfoCache cache) (*IdentityDB, error) {
5156
tables, err := getTableNames(tablePrefix)
5257
if err != nil {

token/services/db/sql/identity_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func TestIdentitySqliteMemory(t *testing.T) {
5454
}
5555

5656
func TestIdentityPostgres(t *testing.T) {
57-
terminate, pgConnStr := startPostgresContainer(t)
57+
terminate, pgConnStr := StartPostgresContainer(t)
5858
defer terminate()
5959

6060
for _, c := range IdentityCases {

token/services/db/sql/init.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type tableNames struct {
5858
IdentityConfigurations string
5959
IdentityInfo string
6060
Signers string
61+
TokenLocks string
6162
}
6263

6364
func getTableNames(prefix string) (tableNames, error) {
@@ -86,5 +87,6 @@ func getTableNames(prefix string) (tableNames, error) {
8687
IdentityConfigurations: fmt.Sprintf("%sid_configs", prefix),
8788
IdentityInfo: fmt.Sprintf("%sid_info", prefix),
8889
Signers: fmt.Sprintf("%ssigners", prefix),
90+
TokenLocks: fmt.Sprintf("%stoken_locks", prefix),
8991
}, nil
9092
}

token/services/db/sql/init_test.go

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,11 @@ SPDX-License-Identifier: Apache-2.0
77
package sql
88

99
import (
10-
"context"
1110
"fmt"
12-
"os"
13-
"runtime/debug"
1411
"testing"
15-
"time"
1612

1713
_ "github.com/lib/pq"
1814
"github.com/test-go/testify/assert"
19-
"github.com/testcontainers/testcontainers-go"
20-
"github.com/testcontainers/testcontainers-go/modules/postgres"
21-
"github.com/testcontainers/testcontainers-go/wait"
2215
_ "modernc.org/sqlite"
2316
)
2417

@@ -39,6 +32,7 @@ func TestGetTableNames(t *testing.T) {
3932
IdentityConfigurations: "id_configs",
4033
IdentityInfo: "id_info",
4134
Signers: "signers",
35+
TokenLocks: "token_locks",
4236
}, names)
4337

4438
names, err = getTableNames("valid_prefix")
@@ -74,38 +68,3 @@ func TestGetTableNames(t *testing.T) {
7468
})
7569
}
7670
}
77-
78-
// https://testcontainers.com/guides/getting-started-with-testcontainers-for-go/
79-
// Note: Before running tests: docker pull postgres:16.0-alpine
80-
// Test may time out if image is not present on machine.
81-
func startPostgresContainer(t *testing.T) (func(), string) {
82-
if os.Getenv("TESTCONTAINERS") != "true" {
83-
t.Skip("set environment variable TESTCONTAINERS to true to include postgres test")
84-
}
85-
if testing.Short() {
86-
t.Skip("skipping postgres test in short mode")
87-
}
88-
89-
ctx := context.Background()
90-
pg, err := postgres.RunContainer(ctx,
91-
testcontainers.WithImage("postgres:16.0-alpine"),
92-
testcontainers.WithWaitStrategy(
93-
wait.ForExposedPort().WithStartupTimeout(30*time.Second)),
94-
postgres.WithDatabase("testdb"),
95-
postgres.WithUsername("postgres"),
96-
postgres.WithPassword("example"),
97-
)
98-
if err != nil {
99-
t.Fatal(err)
100-
}
101-
pgConnStr, err := pg.ConnectionString(ctx, "sslmode=disable")
102-
if err != nil {
103-
t.Fatal(err)
104-
}
105-
106-
return func() {
107-
if err := pg.Terminate(ctx); err != nil {
108-
logger.Errorf("failed to terminate [%s][%s]", err, debug.Stack())
109-
}
110-
}, pgConnStr
111-
}

token/services/db/sql/test_utils.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package sql
8+
9+
import (
10+
"context"
11+
"os"
12+
"runtime/debug"
13+
"testing"
14+
"time"
15+
16+
"github.com/testcontainers/testcontainers-go"
17+
"github.com/testcontainers/testcontainers-go/modules/postgres"
18+
"github.com/testcontainers/testcontainers-go/wait"
19+
)
20+
21+
// https://testcontainers.com/guides/getting-started-with-testcontainers-for-go/
22+
// Note: Before running tests: docker pull postgres:16.0-alpine
23+
// Test may time out if image is not present on machine.
24+
func StartPostgresContainer(t *testing.T) (func(), string) {
25+
if os.Getenv("TESTCONTAINERS") != "true" {
26+
t.Skip("set environment variable TESTCONTAINERS to true to include postgres test")
27+
}
28+
if testing.Short() {
29+
t.Skip("skipping postgres test in short mode")
30+
}
31+
32+
ctx := context.Background()
33+
pg, err := postgres.RunContainer(ctx,
34+
testcontainers.WithImage("postgres:16.0-alpine"),
35+
testcontainers.WithWaitStrategy(
36+
wait.ForExposedPort().WithStartupTimeout(30*time.Second)),
37+
postgres.WithDatabase("testdb"),
38+
postgres.WithUsername("postgres"),
39+
postgres.WithPassword("example"),
40+
)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
pgConnStr, err := pg.ConnectionString(ctx, "sslmode=disable")
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
49+
return func() {
50+
if err := pg.Terminate(ctx); err != nil {
51+
logger.Errorf("failed to terminate [%s][%s]", err, debug.Stack())
52+
}
53+
}, pgConnStr
54+
}

0 commit comments

Comments
 (0)