Fix returning prematurely within if(runCall){...}.

Renames
This commit is contained in:
Jae Kwon
2015-07-24 12:32:22 -07:00
parent feeab6579b
commit 0ef5c3ad07
7 changed files with 125 additions and 92 deletions

View File

@ -39,11 +39,11 @@ func Call(fromAddress, toAddress, data []byte) (*ctypes.ResponseCall, error) {
BlockHeight: int64(st.LastBlockHeight), BlockHeight: int64(st.LastBlockHeight),
BlockHash: LeftPadWord256(st.LastBlockHash), BlockHash: LeftPadWord256(st.LastBlockHash),
BlockTime: st.LastBlockTime.Unix(), BlockTime: st.LastBlockTime.Unix(),
GasLimit: 10000000, GasLimit: st.GetGasLimit(),
} }
vmach := vm.NewVM(txCache, params, caller.Address, nil) vmach := vm.NewVM(txCache, params, caller.Address, nil)
gas := int64(1000000000) gas := st.GetGasLimit()
ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas)
if err != nil { if err != nil {
return nil, err return nil, err
@ -64,11 +64,11 @@ func CallCode(fromAddress, code, data []byte) (*ctypes.ResponseCall, error) {
BlockHeight: int64(st.LastBlockHeight), BlockHeight: int64(st.LastBlockHeight),
BlockHash: LeftPadWord256(st.LastBlockHash), BlockHash: LeftPadWord256(st.LastBlockHash),
BlockTime: st.LastBlockTime.Unix(), BlockTime: st.LastBlockTime.Unix(),
GasLimit: 10000000, GasLimit: st.GetGasLimit(),
} }
vmach := vm.NewVM(txCache, params, caller.Address, nil) vmach := vm.NewVM(txCache, params, caller.Address, nil)
gas := int64(1000000000) gas := st.GetGasLimit()
ret, err := vmach.Call(caller, callee, code, data, 0, &gas) ret, err := vmach.Call(caller, callee, code, data, 0, &gas)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -356,10 +356,10 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
return types.ErrTxInvalidAddress return types.ErrTxInvalidAddress
} }
createAccount := len(tx.Address) == 0 createContract := len(tx.Address) == 0
if createAccount { if createContract {
if !hasCreateContractPermission(blockCache, inAcc) { if !hasCreateContractPermission(blockCache, inAcc) {
return fmt.Errorf("Account %X does not have Create permission", tx.Input.Address) return fmt.Errorf("Account %X does not have CreateContract permission", tx.Input.Address)
} }
} else { } else {
if !hasCallPermission(blockCache, inAcc) { if !hasCallPermission(blockCache, inAcc) {
@ -383,13 +383,18 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
return types.ErrTxInsufficientFunds return types.ErrTxInsufficientFunds
} }
if !createAccount { if !createContract {
// Validate output // Validate output
if len(tx.Address) != 20 { if len(tx.Address) != 20 {
log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address)) log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address))
return types.ErrTxInvalidAddress return types.ErrTxInvalidAddress
} }
// this may be nil if we are still in mempool and contract was created in same block as this tx // check if its a native contract
if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) {
return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)")
}
// Output account may be nil if we are still in mempool and contract was created in same block as this tx
// but that's fine, because the account will be created properly when the create tx runs in the block // but that's fine, because the account will be created properly when the create tx runs in the block
// and then this won't return nil. otherwise, we take their fee // and then this won't return nil. otherwise, we take their fee
outAcc = blockCache.GetAccount(tx.Address) outAcc = blockCache.GetAccount(tx.Address)
@ -400,90 +405,101 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
// Good! // Good!
value := tx.Input.Amount - tx.Fee value := tx.Input.Amount - tx.Fee
inAcc.Sequence += 1 inAcc.Sequence += 1
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
// The logic in runCall MUST NOT return.
if runCall { if runCall {
// VM call variables
var ( var (
gas int64 = tx.GasLimit gas int64 = tx.GasLimit
err error = nil err error = nil
caller *vm.Account = toVMAccount(inAcc) caller *vm.Account = toVMAccount(inAcc)
callee *vm.Account = nil callee *vm.Account = nil // initialized below
code []byte = nil code []byte = nil
ret []byte = nil
txCache = NewTxCache(blockCache) txCache = NewTxCache(blockCache)
params = vm.Params{ params = vm.Params{
BlockHeight: int64(_s.LastBlockHeight), BlockHeight: int64(_s.LastBlockHeight),
BlockHash: LeftPadWord256(_s.LastBlockHash), BlockHash: LeftPadWord256(_s.LastBlockHash),
BlockTime: _s.LastBlockTime.Unix(), BlockTime: _s.LastBlockTime.Unix(),
GasLimit: 10000000, GasLimit: _s.GetGasLimit(),
} }
) )
// get or create callee if !createContract && (outAcc == nil || len(outAcc.Code) == 0) {
if !createAccount { // if you call an account that doesn't exist
// or an account with no code then we take fees (sorry pal)
if outAcc == nil || len(outAcc.Code) == 0 { // NOTE: it's fine to create a contract and call it within one
// check if its a native contract // block (nonce will prevent re-ordering of those txs)
if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) { // but to create with one contract and call with another
return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)") // you have to wait a block to avoid a re-ordering attack
} // that will take your fees
if outAcc == nil {
// if you call an account that doesn't exist log.Info(Fmt("%X tries to call %X but it does not exist.",
// or an account with no code then we take fees (sorry pal) inAcc.Address, tx.Address))
// NOTE: it's fine to create a contract and call it within one } else {
// block (nonce will prevent re-ordering of those txs) log.Info(Fmt("%X tries to call %X but code is blank.",
// but to create with one account and call with another inAcc.Address, tx.Address))
// you have to wait a block to avoid a re-ordering attack }
// that will take your fees err = types.ErrTxInvalidAddress
inAcc.Balance -= tx.Fee goto CALL_COMPLETE
blockCache.UpdateAccount(inAcc) }
if outAcc == nil {
log.Info(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address)) // get or create callee
} else { if createContract {
log.Info(Fmt("Attempting to call an account (%X) with no code. Deducting fee from caller", tx.Address)) if HasPermission(blockCache, inAcc, ptypes.CreateContract) {
} callee = txCache.CreateAccount(caller)
return types.ErrTxInvalidAddress log.Info(Fmt("Created new contract %X", callee.Address))
code = tx.Data
} else {
log.Info(Fmt("Error on execution: Caller %X cannot create contract",
caller.Address))
err = types.ErrTxPermissionDenied
goto CALL_COMPLETE
} }
callee = toVMAccount(outAcc)
code = callee.Code
log.Info(Fmt("Calling contract %X with code %X", callee.Address, callee.Code))
} else { } else {
callee = txCache.CreateAccount(caller) callee = toVMAccount(outAcc)
log.Info(Fmt("Created new account %X", callee.Address)) log.Info(Fmt("Calling contract %X with code %X", callee.Address, callee.Code))
code = tx.Data code = callee.Code
} }
log.Info(Fmt("Code for this contract: %X", code)) log.Info(Fmt("Code for this contract: %X", code))
txCache.UpdateAccount(caller) // because we bumped nonce // Run VM call and sync txCache to blockCache.
txCache.UpdateAccount(callee) // so the txCache knows about the callee and the create and/or transfer takes effect { // Capture scope for goto.
// Write caller/callee to txCache.
vmach := vm.NewVM(txCache, params, caller.Address, types.TxID(_s.ChainID, tx)) txCache.UpdateAccount(caller)
vmach.SetFireable(evc) txCache.UpdateAccount(callee)
vmach := vm.NewVM(txCache, params, caller.Address, types.TxID(_s.ChainID, tx))
// NOTE: Call() transfers the value from caller to callee iff call succeeds. vmach.SetFireable(evc)
ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) // NOTE: Call() transfers the value from caller to callee iff call succeeds.
exception := "" ret, err = vmach.Call(caller, callee, code, tx.Data, value, &gas)
if err != nil { if err != nil {
exception = err.Error() // Failure. Charge the gas fee. The 'value' was otherwise not transferred.
// Failure. Charge the gas fee. The 'value' was otherwise not transferred. log.Info(Fmt("Error on execution: %v", err))
log.Info(Fmt("Error on execution: %v", err)) goto CALL_COMPLETE
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
// Throw away 'txCache' which holds incomplete updates (don't sync it).
} else {
log.Info("Successful execution")
// Success
if createAccount {
callee.Code = ret
} }
log.Info("Successful execution")
if createContract {
callee.Code = ret
}
txCache.Sync() txCache.Sync()
} }
CALL_COMPLETE: // err may or may not be nil.
// Create a receipt from the ret and whether errored. // Create a receipt from the ret and whether errored.
log.Notice("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err) log.Notice("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
// Fire Events for sender and receiver // Fire Events for sender and receiver
// a separate event will be fired from vm for each additional call // a separate event will be fired from vm for each additional call
if evc != nil { if evc != nil {
exception := ""
if err != nil {
exception = err.Error()
}
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventMsgCallTx{tx, ret, exception}) evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventMsgCallTx{tx, ret, exception})
evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventMsgCallTx{tx, ret, exception}) evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventMsgCallTx{tx, ret, exception})
} }
@ -493,7 +509,7 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
// So mempool will skip the actual .Call(), // So mempool will skip the actual .Call(),
// and only deduct from the caller's balance. // and only deduct from the caller's balance.
inAcc.Balance -= value inAcc.Balance -= value
if createAccount { if createContract {
inAcc.Sequence += 1 inAcc.Sequence += 1
} }
blockCache.UpdateAccount(inAcc) blockCache.UpdateAccount(inAcc)
@ -556,7 +572,7 @@ func ExecTx(blockCache *BlockCache, tx types.Tx, runCall bool, evc events.Fireab
// ensure we are owner // ensure we are owner
if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { if bytes.Compare(entry.Owner, tx.Input.Address) != 0 {
log.Info(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name)) log.Info(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name))
return types.ErrIncorrectOwner return types.ErrTxPermissionDenied
} }
} else { } else {
expired = true expired = true

View File

@ -147,6 +147,14 @@ func (s *State) SetDB(db dbm.DB) {
s.DB = db s.DB = db
} }
//-------------------------------------
// State.params
func (s *State) GetGasLimit() int64 {
return 1000000 // TODO
}
// State.params
//------------------------------------- //-------------------------------------
// State.accounts // State.accounts

View File

@ -21,7 +21,7 @@ var (
ErrTxInvalidPubKey = errors.New("Error invalid pubkey") ErrTxInvalidPubKey = errors.New("Error invalid pubkey")
ErrTxInvalidSignature = errors.New("Error invalid signature") ErrTxInvalidSignature = errors.New("Error invalid signature")
ErrTxInvalidString = errors.New("Error invalid string") ErrTxInvalidString = errors.New("Error invalid string")
ErrIncorrectOwner = errors.New("Error incorrect owner") ErrTxPermissionDenied = errors.New("Error permission denied")
) )
type ErrTxInvalidSequence struct { type ErrTxInvalidSequence struct {

View File

@ -5,6 +5,7 @@ const (
GasGetAccount int64 = 1 GasGetAccount int64 = 1
GasStorageUpdate int64 = 1 GasStorageUpdate int64 = 1
GasBaseOp int64 = 0 // TODO: make this 1
GasStackOp int64 = 1 GasStackOp int64 = 1
GasEcRecover int64 = 1 GasEcRecover int64 = 1

View File

@ -21,6 +21,9 @@ type Account struct {
} }
func (acc *Account) String() string { func (acc *Account) String() string {
if acc == nil {
return "nil-VMAccount"
}
return Fmt("VMAccount{%X B:%v C:%X N:%v S:%X}", return Fmt("VMAccount{%X B:%v C:%X N:%v S:%X}",
acc.Address, acc.Balance, acc.Code, acc.Nonce, acc.StorageRoot) acc.Address, acc.Balance, acc.Code, acc.Nonce, acc.StorageRoot)
} }

View File

@ -140,6 +140,18 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas
return return
} }
// Try to deduct gasToUse from gasLeft. If ok return false, otherwise
// set err and return true.
func useGasNegative(gasLeft *int64, gasToUse int64, err *error) bool {
if *gasLeft >= gasToUse {
*gasLeft -= gasToUse
return false
} else if *err == nil {
*err = ErrInsufficientGas
}
return true
}
// Just like Call() but does not transfer 'value' or modify the callDepth. // Just like Call() but does not transfer 'value' or modify the callDepth.
func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) {
dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input)
@ -148,12 +160,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
pc int64 = 0 pc int64 = 0
stack = NewStack(dataStackCapacity, gas, &err) stack = NewStack(dataStackCapacity, gas, &err)
memory = make([]byte, memoryCapacity) memory = make([]byte, memoryCapacity)
ok = false // convenience
) )
for { for {
// If there is an error, return
if err != nil { // Use BaseOp gas.
if useGasNegative(gas, GasBaseOp, &err) {
return nil, err return nil, err
} }
@ -424,8 +436,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
dbg.Printf(" => 0x%X\n", res) dbg.Printf(" => 0x%X\n", res)
case SHA3: // 0x20 case SHA3: // 0x20
if ok = useGas(gas, GasSha3); !ok { if useGasNegative(gas, GasSha3, &err) {
return nil, firstErr(err, ErrInsufficientGas) return nil, err
} }
offset, size := stack.Pop64(), stack.Pop64() offset, size := stack.Pop64(), stack.Pop64()
data, ok := subslice(memory, offset, size) data, ok := subslice(memory, offset, size)
@ -442,8 +454,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
case BALANCE: // 0x31 case BALANCE: // 0x31
addr := stack.Pop() addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok { if useGasNegative(gas, GasGetAccount, &err) {
return nil, firstErr(err, ErrInsufficientGas) return nil, err
} }
acc := vm.appState.GetAccount(addr) acc := vm.appState.GetAccount(addr)
if acc == nil { if acc == nil {
@ -520,8 +532,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
case EXTCODESIZE: // 0x3B case EXTCODESIZE: // 0x3B
addr := stack.Pop() addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok { if useGasNegative(gas, GasGetAccount, &err) {
return nil, firstErr(err, ErrInsufficientGas) return nil, err
} }
acc := vm.appState.GetAccount(addr) acc := vm.appState.GetAccount(addr)
if acc == nil { if acc == nil {
@ -534,8 +546,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
case EXTCODECOPY: // 0x3C case EXTCODECOPY: // 0x3C
addr := stack.Pop() addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok { if useGasNegative(gas, GasGetAccount, &err) {
return nil, firstErr(err, ErrInsufficientGas) return nil, err
} }
acc := vm.appState.GetAccount(addr) acc := vm.appState.GetAccount(addr)
if acc == nil { if acc == nil {
@ -579,8 +591,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
dbg.Printf(" => %v\n", vm.params.GasLimit) dbg.Printf(" => %v\n", vm.params.GasLimit)
case POP: // 0x50 case POP: // 0x50
stack.Pop() popped := stack.Pop()
dbg.Printf(" => %v\n", vm.params.GasLimit) dbg.Printf(" => 0x%X\n", popped)
case MLOAD: // 0x51 case MLOAD: // 0x51
offset := stack.Pop64() offset := stack.Pop64()
@ -616,8 +628,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
case SSTORE: // 0x55 case SSTORE: // 0x55
loc, data := stack.Pop(), stack.Pop() loc, data := stack.Pop(), stack.Pop()
if useGasNegative(gas, GasStorageUpdate, &err) {
return nil, err
}
vm.appState.SetStorage(callee.Address, loc, data) vm.appState.SetStorage(callee.Address, loc, data)
useGas(gas, GasStorageUpdate)
dbg.Printf(" {0x%X : 0x%X}\n", loc, data) dbg.Printf(" {0x%X : 0x%X}\n", loc, data)
case JUMP: // 0x56 case JUMP: // 0x56
@ -762,8 +776,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
vm.fireCallEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, gas) vm.fireCallEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, gas)
} else { } else {
// EVM contract // EVM contract
if ok = useGas(gas, GasGetAccount); !ok { if useGasNegative(gas, GasGetAccount, &err) {
return nil, firstErr(err, ErrInsufficientGas) return nil, err
} }
acc := vm.appState.GetAccount(addr) acc := vm.appState.GetAccount(addr)
// since CALL is used also for sending funds, // since CALL is used also for sending funds,
@ -821,8 +835,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas
case SUICIDE: // 0xFF case SUICIDE: // 0xFF
addr := stack.Pop() addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok { if useGasNegative(gas, GasGetAccount, &err) {
return nil, firstErr(err, ErrInsufficientGas) return nil, err
} }
// TODO if the receiver is , then make it the fee. // TODO if the receiver is , then make it the fee.
receiver := vm.appState.GetAccount(addr) receiver := vm.appState.GetAccount(addr)
@ -893,15 +907,6 @@ func firstErr(errA, errB error) error {
} }
} }
func useGas(gas *int64, gasToUse int64) bool {
if *gas > gasToUse {
*gas -= gasToUse
return true
} else {
return false
}
}
func transfer(from, to *Account, amount int64) error { func transfer(from, to *Account, amount int64) error {
if from.Balance < amount { if from.Balance < amount {
return ErrInsufficientBalance return ErrInsufficientBalance