// create two contracts, one of which calls the other func TestWSCallCall(t *testing.T) { con := newWSCon(t) amt := uint64(10000) code, _, returnVal := simpleContract() txid := new([]byte) // deploy the two contracts _, receipt := broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) contractAddr1 := receipt.ContractAddr code, _, _ = simpleCallContract(contractAddr1) _, receipt = broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) contractAddr2 := receipt.ContractAddr // susbscribe to the new contracts amt = uint64(10001) eid1 := types.EventStringAccReceive(contractAddr1) subscribe(t, con, eid1) defer func() { unsubscribe(t, con, eid1) 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) }, unmarshalValidateCallCall(userByteAddr, returnVal, txid)) }
// If the tx is invalid, an error will be returned. // Unlike ExecBlock(), state will not be altered. func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Fireable) error { // TODO: do something with fees fees := uint64(0) _s := blockCache.State() // hack to access validators and event switch. // Exec tx switch tx := tx_.(type) { case *types.SendTx: accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, tx.Outputs) if err != nil { return err } signBytes := account.SignBytes(tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { return err } outTotal, err := validateOutputs(tx.Outputs) if err != nil { return err } if outTotal > inTotal { return types.ErrTxInsufficientFunds } fee := inTotal - outTotal fees += fee // Good! Adjust accounts adjustByInputs(accounts, tx.Inputs) adjustByOutputs(accounts, tx.Outputs) for _, acc := range accounts { blockCache.UpdateAccount(acc) } // if the evc is nil, nothing will happen if evc != nil { for _, i := range tx.Inputs { evc.FireEvent(types.EventStringAccInput(i.Address), tx) } for _, o := range tx.Outputs { evc.FireEvent(types.EventStringAccOutput(o.Address), tx) } } return nil case *types.CallTx: var inAcc, outAcc *account.Account // Validate input inAcc = blockCache.GetAccount(tx.Input.Address) if inAcc == nil { log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) return types.ErrTxInvalidAddress } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) return err } signBytes := account.SignBytes(tx) err := validateInput(inAcc, signBytes, tx.Input) if err != nil { log.Debug(Fmt("validateInput failed on %X:", tx.Input.Address)) return err } if tx.Input.Amount < tx.Fee { log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) return types.ErrTxInsufficientFunds } createAccount := len(tx.Address) == 0 if !createAccount { // Validate output if len(tx.Address) != 20 { log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address)) return types.ErrTxInvalidAddress } // this 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 // and then this won't return nil. otherwise, we take their fee outAcc = blockCache.GetAccount(tx.Address) } log.Debug(Fmt("Out account: %v", outAcc)) // Good! value := tx.Input.Amount - tx.Fee inAcc.Sequence += 1 if runCall { var ( gas uint64 = tx.GasLimit err error = nil caller *vm.Account = toVMAccount(inAcc) callee *vm.Account = nil code []byte = nil txCache = NewTxCache(blockCache) params = vm.Params{ BlockHeight: uint64(_s.LastBlockHeight), BlockHash: LeftPadWord256(_s.LastBlockHash), BlockTime: _s.LastBlockTime.Unix(), GasLimit: 10000000, } ) // Maybe create a new callee account if // this transaction is creating a new contract. if !createAccount { 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) 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 } callee = toVMAccount(outAcc) code = callee.Code log.Debug(Fmt("Calling contract %X with code %X", callee.Address, callee.Code)) } else { callee = txCache.CreateAccount(caller) log.Debug(Fmt("Created new account %X", callee.Address)) code = tx.Data } log.Debug(Fmt("Code for this contract: %X", code)) txCache.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe. txCache.UpdateAccount(callee) // because we adjusted by input above. vmach := vm.NewVM(txCache, params, caller.Address, account.HashSignBytes(tx)) vmach.SetFireable(evc) // NOTE: Call() transfers the value from caller to callee iff call succeeds. ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) exception := "" if err != nil { exception = err.Error() // Failure. Charge the gas fee. The 'value' was otherwise not transferred. log.Debug(Fmt("Error on execution: %v", err)) inAcc.Balance -= tx.Fee blockCache.UpdateAccount(inAcc) // Throw away 'txCache' which holds incomplete updates (don't sync it). } else { log.Debug("Successful execution") // Success if createAccount { callee.Code = ret } txCache.Sync() } // Create a receipt from the ret and whether errored. log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err) // Fire Events for sender and receiver // 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.EventStringAccOutput(tx.Address), types.EventMsgCallTx{tx, ret, exception}) } } else { // The mempool does not call txs until // the proposer determines the order of txs. // So mempool will skip the actual .Call(), // and only deduct from the caller's balance. inAcc.Balance -= value if createAccount { inAcc.Sequence += 1 } blockCache.UpdateAccount(inAcc) } return nil case *types.BondTx: valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address()) if valInfo != nil { // TODO: In the future, check that the validator wasn't destroyed, // add funds, merge UnbondTo outputs, and unbond validator. return errors.New("Adding coins to existing validators not yet supported") } accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, nil) if err != nil { return err } signBytes := account.SignBytes(tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { return err } if err := tx.PubKey.ValidateBasic(); err != nil { return err } outTotal, err := validateOutputs(tx.UnbondTo) if err != nil { return err } if outTotal > inTotal { return types.ErrTxInsufficientFunds } fee := inTotal - outTotal fees += fee // Good! Adjust accounts adjustByInputs(accounts, tx.Inputs) for _, acc := range accounts { blockCache.UpdateAccount(acc) } // Add ValidatorInfo _s.SetValidatorInfo(&ValidatorInfo{ Address: tx.PubKey.Address(), PubKey: tx.PubKey, UnbondTo: tx.UnbondTo, FirstBondHeight: _s.LastBlockHeight + 1, FirstBondAmount: outTotal, }) // Add Validator added := _s.BondedValidators.Add(&Validator{ Address: tx.PubKey.Address(), PubKey: tx.PubKey, BondHeight: _s.LastBlockHeight + 1, VotingPower: outTotal, Accum: 0, }) if !added { panic("Failed to add validator") } if evc != nil { evc.FireEvent(types.EventStringBond(), tx) } return nil case *types.UnbondTx: // The validator must be active _, val := _s.BondedValidators.GetByAddress(tx.Address) if val == nil { return types.ErrTxInvalidAddress } // Verify the signature signBytes := account.SignBytes(tx) if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { return types.ErrTxInvalidSignature } // tx.Height must be greater than val.LastCommitHeight if tx.Height <= val.LastCommitHeight { return errors.New("Invalid unbond height") } // Good! _s.unbondValidator(val) if evc != nil { evc.FireEvent(types.EventStringUnbond(), tx) } return nil case *types.RebondTx: // The validator must be inactive _, val := _s.UnbondingValidators.GetByAddress(tx.Address) if val == nil { return types.ErrTxInvalidAddress } // Verify the signature signBytes := account.SignBytes(tx) if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { return types.ErrTxInvalidSignature } // tx.Height must be equal to the next height if tx.Height != _s.LastBlockHeight+1 { return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", _s.LastBlockHeight+1, tx.Height)) } // Good! _s.rebondValidator(val) if evc != nil { evc.FireEvent(types.EventStringRebond(), tx) } return nil case *types.DupeoutTx: // Verify the signatures _, accused := _s.BondedValidators.GetByAddress(tx.Address) if accused == nil { _, accused = _s.UnbondingValidators.GetByAddress(tx.Address) if accused == nil { return types.ErrTxInvalidAddress } } voteASignBytes := account.SignBytes(&tx.VoteA) voteBSignBytes := account.SignBytes(&tx.VoteB) if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) || !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) { return types.ErrTxInvalidSignature } // Verify equivocation // TODO: in the future, just require one vote from a previous height that // doesn't exist on this chain. if tx.VoteA.Height != tx.VoteB.Height { return errors.New("DupeoutTx heights don't match") } if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round { // Check special case (not an error, validator must be slashed!) // Validators should not sign another vote after committing. } else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round { // We need to check both orderings of the votes } else { if tx.VoteA.Round != tx.VoteB.Round { return errors.New("DupeoutTx rounds don't match") } if tx.VoteA.Type != tx.VoteB.Type { return errors.New("DupeoutTx types don't match") } if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { return errors.New("DupeoutTx blockhashes shouldn't match") } } // Good! (Bad validator!) _s.destroyValidator(accused) if evc != nil { evc.FireEvent(types.EventStringDupeout(), tx) } return nil default: panic("Unknown Tx type") } }