Skip to content

Commit

Permalink
add precompiled contracts for sharded storage (ethereum#84)
Browse files Browse the repository at this point in the history
* add precompiled contracts for sharded storage

* threadsafe for trie.Database

* add sstorage StateDB snapshot support

* StateDB/Database Sread/write follows the same API as ShardManager

* error handling

Co-authored-by: Qi Zhou <qzhou64@gmail.com>
  • Loading branch information
qizhou and Qi Zhou authored Jun 8, 2022
1 parent 2328c0d commit 12735a3
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 18 deletions.
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ var (
utils.MinerExtraDataFlag,
utils.MinerRecommitIntervalFlag,
utils.MinerNoVerifyFlag,
utils.SstorageShardFlag,
utils.SstorageFileFlag,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV5Flag,
Expand Down
10 changes: 6 additions & 4 deletions cmd/sstorage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
)

var (
chunkIdxLen *uint64
filenames *[]string
chunkLen *uint64
filenames *[]string

verbosity *int

Expand Down Expand Up @@ -60,7 +60,7 @@ var ShardWriteCmd = &cobra.Command{
}

func init() {
chunkIdxLen = CreateCmd.Flags().Uint64("len", 0, "Chunk idx len to create")
chunkLen = CreateCmd.Flags().Uint64("len", 0, "Chunk idx len to create")

filenames = rootCmd.PersistentFlags().StringArray("filename", []string{}, "Data filename")
verbosity = rootCmd.PersistentFlags().Int("verbosity", 3, "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail")
Expand Down Expand Up @@ -100,7 +100,9 @@ func runCreate(cmd *cobra.Command, args []string) {
log.Crit("must provide single filename")
}

_, err := sstorage.Create((*filenames)[0], *chunkIdx, *chunkIdxLen, sstorage.MASK_KECCAK_256)
log.Info("Creating data file", "chunkIdx", *chunkIdx, "chunkLen", *chunkLen)

_, err := sstorage.Create((*filenames)[0], *chunkIdx, *chunkLen, sstorage.MASK_KECCAK_256)
if err != nil {
log.Crit("create failed", "error", err)
}
Expand Down
39 changes: 39 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/sstorage"
pcsclite "github.com/gballet/go-libpcsclite"
gopsutil "github.com/shirou/gopsutil/mem"
"gopkg.in/urfave/cli.v1"
Expand Down Expand Up @@ -554,6 +555,17 @@ var (
Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)",
Value: ethconfig.Defaults.RPCTxFeeCap,
}
// Sharded Storage settings
SstorageShardFlag = cli.StringSliceFlag{
Name: "sstorage.shard",
Usage: "Add perferred storage shard",
Value: nil,
}
SstorageFileFlag = cli.StringSliceFlag{
Name: "sstorage.file",
Usage: "Add sharded storage data file",
Value: nil,
}
// Logging and debug settings
EthStatsURLFlag = cli.StringFlag{
Name: "ethstats",
Expand Down Expand Up @@ -1106,6 +1118,32 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) {
}
}

func setSstorage(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.GlobalIsSet(SstorageShardFlag.Name) {
cfg.SstorageShards = ctx.GlobalStringSlice(SstorageShardFlag.Name)
}
if ctx.GlobalIsSet(SstorageFileFlag.Name) {
cfg.SstorageFiles = ctx.GlobalStringSlice(SstorageFileFlag.Name)
}

sstorage.InitializeConfig()
for _, s := range cfg.SstorageShards {
if err := sstorage.AddDataShardFromConfig(s); err != nil {
Fatalf("Failed to add data shard: %s, %v", s, err)
}
}

for _, s := range cfg.SstorageFiles {
if err := sstorage.AddDataFileFromConfig(s); err != nil {
Fatalf("Failed to add data file: %s, %v", s, err)
}
}

if err := sstorage.IsComplete(); err != nil {
Fatalf("Shard is not complete: %v", err)
}
}

// MakeDatabaseHandles raises out the number of allowed file handles per process
// for Geth and returns half of the allowance to assign to the database.
func MakeDatabaseHandles() int {
Expand Down Expand Up @@ -1571,6 +1609,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
setMiner(ctx, &cfg.Miner)
setWhitelist(ctx, cfg)
setLes(ctx, cfg)
setSstorage(ctx, cfg)

// Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory()
Expand Down
19 changes: 19 additions & 0 deletions core/state/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ type (
address *common.Address
slot *common.Hash
}
// Sstorage change
sstorageChange struct {
prevBytes []byte // nil means not exist in StateDB (but may exist in underlying DB)
address *common.Address
kvIdx uint64
}
)

func (ch createObjectChange) revert(s *StateDB) {
Expand Down Expand Up @@ -267,3 +273,16 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
func (ch accessListAddSlotChange) dirtied() *common.Address {
return nil
}

func (ch sstorageChange) dirtied() *common.Address {
// the account structure is not touched.
return nil
}

func (ch sstorageChange) revert(s *StateDB) {
if ch.prevBytes == nil {
delete(s.shardedStorage[*ch.address], ch.kvIdx)
} else {
s.shardedStorage[*ch.address][ch.kvIdx] = ch.prevBytes
}
}
64 changes: 64 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package state

import (
"bytes"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -79,6 +80,9 @@ type StateDB struct {
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution

// This map holds sharded KV puts
shardedStorage map[common.Address]map[uint64][]byte

// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
Expand Down Expand Up @@ -143,6 +147,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
journal: newJournal(),
accessList: newAccessList(),
hasher: crypto.NewKeccakState(),
shardedStorage: make(map[common.Address]map[uint64][]byte),
}
if sdb.snaps != nil {
if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
Expand All @@ -154,6 +159,46 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
return sdb, nil
}

func (s *StateDB) SstorageMaxKVSize(addr common.Address) uint64 {
return s.db.TrieDB().SstorageMaxKVSize(addr)
}

func (s *StateDB) SstorageWrite(addr common.Address, kvIdx uint64, data []byte) error {
if len(data) > int(s.SstorageMaxKVSize(addr)) {
return fmt.Errorf("put too large")
}

if _, ok := s.shardedStorage[addr]; !ok {
s.shardedStorage[addr] = make(map[uint64][]byte)
}

s.journal.append(sstorageChange{
address: &addr,
prevBytes: s.shardedStorage[addr][kvIdx],
kvIdx: kvIdx,
})
// Assume data is immutable
s.shardedStorage[addr][kvIdx] = data
return nil
}

func (s *StateDB) SstorageRead(addr common.Address, kvIdx uint64, readLen int) ([]byte, bool, error) {
if readLen > int(s.SstorageMaxKVSize(addr)) {
return nil, false, fmt.Errorf("readLen too large")
}

if m, ok0 := s.shardedStorage[addr]; ok0 {
if b, ok1 := m[kvIdx]; ok1 {
if readLen > len(b) {
return append(b, bytes.Repeat([]byte{0}, readLen-len(b))...), true, nil
}
return b[0:readLen], true, nil
}
}

return s.db.TrieDB().SstorageRead(addr, kvIdx, readLen)
}

// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot.
Expand Down Expand Up @@ -657,6 +702,7 @@ func (s *StateDB) Copy() *StateDB {
preimages: make(map[common.Hash][]byte, len(s.preimages)),
journal: newJournal(),
hasher: crypto.NewKeccakState(),
shardedStorage: make(map[common.Address]map[uint64][]byte),
}
// Copy the dirty states, logs, and preimages
for addr := range s.journal.dirties {
Expand Down Expand Up @@ -738,6 +784,12 @@ func (s *StateDB) Copy() *StateDB {
state.snapStorage[k] = temp
}
}
for addr, m := range s.shardedStorage {
state.shardedStorage[addr] = make(map[uint64][]byte)
for k, v := range m {
state.shardedStorage[addr][k] = v
}
}
return state
}

Expand Down Expand Up @@ -981,6 +1033,18 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
}
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
}
// Write the sharded storage changes to the underlying trie db
// This assumes no fork (with instant finality consensus)
triedb := s.db.TrieDB()
for addr, m := range s.shardedStorage {
for k, v := range m {
err := triedb.SstorageWrite(addr, k, v)
if err != nil {
log.Crit("failed to flush sstorage data", "err", err)
}
}
}
s.shardedStorage = make(map[common.Address]map[uint64][]byte)
return root, err
}

Expand Down
166 changes: 163 additions & 3 deletions core/vm/contracts.go

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(p, in, gas); err != nil {
if res, _, err := RunPrecompiledContract(nil, p, in, gas); err != nil {
t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
Expand All @@ -118,7 +118,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := p.RequiredGas(in) - 1

t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
_, _, err := RunPrecompiledContract(p, in, gas)
_, _, err := RunPrecompiledContract(nil, p, in, gas)
if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err)
}
Expand All @@ -135,7 +135,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) {
_, _, err := RunPrecompiledContract(p, in, gas)
_, _, err := RunPrecompiledContract(nil, p, in, gas)
if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
}
Expand Down Expand Up @@ -167,7 +167,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
copy(data, in)
res, _, err = RunPrecompiledContract(p, data, reqGas)
res, _, err = RunPrecompiledContract(nil, p, data, reqGas)
}
bench.StopTimer()
elapsed := uint64(time.Since(start))
Expand Down
15 changes: 11 additions & 4 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/sstorage"
"github.com/holiman/uint256"
)

Expand All @@ -44,6 +45,8 @@ type (
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsPisa:
precompiles = PrecompiledContractsPisa
case evm.chainRules.IsBerlin:
precompiles = PrecompiledContractsBerlin
case evm.chainRules.IsIstanbul:
Expand Down Expand Up @@ -78,6 +81,10 @@ type BlockContext struct {
Random *common.Hash // Provides information for RANDOM
}

type SstorageContext struct {
ContractToShardManager map[common.Address]*sstorage.ShardManager
}

// TxContext provides the EVM with information about a transaction.
// All fields can change between transactions.
type TxContext struct {
Expand Down Expand Up @@ -212,7 +219,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
}

if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunPrecompiledContract(&PrecompiledContractCallEnv{evm, caller}, p, input, gas)
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
Expand Down Expand Up @@ -279,7 +286,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,

// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunPrecompiledContract(&PrecompiledContractCallEnv{evm, caller}, p, input, gas)
} else {
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM.
Expand Down Expand Up @@ -324,7 +331,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by

// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunPrecompiledContract(&PrecompiledContractCallEnv{evm, caller}, p, input, gas)
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
Expand Down Expand Up @@ -377,7 +384,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
}

if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
ret, gas, err = RunPrecompiledContract(&PrecompiledContractCallEnv{evm, caller}, p, input, gas)
} else {
// At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract'
Expand Down
4 changes: 4 additions & 0 deletions core/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ type StateDB interface {
AddPreimage(common.Hash, []byte)

ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error

SstorageMaxKVSize(common.Address) uint64 // 0 means not exist
SstorageWrite(common.Address, uint64, []byte) error // following the same interface as ShardManager.TryWrite()
SstorageRead(common.Address, uint64, int) ([]byte, bool, error) // following the same interface as ShardManager.TryRead()
}

// CallContext provides a basic interface for the EVM calling conventions. The EVM
Expand Down
1 change: 1 addition & 0 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Config struct {
JumpTable *JumpTable // EVM instruction table, automatically populated if unset

ExtraEips []int // Additional EIPS that are to be enabled
IsJsonRpc bool // Whether the call is in context of JsonRpc
}

// ScopeContext contains the things that are per-call, such as stack and memory,
Expand Down
4 changes: 4 additions & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ type Config struct {
ValContract string
ValChainId uint64
ValidatorChangeEpochId uint64

// Sstorage config
SstorageFiles []string `toml:",omitempty"`
SstorageShards []string `toml:",omitempty"`
}

// CreateConsensusEngine creates a consensus engine for the given chain configuration.
Expand Down
2 changes: 1 addition & 1 deletion internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
if err != nil {
return nil, err
}
evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true})
evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true, IsJsonRpc: true})
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions sstorage/data_shard.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ func (ds *DataShard) ReadUnmasked(kvIdx uint64, readLen int) ([]byte, error) {
if !ds.Contains(kvIdx) {
return nil, fmt.Errorf("kv not found")
}
if readLen > int(ds.kvSize) {
return nil, fmt.Errorf("read len too large")
}
var data []byte
for i := uint64(0); i < ds.chunksPerKv; i++ {
if readLen == 0 {
Expand Down
Loading

0 comments on commit 12735a3

Please sign in to comment.