Skip to content

Commit d52ba1b

Browse files
authored
Merge pull request #98 from onflow/tim/localnet-integration-test
Automate flow-go test
2 parents 6ff2deb + ddac19b commit d52ba1b

8 files changed

Lines changed: 584 additions & 50 deletions

File tree

Makefile

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ go-test:
2020
go test -v github.com/onflow/rosetta/state/...
2121
go test -v github.com/onflow/rosetta/script/...
2222

23+
# End-to-end localnet compatibility test (script/README.md). Requires a flow-go
24+
# localnet up at 127.0.0.1:4001, the flow CLI, jq, and python3 with click +
25+
# requests; skips cleanly if any are absent. Build-tagged out of go-test.
26+
.PHONY: localnet-test
27+
localnet-test:
28+
go test -tags localnet -v -timeout 20m github.com/onflow/rosetta/localnettest/...
29+
2330
.PHONY: gen-originator-account
2431
gen-originator-account:
2532
KEYS=$$(go run ./cmd/genkey/genkey.go -csv); \
@@ -68,9 +75,9 @@ create-originator-derived-account:
6875
ROOT_ORIGINATOR_PRIVATE_KEY=$$(grep '$(ORIGINATOR_NAME)' $(ACCOUNT_KEYS_FILENAME) | cut -d ',' -f4 ); \
6976
ROOT_ORIGINATOR_ADDRESS=$$(grep '$(ORIGINATOR_NAME)' $(ACCOUNT_KEYS_FILENAME) | cut -d ',' -f5); \
7077
echo "Originator address: $$ROOT_ORIGINATOR_ADDRESS"; \
71-
TX_HASH=$$(python3 rosetta_handler.py rosetta-create-derived-account $(ROSETTA_HOST_URL) $$ROOT_ORIGINATOR_ADDRESS $$ROOT_ORIGINATOR_PUBLIC_KEY $$ROOT_ORIGINATOR_PRIVATE_KEY $$NEW_ACCOUNT_PUBLIC_ROSETTA_KEY); \
72-
ADDRESS=$$(flow transactions get $$TX_HASH -f $(FLOW_JSON) -n $(ROSETTA_ENV) -o json | jq -r '.events[] | select(.type == "flow.AccountCreated") | .values.value.fields[] | select(.name == "address") | .value.value'); \
73-
echo "TX_HASH: $$TX_HASH , ADDRESS: $$ADDRESS"; \
78+
TX_HASH=$$(python3 rosetta_handler.py rosetta-create-derived-account $(ROSETTA_HOST_URL) $$ROOT_ORIGINATOR_ADDRESS $$ROOT_ORIGINATOR_PUBLIC_KEY $$ROOT_ORIGINATOR_PRIVATE_KEY $$NEW_ACCOUNT_PUBLIC_ROSETTA_KEY) && \
79+
ADDRESS=$$(flow transactions get $$TX_HASH -f $(FLOW_JSON) -n $(ROSETTA_ENV) -o json | jq -r '.events[] | select(.type == "flow.AccountCreated") | .values.value.fields[] | select(.name == "address") | .value.value') && \
80+
echo "TX_HASH: $$TX_HASH , ADDRESS: $$ADDRESS" && \
7481
echo "$(NEW_ACCOUNT_NAME),$$NEW_ACCOUNT_PUBLIC_FLOW_KEY,$$NEW_ACCOUNT_PUBLIC_ROSETTA_KEY,$$NEW_ACCOUNT_PRIVATE_KEY,$$ADDRESS" >> $(ACCOUNT_KEYS_FILENAME);
7582

7683
.PHONY: rosetta-transfer-funds

indexdb/indexdb.go

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ var (
2525
ErrBlockNotIndexed = errors.New("indexdb: block not indexed")
2626
)
2727

28+
var (
29+
accountPrefix = []byte("a")
30+
blockPrefix = []byte("b")
31+
hash2HeightPrefix = []byte("c")
32+
height2HashPrefix = []byte("d")
33+
isProxyPrefix = []byte("p")
34+
)
35+
2836
// NOTE(tav): We store the blockchain data within Badger using the following
2937
// key/value structure:
3038
//
@@ -80,10 +88,9 @@ func (s *Store) Accounts() (map[[8]byte]bool, error) {
8088
err := s.db.View(func(txn *badger.Txn) error {
8189
it := txn.NewIterator(badger.IteratorOptions{})
8290
defer it.Close()
83-
prefix := []byte("a")
84-
it.Seek(prefix)
91+
it.Seek(accountPrefix)
8592
for {
86-
if !it.ValidForPrefix(prefix) {
93+
if !it.ValidForPrefix(accountPrefix) {
8794
break
8895
}
8996
key := it.Item().Key()
@@ -94,10 +101,9 @@ func (s *Store) Accounts() (map[[8]byte]bool, error) {
94101
}
95102
it = txn.NewIterator(badger.IteratorOptions{})
96103
defer it.Close()
97-
prefix = []byte("p")
98-
it.Seek(prefix)
104+
it.Seek(isProxyPrefix)
99105
for {
100-
if !it.ValidForPrefix(prefix) {
106+
if !it.ValidForPrefix(isProxyPrefix) {
101107
break
102108
}
103109
key := it.Item().Key()
@@ -127,10 +133,9 @@ func (s *Store) AccountsInfo() (map[string]*AccountInfo, error) {
127133
err := s.db.View(func(txn *badger.Txn) error {
128134
it := txn.NewIterator(badger.IteratorOptions{})
129135
defer it.Close()
130-
prefix := []byte("a")
131-
it.Seek(prefix)
136+
it.Seek(accountPrefix)
132137
for {
133-
if !it.ValidForPrefix(prefix) {
138+
if !it.ValidForPrefix(accountPrefix) {
134139
break
135140
}
136141
item := it.Item()
@@ -163,10 +168,9 @@ func (s *Store) AccountsInfo() (map[string]*AccountInfo, error) {
163168
err = s.db.View(func(txn *badger.Txn) error {
164169
it := txn.NewIterator(badger.IteratorOptions{})
165170
defer it.Close()
166-
prefix := []byte("p")
167-
it.Seek(prefix)
171+
it.Seek(isProxyPrefix)
168172
for {
169-
if !it.ValidForPrefix(prefix) {
173+
if !it.ValidForPrefix(isProxyPrefix) {
170174
break
171175
}
172176
key := it.Item().Key()
@@ -308,7 +312,7 @@ func (s *Store) Genesis() *model.BlockMeta {
308312
// given height.
309313
func (s *Store) HasBalance(acct []byte, height uint64) (bool, error) {
310314
key := make([]byte, 1+8+8)
311-
key[0] = 'a'
315+
key[0] = 'a' // accountPrefix
312316
copy(key[1:9], acct)
313317
binary.BigEndian.PutUint64(key[9:], height)
314318
ok := false
@@ -337,7 +341,7 @@ func (s *Store) HashForHeight(height uint64) ([]byte, error) {
337341
var hash []byte
338342
heightEnc := make([]byte, 8)
339343
binary.BigEndian.PutUint64(heightEnc, height)
340-
key := append([]byte("d"), heightEnc...)
344+
key := append(height2HashPrefix, heightEnc...)
341345
err := s.db.View(func(txn *badger.Txn) error {
342346
item, err := txn.Get(key)
343347
if err != nil {
@@ -363,7 +367,7 @@ func (s *Store) HashForHeight(height uint64) ([]byte, error) {
363367
// HeightForHash returns the block height for the given hash.
364368
func (s *Store) HeightForHash(hash []byte) (uint64, error) {
365369
height := uint64(0)
366-
key := append([]byte("c"), hash...)
370+
key := append(hash2HeightPrefix, hash...)
367371
err := s.db.View(func(txn *badger.Txn) error {
368372
item, err := txn.Get(key)
369373
if err != nil {
@@ -423,7 +427,7 @@ func (s *Store) Index(ctx context.Context, height uint64, hash []byte, block *mo
423427
}
424428
if len(op.ProxyPublicKey) > 0 {
425429
key := make([]byte, 17)
426-
key[0] = 'p'
430+
key[0] = 'p' // isProxyPrefix
427431
copy(key[1:], op.Account)
428432
binary.BigEndian.PutUint64(key[9:], height)
429433
proxyAccts = append(proxyAccts, key)
@@ -440,7 +444,7 @@ func (s *Store) Index(ctx context.Context, height uint64, hash []byte, block *mo
440444
updates := make([]accountUpdate, len(accts))
441445
for acct, diff := range accts {
442446
key := make([]byte, 1+8+8)
443-
key[0] = 'a'
447+
key[0] = 'a' // accountPrefix
444448
copy(key[1:], acct)
445449
copy(key[9:], hval)
446450
updates[i] = accountUpdate{
@@ -449,13 +453,13 @@ func (s *Store) Index(ctx context.Context, height uint64, hash []byte, block *mo
449453
}
450454
i++
451455
}
452-
blockKey := append([]byte("b"), hval...)
456+
blockKey := append(blockPrefix, hval...)
453457
blockValue, err := proto.Marshal(block)
454458
if err != nil {
455459
log.Fatalf("Failed to encode model.IndexedBlock: %s", err)
456460
}
457-
hash2heightKey := append([]byte("c"), hash...)
458-
height2hashKey := append([]byte("d"), hval...)
461+
hash2heightKey := append(hash2HeightPrefix, hash...)
462+
height2hashKey := append(height2HashPrefix, hval...)
459463
latest = &model.BlockMeta{
460464
Hash: hash,
461465
Height: height,
@@ -576,10 +580,9 @@ func (s *Store) PurgeProxyAccounts() {
576580
err := s.db.View(func(txn *badger.Txn) error {
577581
it := txn.NewIterator(badger.IteratorOptions{})
578582
defer it.Close()
579-
prefix := []byte("p")
580-
it.Seek(prefix)
583+
it.Seek(isProxyPrefix)
581584
for {
582-
if !it.ValidForPrefix(prefix) {
585+
if !it.ValidForPrefix(isProxyPrefix) {
583586
break
584587
}
585588
key := it.Item().KeyCopy(nil)
@@ -595,7 +598,7 @@ func (s *Store) PurgeProxyAccounts() {
595598
err = s.db.View(func(txn *badger.Txn) error {
596599
it := txn.NewIterator(badger.IteratorOptions{})
597600
defer it.Close()
598-
prefix := []byte("a")
601+
prefix := accountPrefix
599602
it.Seek(prefix)
600603
for {
601604
if !it.ValidForPrefix(prefix) {
@@ -640,7 +643,7 @@ func (s *Store) ResetTo(base uint64) error {
640643
delKeys := [][]byte{}
641644
err := s.db.View(func(txn *badger.Txn) error {
642645
it := txn.NewIterator(badger.IteratorOptions{})
643-
prefix := []byte("a")
646+
prefix := accountPrefix
644647
it.Seek(prefix)
645648
for {
646649
if !it.ValidForPrefix(prefix) {
@@ -662,7 +665,7 @@ func (s *Store) ResetTo(base uint64) error {
662665
}
663666
err = s.db.View(func(txn *badger.Txn) error {
664667
it := txn.NewIterator(badger.IteratorOptions{})
665-
prefix := []byte("p")
668+
prefix := isProxyPrefix
666669
it.Seek(prefix)
667670
for {
668671
if !it.ValidForPrefix(prefix) {
@@ -685,7 +688,7 @@ func (s *Store) ResetTo(base uint64) error {
685688
last := uint64(0)
686689
err = s.db.View(func(txn *badger.Txn) error {
687690
it := txn.NewIterator(badger.IteratorOptions{})
688-
prefix := []byte("d")
691+
prefix := height2HashPrefix
689692
it.Seek(prefix)
690693
for {
691694
if !it.ValidForPrefix(prefix) {
@@ -695,21 +698,21 @@ func (s *Store) ResetTo(base uint64) error {
695698
key := item.Key()
696699
height := binary.BigEndian.Uint64(key[1:])
697700
if height > base {
698-
key = item.KeyCopy(nil)
699-
delKeys = append(delKeys, key)
700-
key = make([]byte, 9)
701-
key[0] = 'b'
702-
binary.BigEndian.PutUint64(key[1:], height)
703-
delKeys = append(delKeys, key)
701+
height2HashKey := item.KeyCopy(nil)
702+
delKeys = append(delKeys, height2HashKey)
703+
blockKey := make([]byte, 9)
704+
blockKey[0] = 'b' // blockPrefix
705+
binary.BigEndian.PutUint64(blockKey[1:], height)
706+
delKeys = append(delKeys, blockKey)
704707
hash, err := item.ValueCopy(nil)
705708
if err != nil {
706709
it.Close()
707710
return err
708711
}
709-
key = make([]byte, len(hash)+1)
710-
key[0] = 'c'
711-
copy(key[1:], hash)
712-
delKeys = append(delKeys, key)
712+
hash2HeightKey := make([]byte, len(hash)+1)
713+
hash2HeightKey[0] = 'c' // hash2HeightPrefix
714+
copy(hash2HeightKey[1:], hash)
715+
delKeys = append(delKeys, hash2HeightKey)
713716
} else {
714717
last = height
715718
}
@@ -775,15 +778,15 @@ func (s *Store) SetGenesis(val *model.BlockMeta) error {
775778
}
776779
hval := make([]byte, 8)
777780
binary.BigEndian.PutUint64(hval, val.Height)
778-
blockKey := append([]byte("b"), hval...)
781+
blockKey := append(blockPrefix, hval...)
779782
blockValue, err := proto.Marshal(&model.IndexedBlock{
780783
Timestamp: val.Timestamp,
781784
})
782785
if err != nil {
783786
log.Fatalf("Failed to encode model.IndexedBlock: %s", err)
784787
}
785-
hash2heightKey := append([]byte("c"), val.Hash...)
786-
height2hashKey := append([]byte("d"), hval...)
788+
hash2heightKey := append(hash2HeightPrefix, val.Hash...)
789+
height2hashKey := append(height2HashPrefix, hval...)
787790
err = s.db.Update(func(txn *badger.Txn) error {
788791
if err := txn.Set([]byte("genesis"), genesis); err != nil {
789792
return err

localnet.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
}
2828
],
2929
"root_block": 0,
30-
"version": 7
30+
"version": 8
3131
}
3232
}
3333
}

localnettest/doc.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Package localnettest holds an end-to-end localnet compatibility test for the
2+
// Rosetta server, automating the validation procedure in script/README.md.
3+
//
4+
// The test is guarded by the `localnet` build tag and is excluded from
5+
// `make go-test`; run it with `make localnet-test`. It requires a flow-go
6+
// localnet running at 127.0.0.1:4001 (see flow-go integration/localnet, built
7+
// at the flow-go version under test), plus the flow CLI, jq, and python3 with
8+
// `click` and `requests`. It skips cleanly when any of those are unavailable.
9+
//
10+
// It bootstraps and funds an originator, starts ./server against localnet,
11+
// waits for the indexer to reach tip, then creates a derived account and
12+
// transfers funds through Rosetta's construction API — asserting via the
13+
// Rosetta REST API that the server indexes the network without a block-ID
14+
// mismatch and observes the transfer. A failure here means the current Rosetta
15+
// build is incompatible with the localnet's flow-go version (or its spork
16+
// config is stale), which is exactly the signal the upgrade procedure needs.
17+
package localnettest

0 commit comments

Comments
 (0)