github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/core/state_processor.go (about)

     1  package core
     2  
     3  import (
     4  	"bytes"
     5  	"math/big"
     6  
     7  	"github.com/quickchainproject/quickchain/common"
     8  	"github.com/quickchainproject/quickchain/consensus"
     9  	"github.com/quickchainproject/quickchain/consensus/dpos"
    10  	"github.com/quickchainproject/quickchain/consensus/misc"
    11  	"github.com/quickchainproject/quickchain/core/state"
    12  	"github.com/quickchainproject/quickchain/core/types"
    13  	"github.com/quickchainproject/quickchain/core/vm"
    14  	"github.com/quickchainproject/quickchain/crypto"
    15  	"github.com/quickchainproject/quickchain/log"
    16  	"github.com/quickchainproject/quickchain/params"
    17  )
    18  
    19  // StateProcessor is a basic Processor, which takes care of transitioning
    20  // state from one point to another.
    21  //
    22  // StateProcessor implements Processor.
    23  type StateProcessor struct {
    24  	config *params.ChainConfig // Chain configuration options
    25  	bc     *BlockChain         // Canonical block chain
    26  	engine consensus.Engine    // Consensus engine used for block rewards
    27  }
    28  
    29  // NewStateProcessor initialises a new StateProcessor.
    30  func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor {
    31  	return &StateProcessor{
    32  		config: config,
    33  		bc:     bc,
    34  		engine: engine,
    35  	}
    36  }
    37  
    38  // Process processes the state changes according to the Ethereum rules by running
    39  // the transaction messages using the statedb and applying any rewards to both
    40  // the processor (coinbase) and any included uncles.
    41  //
    42  // Process returns the receipts and logs accumulated during the process and
    43  // returns the amount of gas that was used in the process. If any of the
    44  // transactions failed to execute due to insufficient gas it will return an error.
    45  func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
    46  	var (
    47  		receipts types.Receipts
    48  		usedGas  = new(uint64)
    49  		header   = block.Header()
    50  		allLogs  []*types.Log
    51  		gp       = new(GasPool).AddGas(block.GasLimit())
    52  	)
    53  	// Mutate the the block and state according to any hard-fork specs
    54  	if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
    55  		misc.ApplyDAOHardFork(statedb)
    56  	}
    57  	// Iterate over and process the individual transactions
    58  	for i, tx := range block.Transactions() {
    59  		statedb.Prepare(tx.Hash(), block.Hash(), i)
    60  		receipt, _, err := ApplyTransaction(p.config, block.DposCtx(), p.bc, nil, gp, statedb, header, tx, usedGas, cfg)
    61  		if err != nil {
    62  			return nil, nil, 0, err
    63  		}
    64  		receipts = append(receipts, receipt)
    65  		allLogs = append(allLogs, receipt.Logs...)
    66  	}
    67  	// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
    68  	p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), receipts, block.DposCtx())
    69  
    70  	return receipts, allLogs, *usedGas, nil
    71  }
    72  
    73  // ApplyTransaction attempts to apply a transaction to the given state database
    74  // and uses the input parameters for its environment. It returns the receiptValidateDposState
    75  // for the transaction, gas used and an error if the transaction failed,ValidateDposState
    76  // indicating the block was invalid.ValidateDposState
    77  func ApplyTransaction(config *params.ChainConfig, dposContext *types.DposContext, bc *BlockChain, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error) {
    78  	msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
    79  	if err != nil {
    80  		return nil, 0, err
    81  	}
    82  
    83  	if msg.To() == nil && msg.Type() != types.Binary {
    84  		return nil, 0, types.ErrInvalidType
    85  	}
    86  
    87  	// Create a new context to be used in the EVM environment
    88  	context := NewEVMContext(msg, header, bc, author)
    89  	// Create a new environment which holds all relevant information
    90  	// about the transaction and calling mechanisms.
    91  	vmenv := vm.NewEVM(context, statedb, config, cfg)
    92  	// Apply the transaction to the current state (included in the env)
    93  	_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
    94  	if err != nil {
    95  		log.Error("ApplyTransaction:", "err", err)
    96  		return nil, 0, err
    97  	}
    98  	if msg.Type() != types.Binary && (config.DPOS != nil || config.DBFT != nil) {
    99  		if err = applyDposMessage(dposContext, msg, statedb, header); err != nil {
   100  			log.Error("applyDposMsg:", "err", err)
   101  			return nil, 0, err
   102  		}
   103  	}
   104  
   105  	// Update the state with pending changes
   106  	var root []byte
   107  	if config.IsByzantium(header.Number) {
   108  		statedb.Finalise(true)
   109  	} else {
   110  		root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
   111  	}
   112  	*usedGas += gas
   113  
   114  	// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
   115  	// based on the eip phase, we're passing wether the root touch-delete accounts.
   116  	receipt := types.NewReceipt(root, failed, *usedGas)
   117  	receipt.TxHash = tx.Hash()
   118  	receipt.GasUsed = gas
   119  	// if the transaction created a contract, store the creation address in the receipt.
   120  	if msg.To() == nil {
   121  		receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
   122  	}
   123  	// Set the receipt logs and create a bloom for filtering
   124  	receipt.Logs = statedb.GetLogs(tx.Hash())
   125  	receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
   126  
   127  	return receipt, gas, err
   128  }
   129  
   130  func addDposLog(statedb *state.StateDB, addr common.Address, topics, data string, header *types.Header) {
   131  	hash := crypto.Keccak256Hash([]byte(topics))
   132  	statedb.AddLog(&types.Log{
   133  		Address:     addr,
   134  		Topics:      []common.Hash{hash},
   135  		Data:        []byte(data),
   136  		BlockNumber: header.Number.Uint64(),
   137  	})
   138  }
   139  
   140  func applyDposMessage(dposContext *types.DposContext, msg types.Message, statedb *state.StateDB, header *types.Header) error {
   141  	switch msg.Type() {
   142  	case types.LoginCandidate:
   143  		log.Info("LoginCandidate:")
   144  		if cc, err := dposContext.GetCandidateContext(msg.From()); err != nil {
   145  			log.Error("failed to GetCandidateContext:", "from", msg.From())
   146  			addDposLog(statedb, msg.From(), "LoginCandidate", "failed to GetCandidateContext"+msg.From().String(), header)
   147  
   148  			return nil
   149  		} else {
   150  			log.Info("applyDposMessage:LoginCandidate", "cc", cc)
   151  			// when state is logout, then login, change state to login
   152  			if cc.State == types.LogoutState {
   153  				cc1 := &types.CandidateContext{
   154  					Addr:        msg.From(),
   155  					State:       types.LoginState,
   156  					BlockNumber: header.Number,
   157  					Score:       cc.Score,
   158  				}
   159  				dposContext.SetCandidateContext(*cc1)
   160  				log.Info("applyDposMessage:", "logout->logoin, from", msg.From())
   161  			} else if cc.State == types.LoginState && bytes.Compare(cc.Addr.Bytes(), msg.From().Bytes()) == 0 {
   162  				addDposLog(statedb, msg.From(), "RepeatLoginCandidate", "You are already in login state before, "+msg.From().String(), header)
   163  				log.Info("applyDposMessage:", "RepeatLogin, from", msg.From())
   164  				return nil
   165  			} else {
   166  				// first login
   167  				log.Info("applyDposMessage:", "first LoginCandidate, from", msg.From())
   168  				bal := statedb.GetBalance(msg.From())
   169  				f := new(big.Float).SetInt(bal)
   170  				if f.Cmp(dpos.MinCandidateBalance) < 0 {
   171  					log.Error("insufficient balance to become candidate")
   172  					addDposLog(statedb, msg.From(), "InsufficientBalance", "insufficient balance to become candidate, "+msg.From().String(), header)
   173  					return nil
   174  				}
   175  				minCandidate := new(big.Int)
   176  				dpos.MinCandidateBalance.Int(minCandidate)
   177  				log.Info("LoginCandidate", "minCandidate", minCandidate)
   178  				statedb.SubBalance(msg.From(), minCandidate)
   179  				cc1 := &types.CandidateContext{
   180  					Addr:        msg.From(),
   181  					State:       types.LoginState, //扣币抵押
   182  					BlockNumber: header.Number,    //记录成为候选人时的区块高度
   183  					Score:       big.NewInt(0),
   184  				}
   185  				dposContext.SetCandidateContext(*cc1)
   186  			}
   187  		}
   188  	case types.LogoutCandidate:
   189  		log.Info("LogoutCandidate:")
   190  		if cc, err := dposContext.GetCandidateContext(msg.From()); err != nil {
   191  			log.Error("failed to GetCandidateContext:", "from", msg.From())
   192  			addDposLog(statedb, msg.From(), "LogoutCandidate", "failed to GetCandidateContext"+msg.From().String(), header)
   193  			return nil
   194  		} else {
   195  			log.Info("applyDposMessage:LogoutCandidate", "cc", cc)
   196  			//只在币处于login的状态下,才允许调用logout
   197  			if cc.State == types.LoginState && bytes.Compare(cc.Addr.Bytes(), msg.From().Bytes()) == 0 {
   198  				//更新候选人币的对应状态,更新为解锁状态中,记录开始解锁时的区块高度
   199  				cc1 := &types.CandidateContext{
   200  					Addr:        msg.From(),
   201  					State:       types.LogoutState,
   202  					BlockNumber: header.Number,
   203  					Score:       cc.Score,
   204  				}
   205  				dposContext.SetCandidateContext(*cc1)
   206  				log.Info("applyDposMessage:", "login->logout, from", msg.From())
   207  				return nil
   208  			} else if cc.State == types.LogoutState {
   209  				addDposLog(statedb, msg.From(), "RepeatLogoutCandidate", "You are already in logout state before, "+msg.From().String(), header)
   210  				return nil
   211  			} else {
   212  				addDposLog(statedb, msg.From(), "NoLogin", "please first call login, "+msg.From().String(), header)
   213  				return nil
   214  			}
   215  		}
   216  	case types.WithdrawCandidate:
   217  		if cc, err := dposContext.GetCandidateContext(msg.From()); err != nil {
   218  			log.Error("failed to GetCandidateContext:", "from", msg.From())
   219  			addDposLog(statedb, msg.From(), "WithdrawCandidate", "failed to GetCandidateContext"+msg.From().String(), header)
   220  			return nil
   221  		} else {
   222  			//只在币处于logout的状态下,才允许withdraw
   223  			if cc.State == types.LogoutState {
   224  				log.Info("compare blocktime:")
   225  				//解锁时间已到
   226  				if new(big.Int).Add(cc.BlockNumber, dpos.UnlockInterval).Cmp(header.Number) < 0 {
   227  					if err := dposContext.KickoutCandidate(msg.From()); err != nil {
   228  						addDposLog(statedb, msg.From(), "KickoutCandidate", "failed to KickoutCandidate "+msg.From().String(), header)
   229  						return nil
   230  					}
   231  					minCandidate := new(big.Int)
   232  					minCandidate, _ = dpos.MinCandidateBalance.Int(minCandidate)
   233  					log.Info("add balance:", "from", msg.From(), "minCandidate", minCandidate)
   234  					statedb.AddBalance(msg.From(), minCandidate) //退出成为候选人时,退回抵押的币
   235  				} else {
   236  					addDposLog(statedb, msg.From(), "Locking", "please wait, unlock time isn't arrive, "+msg.From().String(), header)
   237  					return nil
   238  				}
   239  			} else {
   240  				addDposLog(statedb, msg.From(), "NoLogout", "please first call logout, "+msg.From().String(), header)
   241  				return nil
   242  			}
   243  		}
   244  	case types.Delegate:
   245  		dposContext.Delegate(msg.From(), *(msg.To()), header.Number, statedb.GetBalance(msg.From()))
   246  	case types.UnDelegate:
   247  		dposContext.UnDelegate(msg.From(), *(msg.To()))
   248  	default:
   249  		return types.ErrInvalidType
   250  	}
   251  	return nil
   252  }