diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/blockchain.go | 55 | ||||
-rw-r--r-- | core/config.go | 14 | ||||
-rw-r--r-- | core/state/state_object.go | 4 | ||||
-rw-r--r-- | core/state/statedb.go | 11 | ||||
-rw-r--r-- | core/tx_pool.go | 65 | ||||
-rw-r--r-- | core/tx_pool_test.go | 90 | ||||
-rw-r--r-- | core/vm/environment.go | 4 | ||||
-rw-r--r-- | core/vm/gas.go | 34 | ||||
-rw-r--r-- | core/vm/instructions.go | 7 | ||||
-rw-r--r-- | core/vm/runtime/runtime.go | 4 | ||||
-rw-r--r-- | core/vm/util_test.go | 9 | ||||
-rw-r--r-- | core/vm/vm.go | 80 |
12 files changed, 332 insertions, 45 deletions
diff --git a/core/blockchain.go b/core/blockchain.go index 9a8a72d0b..4f11439bc 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -269,7 +269,7 @@ func (self *BlockChain) FastSyncCommitHead(hash common.Hash) error { if block == nil { return fmt.Errorf("non existent block [%x…]", hash[:4]) } - if _, err := trie.NewSecure(block.Root(), self.chainDb); err != nil { + if _, err := trie.NewSecure(block.Root(), self.chainDb, 0); err != nil { return err } // If all checks out, manually set the head block @@ -824,19 +824,16 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { // faster than direct delivery and requires much less mutex // acquiring. var ( - stats struct{ queued, processed, ignored int } + stats = insertStats{startTime: time.Now()} events = make([]interface{}, 0, len(chain)) coalescedLogs vm.Logs - tstart = time.Now() - - nonceChecked = make([]bool, len(chain)) + nonceChecked = make([]bool, len(chain)) ) // Start the parallel nonce verifier. nonceAbort, nonceResults := verifyNoncesFromBlocks(self.pow, chain) defer close(nonceAbort) - txcount := 0 for i, block := range chain { if atomic.LoadInt32(&self.procInterrupt) == 1 { glog.V(logger.Debug).Infoln("Premature abort during block chain processing") @@ -931,7 +928,6 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { return i, err } - txcount += len(block.Transactions()) // write the block to the chain and get the status status, err := self.WriteBlock(block) if err != nil { @@ -966,19 +962,54 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { case SplitStatTy: events = append(events, ChainSplitEvent{block, logs}) } + stats.processed++ + if glog.V(logger.Info) { + stats.report(chain, i) + } } - if (stats.queued > 0 || stats.processed > 0 || stats.ignored > 0) && bool(glog.V(logger.Info)) { - tend := time.Since(tstart) - start, end := chain[0], chain[len(chain)-1] - glog.Infof("imported %d block(s) (%d queued %d ignored) including %d txs in %v. #%v [%x / %x]\n", stats.processed, stats.queued, stats.ignored, txcount, tend, end.Number(), start.Hash().Bytes()[:4], end.Hash().Bytes()[:4]) - } go self.postChainEvents(events, coalescedLogs) return 0, nil } +// insertStats tracks and reports on block insertion. +type insertStats struct { + queued, processed, ignored int + lastIndex int + startTime time.Time +} + +const ( + statsReportLimit = 1024 + statsReportTimeLimit = 8 * time.Second +) + +// report prints statistics if some number of blocks have been processed +// or more than a few seconds have passed since the last message. +func (st *insertStats) report(chain []*types.Block, index int) { + limit := statsReportLimit + if index == len(chain)-1 { + limit = 0 // Always print a message for the last block. + } + now := time.Now() + duration := now.Sub(st.startTime) + if duration > statsReportTimeLimit || st.queued > limit || st.processed > limit || st.ignored > limit { + start, end := chain[st.lastIndex], chain[index] + txcount := countTransactions(chain[st.lastIndex : index+1]) + glog.Infof("imported %d block(s) (%d queued %d ignored) including %d txs in %v. #%v [%x / %x]\n", st.processed, st.queued, st.ignored, txcount, duration, end.Number(), start.Hash().Bytes()[:4], end.Hash().Bytes()[:4]) + *st = insertStats{startTime: now, lastIndex: index} + } +} + +func countTransactions(chain []*types.Block) (c int) { + for _, b := range chain { + c += len(b.Transactions()) + } + return c +} + // reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them // to be part of the new canonical chain and accumulates potential missing transactions and post an // event about them diff --git a/core/config.go b/core/config.go index c0d065a57..3ab04e520 100644 --- a/core/config.go +++ b/core/config.go @@ -21,6 +21,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" ) var ChainConfigNotFoundErr = errors.New("ChainConfig not found") // general config not found error @@ -35,6 +36,8 @@ type ChainConfig struct { DAOForkBlock *big.Int `json:"daoForkBlock"` // TheDAO hard-fork switch block (nil = no fork) DAOForkSupport bool `json:"daoForkSupport"` // Whether the nodes supports or opposes the DAO hard-fork + HomesteadGasRepriceBlock *big.Int `json:"homesteadGasRepriceBlock"` // Homestead gas reprice switch block (nil = no fork) + VmConfig vm.Config `json:"-"` } @@ -45,3 +48,14 @@ func (c *ChainConfig) IsHomestead(num *big.Int) bool { } return num.Cmp(c.HomesteadBlock) >= 0 } + +// GasTable returns the gas table corresponding to the current phase (homestead or homestead reprice). +// +// The returned GasTable's fields shouldn't, under any circumstances, be changed. +func (c *ChainConfig) GasTable(num *big.Int) params.GasTable { + if c.HomesteadGasRepriceBlock == nil || num == nil || num.Cmp(c.HomesteadGasRepriceBlock) < 0 { + return params.GasTableHomestead + } + + return params.GasTableHomesteadGasRepriceFork +} diff --git a/core/state/state_object.go b/core/state/state_object.go index 6eab27d9e..edb073173 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -137,9 +137,9 @@ func (self *StateObject) markSuicided() { func (c *StateObject) getTrie(db trie.Database) *trie.SecureTrie { if c.trie == nil { var err error - c.trie, err = trie.NewSecure(c.data.Root, db) + c.trie, err = trie.NewSecure(c.data.Root, db, 0) if err != nil { - c.trie, _ = trie.NewSecure(common.Hash{}, db) + c.trie, _ = trie.NewSecure(common.Hash{}, db, 0) c.setError(fmt.Errorf("can't create storage trie: %v", err)) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index ec9e9392f..dcb897628 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -41,7 +41,10 @@ var StartingNonce uint64 const ( // Number of past tries to keep. The arbitrarily chosen value here // is max uncle depth + 1. - maxTrieCacheLength = 8 + maxPastTries = 8 + + // Trie cache generation limit. + maxTrieCacheGen = 100 // Number of codehash->size associations to keep. codeSizeCacheSize = 100000 @@ -86,7 +89,7 @@ type StateDB struct { // Create a new state from a given trie func New(root common.Hash, db ethdb.Database) (*StateDB, error) { - tr, err := trie.NewSecure(root, db) + tr, err := trie.NewSecure(root, db, maxTrieCacheGen) if err != nil { return nil, err } @@ -155,14 +158,14 @@ func (self *StateDB) openTrie(root common.Hash) (*trie.SecureTrie, error) { return &tr, nil } } - return trie.NewSecure(root, self.db) + return trie.NewSecure(root, self.db, maxTrieCacheGen) } func (self *StateDB) pushTrie(t *trie.SecureTrie) { self.lock.Lock() defer self.lock.Unlock() - if len(self.pastTries) >= maxTrieCacheLength { + if len(self.pastTries) >= maxPastTries { copy(self.pastTries, self.pastTries[1:]) self.pastTries[len(self.pastTries)-1] = t } else { diff --git a/core/tx_pool.go b/core/tx_pool.go index 4c9410d8e..2c8a5c396 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "gopkg.in/karalabe/cookiejar.v2/collections/prque" ) var ( @@ -46,10 +47,12 @@ var ( ) var ( - maxQueuedPerAccount = uint64(64) // Max limit of queued transactions per address - maxQueuedInTotal = uint64(8192) // Max limit of queued transactions from all accounts - maxQueuedLifetime = 3 * time.Hour // Max amount of time transactions from idle accounts are queued - evictionInterval = time.Minute // Time interval to check for evictable transactions + minPendingPerAccount = uint64(16) // Min number of guaranteed transaction slots per address + maxPendingTotal = uint64(4096) // Max limit of pending transactions from all accounts (soft) + maxQueuedPerAccount = uint64(64) // Max limit of queued transactions per address + maxQueuedInTotal = uint64(1024) // Max limit of queued transactions from all accounts + maxQueuedLifetime = 3 * time.Hour // Max amount of time transactions from idle accounts are queued + evictionInterval = time.Minute // Time interval to check for evictable transactions ) type stateFn func() (*state.StateDB, error) @@ -481,7 +484,6 @@ func (pool *TxPool) promoteExecutables() { } // Iterate over all accounts and promote any executable transactions queued := uint64(0) - for addr, list := range pool.queue { // Drop all transactions that are deemed too old (low nonce) for _, tx := range list.Forward(state.GetNonce(addr)) { @@ -519,6 +521,59 @@ func (pool *TxPool) promoteExecutables() { delete(pool.queue, addr) } } + // If the pending limit is overflown, start equalizing allowances + pending := uint64(0) + for _, list := range pool.pending { + pending += uint64(list.Len()) + } + if pending > maxPendingTotal { + // Assemble a spam order to penalize large transactors first + spammers := prque.New() + for addr, list := range pool.pending { + // Only evict transactions from high rollers + if uint64(list.Len()) > minPendingPerAccount { + // Skip local accounts as pools should maintain backlogs for themselves + for _, tx := range list.txs.items { + if !pool.localTx.contains(tx.Hash()) { + spammers.Push(addr, float32(list.Len())) + } + break // Checking on transaction for locality is enough + } + } + } + // Gradually drop transactions from offenders + offenders := []common.Address{} + for pending > maxPendingTotal && !spammers.Empty() { + // Retrieve the next offender if not local address + offender, _ := spammers.Pop() + offenders = append(offenders, offender.(common.Address)) + + // Equalize balances until all the same or below threshold + if len(offenders) > 1 { + // Calculate the equalization threshold for all current offenders + threshold := pool.pending[offender.(common.Address)].Len() + + // Iteratively reduce all offenders until below limit or threshold reached + for pending > maxPendingTotal && pool.pending[offenders[len(offenders)-2]].Len() > threshold { + for i := 0; i < len(offenders)-1; i++ { + list := pool.pending[offenders[i]] + list.Cap(list.Len() - 1) + pending-- + } + } + } + } + // If still above threshold, reduce to limit or min allowance + if pending > maxPendingTotal && len(offenders) > 0 { + for pending > maxPendingTotal && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > minPendingPerAccount { + for _, addr := range offenders { + list := pool.pending[addr] + list.Cap(list.Len() - 1) + pending-- + } + } + } + } // If we've queued more transactions than the hard limit, drop oldest ones if queued > maxQueuedInTotal { // Sort all accounts with queued transactions by heartbeat diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 4bc5aed38..dbe6fa635 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -618,6 +618,96 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) { } } +// Tests that if the transaction count belonging to multiple accounts go above +// some hard threshold, the higher transactions are dropped to prevent DOS +// attacks. +func TestTransactionPendingGlobalLimiting(t *testing.T) { + // Reduce the queue limits to shorten test time + defer func(old uint64) { maxPendingTotal = old }(maxPendingTotal) + maxPendingTotal = minPendingPerAccount * 10 + + // Create the pool to test the limit enforcement with + db, _ := ethdb.NewMemDatabase() + statedb, _ := state.New(common.Hash{}, db) + + pool := NewTxPool(testChainConfig(), new(event.TypeMux), func() (*state.StateDB, error) { return statedb, nil }, func() *big.Int { return big.NewInt(1000000) }) + pool.resetState() + + // Create a number of test accounts and fund them + state, _ := pool.currentState() + + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + state.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := types.Transactions{} + for _, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + for j := 0; j < int(maxPendingTotal)/len(keys)*2; j++ { + txs = append(txs, transaction(nonces[addr], big.NewInt(100000), key)) + nonces[addr]++ + } + } + // Import the batch and verify that limits have been enforced + pool.AddBatch(txs) + + pending := 0 + for _, list := range pool.pending { + pending += list.Len() + } + if pending > int(maxPendingTotal) { + t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, maxPendingTotal) + } +} + +// Tests that if the transaction count belonging to multiple accounts go above +// some hard threshold, if they are under the minimum guaranteed slot count then +// the transactions are still kept. +func TestTransactionPendingMinimumAllowance(t *testing.T) { + // Reduce the queue limits to shorten test time + defer func(old uint64) { maxPendingTotal = old }(maxPendingTotal) + maxPendingTotal = 0 + + // Create the pool to test the limit enforcement with + db, _ := ethdb.NewMemDatabase() + statedb, _ := state.New(common.Hash{}, db) + + pool := NewTxPool(testChainConfig(), new(event.TypeMux), func() (*state.StateDB, error) { return statedb, nil }, func() *big.Int { return big.NewInt(1000000) }) + pool.resetState() + + // Create a number of test accounts and fund them + state, _ := pool.currentState() + + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + state.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := types.Transactions{} + for _, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + for j := 0; j < int(minPendingPerAccount)*2; j++ { + txs = append(txs, transaction(nonces[addr], big.NewInt(100000), key)) + nonces[addr]++ + } + } + // Import the batch and verify that limits have been enforced + pool.AddBatch(txs) + + for addr, list := range pool.pending { + if list.Len() != int(minPendingPerAccount) { + t.Errorf("addr %x: total pending transactions mismatch: have %d, want %d", addr, list.Len(), minPendingPerAccount) + } + } +} + // Benchmarks the speed of validating the contents of the pending queue of the // transaction pool. func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) } diff --git a/core/vm/environment.go b/core/vm/environment.go index a4b2ac196..f8996e648 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -20,12 +20,16 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" ) // RuleSet is an interface that defines the current rule set during the // execution of the EVM instructions (e.g. whether it's homestead) type RuleSet interface { IsHomestead(*big.Int) bool + // GasTable returns the gas prices for this phase, which is based on + // block number passed in. + GasTable(*big.Int) params.GasTable } // Environment is an EVM requirement and helper which allows access to outside diff --git a/core/vm/gas.go b/core/vm/gas.go index 09feddd7d..387233ed7 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -35,8 +35,27 @@ var ( GasStop = big.NewInt(0) GasContractByte = big.NewInt(200) + + n64 = big.NewInt(64) ) +// calcGas returns the actual gas cost of the call. +// +// The cost of gas was changed during the homestead price change HF. To allow for EIP150 +// to be implemented. The returned gas is gas - base * 63 / 64. +func callGas(gasTable params.GasTable, availableGas, base, callCost *big.Int) *big.Int { + if gasTable.CreateBySuicide != nil { + availableGas = new(big.Int).Sub(availableGas, base) + g := new(big.Int).Div(availableGas, n64) + g.Sub(availableGas, g) + + if g.Cmp(callCost) < 0 { + return g + } + } + return callCost +} + // baseCheck checks for any stack error underflows func baseCheck(op OpCode, stack *stack, gas *big.Int) error { // PUSH and DUP are a bit special. They all cost the same but we do want to have checking on stack push limit @@ -127,18 +146,19 @@ var _baseCheck = map[OpCode]req{ MSIZE: {0, GasQuickStep, 1}, GAS: {0, GasQuickStep, 1}, BLOCKHASH: {1, GasExtStep, 1}, - BALANCE: {1, GasExtStep, 1}, - EXTCODESIZE: {1, GasExtStep, 1}, - EXTCODECOPY: {4, GasExtStep, 0}, + BALANCE: {1, Zero, 1}, + EXTCODESIZE: {1, Zero, 1}, + EXTCODECOPY: {4, Zero, 0}, SLOAD: {1, params.SloadGas, 1}, SSTORE: {2, Zero, 0}, SHA3: {2, params.Sha3Gas, 1}, CREATE: {3, params.CreateGas, 1}, - CALL: {7, params.CallGas, 1}, - CALLCODE: {7, params.CallGas, 1}, - DELEGATECALL: {6, params.CallGas, 1}, - JUMPDEST: {0, params.JumpdestGas, 0}, + // Zero is calculated in the gasSwitch + CALL: {7, Zero, 1}, + CALLCODE: {7, Zero, 1}, + DELEGATECALL: {6, Zero, 1}, SUICIDE: {1, Zero, 0}, + JUMPDEST: {0, params.JumpdestGas, 0}, RETURN: {2, Zero, 0}, PUSH1: {0, GasFastestStep, 1}, DUP1: {0, Zero, 1}, diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 1585ce6d5..5682d2a1b 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -514,7 +514,12 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract input = memory.Get(offset.Int64(), size.Int64()) gas = new(big.Int).Set(contract.Gas) ) - contract.UseGas(contract.Gas) + if env.RuleSet().GasTable(env.BlockNumber()).CreateBySuicide != nil { + gas.Div(gas, n64) + gas = gas.Sub(contract.Gas, gas) + } + + contract.UseGas(gas) _, addr, suberr := env.Create(contract, input, gas, contract.Price, value) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 6d980cb32..343aee514 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -25,12 +25,16 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" ) // The default, always homestead, rule set for the vm env type ruleSet struct{} func (ruleSet) IsHomestead(*big.Int) bool { return true } +func (ruleSet) GasTable(*big.Int) params.GasTable { + return params.GasTableHomesteadGasRepriceFork +} // Config is a basic type specifying certain configuration flags for running // the EVM. diff --git a/core/vm/util_test.go b/core/vm/util_test.go index f0d825555..5783ee015 100644 --- a/core/vm/util_test.go +++ b/core/vm/util_test.go @@ -16,10 +16,17 @@ package vm -import "math/big" +import ( + "math/big" + + "github.com/ethereum/go-ethereum/params" +) type ruleSet struct { hs *big.Int } func (r ruleSet) IsHomestead(n *big.Int) bool { return n.Cmp(r.hs) >= 0 } +func (r ruleSet) GasTable(*big.Int) params.GasTable { + return params.GasTableHomestead +} diff --git a/core/vm/vm.go b/core/vm/vm.go index 376e53543..a73dfc811 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -44,8 +44,8 @@ type EVM struct { env Environment jumpTable vmJumpTable cfg Config - - logger *Logger + logger *Logger + gasTable params.GasTable } // New returns a new instance of the EVM. @@ -60,6 +60,7 @@ func New(env Environment, cfg Config) *EVM { jumpTable: newJumpTable(env.RuleSet(), env.BlockNumber()), cfg: cfg, logger: logger, + gasTable: env.RuleSet().GasTable(env.BlockNumber()), } } @@ -177,7 +178,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { // Get the memory location of pc op = contract.GetOp(pc) // calculate the new memory size and gas price for the current executing opcode - newMemSize, cost, err = calculateGasAndSize(evm.env, contract, caller, op, statedb, mem, stack) + newMemSize, cost, err = calculateGasAndSize(evm.gasTable, evm.env, contract, caller, op, statedb, mem, stack) if err != nil { return nil, err } @@ -242,7 +243,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { // calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for // the operation. This does not reduce gas or resizes the memory. -func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *stack) (*big.Int, *big.Int, error) { +func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *stack) (*big.Int, *big.Int, error) { var ( gas = new(big.Int) newMemSize *big.Int = new(big.Int) @@ -254,6 +255,24 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef // stack Check, memory resize & gas phase switch op { + case SUICIDE: + // if suicide is not nil: homestead gas fork + if gasTable.CreateBySuicide != nil { + gas.Set(gasTable.Suicide) + if !env.Db().Exist(common.BigToAddress(stack.data[len(stack.data)-1])) { + gas.Add(gas, gasTable.CreateBySuicide) + } + } + + if !statedb.HasSuicided(contract.Address()) { + statedb.AddRefund(params.SuicideRefundGas) + } + case EXTCODESIZE: + gas.Set(gasTable.ExtcodeSize) + case BALANCE: + gas.Set(gasTable.Balance) + case SLOAD: + gas.Set(gasTable.SLoad) case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: n := int(op - SWAP1 + 2) err := stack.require(n) @@ -282,6 +301,8 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef gas.Add(gas, new(big.Int).Mul(mSize, params.LogDataGas)) newMemSize = calcMemSize(mStart, mSize) + + quadMemGas(mem, newMemSize, gas) case EXP: gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(len(stack.data[stack.len()-2].Bytes()))), params.ExpByteGas)) case SSTORE: @@ -310,67 +331,100 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef g = params.SstoreClearGas } gas.Set(g) - case SUICIDE: - if !statedb.HasSuicided(contract.Address()) { - statedb.AddRefund(params.SuicideRefundGas) - } case MLOAD: newMemSize = calcMemSize(stack.peek(), u256(32)) + quadMemGas(mem, newMemSize, gas) case MSTORE8: newMemSize = calcMemSize(stack.peek(), u256(1)) + quadMemGas(mem, newMemSize, gas) case MSTORE: newMemSize = calcMemSize(stack.peek(), u256(32)) + quadMemGas(mem, newMemSize, gas) case RETURN: newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) + quadMemGas(mem, newMemSize, gas) case SHA3: newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) words := toWordSize(stack.data[stack.len()-2]) gas.Add(gas, words.Mul(words, params.Sha3WordGas)) + + quadMemGas(mem, newMemSize, gas) case CALLDATACOPY: newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) words := toWordSize(stack.data[stack.len()-3]) gas.Add(gas, words.Mul(words, params.CopyGas)) + + quadMemGas(mem, newMemSize, gas) case CODECOPY: newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) words := toWordSize(stack.data[stack.len()-3]) gas.Add(gas, words.Mul(words, params.CopyGas)) + + quadMemGas(mem, newMemSize, gas) case EXTCODECOPY: + gas.Set(gasTable.ExtcodeCopy) + newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4]) words := toWordSize(stack.data[stack.len()-4]) gas.Add(gas, words.Mul(words, params.CopyGas)) + quadMemGas(mem, newMemSize, gas) case CREATE: newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3]) + + quadMemGas(mem, newMemSize, gas) case CALL, CALLCODE: - gas.Add(gas, stack.data[stack.len()-1]) + gas.Set(gasTable.Calls) if op == CALL { if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) { gas.Add(gas, params.CallNewAccountGas) } } - if len(stack.data[stack.len()-3].Bytes()) > 0 { gas.Add(gas, params.CallValueTransferGas) } - x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7]) y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) newMemSize = common.BigMax(x, y) + + quadMemGas(mem, newMemSize, gas) + + cg := callGas(gasTable, contract.Gas, gas, stack.data[stack.len()-1]) + // Replace the stack item with the new gas calculation. This means that + // either the original item is left on the stack or the item is replaced by: + // (availableGas - gas) * 63 / 64 + // We replace the stack item so that it's available when the opCall instruction is + // called. This information is otherwise lost due to the dependency on *current* + // available gas. + stack.data[stack.len()-1] = cg + gas.Add(gas, cg) + case DELEGATECALL: - gas.Add(gas, stack.data[stack.len()-1]) + gas.Set(gasTable.Calls) x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4]) newMemSize = common.BigMax(x, y) + + quadMemGas(mem, newMemSize, gas) + + cg := callGas(gasTable, contract.Gas, gas, stack.data[stack.len()-1]) + // Replace the stack item with the new gas calculation. This means that + // either the original item is left on the stack or the item is replaced by: + // (availableGas - gas) * 63 / 64 + // We replace the stack item so that it's available when the opCall instruction is + // called. + stack.data[stack.len()-1] = cg + gas.Add(gas, cg) + } - quadMemGas(mem, newMemSize, gas) return newMemSize, gas, nil } |