-
Notifications
You must be signed in to change notification settings - Fork 8
Automate flow-go test #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f25bbf1
6196369
343cca2
a0027e3
4231635
ddac19b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,14 @@ var ( | |
| ErrBlockNotIndexed = errors.New("indexdb: block not indexed") | ||
| ) | ||
|
|
||
| var ( | ||
| accountPrefix = []byte("a") | ||
| blockPrefix = []byte("b") | ||
| hash2HeightPrefix = []byte("c") | ||
| height2HashPrefix = []byte("d") | ||
| isProxyPrefix = []byte("p") | ||
| ) | ||
|
|
||
| // NOTE(tav): We store the blockchain data within Badger using the following | ||
| // key/value structure: | ||
| // | ||
|
|
@@ -80,10 +88,9 @@ func (s *Store) Accounts() (map[[8]byte]bool, error) { | |
| err := s.db.View(func(txn *badger.Txn) error { | ||
| it := txn.NewIterator(badger.IteratorOptions{}) | ||
| defer it.Close() | ||
| prefix := []byte("a") | ||
| it.Seek(prefix) | ||
| it.Seek(accountPrefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
| if !it.ValidForPrefix(accountPrefix) { | ||
| break | ||
| } | ||
| key := it.Item().Key() | ||
|
|
@@ -94,10 +101,9 @@ func (s *Store) Accounts() (map[[8]byte]bool, error) { | |
| } | ||
| it = txn.NewIterator(badger.IteratorOptions{}) | ||
| defer it.Close() | ||
| prefix = []byte("p") | ||
| it.Seek(prefix) | ||
| it.Seek(isProxyPrefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
| if !it.ValidForPrefix(isProxyPrefix) { | ||
| break | ||
| } | ||
| key := it.Item().Key() | ||
|
|
@@ -127,10 +133,9 @@ func (s *Store) AccountsInfo() (map[string]*AccountInfo, error) { | |
| err := s.db.View(func(txn *badger.Txn) error { | ||
| it := txn.NewIterator(badger.IteratorOptions{}) | ||
| defer it.Close() | ||
| prefix := []byte("a") | ||
| it.Seek(prefix) | ||
| it.Seek(accountPrefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
| if !it.ValidForPrefix(accountPrefix) { | ||
| break | ||
| } | ||
| item := it.Item() | ||
|
|
@@ -163,10 +168,9 @@ func (s *Store) AccountsInfo() (map[string]*AccountInfo, error) { | |
| err = s.db.View(func(txn *badger.Txn) error { | ||
| it := txn.NewIterator(badger.IteratorOptions{}) | ||
| defer it.Close() | ||
| prefix := []byte("p") | ||
| it.Seek(prefix) | ||
| it.Seek(isProxyPrefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
| if !it.ValidForPrefix(isProxyPrefix) { | ||
| break | ||
| } | ||
| key := it.Item().Key() | ||
|
|
@@ -308,7 +312,7 @@ func (s *Store) Genesis() *model.BlockMeta { | |
| // given height. | ||
| func (s *Store) HasBalance(acct []byte, height uint64) (bool, error) { | ||
| key := make([]byte, 1+8+8) | ||
| key[0] = 'a' | ||
| key[0] = 'a' // accountPrefix | ||
| copy(key[1:9], acct) | ||
| binary.BigEndian.PutUint64(key[9:], height) | ||
| ok := false | ||
|
|
@@ -337,7 +341,7 @@ func (s *Store) HashForHeight(height uint64) ([]byte, error) { | |
| var hash []byte | ||
| heightEnc := make([]byte, 8) | ||
| binary.BigEndian.PutUint64(heightEnc, height) | ||
| key := append([]byte("d"), heightEnc...) | ||
| key := append(height2HashPrefix, heightEnc...) | ||
| err := s.db.View(func(txn *badger.Txn) error { | ||
|
Comment on lines
341
to
345
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win Data race hazard: Using
📍 Affects 1 file
🤖 Prompt for AI Agents |
||
| item, err := txn.Get(key) | ||
| if err != nil { | ||
|
|
@@ -363,7 +367,7 @@ func (s *Store) HashForHeight(height uint64) ([]byte, error) { | |
| // HeightForHash returns the block height for the given hash. | ||
| func (s *Store) HeightForHash(hash []byte) (uint64, error) { | ||
| height := uint64(0) | ||
| key := append([]byte("c"), hash...) | ||
| key := append(hash2HeightPrefix, hash...) | ||
| err := s.db.View(func(txn *badger.Txn) error { | ||
| item, err := txn.Get(key) | ||
| if err != nil { | ||
|
|
@@ -423,7 +427,7 @@ func (s *Store) Index(ctx context.Context, height uint64, hash []byte, block *mo | |
| } | ||
| if len(op.ProxyPublicKey) > 0 { | ||
| key := make([]byte, 17) | ||
| key[0] = 'p' | ||
| key[0] = 'p' // isProxyPrefix | ||
| copy(key[1:], op.Account) | ||
| binary.BigEndian.PutUint64(key[9:], height) | ||
| proxyAccts = append(proxyAccts, key) | ||
|
|
@@ -440,7 +444,7 @@ func (s *Store) Index(ctx context.Context, height uint64, hash []byte, block *mo | |
| updates := make([]accountUpdate, len(accts)) | ||
| for acct, diff := range accts { | ||
| key := make([]byte, 1+8+8) | ||
| key[0] = 'a' | ||
| key[0] = 'a' // accountPrefix | ||
| copy(key[1:], acct) | ||
| copy(key[9:], hval) | ||
| updates[i] = accountUpdate{ | ||
|
|
@@ -449,13 +453,13 @@ func (s *Store) Index(ctx context.Context, height uint64, hash []byte, block *mo | |
| } | ||
| i++ | ||
| } | ||
| blockKey := append([]byte("b"), hval...) | ||
| blockKey := append(blockPrefix, hval...) | ||
| blockValue, err := proto.Marshal(block) | ||
| if err != nil { | ||
| log.Fatalf("Failed to encode model.IndexedBlock: %s", err) | ||
| } | ||
| hash2heightKey := append([]byte("c"), hash...) | ||
| height2hashKey := append([]byte("d"), hval...) | ||
| hash2heightKey := append(hash2HeightPrefix, hash...) | ||
| height2hashKey := append(height2HashPrefix, hval...) | ||
| latest = &model.BlockMeta{ | ||
| Hash: hash, | ||
| Height: height, | ||
|
|
@@ -576,10 +580,9 @@ func (s *Store) PurgeProxyAccounts() { | |
| err := s.db.View(func(txn *badger.Txn) error { | ||
| it := txn.NewIterator(badger.IteratorOptions{}) | ||
| defer it.Close() | ||
| prefix := []byte("p") | ||
| it.Seek(prefix) | ||
| it.Seek(isProxyPrefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
| if !it.ValidForPrefix(isProxyPrefix) { | ||
| break | ||
| } | ||
| key := it.Item().KeyCopy(nil) | ||
|
|
@@ -595,7 +598,7 @@ func (s *Store) PurgeProxyAccounts() { | |
| err = s.db.View(func(txn *badger.Txn) error { | ||
| it := txn.NewIterator(badger.IteratorOptions{}) | ||
| defer it.Close() | ||
| prefix := []byte("a") | ||
| prefix := accountPrefix | ||
| it.Seek(prefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
|
|
@@ -640,7 +643,7 @@ func (s *Store) ResetTo(base uint64) error { | |
| delKeys := [][]byte{} | ||
| err := s.db.View(func(txn *badger.Txn) error { | ||
| it := txn.NewIterator(badger.IteratorOptions{}) | ||
| prefix := []byte("a") | ||
| prefix := accountPrefix | ||
| it.Seek(prefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
|
|
@@ -662,7 +665,7 @@ func (s *Store) ResetTo(base uint64) error { | |
| } | ||
| err = s.db.View(func(txn *badger.Txn) error { | ||
| it := txn.NewIterator(badger.IteratorOptions{}) | ||
| prefix := []byte("p") | ||
| prefix := isProxyPrefix | ||
| it.Seek(prefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
|
|
@@ -685,7 +688,7 @@ func (s *Store) ResetTo(base uint64) error { | |
| last := uint64(0) | ||
| err = s.db.View(func(txn *badger.Txn) error { | ||
| it := txn.NewIterator(badger.IteratorOptions{}) | ||
| prefix := []byte("d") | ||
| prefix := height2HashPrefix | ||
| it.Seek(prefix) | ||
| for { | ||
| if !it.ValidForPrefix(prefix) { | ||
|
|
@@ -695,21 +698,21 @@ func (s *Store) ResetTo(base uint64) error { | |
| key := item.Key() | ||
| height := binary.BigEndian.Uint64(key[1:]) | ||
| if height > base { | ||
| key = item.KeyCopy(nil) | ||
| delKeys = append(delKeys, key) | ||
| key = make([]byte, 9) | ||
| key[0] = 'b' | ||
| binary.BigEndian.PutUint64(key[1:], height) | ||
| delKeys = append(delKeys, key) | ||
| height2HashKey := item.KeyCopy(nil) | ||
| delKeys = append(delKeys, height2HashKey) | ||
| blockKey := make([]byte, 9) | ||
| blockKey[0] = 'b' // blockPrefix | ||
| binary.BigEndian.PutUint64(blockKey[1:], height) | ||
| delKeys = append(delKeys, blockKey) | ||
| hash, err := item.ValueCopy(nil) | ||
| if err != nil { | ||
| it.Close() | ||
| return err | ||
| } | ||
| key = make([]byte, len(hash)+1) | ||
| key[0] = 'c' | ||
| copy(key[1:], hash) | ||
| delKeys = append(delKeys, key) | ||
| hash2HeightKey := make([]byte, len(hash)+1) | ||
| hash2HeightKey[0] = 'c' // hash2HeightPrefix | ||
| copy(hash2HeightKey[1:], hash) | ||
| delKeys = append(delKeys, hash2HeightKey) | ||
| } else { | ||
| last = height | ||
| } | ||
|
|
@@ -775,15 +778,15 @@ func (s *Store) SetGenesis(val *model.BlockMeta) error { | |
| } | ||
| hval := make([]byte, 8) | ||
| binary.BigEndian.PutUint64(hval, val.Height) | ||
| blockKey := append([]byte("b"), hval...) | ||
| blockKey := append(blockPrefix, hval...) | ||
| blockValue, err := proto.Marshal(&model.IndexedBlock{ | ||
| Timestamp: val.Timestamp, | ||
| }) | ||
| if err != nil { | ||
| log.Fatalf("Failed to encode model.IndexedBlock: %s", err) | ||
| } | ||
| hash2heightKey := append([]byte("c"), val.Hash...) | ||
| height2hashKey := append([]byte("d"), hval...) | ||
| hash2heightKey := append(hash2HeightPrefix, val.Hash...) | ||
| height2hashKey := append(height2HashPrefix, hval...) | ||
| err = s.db.Update(func(txn *badger.Txn) error { | ||
| if err := txn.Set([]byte("genesis"), genesis); err != nil { | ||
| return err | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,7 +27,7 @@ | |
| } | ||
| ], | ||
| "root_block": 0, | ||
| "version": 7 | ||
| "version": 8 | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // Package localnettest holds an end-to-end localnet compatibility test for the | ||
| // Rosetta server, automating the validation procedure in script/README.md. | ||
| // | ||
| // The test is guarded by the `localnet` build tag and is excluded from | ||
| // `make go-test`; run it with `make localnet-test`. It requires a flow-go | ||
| // localnet running at 127.0.0.1:4001 (see flow-go integration/localnet, built | ||
| // at the flow-go version under test), plus the flow CLI, jq, and python3 with | ||
| // `click` and `requests`. It skips cleanly when any of those are unavailable. | ||
| // | ||
| // It bootstraps and funds an originator, starts ./server against localnet, | ||
| // waits for the indexer to reach tip, then creates a derived account and | ||
| // transfers funds through Rosetta's construction API — asserting via the | ||
| // Rosetta REST API that the server indexes the network without a block-ID | ||
| // mismatch and observes the transfer. A failure here means the current Rosetta | ||
| // build is incompatible with the localnet's flow-go version (or its spork | ||
| // config is stale), which is exactly the signal the upgrade procedure needs. | ||
| package localnettest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Consider defining byte constants to fix inconsistencies and avoid potential slice-append races.
While centralizing the prefix definitions is a great refactor, defining them solely as global slices creates an unsafe pattern later in the file where
append(prefix, ...)is called on them. If the capacity of a global slice happens to be larger than its length, concurrentappendcalls will write to the same backing array, causing a data race.Additionally, because
accountPrefixis a slice, other areas of the code (like line 315) are still forced to hardcodekey[0] = 'a', defeating the purpose of centralizing the prefixes.Consider defining the exact bytes as constants, and deriving the slices from them for iterators:
This allows the rest of the file to use
key[0] = accountPrefixBytesafely and cleanly, while avoiding the unsafeappendon global slices.🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Consider defining byte constants to fix inconsistencies and avoid potential slice-append races.
While centralizing the prefix definitions is a great refactor, defining them solely as global slices creates an unsafe pattern later in the file where
append(prefix, ...)is called on them. If the capacity of a global slice happens to be larger than its length, concurrentappendcalls will write to the same backing array, causing a data race.Additionally, because
accountPrefixis a slice, other areas of the code (like line 315) are still forced to hardcodekey[0] = 'a', defeating the purpose of centralizing the prefixes.Consider defining the exact bytes as constants, and deriving the slices from them for iterators:
This allows the rest of the file to use
key[0] = accountPrefixBytesafely and cleanly, while avoiding the unsafeappendon global slices.🤖 Prompt for AI Agents