Browse Source

Make Call() handle value transfers and reverts.

pull/32/head
Jae Kwon 9 years ago
parent
commit
36dca3981b
2 changed files with 57 additions and 59 deletions
  1. +37
    -45
      state/state.go
  2. +20
    -14
      vm/vm.go

+ 37
- 45
state/state.go View File

@ -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()


+ 20
- 14
vm/vm.go View File

@ -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)
}
}


Loading…
Cancel
Save