diff --git a/state/state.go b/state/state.go index 78a065e78..3340840ce 100644 --- a/state/state.go +++ b/state/state.go @@ -98,12 +98,6 @@ func (s *State) Save() { s.DB.Set(stateKey, buf.Bytes()) } -// NOTE: the valSets are not Copy()'d. -// See TODOs in Copy() below. -func (s *State) Reset(checkpoint *State) { - *s = *checkpoint -} - func (s *State) Copy() *State { return &State{ DB: s.DB, @@ -181,21 +175,9 @@ func (s *State) ValidateInputs(accounts map[string]*account.Account, signBytes [ if acc == nil { panic("ValidateInputs() expects account in accounts") } - // Check TxInput basic - if err := in.ValidateBasic(); err != nil { - return 0, err - } - // Check signatures - if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { - return 0, blk.ErrTxInvalidSignature - } - // Check sequences - if acc.Sequence+1 != in.Sequence { - return 0, blk.ErrTxInvalidSequence - } - // Check amount - if acc.Balance < in.Amount { - return 0, blk.ErrTxInsufficientFunds + err = s.ValidateInput(acc, signBytes, in) + if err != nil { + return } // Good. Add amount to total total += in.Amount @@ -203,6 +185,26 @@ func (s *State) ValidateInputs(accounts map[string]*account.Account, signBytes [ return total, nil } +func (s *State) ValidateInput(acc *account.Account, signBytes []byte, in *blk.TxInput) (err error) { + // Check TxInput basic + if err := in.ValidateBasic(); err != nil { + return err + } + // Check signatures + if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { + return blk.ErrTxInvalidSignature + } + // Check sequences + if acc.Sequence+1 != in.Sequence { + return blk.ErrTxInvalidSequence + } + // Check amount + if acc.Balance < in.Amount { + return blk.ErrTxInsufficientFunds + } + return nil +} + func (s *State) ValidateOutputs(outs []*blk.TxOutput) (total uint64, err error) { for _, out := range outs { // Check TxOutput basic @@ -275,25 +277,25 @@ func (s *State) ExecTx(tx_ blk.Tx, runCall bool) error { return nil case *blk.CallTx: - accounts := make(map[string]*account.Account) - - // validate input + // Validate input inAcc := s.GetAccount(tx.Input.Address) if inAcc == nil { return blk.ErrTxInvalidAddress } - // PubKey should be present in either "inAcc" or "tx.Input" + // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { return err } - accounts[string(tx.Input.Address)] = inAcc signBytes := account.SignBytes(tx) - inTotal, err := s.ValidateInputs(accounts, signBytes, []*blk.TxInput{tx.Input}) + err := s.ValidateInput(inAcc, signBytes, tx.Input) if err != nil { return err } + if tx.Input.Amount < tx.Fee { + return blk.ErrTxInsufficientFunds + } - // validate output + // Validate output if len(tx.Address) != 20 { return blk.ErrTxInvalidAddress } @@ -301,19 +303,10 @@ func (s *State) ExecTx(tx_ blk.Tx, runCall bool) error { if outAcc == nil { return blk.ErrTxInvalidAddress } - if inTotal < tx.Fee { - return blk.ErrTxInsufficientFunds - } - accounts[string(tx.Address)] = outAcc - // Remember unmodified state in case call fails. - checkpoint := s.Copy() - - // Good! Adjust accounts and maybe actually run the tx. - value := inTotal - tx.Fee - s.AdjustByInputs(accounts, []*blk.TxInput{tx.Input}) // adjusts inAcc. - outAcc.Balance += value - s.UpdateAccounts(accounts) + // Good! + value := tx.Input.Amount - tx.Fee + inAcc.Sequence += 1 if runCall { appState := NewVMAppState(s) @@ -331,11 +324,10 @@ func (s *State) ExecTx(tx_ blk.Tx, runCall bool) error { gas := tx.GasLimit ret, err_ := vmach.Call(caller, callee, outAcc.Code, tx.Data, value, &gas) if err_ != nil { - // Failure - // Revert the state while charging gas for the user. - s.Reset(checkpoint) - callee.Balance -= tx.Fee - s.UpdateAccounts(accounts) + // Failure. Charge the gas fee. The 'value' was otherwise not transferred. + inAcc.Balance -= tx.Fee + s.UpdateAccount(inAcc) + // Throw away 'appState' which holds incomplete updates. } else { // Success appState.Sync() diff --git a/vm/vm.go b/vm/vm.go index a18f153e5..3b0f6c53b 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -46,17 +46,32 @@ func NewVM(appState AppState, params Params, origin Word) *VM { } } -// value: The value transfered from caller to callee. -// NOTE: The value should already have been transferred to the callee. -// NOTE: When Call() fails, the value should be returned to the caller. -// gas: The maximum gas that will be run. -// When the function returns, *gas will be the amount of remaining gas. +// value: To be transferred from caller to callee. Refunded upon error. +// gas: Available gas. No refunds for gas. func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { if len(code) == 0 { panic("Call() requires code") } + if err = transfer(caller, callee, value); err != nil { + return + } + + vm.callDepth += 1 + output, err = vm.call(caller, callee, code, input, value, gas) + vm.callDepth -= 1 + if err != nil { + err = transfer(callee, caller, value) + if err != nil { + panic("Could not return value to caller") + } + } + return +} + +// Just like Call() but does not transfer 'value' or modify the callDepth. +func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { fmt.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) var ( @@ -538,15 +553,9 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga stack.Push(Zero) fmt.Printf(" (*) 0x0 %v\n", err) } else { - if err_ := transfer(callee, newAccount, value); err_ != nil { - return nil, err_ // prob never happens... - } // Run the input to get the contract code. - // The code as well as the input to the code are the same. - // Will it halt? Yes. ret, err_ := vm.Call(callee, newAccount, input, input, value, gas) if err_ != nil { - caller.Balance += value // Return the balance stack.Push(Zero) } else { newAccount.Code = ret // Set the code @@ -593,9 +602,6 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga if op == CALLCODE { ret, err = vm.Call(callee, callee, account.Code, args, value, gas) } else { - if err := transfer(callee, account, value); err != nil { - return nil, err - } ret, err = vm.Call(callee, account, account.Code, args, value, gas) } }