Make Call() handle value transfers and reverts.
This commit is contained in:
parent
7f12738415
commit
36dca3981b
|
@ -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()
|
||||
|
|
34
vm/vm.go
34
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue