Skip to content

Commit 4800c80

Browse files
committed
Merge branch 'master' into leo/refactor-index-result
2 parents 8d8d964 + 98a6ac4 commit 4800c80

File tree

10 files changed

+229
-6
lines changed

10 files changed

+229
-6
lines changed

cmd/execution_builder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@ func (exeNode *ExecutionNode) LoadExecutionState(
767767
storedChunkDataPacks := store.NewStoredChunkDataPacks(
768768
node.Metrics.Cache, chunkDB, exeNode.exeConf.chunkDataPackCacheSize)
769769
chunkDataPacks := store.NewChunkDataPacks(node.Metrics.Cache,
770-
chunkDB, storedChunkDataPacks, exeNode.collections, exeNode.exeConf.chunkDataPackCacheSize)
770+
node.ProtocolDB, storedChunkDataPacks, exeNode.collections, exeNode.exeConf.chunkDataPackCacheSize)
771771

772772
getLatestFinalized := func() (uint64, error) {
773773
final, err := node.State.Final().Head()

cmd/util/cmd/rollback-executed-height/cmd/rollback_executed_height.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func runE(*cobra.Command, []string) error {
8888
}
8989
chunkDataPacksDB := pebbleimpl.ToDB(chunkDataPacksPebbleDB)
9090
storedChunkDataPacks := store.NewStoredChunkDataPacks(metrics, chunkDataPacksDB, 1000)
91-
chunkDataPacks := store.NewChunkDataPacks(metrics, chunkDataPacksDB, storedChunkDataPacks, collections, 1000)
91+
chunkDataPacks := store.NewChunkDataPacks(metrics, db, storedChunkDataPacks, collections, 1000)
9292
protocolDBBatch := db.NewBatch()
9393
defer protocolDBBatch.Close()
9494

78.8 KB
Loading
79.1 KB
Loading
313 KB
Loading

engine/verification/verifier/verifiers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ func initStorages(
255255
}
256256
storedChunkDataPacks := store.NewStoredChunkDataPacks(metrics.NewNoopCollector(), pebbleimpl.ToDB(chunkDataPackDB), 1000)
257257
chunkDataPacks := store.NewChunkDataPacks(metrics.NewNoopCollector(),
258-
pebbleimpl.ToDB(chunkDataPackDB), storedChunkDataPacks, storages.Collections, 1000)
258+
db, storedChunkDataPacks, storages.Collections, 1000)
259259

260260
verifier := makeVerifier(log.Logger, chainID, storages.Headers, transactionFeesDisabled, scheduledCallbacksEnabled)
261261
closer := func() error {

fvm/evm/emulator/config.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ type Config struct {
5454
func (c *Config) ChainRules() gethParams.Rules {
5555
return c.ChainConfig.Rules(
5656
c.BlockContext.BlockNumber,
57-
c.BlockContext.Random != nil,
58-
c.BlockContext.Time)
57+
true, // we are already on Merge
58+
c.BlockContext.Time,
59+
)
5960
}
6061

6162
// PreviewNetChainConfig is the chain config used by the previewnet

fvm/evm/evm_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3118,6 +3118,96 @@ func TestCadenceArch(t *testing.T) {
31183118
})
31193119
}
31203120

3121+
func TestNativePrecompiles(t *testing.T) {
3122+
t.Parallel()
3123+
3124+
chain := flow.Emulator.Chain()
3125+
3126+
t.Run("testing out of gas precompile call", func(t *testing.T) {
3127+
t.Parallel()
3128+
3129+
RunWithNewEnvironment(t,
3130+
chain, func(
3131+
ctx fvm.Context,
3132+
vm fvm.VM,
3133+
snapshot snapshot.SnapshotTree,
3134+
testContract *TestContract,
3135+
testAccount *EOATestAccount,
3136+
) {
3137+
sc := systemcontracts.SystemContractsForChain(chain.ChainID())
3138+
code := []byte(fmt.Sprintf(
3139+
`
3140+
import EVM from %s
3141+
3142+
transaction(tx: [UInt8], coinbaseBytes: [UInt8; 20]){
3143+
prepare(account: &Account) {
3144+
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
3145+
let res = EVM.run(tx: tx, coinbase: coinbase)
3146+
3147+
assert(res.status == EVM.Status.failed, message: "unexpected status")
3148+
assert(res.errorCode == 301, message: "unexpected error code: \(res.errorCode)")
3149+
assert(res.errorMessage == "out of gas", message: "unexpected error message: \(res.errorMessage)")
3150+
}
3151+
}
3152+
`,
3153+
sc.EVMContract.Address.HexWithPrefix(),
3154+
))
3155+
3156+
coinbaseAddr := types.Address{1, 2, 3}
3157+
coinbaseBalance := getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr)
3158+
require.Zero(t, types.BalanceToBigInt(coinbaseBalance).Uint64())
3159+
3160+
// The address below is the latest precompile on the Prague hard-fork:
3161+
// https://github.com/ethereum/go-ethereum/blob/v1.16.3/core/vm/contracts.go#L140 .
3162+
to := common.HexToAddress("0x00000000000000000000000000000000000000011")
3163+
innerTxBytes := testAccount.PrepareSignAndEncodeTx(t,
3164+
to,
3165+
nil,
3166+
big.NewInt(1_000_000),
3167+
uint64(21_000),
3168+
big.NewInt(1),
3169+
)
3170+
3171+
innerTx := cadence.NewArray(
3172+
unittest.BytesToCdcUInt8(innerTxBytes),
3173+
).WithType(stdlib.EVMTransactionBytesCadenceType)
3174+
3175+
coinbase := cadence.NewArray(
3176+
unittest.BytesToCdcUInt8(coinbaseAddr.Bytes()),
3177+
).WithType(stdlib.EVMAddressBytesCadenceType)
3178+
3179+
txBody, err := flow.NewTransactionBodyBuilder().
3180+
SetScript(code).
3181+
SetPayer(sc.FlowServiceAccount.Address).
3182+
AddAuthorizer(sc.FlowServiceAccount.Address).
3183+
AddArgument(json.MustEncode(innerTx)).
3184+
AddArgument(json.MustEncode(coinbase)).
3185+
Build()
3186+
require.NoError(t, err)
3187+
3188+
tx := fvm.Transaction(txBody, 0)
3189+
3190+
state, output, err := vm.Run(
3191+
ctx,
3192+
tx,
3193+
snapshot,
3194+
)
3195+
require.NoError(t, err)
3196+
require.NoError(t, output.Err)
3197+
require.NotEmpty(t, state.WriteSet)
3198+
3199+
// assert event fields are correct
3200+
require.Len(t, output.Events, 2)
3201+
txEvent := output.Events[0]
3202+
txEventPayload := TxEventToPayload(t, txEvent, sc.EVMContract.Address)
3203+
require.Equal(t, uint16(types.ExecutionErrCodeOutOfGas), txEventPayload.ErrorCode)
3204+
require.Equal(t, uint16(0), txEventPayload.Index)
3205+
require.Equal(t, uint64(21000), txEventPayload.GasConsumed)
3206+
require.NoError(t, err)
3207+
})
3208+
})
3209+
}
3210+
31213211
func TestEVMFileSystemContract(t *testing.T) {
31223212
chain := flow.Emulator.Chain()
31233213
sc := systemcontracts.SystemContractsForChain(chain.ChainID())

storage/README.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Flow Storage
2+
3+
The storage package subtree (`./storage/**`) implements persistent data storage for Flow.
4+
5+
## Overview
6+
The storage layer is divided into layers:
7+
8+
![Storage Layer Diagram](../docs/images/Storage_Layer_Overview.png)
9+
10+
[Diagram source](https://drive.google.com/file/d/1nF5k4RT78vRB8n5C5Nwalc2PdX-k2uKP/view?usp=sharing)
11+
12+
### 1. Common Database Interface
13+
`storage.DB` defines an interface for direct interaction with a database backend.
14+
This layer operates on keys and values as `[]byte` and is unaware of what resource types are being stored.
15+
`pebbleimpl` and `badgerimpl` packages implement this interface for [Pebble](https://github.com/cockroachdb/pebble) and [Badger](https://github.com/hypermodeinc/badger) respectively.
16+
17+
Flow used Badger as the primary database backend until Mainnet26.
18+
Flow began using Pebble as the primary database backend with Mainnet27, starting Oct 2025.
19+
20+
### 2. Resource Operations
21+
The `operation` package implements basic low-level database operations.
22+
Most exported function in `operation` is typically one read or write operation for a specific resource type.
23+
Low-level storage operations which are always performed together can be combined into one exported "procedure" function.
24+
In this case, the low-level operations used in the procedure should be private.
25+
26+
### 3. Resource Stores
27+
The `store` package implements resource-level database operations.
28+
Typically one resource type (eg. a `Block` or a `Collection`) has one corresponding resource store.
29+
Caching, if applicable, is implemented at this layer.
30+
31+
## Best Practices
32+
33+
### Prefer content hash keys
34+
We consider two types of keys:
35+
1. Collision-resistant content hash of value (eg. `block.ID() -> block`)
36+
2. Index keys (eg. `finalizedHeight -> block.ID()`)
37+
38+
It is generally safe to upsert Type 1 keys without synchronization, because updates will not change existing values.
39+
For this reason, **prefer Type 1 keys wherever possible**.
40+
41+
All Type 2 keys must be explicitly synchronized to protect against concurrent updates.
42+
43+
### Use functors to front-load expensive operations
44+
If an operation function does not require any lock, it should immediately perform the storage operation and return an error.
45+
46+
If an operation function does require any lock, it should return a functor to allow deferring lock acquisition.
47+
Expensive independent operations such as encoding and hashing should be performed immediately, outside the functor.
48+
49+
#### Example 1: Operation without lock
50+
```go
51+
func UpsertCollection(w storage.Writer, col *flow.LightCollection) error {
52+
return UpsertByKey(w, MakePrefix(codeCollection, col.ID()), col)
53+
}
54+
```
55+
56+
#### Example 2: Operation with lock
57+
```go
58+
func UpsertCollection(col *flow.LightCollection) func(lctx lockctx.Proof, rw storage.ReaderBatchWriter) error {
59+
id := col.ID() // compute the ID (hash) immediately before acquiring the lock
60+
key := MakePrefix(codeCollection, id)
61+
deferredUpsert := Upserting(key, col) // the Upserting function is a helper to perform encoding before acquiring the lock
62+
63+
return func(lctx lockctx.Proof, rw storage.ReaderBatchWriter) error {
64+
// check lock context
65+
return deferredUpsert(rw)
66+
}
67+
}
68+
```
69+
70+
71+
## Isolation
72+
The common database interface (Layer 1) provides read-committed isolation and serializable atomic writes.
73+
74+
Write operations are grouped into write batches, which are committed atomically.
75+
76+
The `ReaderBatchWriter` is commonly used and provides both read and write methods.
77+
CAUTION: Unlike Badger transactions, reads here observe the **latest committed state**.
78+
- Reads DO NOT observe writes in the write batch
79+
- Reads DO observe writes committed concurrently by other threads
80+
- Subsequent reads of the same key DO NOT always observe the same value
81+
82+
### Badger Transaction (for reference only - no longer supported)
83+
Badger (**no longer supported**) transactions read their own writes and read a consistent prior snapshot for the duration of the transaction.
84+
![Badger transaction](./../docs/images/Badger_SSI_Transaction.png)
85+
86+
### Pebble Write Batch
87+
Pebble reads the latest committed state, which may change between subsequent reads.
88+
![Pebble write batch](./../docs/images/Pebble_Read_Committed_WriteBatch.png)
89+
90+
91+
## Synchronization with Lock Context Manager
92+
The storage package exposes a `LockManager`, which must be a process-wide singleton.
93+
All synchronized functions in the storage package should register their locks in [`storage/locks.go`](locks.go).
94+
- High-level functions should acquire locks using a `lockctx.Context`
95+
- Low-level functions should validate locks by accepting a `lockctx.Proof`
96+
97+
The `LockManager` enforces an ordering policy and guarantees deadlock-free operation.
98+
99+
For additional information, see [the package documentation](https://github.com/jordanschalm/lockctx).
100+
101+
#### Example: Contexts & Proofs
102+
In this example, the high-level `storage.Blocks` uses ` lockctx.Context` to acquire necessary locks.
103+
```go
104+
func (blocks *Blocks) Insert(block *Block) {
105+
lctx := blocks.LockManager.NewContext()
106+
defer lctx.Release()
107+
108+
lctx.AcquireLock(storage.LockInsertHeader)
109+
lctx.AcquireLock(storage.LockInsertPayload)
110+
111+
blocks.db.WithReaderBatchWriter(func(batch) {
112+
operation.InsertHeader(lctx, block.Header)
113+
operation.InsertPayload(lctx, block.Payload)
114+
}
115+
}
116+
```
117+
Then a `lockctx.Proof` is passed down to lower level functions, which validate the lock was acquired.
118+
```go
119+
func InsertHeader(lctx lockctx.Proof, header *Header) {
120+
if !lctx.HoldsLock(storage.LockInsertHeader) {
121+
// return error
122+
}
123+
// insert header
124+
}
125+
func InsertPayload(lctx lockctx.Proof, payload *Payload) {
126+
if !lctx.HoldsLock(storage.LockInsertPayload) {
127+
// return error
128+
}
129+
// insert payload
130+
}
131+
```
132+

storage/store/protocol_kv_store_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/onflow/flow-go/utils/unittest"
1515
)
1616

17-
// TesKeyValueStoreStorage tests if the KV store is stored, retrieved and indexed correctly
17+
// TestKeyValueStoreStorage tests if the KV store is stored, retrieved and indexed correctly
1818
func TestKeyValueStoreStorage(t *testing.T) {
1919
dbtest.RunWithDB(t, func(t *testing.T, db storage.DB) {
2020
lockManager := storage.NewTestingLockManager()

0 commit comments

Comments
 (0)