Skip to content

Commit c6bbc35

Browse files
committed
feat(SPV-1592): create engine.V2
1 parent 74de94f commit c6bbc35

File tree

9 files changed

+701
-3
lines changed

9 files changed

+701
-3
lines changed

engine/v2/database/testabilities/fixture_database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type databaseFixture struct {
4545
func Given(t testing.TB, opts ...testengine.ConfigOpts) (given DatabaseFixture, cleanup func()) {
4646
engineWithConfig, cleanup := testengine.Given(t).EngineWithConfiguration(opts...)
4747

48-
db := engineWithConfig.Engine.Datastore().DB()
48+
db := engineWithConfig.Engine.DB()
4949
fixture := &databaseFixture{
5050
t: t,
5151
db: db,

engine/v2/engine/engine.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package engine
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/bitcoin-sv/go-paymail/server"
8+
"github.com/bitcoin-sv/spv-wallet/config"
9+
"github.com/bitcoin-sv/spv-wallet/engine/chain"
10+
"github.com/bitcoin-sv/spv-wallet/engine/chain/models"
11+
"github.com/bitcoin-sv/spv-wallet/engine/paymail"
12+
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
13+
"github.com/bitcoin-sv/spv-wallet/engine/utils"
14+
"github.com/bitcoin-sv/spv-wallet/engine/v2/addresses"
15+
"github.com/bitcoin-sv/spv-wallet/engine/v2/data"
16+
"github.com/bitcoin-sv/spv-wallet/engine/v2/database/repository"
17+
"github.com/bitcoin-sv/spv-wallet/engine/v2/engine/internal"
18+
"github.com/bitcoin-sv/spv-wallet/engine/v2/fee"
19+
"github.com/bitcoin-sv/spv-wallet/engine/v2/operations"
20+
"github.com/bitcoin-sv/spv-wallet/engine/v2/paymails"
21+
"github.com/bitcoin-sv/spv-wallet/engine/v2/paymailserver"
22+
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction/beef"
23+
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction/outlines"
24+
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction/record"
25+
"github.com/bitcoin-sv/spv-wallet/engine/v2/transaction/txsync"
26+
"github.com/bitcoin-sv/spv-wallet/engine/v2/users"
27+
"github.com/bitcoin-sv/spv-wallet/engine/v2/utils/must"
28+
"github.com/go-resty/resty/v2"
29+
"github.com/rs/zerolog"
30+
"gorm.io/gorm"
31+
)
32+
33+
type V2 struct {
34+
cfg *config.AppConfig
35+
storage *internal.Storage
36+
37+
repositories *repository.All
38+
39+
chainService chain.Service
40+
41+
usersService *users.Service
42+
paymailsService *paymails.Service
43+
addressesService *addresses.Service
44+
dataService *data.Service
45+
operationsService *operations.Service
46+
transactionsOutlineService outlines.Service
47+
transactionsRecordService *record.Service
48+
txSyncService *txsync.Service
49+
paymailServerConfig *server.Configuration
50+
}
51+
52+
func NewEngine(cfg *config.AppConfig, logger zerolog.Logger, overridesOpts ...InternalsOverride) *V2 {
53+
logger = logger.With().Int("v", 2).Str("service", "engine").Logger()
54+
55+
overridesToApply := &overrides{}
56+
for _, opt := range overridesOpts {
57+
opt(overridesToApply)
58+
}
59+
60+
// Database
61+
storage := internal.NewStorage(cfg, logger)
62+
err := storage.Start()
63+
must.HaveNoErrorf(err, "failed to start wallet storage")
64+
65+
repos := storage.CreateRepositories()
66+
67+
// Low level services
68+
var httpClient *resty.Client
69+
if overridesToApply.resty != nil {
70+
httpClient = overridesToApply.resty
71+
} else {
72+
httpClient = resty.New()
73+
if overridesToApply.transport != nil {
74+
httpClient.SetTransport(overridesToApply.transport)
75+
}
76+
}
77+
78+
paymailClient := setupPaymailClient(overridesToApply, httpClient)
79+
80+
cache := internal.NewCache(cfg, logger)
81+
82+
chainService := chain.NewChainService(logger, httpClient, extractARCConfig(cfg), extractBHSConfig(cfg))
83+
feeService := fee.NewService(cfg, chainService, logger)
84+
85+
// TODO: use feeService instead of fee unit in services
86+
feeUnit, err := feeService.GetFeeUnit(context.Background())
87+
must.HaveNoErrorf(err, "failed to setup fee unit")
88+
89+
paymailServiceClient := paymail.NewServiceClient(cache, paymailClient, logger)
90+
91+
utxoSelector := storage.CreateUTXOSelector(feeService)
92+
93+
beefService := beef.NewService(repos.Transactions)
94+
95+
userService := users.NewService(repos.Users, cfg)
96+
paymailService := paymails.NewService(repos.Paymails, userService, cfg)
97+
addressesService := addresses.NewService(repos.Addresses)
98+
dataService := data.NewService(repos.Data)
99+
operationsService := operations.NewService(repos.Operations)
100+
txSyncService := txsync.NewService(logger, repos.Transactions)
101+
102+
transactionsOutlineService := outlines.NewService(
103+
paymailServiceClient,
104+
paymailService,
105+
beefService,
106+
utxoSelector,
107+
feeUnit,
108+
logger,
109+
userService,
110+
)
111+
112+
transactionsRecordService := record.NewService(
113+
logger,
114+
addressesService,
115+
userService,
116+
repos.Outputs,
117+
repos.Operations,
118+
repos.Transactions,
119+
chainService,
120+
paymailServiceClient,
121+
)
122+
123+
paymailServiceProvider := paymailserver.NewServiceProvider(
124+
logger,
125+
paymailService,
126+
userService,
127+
addressesService,
128+
chainService,
129+
transactionsRecordService,
130+
)
131+
132+
paymailServerConfig := setupPaymailServer(cfg, logger, paymailServiceProvider)
133+
134+
return &V2{
135+
cfg: cfg,
136+
storage: storage,
137+
repositories: repos,
138+
chainService: chainService,
139+
usersService: userService,
140+
paymailsService: paymailService,
141+
addressesService: addressesService,
142+
dataService: dataService,
143+
operationsService: operationsService,
144+
transactionsOutlineService: transactionsOutlineService,
145+
transactionsRecordService: transactionsRecordService,
146+
txSyncService: txSyncService,
147+
paymailServerConfig: paymailServerConfig,
148+
}
149+
}
150+
151+
// Close closes the V2 and all its services
152+
func (e *V2) Close(_ context.Context) error {
153+
var allErrors error
154+
err := e.storage.Close()
155+
if err != nil {
156+
allErrors = errors.Join(allErrors, spverrors.Wrapf(err, "couldn't close storage"))
157+
}
158+
return allErrors
159+
}
160+
161+
// Deprecated: used as adapter for engine v1
162+
func (e *V2) DB() *gorm.DB {
163+
return e.storage.DB()
164+
}
165+
166+
func (e *V2) Repositories() *repository.All {
167+
return e.repositories
168+
}
169+
170+
func (e *V2) Chain() chain.Service {
171+
return e.chainService
172+
}
173+
174+
func (e *V2) UsersService() *users.Service {
175+
return e.usersService
176+
}
177+
178+
func (e *V2) PaymailsService() *paymails.Service {
179+
return e.paymailsService
180+
}
181+
182+
func (e *V2) AddressesService() *addresses.Service {
183+
return e.addressesService
184+
}
185+
186+
func (e *V2) DataService() *data.Service {
187+
return e.dataService
188+
}
189+
190+
func (e *V2) OperationsService() *operations.Service {
191+
return e.operationsService
192+
}
193+
194+
func (e *V2) TransactionOutlinesService() outlines.Service {
195+
return e.transactionsOutlineService
196+
}
197+
198+
func (e *V2) TransactionRecordService() *record.Service {
199+
return e.transactionsRecordService
200+
}
201+
202+
func (e *V2) TxSyncService() *txsync.Service {
203+
return e.txSyncService
204+
}
205+
206+
func (e *V2) PaymailServerConfiguration() *server.Configuration {
207+
return e.paymailServerConfig
208+
}
209+
210+
func extractBHSConfig(cfg *config.AppConfig) chainmodels.BHSConfig {
211+
return chainmodels.BHSConfig{
212+
URL: cfg.BHS.URL,
213+
AuthToken: cfg.BHS.AuthToken,
214+
}
215+
}
216+
217+
func extractARCConfig(cfg *config.AppConfig) chainmodels.ARCConfig {
218+
arcCfg := chainmodels.ARCConfig{
219+
URL: cfg.ARC.URL,
220+
Token: cfg.ARC.Token,
221+
DeploymentID: cfg.ARC.DeploymentID,
222+
WaitFor: cfg.ARC.WaitForStatus,
223+
}
224+
225+
if cfg.ARC.Callback.Enabled {
226+
var err error
227+
if cfg.ARC.Callback.Token == "" {
228+
// This also sets the token to the config reference and, it is used in the callbacktoken_middleware
229+
// TODO: consider moving config modification to a PostLoad method and make this ToEngineOptions pure (no side effects)
230+
cfg.ARC.Callback.Token, err = utils.HashAdler32(config.DefaultAdminXpub)
231+
must.HaveNoErrorf(err, "error while generating callback token")
232+
}
233+
arcCfg.Callback = &chainmodels.ARCCallbackConfig{
234+
URL: cfg.ARC.Callback.Host + config.BroadcastCallbackRoute,
235+
Token: cfg.ARC.Callback.Token,
236+
}
237+
}
238+
239+
if cfg.ExperimentalFeatures != nil && cfg.ExperimentalFeatures.UseJunglebus {
240+
arcCfg.UseJunglebus = true
241+
}
242+
243+
return arcCfg
244+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package engine
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/bitcoin-sv/spv-wallet/engine/paymail"
7+
"github.com/go-resty/resty/v2"
8+
)
9+
10+
// InternalsOverride is a function that can be used to override internal dependencies.
11+
// This is meant to be used for testing purposes.
12+
type InternalsOverride = func(*overrides)
13+
14+
type overrides struct {
15+
transport http.RoundTripper
16+
resty *resty.Client
17+
paymailClient paymail.ClientInterface
18+
}
19+
20+
// WithResty is a function that can be used to override the resty.Client used by the engine.
21+
// This is meant to be used for testing purposes.
22+
func WithResty(resty *resty.Client) InternalsOverride {
23+
return func(o *overrides) {
24+
o.resty = resty
25+
}
26+
}
27+
28+
// WithTransport is a function that can be used to override the http.RoundTripper used by the engine.
29+
// This is meant to be used for testing purposes.
30+
func WithTransport(transport http.RoundTripper) InternalsOverride {
31+
return func(o *overrides) {
32+
o.transport = transport
33+
}
34+
}
35+
36+
// WithPaymailClient is a function that can be used to override the paymail.ClientInterface used by the engine.
37+
// This is meant to be used for testing purposes.
38+
func WithPaymailClient(client paymail.ClientInterface) InternalsOverride {
39+
return func(o *overrides) {
40+
o.paymailClient = client
41+
}
42+
}

engine/v2/engine/internal/cache.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/bitcoin-sv/spv-wallet/config"
9+
"github.com/bitcoin-sv/spv-wallet/engine/logging"
10+
"github.com/bitcoin-sv/spv-wallet/engine/v2/utils/must"
11+
"github.com/mrz1836/go-cachestore"
12+
"github.com/rs/zerolog"
13+
)
14+
15+
type Cache interface {
16+
GetModel(ctx context.Context, key string, model interface{}) error
17+
SetModel(ctx context.Context, key string, model interface{}, ttl time.Duration, dependencies ...string) error
18+
}
19+
20+
type cacheOptions struct {
21+
opts []cachestore.ClientOps
22+
}
23+
24+
func (o *cacheOptions) configureLogger(cfg *config.AppConfig, logger zerolog.Logger) *cacheOptions {
25+
cachestoreLogger := logging.CreateGormLoggerAdapter(&logger, "cachestore")
26+
o.opts = append(o.opts, cachestore.WithLogger(cachestoreLogger))
27+
if logger.GetLevel() == zerolog.DebugLevel || logger.GetLevel() == zerolog.TraceLevel {
28+
o.opts = append(o.opts, cachestore.WithDebugging())
29+
}
30+
return o
31+
}
32+
33+
func (o *cacheOptions) configureEngine(cfg *config.AppConfig) *cacheOptions {
34+
if cfg.Cache.Engine == cachestore.Redis {
35+
o.opts = append(o.opts, cachestore.WithRedis(&cachestore.RedisConfig{
36+
DependencyMode: cfg.Cache.Redis.DependencyMode,
37+
MaxActiveConnections: cfg.Cache.Redis.MaxActiveConnections,
38+
MaxConnectionLifetime: cfg.Cache.Redis.MaxConnectionLifetime,
39+
MaxIdleConnections: cfg.Cache.Redis.MaxIdleConnections,
40+
MaxIdleTimeout: cfg.Cache.Redis.MaxIdleTimeout,
41+
URL: cfg.Cache.Redis.URL,
42+
UseTLS: cfg.Cache.Redis.UseTLS,
43+
}))
44+
} else if cfg.Cache.Engine == cachestore.FreeCache {
45+
o.opts = append(o.opts, cachestore.WithFreeCache())
46+
} else {
47+
panic(fmt.Sprintf("invalid configuration: unsupported cache engine: %s", cfg.Cache.Engine))
48+
}
49+
50+
return o
51+
}
52+
53+
func NewCache(cfg *config.AppConfig, logger zerolog.Logger) Cache {
54+
logger.With().Str("service", "cache").Logger()
55+
56+
options := cacheOptions{make([]cachestore.ClientOps, 0)}
57+
58+
options.configureLogger(cfg, logger).configureEngine(cfg)
59+
60+
cache, err := cachestore.NewClient(context.Background(), options.opts...)
61+
must.HaveNoErrorf(err, "failed to create cache storage: %v", err)
62+
63+
return cache
64+
}

0 commit comments

Comments
 (0)