Skip to content

Commit 7f3c75d

Browse files
Merge staging into master
* Add telemetry on datastore (#56) * Add debug logs * Add runtime calculation logging * add more log * add datastore telemtry Co-authored-by: Dylan Tinianov <[email protected]> * add better telemtry (#58) * Implement SQL storage (#59) * implemented sql storage * remove persistence specific models * remove unused storage impl * fix storage interface * update account controller to changes * update project controller * update storage interface * update scripts controller * update transactions controller * update embed controller * fix project * init storage * fix API changes * test todo * mod tidy * move sql to top * fix models - remove logs * dissable telemtry * remove unused fields * improve init * fix tests * fix tests * add postresql driver * postgresql client init * postgresql client init * update server with const sql * add host * add logging * debug logging * more debug logs * more debug logs * add sentry panic * clean up code * update version * change cors * Revert "change cors" This reverts commit fe6c2ea. * set verison on sentry * fix update methods * fix update methods * clean up code * clean up code * clean up stale code (#63) * Test bugfixes (#65) * fix factory methods * add sqlite db to gitignore * fix test * fix update * check and standarize update * refactor account update * validate update * fix missing value * store init test * fix execution index * Cache invalidation and mutex improvements (#70) * fix factory methods * add sqlite db to gitignore * fix test * fix update * check and standarize update * refactor account update * validate update * fix missing value * store init test * fix execution index * rename shadowed var * mutex changes tests * refactor mutex sync map * fix test store init * simplify mutex * simplify mutex * refactor cache * refactor cache * add eviction handler * add logger * bug reproduction * refactor create init accounts * track cache eviction * disable cache test * added get batch accounts * optimize get account * batch get accounts * log len and cap exe * add explicit order * add logging * add logging * add logging * add logging * clean code * add extra log for state recreation issue * fix test * add missing headers * State recreation bugfix (#78) * no version fix * fix order clause * added state test * changed make for pgs store * temp disable cache * enable cache * bugfix recreation failure * bugfix recreation failure * added more comments explaning cache * Emulator pool (#79) * increase idle connections * update emulator * clean up mutex * get accounts test * add to cache * add instance pool * add tests for instance pool * integrate instance pool * create pool * tidy * Fix linter (#83) * Fix linter * Include CI on staging * Update linter * Fix Test_DeployContracts * Improvement/pool error handling (#80) * Refill pool when empty * Fill pool async * Remove pool refill * Parallelize test cases (#84) * Testing/add replica testing (#95) * Add replica testing * Found potential cached emulator issue * Update replica testing * Update dockerfile * Create replica tests * Undo dockerfile change * temp log * add version log * allow all origins * allow all origins * origins fix * test deploy * update allowed origins * use env for allowed origins * explicit order (#96) * Change cache to thread safe lru (#77) * Change cache to thread safe lru * Add cache tests * Add cache test with executions * Verify execution block heights * Fix linter * Fix linter * Add error handling to emulatorCache * Remove cache evicted messages * Improve cache error handling * Fix comment * Update cache error handling * Update cache error handling * Update cache error handling * Add disabled emulator test * Revert "Parallelize test cases (#84)" (#97) This reverts commit 91772f0. Co-authored-by: Dylan Tinianov <[email protected]>
1 parent 8056249 commit 7f3c75d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1834
-3027
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ name: CI
44
# events but only for the master branch
55
on:
66
push:
7-
branches: [ master ]
7+
branches: [ master, staging ]
88
pull_request:
9-
branches: [ master ]
9+
branches: [ master, staging ]
1010

1111
jobs:
1212
test:
@@ -17,7 +17,7 @@ jobs:
1717
fetch-depth: 0
1818
- uses: actions/setup-go@v2
1919
with:
20-
go-version: '1.18.1'
20+
go-version: '1.18.4'
2121
- uses: actions/cache@v1
2222
with:
2323
path: ~/go/pkg/mod
@@ -29,10 +29,15 @@ jobs:
2929
lint:
3030
runs-on: ubuntu-latest
3131
steps:
32-
- uses: actions/checkout@v2
32+
- name: Install Go
33+
uses: actions/setup-go@v3
34+
with:
35+
go-version: '1.18.4'
36+
- name: Checkout code
37+
uses: actions/checkout@v2
3338
- name: Run golangci-lint
3439
uses: golangci/[email protected]
3540
with:
3641
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
37-
version: v1.47
42+
version: v1.49
3843
args: --timeout=10m

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.idea
22
.DS_Store
3+
e2e-db

Makefile

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ test:
1515
GO111MODULE=on go test -v ./...
1616

1717
.PHONY: test-datastore
18-
test-datastore:
18+
test-pg:
1919
DATASTORE_EMULATOR_HOST=localhost:8081 FLOW_STORAGEBACKEND=datastore GO111MODULE=on go test -v ./...
2020

2121
.PHONY: test-migration
2222
test-migration:
23-
DATASTORE_EMULATOR_HOST=localhost:8081 FLOW_STORAGEBACKEND=datastore GO111MODULE=on go test ./migrate/... -run Test_MigrationV0_12_0
23+
DATASTORE_EMULATOR_HOST=localhost:8081 FLOW_STORAGEBACKEND=datastore GO111MODULE=on go test ./migrate/...
2424

2525

2626
.PHONY: run
@@ -32,21 +32,19 @@ run:
3232
-ldflags "-X github.com/dapperlabs/flow-playground-api/build.version=$(LAST_KNOWN_VERSION)" \
3333
server/server.go
3434

35-
.PHONY: run-datastore
36-
run-datastore:
37-
DATASTORE_EMULATOR_HOST=localhost:8081 \
38-
FLOW_STORAGEBACKEND=datastore \
39-
FLOW_DATASTORE_GCPPROJECTID=flow-developer-playground \
35+
.PHONY: run-pg
36+
run-pg:
37+
FLOW_DB_USER=postgres \
38+
FLOW_DB_PORT=5432 \
39+
FLOW_DB_NAME=dapper \
40+
FLOW_DB_HOST=localhost \
41+
FLOW_STORAGEBACKEND=postgresql \
4042
FLOW_DEBUG=true FLOW_SESSIONCOOKIESSECURE=false \
4143
GO111MODULE=on \
4244
go run \
4345
-ldflags "-X github.com/dapperlabs/flow-playground-api/build.version=$(LAST_KNOWN_VERSION)" \
4446
server/server.go
4547

46-
.PHONY: start-datastore-emulator
47-
start-datastore-emulator:
48-
gcloud beta emulators datastore start --no-store-on-disk
49-
5048
.PHONY: ci
5149
ci: check-tidy test check-headers
5250

adapter/state.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type accountState struct {
2929
// stateToAPI removes any state values that are blockchain system values and not relevant to user usage of the playground.
3030
func stateToAPI(state string) string {
3131
if state == "" {
32-
return state
32+
return "{}"
3333
}
3434

3535
var accState accountState
@@ -41,8 +41,7 @@ func stateToAPI(state string) string {
4141

4242
// return empty as empty object, no keys - v0 adapter
4343
if len(accState.Public)+len(accState.Private)+len(accState.Storage) == 0 {
44-
emptyState, _ := json.Marshal("{}")
45-
return string(emptyState)
44+
return "{}"
4645
}
4746

4847
adaptedState, _ := json.Marshal(accState)

auth/auth.go

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ package auth
2020

2121
import (
2222
"context"
23-
"github.com/dapperlabs/flow-playground-api/telemetry"
24-
2523
"github.com/google/uuid"
2624
"github.com/pkg/errors"
2725

@@ -90,10 +88,7 @@ func (a *Authenticator) GetOrCreateUser(ctx context.Context) (*model.User, error
9088
//
9189
// This function checks for access using both the new and legacy authentication schemes. If
9290
// a user has legacy access, their authentication is then migrated to use the new scheme.
93-
func (a *Authenticator) CheckProjectAccess(ctx context.Context, proj *model.InternalProject) error {
94-
telemetry.StartRuntimeCalculation()
95-
defer telemetry.EndRuntimeCalculation()
96-
telemetry.DebugLog("[auth] Check Project Access")
91+
func (a *Authenticator) CheckProjectAccess(ctx context.Context, proj *model.Project) error {
9792
var user *model.User
9893
var err error
9994

@@ -107,7 +102,6 @@ func (a *Authenticator) CheckProjectAccess(ctx context.Context, proj *model.Inte
107102
}
108103

109104
if a.hasProjectAccess(user, proj) {
110-
telemetry.DebugLog("[auth] Check Project Access")
111105
err = sessions.Save(ctx, session)
112106
if err != nil {
113107
return errors.New("access denied")
@@ -136,10 +130,7 @@ func (a *Authenticator) CheckProjectAccess(ctx context.Context, proj *model.Inte
136130
}
137131

138132
func (a *Authenticator) getCurrentUser(userIDStr string) (*model.User, error) {
139-
telemetry.StartRuntimeCalculation()
140-
defer telemetry.EndRuntimeCalculation()
141133
var user model.User
142-
143134
var userID uuid.UUID
144135

145136
err := userID.UnmarshalText([]byte(userIDStr))
@@ -155,15 +146,15 @@ func (a *Authenticator) getCurrentUser(userIDStr string) (*model.User, error) {
155146
return &user, nil
156147
}
157148

158-
func (a *Authenticator) hasProjectAccess(user *model.User, proj *model.InternalProject) bool {
149+
func (a *Authenticator) hasProjectAccess(user *model.User, proj *model.Project) bool {
159150
return user != nil && proj.IsOwnedBy(user.ID)
160151
}
161152

162-
func (a *Authenticator) hasLegacyProjectAccess(ctx context.Context, proj *model.InternalProject) bool {
153+
func (a *Authenticator) hasLegacyProjectAccess(ctx context.Context, proj *model.Project) bool {
163154
return legacyauth.ProjectInSession(ctx, proj)
164155
}
165156

166-
func (a *Authenticator) migrateLegacyProjectAccess(user *model.User, proj *model.InternalProject) (*model.User, error) {
157+
func (a *Authenticator) migrateLegacyProjectAccess(user *model.User, proj *model.Project) (*model.User, error) {
167158
var err error
168159

169160
if user == nil {
@@ -188,7 +179,7 @@ func (a *Authenticator) createNewUser() (*model.User, error) {
188179

189180
err := a.store.InsertUser(user)
190181
if err != nil {
191-
return nil, err
182+
return nil, errors.Wrap(err, "could not insert the user")
192183
}
193184

194185
return user, nil

auth/legacy/auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const projectSecretKeyName = "project-secret"
3636
//
3737
// A project is authorized in a session if the session contains a reference to the
3838
// project's secret.
39-
func ProjectInSession(ctx context.Context, proj *model.InternalProject) bool {
39+
func ProjectInSession(ctx context.Context, proj *model.Project) bool {
4040
session := sessions.Get(ctx, getProjectSessionName(proj))
4141

4242
secret, ok := session.Values[projectSecretKeyName]
@@ -52,7 +52,7 @@ func ProjectInSession(ctx context.Context, proj *model.InternalProject) bool {
5252
return secretStr == proj.Secret.String()
5353
}
5454

55-
func getProjectSessionName(proj *model.InternalProject) string {
55+
func getProjectSessionName(proj *model.Project) string {
5656
return getProjectSessionNameFromString(proj.ID.String())
5757
}
5858

blockchain/cache.go

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,69 +19,61 @@
1919
package blockchain
2020

2121
import (
22-
"fmt"
23-
"github.com/dapperlabs/flow-playground-api/model"
24-
"github.com/dapperlabs/flow-playground-api/telemetry"
2522
"github.com/getsentry/sentry-go"
26-
"github.com/golang/groupcache/lru"
2723
"github.com/google/uuid"
28-
"github.com/pkg/errors"
24+
lru "github.com/hashicorp/golang-lru"
2925
)
3026

31-
// newCache returns a new instance of cache with provided capacity.
32-
func newCache(capacity int) *cache {
33-
return &cache{
34-
cache: lru.New(capacity),
35-
}
27+
// emulatorCache caches the emulator state.
28+
//
29+
// In the environment where multiple replicas maintain its own cache copy it can get into multiple states:
30+
// - it can get stale because replica A receives transaction execution 1, and replica B receives transaction execution 2,
31+
// then replica A needs to apply missed transaction execution 2 before continuing
32+
// - it can be outdated because replica A receives project reset, which clears all executions and the cache, but replica B
33+
// doesn't receive that request so on next run it receives 0 executions but cached emulator contains state from previous
34+
// executions that wasn't cleared
35+
type emulatorCache struct {
36+
cache *lru.Cache
3637
}
3738

38-
type cache struct {
39-
cache *lru.Cache
39+
// newEmulatorCache returns a new instance of emulatorCache with provided capacity.
40+
func newEmulatorCache(capacity int) *emulatorCache {
41+
cache, err := lru.New(capacity)
42+
if err != nil {
43+
sentry.CaptureMessage("Continuing without emulator caching: " + err.Error())
44+
}
45+
46+
return &emulatorCache{
47+
cache: cache,
48+
}
4049
}
4150

42-
// reset the cache for the ID.
43-
func (c *cache) reset(ID uuid.UUID) {
51+
// reset the cached emulator for the ID.
52+
func (c *emulatorCache) reset(ID uuid.UUID) {
53+
if c.cache == nil {
54+
return
55+
}
4456
c.cache.Remove(ID)
4557
}
4658

47-
// get returns a cached emulator if exists, but also checks if it's stale.
48-
//
49-
// based on the executions the function receives it compares that to the emulator block height, since
50-
// one execution is always one block it can compare the heights to the length. If it finds some executions
51-
// that are not part of emulator it returns that subset, so they can be applied on top.
52-
func (c *cache) get(
53-
ID uuid.UUID,
54-
executions []*model.TransactionExecution,
55-
) (blockchain, []*model.TransactionExecution, error) {
56-
telemetry.DebugLog("[cache] get - start get executions from ID")
59+
// get returns a cached emulator for specified ID if it exists
60+
func (c *emulatorCache) get(ID uuid.UUID) *emulator {
61+
if c.cache == nil {
62+
return nil
63+
}
5764

5865
val, ok := c.cache.Get(ID)
5966
if !ok {
60-
return nil, executions, nil
61-
}
62-
63-
emulator := val.(blockchain)
64-
latest, err := emulator.getLatestBlock()
65-
if err != nil {
66-
return nil, nil, errors.Wrap(err, "cache failure")
67+
return nil
6768
}
6869

69-
// this should never happen, sanity check
70-
if int(latest.Header.Height) > len(executions) {
71-
err := fmt.Errorf("cache failure, block height is higher than executions count")
72-
sentry.CaptureException(err)
73-
return nil, nil, err
74-
}
75-
76-
telemetry.DebugLog("[cache] get - got latest block from emulator")
77-
78-
// this will return only executions that are missing from the emulator
79-
return emulator, executions[latest.Header.Height:], nil
70+
return val.(*emulator)
8071
}
8172

82-
// add new entry in the cache.
83-
func (c *cache) add(ID uuid.UUID, emulator blockchain) {
84-
telemetry.DebugLog("[cache] add - start add emulator to cache")
73+
// add new emulator to the cache.
74+
func (c *emulatorCache) add(ID uuid.UUID, emulator *emulator) {
75+
if c.cache == nil {
76+
return
77+
}
8578
c.cache.Add(ID, emulator)
86-
telemetry.DebugLog("[cache] add - added emulator to cache")
8779
}

0 commit comments

Comments
 (0)