github.com/iotexproject/iotex-core@v1.14.1-rc1/state/factory/statedb.go (about)

     1  // Copyright (c) 2020 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package factory
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"strconv"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/pkg/errors"
    16  	"go.uber.org/zap"
    17  
    18  	"github.com/iotexproject/go-pkgs/cache"
    19  	"github.com/iotexproject/go-pkgs/hash"
    20  	"github.com/iotexproject/iotex-address/address"
    21  
    22  	"github.com/iotexproject/iotex-core/action"
    23  	"github.com/iotexproject/iotex-core/action/protocol"
    24  	"github.com/iotexproject/iotex-core/action/protocol/execution/evm"
    25  	"github.com/iotexproject/iotex-core/action/protocol/staking"
    26  	"github.com/iotexproject/iotex-core/actpool"
    27  	"github.com/iotexproject/iotex-core/blockchain/block"
    28  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    29  	"github.com/iotexproject/iotex-core/db"
    30  	"github.com/iotexproject/iotex-core/db/batch"
    31  	"github.com/iotexproject/iotex-core/pkg/log"
    32  	"github.com/iotexproject/iotex-core/pkg/prometheustimer"
    33  	"github.com/iotexproject/iotex-core/pkg/tracer"
    34  	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
    35  	"github.com/iotexproject/iotex-core/state"
    36  )
    37  
    38  // stateDB implements StateFactory interface, tracks changes to account/contract and batch-commits to DB
    39  type stateDB struct {
    40  	mutex                    sync.RWMutex
    41  	currentChainHeight       uint64
    42  	cfg                      Config
    43  	registry                 *protocol.Registry
    44  	dao                      db.KVStore // the underlying DB for account/contract storage
    45  	timerFactory             *prometheustimer.TimerFactory
    46  	workingsets              cache.LRUCache // lru cache for workingsets
    47  	protocolView             protocol.View
    48  	skipBlockValidationOnPut bool
    49  	ps                       *patchStore
    50  }
    51  
    52  // StateDBOption sets stateDB construction parameter
    53  type StateDBOption func(*stateDB, *Config) error
    54  
    55  // DefaultPatchOption loads patchs
    56  func DefaultPatchOption() StateDBOption {
    57  	return func(sdb *stateDB, cfg *Config) (err error) {
    58  		sdb.ps, err = newPatchStore(cfg.Chain.TrieDBPatchFile)
    59  		return
    60  	}
    61  }
    62  
    63  // RegistryStateDBOption sets the registry in state db
    64  func RegistryStateDBOption(reg *protocol.Registry) StateDBOption {
    65  	return func(sdb *stateDB, cfg *Config) error {
    66  		sdb.registry = reg
    67  		return nil
    68  	}
    69  }
    70  
    71  // SkipBlockValidationStateDBOption skips block validation on PutBlock
    72  func SkipBlockValidationStateDBOption() StateDBOption {
    73  	return func(sdb *stateDB, cfg *Config) error {
    74  		sdb.skipBlockValidationOnPut = true
    75  		return nil
    76  	}
    77  }
    78  
    79  // DisableWorkingSetCacheOption disable workingset cache
    80  func DisableWorkingSetCacheOption() StateDBOption {
    81  	return func(sdb *stateDB, cfg *Config) error {
    82  		sdb.workingsets = cache.NewDummyLruCache()
    83  		return nil
    84  	}
    85  }
    86  
    87  // NewStateDB creates a new state db
    88  func NewStateDB(cfg Config, dao db.KVStore, opts ...StateDBOption) (Factory, error) {
    89  	sdb := stateDB{
    90  		cfg:                cfg,
    91  		currentChainHeight: 0,
    92  		registry:           protocol.NewRegistry(),
    93  		protocolView:       protocol.View{},
    94  		workingsets:        cache.NewThreadSafeLruCache(int(cfg.Chain.WorkingSetCacheSize)),
    95  		dao:                dao,
    96  	}
    97  	for _, opt := range opts {
    98  		if err := opt(&sdb, &cfg); err != nil {
    99  			log.S().Errorf("Failed to execute state factory creation option %p: %v", opt, err)
   100  			return nil, err
   101  		}
   102  	}
   103  	timerFactory, err := prometheustimer.New(
   104  		"iotex_statefactory_perf",
   105  		"Performance of state factory module",
   106  		[]string{"topic", "chainID"},
   107  		[]string{"default", strconv.FormatUint(uint64(cfg.Chain.ID), 10)},
   108  	)
   109  	if err != nil {
   110  		log.L().Error("Failed to generate prometheus timer factory.", zap.Error(err))
   111  	}
   112  	sdb.timerFactory = timerFactory
   113  	return &sdb, nil
   114  }
   115  
   116  func (sdb *stateDB) Start(ctx context.Context) error {
   117  	ctx = protocol.WithRegistry(ctx, sdb.registry)
   118  	if err := sdb.dao.Start(ctx); err != nil {
   119  		return err
   120  	}
   121  	// check factory height
   122  	h, err := sdb.dao.Get(AccountKVNamespace, []byte(CurrentHeightKey))
   123  	switch errors.Cause(err) {
   124  	case nil:
   125  		sdb.currentChainHeight = byteutil.BytesToUint64(h)
   126  		// start all protocols
   127  		if sdb.protocolView, err = sdb.registry.StartAll(ctx, sdb); err != nil {
   128  			return err
   129  		}
   130  	case db.ErrNotExist:
   131  		sdb.currentChainHeight = 0
   132  		if err = sdb.dao.Put(AccountKVNamespace, []byte(CurrentHeightKey), byteutil.Uint64ToBytes(0)); err != nil {
   133  			return errors.Wrap(err, "failed to init statedb's height")
   134  		}
   135  		// start all protocols
   136  		if sdb.protocolView, err = sdb.registry.StartAll(ctx, sdb); err != nil {
   137  			return err
   138  		}
   139  		ctx = protocol.WithBlockCtx(
   140  			ctx,
   141  			protocol.BlockCtx{
   142  				BlockHeight:    0,
   143  				BlockTimeStamp: time.Unix(sdb.cfg.Genesis.Timestamp, 0),
   144  				Producer:       sdb.cfg.Chain.ProducerAddress(),
   145  				GasLimit:       sdb.cfg.Genesis.BlockGasLimitByHeight(0),
   146  			})
   147  		ctx = protocol.WithFeatureCtx(ctx)
   148  		// init the state factory
   149  		if err = sdb.createGenesisStates(ctx); err != nil {
   150  			return errors.Wrap(err, "failed to create genesis states")
   151  		}
   152  	default:
   153  		return err
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  func (sdb *stateDB) Stop(ctx context.Context) error {
   160  	sdb.mutex.Lock()
   161  	defer sdb.mutex.Unlock()
   162  	sdb.workingsets.Clear()
   163  	return sdb.dao.Stop(ctx)
   164  }
   165  
   166  // Height returns factory's height
   167  func (sdb *stateDB) Height() (uint64, error) {
   168  	sdb.mutex.RLock()
   169  	defer sdb.mutex.RUnlock()
   170  	height, err := sdb.dao.Get(AccountKVNamespace, []byte(CurrentHeightKey))
   171  	if err != nil {
   172  		return 0, errors.Wrap(err, "failed to get factory's height from underlying DB")
   173  	}
   174  	return byteutil.BytesToUint64(height), nil
   175  }
   176  
   177  func (sdb *stateDB) newWorkingSet(ctx context.Context, height uint64) (*workingSet, error) {
   178  	g := genesis.MustExtractGenesisContext(ctx)
   179  	flusher, err := db.NewKVStoreFlusher(
   180  		sdb.dao,
   181  		batch.NewCachedBatch(),
   182  		sdb.flusherOptions(!g.IsEaster(height))...,
   183  	)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	for _, p := range sdb.ps.Get(height) {
   188  		if p.Type == _Delete {
   189  			flusher.KVStoreWithBuffer().MustDelete(p.Namespace, p.Key)
   190  		} else {
   191  			flusher.KVStoreWithBuffer().MustPut(p.Namespace, p.Key, p.Value)
   192  		}
   193  	}
   194  	store := newStateDBWorkingSetStore(sdb.protocolView, flusher, g.IsNewfoundland(height))
   195  	if err := store.Start(ctx); err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	return newWorkingSet(height, store), nil
   200  }
   201  
   202  func (sdb *stateDB) Register(p protocol.Protocol) error {
   203  	return p.Register(sdb.registry)
   204  }
   205  
   206  func (sdb *stateDB) Validate(ctx context.Context, blk *block.Block) error {
   207  	ctx = protocol.WithRegistry(ctx, sdb.registry)
   208  	key := generateWorkingSetCacheKey(blk.Header, blk.Header.ProducerAddress())
   209  	ws, isExist, err := sdb.getFromWorkingSets(ctx, key)
   210  	if err != nil {
   211  		return err
   212  	}
   213  	if !isExist {
   214  		if err = ws.ValidateBlock(ctx, blk); err != nil {
   215  			return errors.Wrap(err, "failed to validate block with workingset in statedb")
   216  		}
   217  		sdb.workingsets.Add(key, ws)
   218  	}
   219  	receipts, err := ws.Receipts()
   220  	if err != nil {
   221  		return err
   222  	}
   223  	blk.Receipts = receipts
   224  	return nil
   225  }
   226  
   227  // NewBlockBuilder returns block builder which hasn't been signed yet
   228  func (sdb *stateDB) NewBlockBuilder(
   229  	ctx context.Context,
   230  	ap actpool.ActPool,
   231  	sign func(action.Envelope) (*action.SealedEnvelope, error),
   232  ) (*block.Builder, error) {
   233  	ctx = protocol.WithRegistry(ctx, sdb.registry)
   234  	sdb.mutex.RLock()
   235  	currHeight := sdb.currentChainHeight
   236  	sdb.mutex.RUnlock()
   237  	ws, err := sdb.newWorkingSet(ctx, currHeight+1)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  	postSystemActions := make([]*action.SealedEnvelope, 0)
   242  	unsignedSystemActions, err := ws.generateSystemActions(ctx)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  	for _, elp := range unsignedSystemActions {
   247  		se, err := sign(elp)
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  		postSystemActions = append(postSystemActions, se)
   252  	}
   253  	blkBuilder, err := ws.CreateBuilder(ctx, ap, postSystemActions, sdb.cfg.Chain.AllowedBlockGasResidue)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	blkCtx := protocol.MustGetBlockCtx(ctx)
   259  	key := generateWorkingSetCacheKey(blkBuilder.GetCurrentBlockHeader(), blkCtx.Producer.String())
   260  	sdb.workingsets.Add(key, ws)
   261  	return blkBuilder, nil
   262  }
   263  
   264  // SimulateExecution simulates a running of smart contract operation, this is done off the network since it does not
   265  // cause any state change
   266  func (sdb *stateDB) SimulateExecution(
   267  	ctx context.Context,
   268  	caller address.Address,
   269  	ex *action.Execution,
   270  ) ([]byte, *action.Receipt, error) {
   271  	ctx, span := tracer.NewSpan(ctx, "stateDB.SimulateExecution")
   272  	defer span.End()
   273  
   274  	sdb.mutex.RLock()
   275  	currHeight := sdb.currentChainHeight
   276  	sdb.mutex.RUnlock()
   277  	ws, err := sdb.newWorkingSet(ctx, currHeight+1)
   278  	if err != nil {
   279  		return nil, nil, err
   280  	}
   281  
   282  	return evm.SimulateExecution(ctx, ws, caller, ex)
   283  }
   284  
   285  // ReadContractStorage reads contract's storage
   286  func (sdb *stateDB) ReadContractStorage(ctx context.Context, contract address.Address, key []byte) ([]byte, error) {
   287  	sdb.mutex.RLock()
   288  	currHeight := sdb.currentChainHeight
   289  	sdb.mutex.RUnlock()
   290  	ws, err := sdb.newWorkingSet(ctx, currHeight+1)
   291  	if err != nil {
   292  		return nil, errors.Wrap(err, "failed to generate working set from state db")
   293  	}
   294  	return evm.ReadContractStorage(ctx, ws, contract, key)
   295  }
   296  
   297  // PutBlock persists all changes in RunActions() into the DB
   298  func (sdb *stateDB) PutBlock(ctx context.Context, blk *block.Block) error {
   299  	sdb.mutex.Lock()
   300  	timer := sdb.timerFactory.NewTimer("Commit")
   301  	sdb.mutex.Unlock()
   302  	defer timer.End()
   303  	producer := blk.PublicKey().Address()
   304  	if producer == nil {
   305  		return errors.New("failed to get address")
   306  	}
   307  	g := genesis.MustExtractGenesisContext(ctx)
   308  	ctx = protocol.WithBlockCtx(
   309  		protocol.WithRegistry(ctx, sdb.registry),
   310  		protocol.BlockCtx{
   311  			BlockHeight:    blk.Height(),
   312  			BlockTimeStamp: blk.Timestamp(),
   313  			GasLimit:       g.BlockGasLimitByHeight(blk.Height()),
   314  			Producer:       producer,
   315  		},
   316  	)
   317  	ctx = protocol.WithFeatureCtx(ctx)
   318  	key := generateWorkingSetCacheKey(blk.Header, blk.Header.ProducerAddress())
   319  	ws, isExist, err := sdb.getFromWorkingSets(ctx, key)
   320  	if err != nil {
   321  		return err
   322  	}
   323  	if !isExist {
   324  		if !sdb.skipBlockValidationOnPut {
   325  			err = ws.ValidateBlock(ctx, blk)
   326  		} else {
   327  			err = ws.Process(ctx, blk.RunnableActions().Actions())
   328  		}
   329  		if err != nil {
   330  			log.L().Error("Failed to update state.", zap.Error(err))
   331  			return err
   332  		}
   333  	}
   334  	sdb.mutex.Lock()
   335  	defer sdb.mutex.Unlock()
   336  	receipts, err := ws.Receipts()
   337  	if err != nil {
   338  		return err
   339  	}
   340  	blk.Receipts = receipts
   341  	h, _ := ws.Height()
   342  	if sdb.currentChainHeight+1 != h {
   343  		// another working set with correct version already committed, do nothing
   344  		return fmt.Errorf(
   345  			"current state height %d + 1 doesn't match working set height %d",
   346  			sdb.currentChainHeight, h,
   347  		)
   348  	}
   349  
   350  	if err := ws.Commit(ctx); err != nil {
   351  		return err
   352  	}
   353  	sdb.currentChainHeight = h
   354  	return nil
   355  }
   356  
   357  func (sdb *stateDB) DeleteTipBlock(_ context.Context, _ *block.Block) error {
   358  	return errors.Wrap(ErrNotSupported, "cannot delete tip block from state db")
   359  }
   360  
   361  // State returns a confirmed state in the state factory
   362  func (sdb *stateDB) State(s interface{}, opts ...protocol.StateOption) (uint64, error) {
   363  	cfg, err := processOptions(opts...)
   364  	if err != nil {
   365  		return 0, err
   366  	}
   367  	sdb.mutex.RLock()
   368  	defer sdb.mutex.RUnlock()
   369  	if cfg.Keys != nil {
   370  		return 0, errors.Wrap(ErrNotSupported, "Read state with keys option has not been implemented yet")
   371  	}
   372  	return sdb.currentChainHeight, sdb.state(cfg.Namespace, cfg.Key, s)
   373  }
   374  
   375  // State returns a set of states in the state factory
   376  func (sdb *stateDB) States(opts ...protocol.StateOption) (uint64, state.Iterator, error) {
   377  	cfg, err := processOptions(opts...)
   378  	if err != nil {
   379  		return 0, nil, err
   380  	}
   381  	sdb.mutex.RLock()
   382  	defer sdb.mutex.RUnlock()
   383  	if cfg.Key != nil {
   384  		return sdb.currentChainHeight, nil, errors.Wrap(ErrNotSupported, "Read states with key option has not been implemented yet")
   385  	}
   386  	values, err := readStates(sdb.dao, cfg.Namespace, cfg.Keys)
   387  	if err != nil {
   388  		return 0, nil, err
   389  	}
   390  
   391  	return sdb.currentChainHeight, state.NewIterator(values), nil
   392  }
   393  
   394  // StateAtHeight returns a confirmed state at height -- archive mode
   395  func (sdb *stateDB) StateAtHeight(height uint64, s interface{}, opts ...protocol.StateOption) error {
   396  	return ErrNotSupported
   397  }
   398  
   399  // StatesAtHeight returns a set states in the state factory at height -- archive mode
   400  func (sdb *stateDB) StatesAtHeight(height uint64, opts ...protocol.StateOption) (state.Iterator, error) {
   401  	return nil, errors.Wrap(ErrNotSupported, "state db does not support archive mode")
   402  }
   403  
   404  // ReadView reads the view
   405  func (sdb *stateDB) ReadView(name string) (interface{}, error) {
   406  	return sdb.protocolView.Read(name)
   407  }
   408  
   409  //======================================
   410  // private trie constructor functions
   411  //======================================
   412  
   413  func (sdb *stateDB) flusherOptions(preEaster bool) []db.KVStoreFlusherOption {
   414  	opts := []db.KVStoreFlusherOption{
   415  		db.SerializeOption(func(wi *batch.WriteInfo) []byte {
   416  			if preEaster {
   417  				return wi.SerializeWithoutWriteType()
   418  			}
   419  			return wi.Serialize()
   420  		}),
   421  	}
   422  	if !preEaster {
   423  		return opts
   424  	}
   425  	return append(
   426  		opts,
   427  		db.SerializeFilterOption(func(wi *batch.WriteInfo) bool {
   428  			return wi.Namespace() == evm.CodeKVNameSpace || wi.Namespace() == staking.CandsMapNS
   429  		}),
   430  	)
   431  }
   432  
   433  func (sdb *stateDB) state(ns string, addr []byte, s interface{}) error {
   434  	data, err := sdb.dao.Get(ns, addr)
   435  	if err != nil {
   436  		if errors.Cause(err) == db.ErrNotExist {
   437  			return errors.Wrapf(state.ErrStateNotExist, "state of %x doesn't exist", addr)
   438  		}
   439  		return errors.Wrapf(err, "error when getting the state of %x", addr)
   440  	}
   441  	if err := state.Deserialize(s, data); err != nil {
   442  		return errors.Wrapf(err, "error when deserializing state data into %T", s)
   443  	}
   444  	return nil
   445  }
   446  
   447  func (sdb *stateDB) createGenesisStates(ctx context.Context) error {
   448  	ws, err := sdb.newWorkingSet(ctx, 0)
   449  	if err != nil {
   450  		return err
   451  	}
   452  	if err := ws.CreateGenesisStates(ctx); err != nil {
   453  		return err
   454  	}
   455  
   456  	return ws.Commit(ctx)
   457  }
   458  
   459  // getFromWorkingSets returns (workingset, true) if it exists in a cache, otherwise generates new workingset and return (ws, false)
   460  func (sdb *stateDB) getFromWorkingSets(ctx context.Context, key hash.Hash256) (*workingSet, bool, error) {
   461  	if data, ok := sdb.workingsets.Get(key); ok {
   462  		if ws, ok := data.(*workingSet); ok {
   463  			// if it is already validated, return workingset
   464  			return ws, true, nil
   465  		}
   466  		return nil, false, errors.New("type assertion failed to be WorkingSet")
   467  	}
   468  	sdb.mutex.RLock()
   469  	currHeight := sdb.currentChainHeight
   470  	sdb.mutex.RUnlock()
   471  	tx, err := sdb.newWorkingSet(ctx, currHeight+1)
   472  	return tx, false, err
   473  }