Пример #1
0
// 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))
}
Пример #2
0
// 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")
	}
}