Browse Source

Merge pull request #66 from tendermint/fixvmsend

Fixvmsend
pull/68/head
Jae Kwon 10 years ago
parent
commit
cb8261f2ff
6 changed files with 173 additions and 50 deletions
  1. +9
    -5
      rpc/test/client_ws_test.go
  2. +0
    -1
      rpc/test/ws_helpers_test.go
  3. +14
    -4
      state/execution.go
  4. +2
    -11
      vm/test/fake_app_state.go
  5. +105
    -2
      vm/test/vm_test.go
  6. +43
    -27
      vm/vm.go

+ 9
- 5
rpc/test/client_ws_test.go View File

@ -230,7 +230,7 @@ func TestWSCallWait(t *testing.T) {
// susbscribe to the new contract
amt = uint64(10001)
eid2 := types.EventStringAccReceive(contractAddr)
eid2 := types.EventStringAccOutput(contractAddr)
subscribe(t, con, eid2)
defer func() {
unsubscribe(t, con, eid2)
@ -254,7 +254,7 @@ func TestWSCallNoWait(t *testing.T) {
// susbscribe to the new contract
amt = uint64(10001)
eid := types.EventStringAccReceive(contractAddr)
eid := types.EventStringAccOutput(contractAddr)
subscribe(t, con, eid)
defer func() {
unsubscribe(t, con, eid)
@ -284,16 +284,20 @@ func TestWSCallCall(t *testing.T) {
// susbscribe to the new contracts
amt = uint64(10001)
eid1 := types.EventStringAccReceive(contractAddr1)
eid2 := types.EventStringAccReceive(contractAddr2)
subscribe(t, con, eid1)
subscribe(t, con, eid2)
defer func() {
unsubscribe(t, con, eid1)
unsubscribe(t, con, eid2)
con.Close()
}()
// call contract2, which should call contract1, and wait for ev1
data := []byte{0x1} // just needs to be non empty for this to be a CallTx
// let the contract get created first
waitForEvent(t, con, eid1, true, func() {
}, func(eid string, b []byte) error {
return nil
})
// call it
waitForEvent(t, con, eid1, true, func() {
tx, _ := broadcastTx(t, "JSONRPC", userByteAddr, contractAddr2, data, userBytePriv, amt, 1000, 1000)
*txid = account.HashSignBytes(tx)


+ 0
- 1
rpc/test/ws_helpers_test.go View File

@ -130,7 +130,6 @@ func unmarshalValidateCallCall(origin, returnCode []byte, txid *[]byte) func(str
if bytes.Compare(response.Data.TxId, *txid) != 0 {
return fmt.Errorf("TxIds do not match up! Got %x, expected %x", response.Data.TxId, *txid)
}
// calldata := response.Data.CallData
return nil
}
}

+ 14
- 4
state/execution.go View File

@ -403,11 +403,21 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea
// Maybe create a new callee account if
// this transaction is creating a new contract.
if !createAccount {
if outAcc == nil {
// take fees (sorry pal)
if outAcc == nil || len(outAcc.Code) == 0 {
// if you call an account that doesn't exist
// or an account with no code then we take fees (sorry pal)
// NOTE: it's fine to create a contract and call it within one
// block (nonce will prevent re-ordering of those txs)
// but to create with one account and call with another
// you have to wait a block to avoid a re-ordering attack
// that will take your fees
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
if outAcc == nil {
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
} else {
log.Debug(Fmt("Attempting to call an account (%X) with no code. Deducting fee from caller", tx.Address))
}
return types.ErrTxInvalidAddress
}
@ -452,7 +462,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea
// a separate event will be fired from vm for each additional call
if evc != nil {
evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventMsgCallTx{tx, ret, exception})
evc.FireEvent(types.EventStringAccReceive(tx.Address), types.EventMsgCallTx{tx, ret, exception})
evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventMsgCallTx{tx, ret, exception})
}
} else {
// The mempool does not call txs until


+ 2
- 11
vm/test/fake_app_state.go View File

@ -14,20 +14,11 @@ type FakeAppState struct {
func (fas *FakeAppState) GetAccount(addr Word256) *Account {
account := fas.accounts[addr.String()]
if account != nil {
return account
} else {
panic(Fmt("Invalid account addr: %X", addr))
}
return account
}
func (fas *FakeAppState) UpdateAccount(account *Account) {
_, ok := fas.accounts[account.Address.String()]
if !ok {
panic(Fmt("Invalid account addr: %X", account.Address))
} else {
// Nothing to do
}
fas.accounts[account.Address.String()] = account
}
func (fas *FakeAppState) RemoveAccount(account *Account) {


+ 105
- 2
vm/test/vm_test.go View File

@ -9,6 +9,8 @@ import (
"time"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/events"
"github.com/tendermint/tendermint/types"
. "github.com/tendermint/tendermint/vm"
)
@ -35,6 +37,7 @@ func makeBytes(n int) []byte {
return b
}
// Runs a basic loop
func TestVM(t *testing.T) {
ourVm := NewVM(newAppState(), newParams(), Zero256, nil)
@ -46,8 +49,8 @@ func TestVM(t *testing.T) {
Address: Uint64ToWord256(101),
}
var gas uint64 = 1000
N := []byte{0xff, 0xff}
var gas uint64 = 100000
N := []byte{0x0f, 0x0f}
// Loop N times
code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)}
for i := 0; i < len(N); i++ {
@ -58,8 +61,12 @@ func TestVM(t *testing.T) {
output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
fmt.Println("Call took:", time.Since(start))
if err != nil {
t.Fatal(err)
}
}
// Tests the code for a subcurrency contract compiled by serpent
func TestSubcurrency(t *testing.T) {
st := newAppState()
// Create accounts
@ -86,7 +93,103 @@ func TestSubcurrency(t *testing.T) {
data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005")
output, err := ourVm.Call(account1, account2, code, data, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
if err != nil {
t.Fatal(err)
}
}
// Test sending tokens from a contract to another account
func TestSendCall(t *testing.T) {
fakeAppState := newAppState()
ourVm := NewVM(fakeAppState, newParams(), Zero256, nil)
// Create accounts
account1 := &Account{
Address: Uint64ToWord256(100),
}
account2 := &Account{
Address: Uint64ToWord256(101),
}
account3 := &Account{
Address: Uint64ToWord256(102),
}
// account1 will call account2 which will trigger CALL opcode to account3
addr := account3.Address.Postfix(20)
contractCode := callContractCode(addr)
//----------------------------------------------
// account2 has insufficient balance, should fail
exception := runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000)
if exception == "" {
t.Fatal("Expected exception")
}
//----------------------------------------------
// give account2 sufficient balance, should pass
account2.Balance = 100000
exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 1000)
if exception != "" {
t.Fatal("Unexpected exception", exception)
}
//----------------------------------------------
// insufficient gas, should fail
account2.Balance = 100000
exception = runVMWaitEvents(t, ourVm, account1, account2, addr, contractCode, 100)
if exception == "" {
t.Fatal("Expected exception")
}
}
// subscribes to an AccReceive, runs the vm, returns the exception
func runVMWaitEvents(t *testing.T, ourVm *VM, caller, callee *Account, subscribeAddr, contractCode []byte, gas uint64) string {
// we need to catch the event from the CALL to check for exceptions
evsw := new(events.EventSwitch)
evsw.Start()
ch := make(chan interface{})
fmt.Printf("subscribe to %x\n", subscribeAddr)
evsw.AddListenerForEvent("test", types.EventStringAccReceive(subscribeAddr), func(msg interface{}) {
ch <- msg
})
evc := events.NewEventCache(evsw)
ourVm.SetFireable(evc)
go func() {
start := time.Now()
output, err := ourVm.Call(caller, callee, contractCode, []byte{}, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
fmt.Println("Call took:", time.Since(start))
if err != nil {
ch <- err.Error()
}
evc.Flush()
}()
msg := <-ch
switch ev := msg.(type) {
case types.EventMsgCallTx:
return ev.Exception
case types.EventMsgCall:
return ev.Exception
case string:
return ev
}
return ""
}
// this is code to call another contract (hardcoded as addr)
func callContractCode(addr []byte) []byte {
gas1, gas2 := byte(0x1), byte(0x1)
value := byte(0x69)
inOff, inSize := byte(0x0), byte(0x0) // no call data
retOff, retSize := byte(0x0), byte(0x20)
// this is the code we want to run (send funds to an account and return)
contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73}
contractCode = append(contractCode, addr...)
contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...)
return contractCode
}
/*


+ 43
- 27
vm/vm.go View File

@ -71,38 +71,40 @@ func (vm *VM) SetFireable(evc events.Fireable) {
// CONTRACT appState is aware of caller and callee, so we can just mutate them.
// value: To be transferred from caller to callee. Refunded upon error.
// gas: Available gas. No refunds for gas.
// code: May be nil, since the CALL opcode may be used to send value from contracts to accounts
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")
}
exception := new(string)
defer func() {
if vm.evc != nil {
vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{
&types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas},
vm.origin.Postfix(20),
vm.txid,
output,
*exception,
})
}
}()
if err = transfer(caller, callee, value); err != nil {
*exception = err.Error()
return
}
vm.callDepth += 1
output, err = vm.call(caller, callee, code, input, value, gas)
vm.callDepth -= 1
exception := ""
if err != nil {
exception = err.Error()
err := transfer(callee, caller, value)
if len(code) > 0 {
vm.callDepth += 1
output, err = vm.call(caller, callee, code, input, value, gas)
vm.callDepth -= 1
if err != nil {
panic("Could not return value to caller")
*exception = err.Error()
err := transfer(callee, caller, value)
if err != nil {
panic("Could not return value to caller")
}
}
}
// if callDepth is 0 the event is fired from ExecTx (along with the Input event)
// otherwise, we fire from here.
if vm.callDepth != 0 && vm.evc != nil {
vm.evc.FireEvent(types.EventStringAccReceive(callee.Address.Postfix(20)), types.EventMsgCall{
&types.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas},
vm.origin.Postfix(20),
vm.txid,
output,
exception,
})
}
return
}
@ -622,7 +624,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
stack.Push(res)
pc += a
dbg.Printf(" => 0x%X\n", res)
stack.Print(10)
//stack.Print(10)
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
n := int(op - DUP1 + 1)
@ -633,7 +635,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
n := int(op - SWAP1 + 2)
stack.Swap(n)
dbg.Printf(" => [%d] %X\n", n, stack.Peek())
stack.Print(10)
//stack.Print(10)
case LOG0, LOG1, LOG2, LOG3, LOG4:
n := int(op - LOG0)
@ -713,18 +715,32 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrInsufficientGas)
}
acc := vm.appState.GetAccount(addr)
if acc == nil {
return nil, firstErr(err, ErrUnknownAddress)
}
// since CALL is used also for sending funds,
// acc may not exist yet. This is an error for
// CALLCODE, but not for CALL, though I don't think
// ethereum actually cares
if op == CALLCODE {
if acc == nil {
return nil, firstErr(err, ErrUnknownAddress)
}
ret, err = vm.Call(callee, callee, acc.Code, args, value, gas)
} else {
if acc == nil {
// if we have not seen the account before, create it
// so we can send funds
acc = &Account{
Address: addr,
}
vm.appState.UpdateAccount(acc)
}
ret, err = vm.Call(callee, acc, acc.Code, args, value, gas)
}
}
// Push result
if err != nil {
dbg.Printf("error on call: %s", err.Error())
// TODO: fire event
stack.Push(Zero256)
} else {
stack.Push(One256)


Loading…
Cancel
Save