From 5aec5da39fd67c83eef223d4ba44a5ea79eaea8e Mon Sep 17 00:00:00 2001 From: tanyuan <1067598718@qq.com> Date: Wed, 27 Jun 2018 20:32:15 +0800 Subject: [PATCH] add invoke and deploy gas cost by code length (#435) * add invoke and deploy gas cost by code length * gofmt * update gas code length cost * optimize code * fix vm execute ingore cost gas * optimize code * fix deploy and invoke gas bug * optimize contract execute less than min transaction gas * fix get ong from db bug * go fmt. Signed-off-by: luodanwg --- core/store/ledgerstore/ledger_store.go | 122 +++++++++++++------ core/store/ledgerstore/tx_handler.go | 156 +++++++++++++++++-------- http/base/rpc/interfaces.go | 14 +-- smartcontract/service/neovm/config.go | 27 +++-- 4 files changed, 219 insertions(+), 100 deletions(-) diff --git a/core/store/ledgerstore/ledger_store.go b/core/store/ledgerstore/ledger_store.go index de5edf9be0..a51ae1128b 100644 --- a/core/store/ledgerstore/ledger_store.go +++ b/core/store/ledgerstore/ledger_store.go @@ -19,11 +19,19 @@ package ledgerstore import ( + "bytes" "fmt" + "math" + "os" + "sort" + "strings" + "sync" + "github.com/ontio/ontology-crypto/keypair" "github.com/ontio/ontology/common" "github.com/ontio/ontology/common/config" "github.com/ontio/ontology/common/log" + "github.com/ontio/ontology/common/serialization" "github.com/ontio/ontology/core/payload" "github.com/ontio/ontology/core/signature" "github.com/ontio/ontology/core/states" @@ -36,14 +44,12 @@ import ( "github.com/ontio/ontology/smartcontract" scommon "github.com/ontio/ontology/smartcontract/common" "github.com/ontio/ontology/smartcontract/event" + "github.com/ontio/ontology/smartcontract/service/native/global_params" + "github.com/ontio/ontology/smartcontract/service/native/utils" "github.com/ontio/ontology/smartcontract/service/neovm" sstate "github.com/ontio/ontology/smartcontract/states" "github.com/ontio/ontology/smartcontract/storage" - "math" - "os" - "sort" - "strings" - "sync" + "strconv" ) const ( @@ -623,7 +629,6 @@ func (this *LedgerStoreImp) handleTransaction(stateBatch *statestore.StateBatch, if err != nil { if stateBatch.Error() == nil { log.Debugf("HandleDeployTransaction tx %x error %s", txHash, err) - SaveNotify(this.eventStore, txHash, []*event.NotifyEventInfo{}, false) } else { return fmt.Errorf("HandleDeployTransaction tx %x error %s", txHash, stateBatch.Error()) } @@ -633,7 +638,6 @@ func (this *LedgerStoreImp) handleTransaction(stateBatch *statestore.StateBatch, if err != nil { if stateBatch.Error() == nil { log.Debugf("HandleInvokeTransaction tx %x error %s", txHash, err) - SaveNotify(this.eventStore, txHash, []*event.NotifyEventInfo{}, false) } else { return fmt.Errorf("HandleInvokeTransaction tx %x error %s", txHash, stateBatch.Error()) } @@ -765,52 +769,104 @@ func (this *LedgerStoreImp) GetEventNotifyByBlock(height uint32) ([]*event.Execu //PreExecuteContract return the result of smart contract execution without commit to store func (this *LedgerStoreImp) PreExecuteContract(tx *types.Transaction) (*sstate.PreExecResult, error) { - if tx.TxType != types.Invoke { - return nil, errors.NewErr("transaction type error") - } - - invoke, ok := tx.Payload.(*payload.InvokeCode) - if !ok { - return nil, errors.NewErr("transaction type error") - } - header, err := this.GetHeaderByHeight(this.GetCurrentBlockHeight()) if err != nil { return nil, errors.NewDetailErr(err, errors.ErrNoCode, "[PreExecuteContract] Get current block error!") } - // init smart contract configuration info + config := &smartcontract.Config{ Time: header.Timestamp, Height: header.Height, Tx: tx, } - //init smart contract info + cache := storage.NewCloneCache(this.stateStore.NewStateBatch()) + preGas, err := this.getPreGas(config, cache) + if err != nil { + return nil, err + } + + if tx.TxType == types.Invoke { + invoke, ok := tx.Payload.(*payload.InvokeCode) + if !ok { + return nil, errors.NewErr("transaction payload not invokeCode!") + } + + sc := smartcontract.SmartContract{ + Config: config, + Store: this, + CloneCache: cache, + Gas: math.MaxUint64 - calcGasByCodeLen(len(invoke.Code), preGas[neovm.UINT_INVOKE_CODE_LEN_NAME]), + } + + //start the smart contract executive function + engine, err := sc.NewExecuteEngine(invoke.Code) + if err != nil { + return nil, err + } + result, err := engine.Invoke() + if err != nil { + return nil, err + } + gasCost := math.MaxUint64 - sc.Gas + mixGas := neovm.MIN_TRANSACTION_GAS + if gasCost < mixGas { + gasCost = mixGas + } + return &sstate.PreExecResult{State: event.CONTRACT_STATE_SUCCESS, Gas: gasCost, Result: scommon.ConvertNeoVmTypeHexString(result)}, nil + } else if tx.TxType == types.Deploy { + deploy, ok := tx.Payload.(*payload.DeployCode) + if !ok { + return nil, errors.NewErr("transaction payload not deployCode!") + } + + return &sstate.PreExecResult{State: event.CONTRACT_STATE_SUCCESS, Gas: preGas[neovm.CONTRACT_CREATE_NAME] + calcGasByCodeLen(len(deploy.Code), preGas[neovm.UINT_DEPLOY_CODE_LEN_NAME]), Result: nil}, nil + } else { + return nil, errors.NewErr("transaction type error") + } +} + +func (this *LedgerStoreImp) getPreGas(config *smartcontract.Config, cache *storage.CloneCache) (map[string]uint64, error) { + bf := new(bytes.Buffer) + names := []string{neovm.CONTRACT_CREATE_NAME, neovm.UINT_INVOKE_CODE_LEN_NAME, neovm.UINT_DEPLOY_CODE_LEN_NAME} + if err := utils.WriteVarUint(bf, uint64(len(names))); err != nil { + return nil, fmt.Errorf("write gas_table_keys length error:%s", err) + } + + for _, v := range names { + if err := serialization.WriteString(bf, v); err != nil { + return nil, fmt.Errorf("serialize param name error:%s", err) + } + } + sc := smartcontract.SmartContract{ Config: config, + CloneCache: cache, Store: this, - CloneCache: storage.NewCloneCache(this.stateStore.NewStateBatch()), Gas: math.MaxUint64, } - //start the smart contract executive function - engine, err := sc.NewExecuteEngine(invoke.Code) - if err != nil { - return nil, err - } - result, err := engine.Invoke() + service, _ := sc.NewNativeService() + result, err := service.NativeCall(utils.ParamContractAddress, "getGlobalParam", bf.Bytes()) if err != nil { return nil, err } - gasCost := math.MaxUint64 - sc.Gas - mixGas := neovm.MIN_TRANSACTION_GAS - if gasCost < mixGas { - gasCost = mixGas - } - if err != nil { - return &sstate.PreExecResult{State: event.CONTRACT_STATE_FAIL, Gas: gasCost, Result: nil}, err + params := new(global_params.Params) + if err := params.Deserialize(bytes.NewBuffer(result.([]byte))); err != nil { + return nil, fmt.Errorf("deserialize global params error:%s", err) + } + m := make(map[string]uint64, 0) + for _, v := range names { + n, ps := params.GetParam(v) + if n != -1 && ps.Value != "" { + pu, err := strconv.ParseUint(ps.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse uint %v", err) + } + m[v] = pu + } } - return &sstate.PreExecResult{State: event.CONTRACT_STATE_SUCCESS, Gas: gasCost, Result: scommon.ConvertNeoVmTypeHexString(result)}, nil + return m, nil } //Close ledger store. diff --git a/core/store/ledgerstore/tx_handler.go b/core/store/ledgerstore/tx_handler.go index 81b5e659d5..29beddf27b 100644 --- a/core/store/ledgerstore/tx_handler.go +++ b/core/store/ledgerstore/tx_handler.go @@ -29,7 +29,6 @@ import ( "github.com/ontio/ontology/common/log" "github.com/ontio/ontology/common/serialization" "github.com/ontio/ontology/core/payload" - "github.com/ontio/ontology/core/states" "github.com/ontio/ontology/core/store" scommon "github.com/ontio/ontology/core/store/common" "github.com/ontio/ontology/core/store/statestore" @@ -42,6 +41,7 @@ import ( "github.com/ontio/ontology/smartcontract/service/native/utils" "github.com/ontio/ontology/smartcontract/service/neovm" "github.com/ontio/ontology/smartcontract/storage" + "math/big" ) //HandleDeployTransaction deal with smart contract deploy transaction @@ -56,25 +56,28 @@ func (self *StateStore) HandleDeployTransaction(store store.LedgerStore, stateBa ) if tx.GasPrice != 0 { - gas, overflow := common.SafeMul(tx.GasLimit, tx.GasPrice) - if overflow { - return fmt.Errorf("gaslimit:%d*gasprice:%d overflow!", tx.GasLimit, tx.GasPrice) - } - - if err := isBalanceSufficient(tx.Payer, stateBatch, gas); err != nil { - return err - } - - cache := storage.NewCloneCache(stateBatch) - // init smart contract configuration info config := &smartcontract.Config{ Time: block.Header.Timestamp, Height: block.Header.Height, Tx: tx, } - - notifies, err = costGas(tx.Payer, gas, config, cache, store) + cache := storage.NewCloneCache(stateBatch) + gasLimit := neovm.GAS_TABLE[neovm.CONTRACT_CREATE_NAME] + calcGasByCodeLen(len(deploy.Code), neovm.GAS_TABLE[neovm.UINT_DEPLOY_CODE_LEN_NAME]) + balance, err := isBalanceSufficient(tx.Payer, cache, config, store, gasLimit*tx.GasPrice) + if err != nil { + if err := costInvalidGas(tx.Payer, balance, config, stateBatch, store, eventStore, txHash); err != nil { + return err + } + return err + } + if tx.GasLimit < gasLimit { + log.Errorf("gasLimit insufficient, need:%d actual:%d", gasLimit, tx.GasLimit) + if err := costInvalidGas(tx.Payer, tx.GasLimit*tx.GasPrice, config, stateBatch, store, eventStore, txHash); err != nil { + return err + } + } + notifies, err = costGas(tx.Payer, gasLimit*tx.GasPrice, config, cache, store) if err != nil { return err } @@ -102,16 +105,6 @@ func (self *StateStore) HandleInvokeTransaction(store store.LedgerStore, stateBa isCharge := !sysTransFlag && tx.GasPrice != 0 - gas, overflow := common.SafeMul(tx.GasLimit, tx.GasPrice) - if overflow { - return fmt.Errorf("gaslimit:%d*gasprice:%d overflow!", tx.GasLimit, tx.GasPrice) - } - if isCharge { - if err := isBalanceSufficient(tx.Payer, stateBatch, gas); err != nil { - return err - } - } - // init smart contract configuration info config := &smartcontract.Config{ Time: block.Header.Timestamp, @@ -119,38 +112,84 @@ func (self *StateStore) HandleInvokeTransaction(store store.LedgerStore, stateBa Tx: tx, } + var ( + codeLenGas uint64 + gasLimit uint64 + gas uint64 + balance uint64 + err error + ) cache := storage.NewCloneCache(stateBatch) + if isCharge { + codeLenGas = calcGasByCodeLen(len(invoke.Code), neovm.GAS_TABLE[neovm.UINT_INVOKE_CODE_LEN_NAME]) + balance, err := isBalanceSufficient(tx.Payer, cache, config, store, gasLimit*tx.GasPrice) + if err != nil { + if err := costInvalidGas(tx.Payer, balance, config, stateBatch, store, eventStore, txHash); err != nil { + return err + } + return err + } + + if tx.GasLimit < codeLenGas { + if err := costInvalidGas(tx.Payer, tx.GasLimit*tx.GasPrice, config, stateBatch, store, eventStore, txHash); err != nil { + return err + } + return fmt.Errorf("transaction gas: %d less than code length gas: %d", tx.GasLimit, codeLenGas) + } + } //init smart contract info sc := smartcontract.SmartContract{ Config: config, CloneCache: cache, Store: store, - Gas: tx.GasLimit, + Gas: tx.GasLimit - codeLenGas, } //start the smart contract executive function - engine, err := sc.NewExecuteEngine(invoke.Code) - if err != nil { - return err - } + engine, _ := sc.NewExecuteEngine(invoke.Code) _, err = engine.Invoke() + + if isCharge { + gasLimit = tx.GasLimit - sc.Gas + gas = gasLimit * tx.GasPrice + balance, err = getBalance(config, cache, store, tx.Payer) + if err != nil { + return err + } + if balance < gas { + if err := costInvalidGas(tx.Payer, balance, config, stateBatch, store, eventStore, txHash); err != nil { + return err + } + } + } + if err != nil { + if isCharge { + if err := costInvalidGas(tx.Payer, gas, config, stateBatch, store, eventStore, txHash); err != nil { + return err + } + } return err } var notifies []*event.NotifyEventInfo if isCharge { - totalGas := tx.GasLimit - sc.Gas mixGas := neovm.MIN_TRANSACTION_GAS - if totalGas < mixGas { - totalGas = mixGas + if gasLimit < mixGas { + if balance < mixGas*tx.GasPrice { + if err := costInvalidGas(tx.Payer, balance, config, stateBatch, store, eventStore, txHash); err != nil { + return err + } + } + gas = mixGas * tx.GasPrice } notifies, err = costGas(tx.Payer, gas, config, sc.CloneCache, store) if err != nil { return err } + } SaveNotify(eventStore, txHash, append(sc.Notifications, notifies...), true) @@ -185,15 +224,15 @@ func genNativeTransferCode(from, to common.Address, value uint64) []byte { } // check whether payer ong balance sufficient -func isBalanceSufficient(payer common.Address, stateBatch *statestore.StateBatch, gas uint64) error { - balance, err := getBalance(stateBatch, payer, utils.OngContractAddress) +func isBalanceSufficient(payer common.Address, cache *storage.CloneCache, config *smartcontract.Config, store store.LedgerStore, gas uint64) (uint64, error) { + balance, err := getBalance(config, cache, store, payer) if err != nil { - return err + return 0, err } if balance < gas { - return fmt.Errorf("payer gas insufficient, need %d , only have %d", gas, balance) + return 0, fmt.Errorf("payer gas insufficient, need %d , only have %d", gas, balance) } - return nil + return balance, nil } func costGas(payer common.Address, gas uint64, config *smartcontract.Config, @@ -241,7 +280,7 @@ func refreshGlobalParam(config *smartcontract.Config, cache *storage.CloneCache, } params := new(global_params.Params) if err := params.Deserialize(bytes.NewBuffer(result.([]byte))); err != nil { - fmt.Errorf("deserialize global params error:%s", err) + return fmt.Errorf("deserialize global params error:%s", err) } for k, _ := range neovm.GAS_TABLE { @@ -257,21 +296,38 @@ func refreshGlobalParam(config *smartcontract.Config, cache *storage.CloneCache, return nil } -func getBalance(stateBatch *statestore.StateBatch, address, contract common.Address) (uint64, error) { - bl, err := stateBatch.TryGet(scommon.ST_STORAGE, append(contract[:], address[:]...)) - if err != nil { - return 0, fmt.Errorf("get balance error:%s", err) +func getBalance(config *smartcontract.Config, cache *storage.CloneCache, store store.LedgerStore, address common.Address) (uint64, error) { + bf := new(bytes.Buffer) + if err := utils.WriteAddress(bf, address); err != nil { + return 0, err } - if bl == nil || bl.Value == nil { - return 0, fmt.Errorf("get %s balance fail from %s", address.ToHexString(), contract.ToHexString()) + sc := smartcontract.SmartContract{ + Config: config, + CloneCache: cache, + Store: store, + Gas: math.MaxUint64, } - item, ok := bl.Value.(*states.StorageItem) - if !ok { - return 0, fmt.Errorf("%s", "instance doesn't StorageItem!") + + service, _ := sc.NewNativeService() + result, err := service.NativeCall(utils.OngContractAddress, ont.BALANCEOF_NAME, bf.Bytes()) + if err != nil { + return 0, err } - balance, err := serialization.ReadUint64(bytes.NewBuffer(item.Value)) + return new(big.Int).SetBytes(result.([]byte)).Uint64(), nil +} + +func costInvalidGas(address common.Address, gas uint64, config *smartcontract.Config, stateBatch *statestore.StateBatch, + store store.LedgerStore, eventStore scommon.EventStore, txHash common.Uint256) error { + cache := storage.NewCloneCache(stateBatch) + notifies, err := costGas(address, gas, config, cache, store) if err != nil { - return 0, fmt.Errorf("read balance error:%s", err) + return err } - return balance, nil + cache.Commit() + SaveNotify(eventStore, txHash, notifies, false) + return nil +} + +func calcGasByCodeLen(codeLen int, codeGas uint64) uint64 { + return uint64(codeLen/neovm.PER_UNIT_CODE_LEN) * codeGas } diff --git a/http/base/rpc/interfaces.go b/http/base/rpc/interfaces.go index 1fd804dfe8..6db0fe74d5 100644 --- a/http/base/rpc/interfaces.go +++ b/http/base/rpc/interfaces.go @@ -279,17 +279,15 @@ func SendRawTransaction(params []interface{}) map[string]interface{} { if err := txn.Deserialize(bytes.NewReader(hex)); err != nil { return responsePack(berr.INVALID_TRANSACTION, "") } - if txn.TxType == types.Invoke && len(params) > 1 { + if len(params) > 1 { preExec, ok := params[1].(float64) if ok && preExec == 1 { - if _, ok := txn.Payload.(*payload.InvokeCode); ok { - result, err := bactor.PreExecuteContract(&txn) - if err != nil { - log.Infof("PreExec: ", err) - return responsePack(berr.SMARTCODE_ERROR, "") - } - return responseSuccess(result) + result, err := bactor.PreExecuteContract(&txn) + if err != nil { + log.Infof("PreExec: ", err) + return responsePack(berr.SMARTCODE_ERROR, "") } + return responseSuccess(result) } } hash = txn.Hash() diff --git a/smartcontract/service/neovm/config.go b/smartcontract/service/neovm/config.go index fcab49e581..e45edd3243 100644 --- a/smartcontract/service/neovm/config.go +++ b/smartcontract/service/neovm/config.go @@ -27,9 +27,11 @@ var ( BLOCKCHAIN_GETCONTRACT_GAS uint64 = 100 CONTRACT_CREATE_GAS uint64 = 20000000 CONTRACT_MIGRATE_GAS uint64 = 20000000 - NATIVE_INVOKE_GAS uint64 = 10000 - STORAGE_GET_GAS uint64 = 100 - STORAGE_PUT_GAS uint64 = 1000 + UINT_DEPLOY_CODE_LEN_GAS uint64 = 200000 + UINT_INVOKE_CODE_LEN_GAS uint64 = 20000 + NATIVE_INVOKE_GAS uint64 = 1000 + STORAGE_GET_GAS uint64 = 200 + STORAGE_PUT_GAS uint64 = 4000 STORAGE_DELETE_GAS uint64 = 100 RUNTIME_CHECKWITNESS_GAS uint64 = 200 APPCALL_GAS uint64 = 10 @@ -40,6 +42,7 @@ var ( HASH256_GAS uint64 = 20 OPCODE_GAS uint64 = 1 + PER_UNIT_CODE_LEN int = 1024 METHOD_LENGTH_LIMIT int = 1024 MAX_STACK_SIZE int = 1024 VM_STEP_LIMIT int = 400000 @@ -100,12 +103,14 @@ var ( GETCALLINGSCRIPTHASH_NAME = "System.ExecutionEngine.GetCallingScriptHash" GETENTRYSCRIPTHASH_NAME = "System.ExecutionEngine.GetEntryScriptHash" - APPCALL_NAME = "APPCALL" - TAILCALL_NAME = "TAILCALL" - SHA1_NAME = "SHA1" - SHA256_NAME = "SHA256" - HASH160_NAME = "HASH160" - HASH256_NAME = "HASH256" + APPCALL_NAME = "APPCALL" + TAILCALL_NAME = "TAILCALL" + SHA1_NAME = "SHA1" + SHA256_NAME = "SHA256" + HASH160_NAME = "HASH160" + HASH256_NAME = "HASH256" + UINT_DEPLOY_CODE_LEN_NAME = "Deploy.Code.Gas" + UINT_INVOKE_CODE_LEN_NAME = "Invoke.Code.Gas" GAS_TABLE = map[string]uint64{ BLOCKCHAIN_GETHEADER_NAME: BLOCKCHAIN_GETHEADER_GAS, @@ -125,6 +130,8 @@ var ( SHA256_NAME: SHA256_GAS, HASH160_NAME: HASH160_GAS, HASH256_NAME: HASH256_GAS, + UINT_DEPLOY_CODE_LEN_NAME: UINT_DEPLOY_CODE_LEN_GAS, + UINT_INVOKE_CODE_LEN_NAME: UINT_INVOKE_CODE_LEN_GAS, } GAS_TABLE_KEYS = []string{ @@ -146,5 +153,7 @@ var ( SHA256_NAME, HASH160_NAME, HASH256_NAME, + UINT_DEPLOY_CODE_LEN_NAME, + UINT_INVOKE_CODE_LEN_NAME, } )