Skip to content

Commit

Permalink
add flex block storage
Browse files Browse the repository at this point in the history
  • Loading branch information
ramtinms authored Sep 22, 2023
1 parent 0d46322 commit bc914c5
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 46 deletions.
84 changes: 69 additions & 15 deletions fvm/flex/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package env

import (
"bytes"
"encoding/binary"
"fmt"
"math/big"

"github.com/onflow/flow-go/fvm/flex/models"
"github.com/onflow/flow-go/fvm/flex/storage"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -19,12 +22,13 @@ import (
// Environment is a one-time use flex environment and
// should not be used more than once
type Environment struct {
Config *Config
EVM *vm.EVM
Database *storage.Database
State *state.StateDB
Result *Result
Used bool
Config *Config
EVM *vm.EVM
Database *storage.Database
State *state.StateDB
LastExecutedBlock *models.FlexBlock
Result *Result
Used bool
}

// NewEnvironment constructs a new Flex Enviornment
Expand All @@ -33,12 +37,12 @@ func NewEnvironment(
db *storage.Database,
) (*Environment, error) {

rootHash, err := db.GetRootHash()
lastExcutedBlock, err := db.GetLatestBlock()
if err != nil {
return nil, err
}

execState, err := state.New(rootHash,
execState, err := state.New(lastExcutedBlock.StateRoot,
state.NewDatabase(
rawdb.NewDatabase(db),
),
Expand All @@ -56,10 +60,14 @@ func NewEnvironment(
cfg.ChainConfig,
cfg.EVMConfig,
),
Database: db,
State: execState,
Result: &Result{},
Used: false,
Database: db,
State: execState,
LastExecutedBlock: lastExcutedBlock,
Result: &Result{
UUIDIndex: lastExcutedBlock.UUIDIndex,
TotalSupplyOfNativeToken: lastExcutedBlock.TotalSupply,
},
Used: false,
}, nil
}

Expand Down Expand Up @@ -98,7 +106,14 @@ func (fe *Environment) commit() error {
return err
}

err = fe.Database.SetRootHash(newRoot)
newBlock := models.NewFlexBlock(fe.LastExecutedBlock.Height,
fe.Result.UUIDIndex,
fe.Result.TotalSupplyOfNativeToken,
newRoot,
types.EmptyRootHash,
)

err = fe.Database.SetLatestBlock(newBlock)
if err != nil {
return err
}
Expand All @@ -108,6 +123,35 @@ func (fe *Environment) commit() error {
return nil
}

// TODO: properly use an address generator (zeros + random section) and verify collision
// TODO: does this leads to trie depth issue?
func (fe *Environment) AllocateAddressAndMintTo(balance *big.Int) (*models.FlexAddress, error) {
if err := fe.checkExecuteOnce(); err != nil {
return nil, err
}

target := fe.allocateAddress()
fe.mintTo(balance, target.ToCommon())

// TODO: emit an event

return target, fe.commit()
}

func (fe *Environment) allocateAddress() *models.FlexAddress {
target := models.FlexAddress{}
// first 12 bytes would be zero
// the next 8 bytes would be incremented of uuid
binary.BigEndian.PutUint64(target[12:], fe.LastExecutedBlock.UUIDIndex)
fe.Result.UUIDIndex++

// TODO: if account exist try some new number
// if fe.State.Exist(target.ToCommon()) {
// }

return &target
}

// MintTo mints tokens into the target address, if the address dees not
// exist it would create it first.
//
Expand All @@ -119,6 +163,12 @@ func (fe *Environment) MintTo(balance *big.Int, target common.Address) error {
return err
}

fe.mintTo(balance, target)

return fe.commit()
}

func (fe *Environment) mintTo(balance *big.Int, target common.Address) {
// update the gas consumed // TODO: revisit
// do it as the very first thing to prevent attacks
fe.Result.GasConsumed = TransferGasUsage
Expand All @@ -130,12 +180,11 @@ func (fe *Environment) MintTo(balance *big.Int, target common.Address) error {

// add balance
fe.State.AddBalance(target, balance)
fe.Result.TotalSupplyOfNativeToken += balance.Uint64()

// we don't need to increment any nonce, given the origin doesn't exist

// TODO: emit an event

return fe.commit()
}

// WithdrawFrom deduct the balance from the given source account.
Expand All @@ -148,6 +197,10 @@ func (fe *Environment) WithdrawFrom(amount *big.Int, source common.Address) erro
return err
}

if amount.Uint64() > fe.Result.TotalSupplyOfNativeToken {
return fmt.Errorf("total supply does not match %d > %d", amount.Uint64(), fe.Result.TotalSupplyOfNativeToken)
}

// update the gas consumed // TODO: revisit
// do it as the very first thing to prevent attacks
fe.Result.GasConsumed = TransferGasUsage
Expand All @@ -167,6 +220,7 @@ func (fe *Environment) WithdrawFrom(amount *big.Int, source common.Address) erro

// add balance
fe.State.SubBalance(source, amount)
fe.Result.TotalSupplyOfNativeToken -= amount.Uint64()

// we increment the nonce for source account cause
// withdraw counts as a transaction (similar to the way calls increment the nonce)
Expand Down
14 changes: 8 additions & 6 deletions fvm/flex/environment/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import (
// TODO we might not need this and reuse the ExecutionResult provided by VM
// extra methods like convert can be provided by the interface layer
type Result struct {
Failed bool // this tracks user level failures, other errors indicates fatal issues
RootHash common.Hash
DeployedContractAddress common.Address
RetValue []byte
GasConsumed uint64
Logs []*types.Log
Failed bool // this tracks user level failures, other errors indicates fatal issues
RootHash common.Hash
DeployedContractAddress common.Address
RetValue []byte
GasConsumed uint64
Logs []*types.Log
UUIDIndex uint64
TotalSupplyOfNativeToken uint64
}

func (r *Result) Events() {
Expand Down
4 changes: 2 additions & 2 deletions fvm/flex/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ func (h FlexContractHandler) NewFlowOwnedAccount() models.FlowOwnedAccount {
// allocate a new address
// TODO check for collission
// from 20 bytes, the first 12 could be zero, the next 8 could be the output of a uuid generator (resourceID?)
// does this leads to trie depth issue?

panic("not implemented yet")
}

func (h FlexContractHandler) LastExecutedBlock() models.FlexBlock {
func (h FlexContractHandler) LastExecutedBlock() *models.FlexBlock {
panic("not implemented yet")
}

Expand Down
100 changes: 100 additions & 0 deletions fvm/flex/models/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package models

import (
"encoding/binary"

gethCommon "github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
)

// FlexBlock represents a Flex block.
// It captures block info such as height and state
type FlexBlock struct {
// Height returns the height of this block
Height uint64

// UUID Index
UUIDIndex uint64

// holds the total amount of the native token deposited in Flex
TotalSupply uint64

// StateRoot returns the EVM root hash of the state after executing this block
StateRoot gethCommon.Hash

// EventRoot returns the EVM root hash of the events emitted during execution of this block
EventRoot gethCommon.Hash
}

const (
// encodedUInt64Size = 8
// encodedHashSize = 32
encodedHeightSize = 8
encodedUUIDIndexSize = 8
encodedTotalSupplySize = 8
encodedStateRootSize = 32
encodedEventRootSize = 32
// ( height + uuid + state root + event root)
encodedBlockSize = encodedHeightSize +
encodedUUIDIndexSize +
encodedTotalSupplySize +
encodedStateRootSize +
encodedEventRootSize
)

func (b *FlexBlock) ToBytes() []byte {
encoded := make([]byte, encodedBlockSize)
var index int
// encode height first
binary.BigEndian.PutUint64(encoded[index:index+encodedHeightSize], b.Height)
index += encodedHeightSize
// encode the uuid index
binary.BigEndian.PutUint64(encoded[index:index+encodedUUIDIndexSize], b.UUIDIndex)
index += encodedUUIDIndexSize
// encode the total supply
binary.BigEndian.PutUint64(encoded[index:index+encodedTotalSupplySize], b.TotalSupply)
index += encodedTotalSupplySize
// encode state root
copy(encoded[index:index+encodedStateRootSize], b.StateRoot[:])
index += encodedStateRootSize
// encode event root
copy(encoded[index:index+encodedEventRootSize], b.StateRoot[:])
return encoded[:]
}

func NewFlexBlock(height, uuidIndex, totalSupply uint64, stateRoot, eventRoot gethCommon.Hash) *FlexBlock {
return &FlexBlock{
Height: height,
UUIDIndex: uuidIndex,
TotalSupply: totalSupply,
StateRoot: stateRoot,
EventRoot: eventRoot,
}
}

func NewFlexBlockFromEncoded(encoded []byte) *FlexBlock {
var index int
height := binary.BigEndian.Uint64(encoded[index : index+encodedHeightSize])
index += encodedHeightSize
uuidIndex := binary.BigEndian.Uint64(encoded[index : index+encodedUUIDIndexSize])
index += encodedUUIDIndexSize
totalSupply := binary.BigEndian.Uint64(encoded[index : index+encodedTotalSupplySize])
index += encodedTotalSupplySize
stateRoot := gethCommon.BytesToHash(encoded[index : index+encodedStateRootSize])
index += encodedStateRootSize
eventRoot := gethCommon.BytesToHash(encoded[index : index+encodedEventRootSize])
return &FlexBlock{
Height: height,
UUIDIndex: uuidIndex,
TotalSupply: totalSupply,
StateRoot: stateRoot,
EventRoot: eventRoot,
}
}

var GenesisFlexBlock = &FlexBlock{
Height: uint64(0),
UUIDIndex: uint64(1),
StateRoot: gethTypes.EmptyRootHash,
EventRoot: gethTypes.EmptyRootHash,
}
13 changes: 1 addition & 12 deletions fvm/flex/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,6 @@ func NewFlexAddressFromString(str string) FlexAddress {
return FlexAddress(gethCommon.BytesToAddress([]byte(str)))
}

// FlexBlock represents an EVM block.
// It captures block info such as height and state
type FlexBlock interface {
// Height returns the height of this EVM block (auto-incremented number)
Height() uint64
// StateRoot returns the EVM root hash of the state after executing this EVM block
StateRoot() gethCommon.Hash
// EventRoot returns the EVM root hash of the events emitted during execution of this EVM block
EventRoot() gethCommon.Hash
}

// Balance represents the balance of a Flex account
// a separate type has been considered here to prevent
// accidental dev mistakes when dealing with the conversion
Expand Down Expand Up @@ -114,7 +103,7 @@ type FlexContractHandler interface {
NewFlowOwnedAccount() FlowOwnedAccount

// LastExecutedBlock returns information about the last executed block
LastExecutedBlock() FlexBlock
LastExecutedBlock() *FlexBlock

// Run runs a transaction in the Flex environment,
// collects the gas fees, and transfers the gas fees to the given coinbase account.
Expand Down
25 changes: 14 additions & 11 deletions fvm/flex/storage/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,27 @@ import (
"sync"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"

"github.com/onflow/atree"

"github.com/onflow/flow-go/fvm/flex/models"
"github.com/onflow/flow-go/model/flow"
)

// TODO: add a vault to hold on to the passed Cadence token vaults under Flex account
// this could make sure even if there is a bug in EVM there won't be more withdraws

// TODO: I put a lot of functionality into this database, but it could later on be by different
// components holding access to the ledger or runtime.Environment

var (
// err not implemented
errNotImplemented = errors.New("not implemented yet")
)

var FlexAddress = flow.BytesToAddress([]byte("Flex"))
var RootHashKey = "RootHash"
var FlexLatextBlockKey = "LatestBlock"

// Database is an ephemeral key-value store. Apart from basic data storage
// functionality it also supports batch writes and iterating over the keyspace in
Expand Down Expand Up @@ -88,20 +91,20 @@ func (db *Database) Delete(key []byte) error {
return err
}

// SetRootHash sets the root hash
// SetLatestBlock sets the latest executed block
// we have this functionality given we only allow on state to exist
func (db *Database) SetRootHash(root common.Hash) error {
return db.led.SetValue(FlexAddress[:], []byte(RootHashKey), root[:])
func (db *Database) SetLatestBlock(block *models.FlexBlock) error {
return db.led.SetValue(FlexAddress[:], []byte(FlexLatextBlockKey), block.ToBytes())
}

// GetRootHash returns the latest root hash
// we have this functionality given we only allow on state to exist
func (db *Database) GetRootHash() (common.Hash, error) {
data, err := db.led.GetValue(FlexAddress[:], []byte(RootHashKey))
// GetLatestBlock returns the latest executed block
// we have this functionality given we only allow on state to exist (no forks, etc.)
func (db *Database) GetLatestBlock() (*models.FlexBlock, error) {
data, err := db.led.GetValue(FlexAddress[:], []byte(FlexLatextBlockKey))
if len(data) == 0 {
return types.EmptyRootHash, err
return models.GenesisFlexBlock, err
}
return common.Hash(data), err
return models.NewFlexBlockFromEncoded(data), err
}

// Close deallocates the internal map and ensures any consecutive data access op
Expand Down

0 comments on commit bc914c5

Please sign in to comment.