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 }