示例#1
0
func TestNameTxs(t *testing.T) {
	state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000)

	types.MinNameRegistrationPeriod = 5
	startingBlock := state.LastBlockHeight

	// try some bad names. these should all fail
	names := []string{"", "\n", "123#$%", "\x00", string([]byte{20, 40, 60, 80}), "baffledbythespectacleinallofthisyouseeehesaidwithouteyessurprised", "no spaces please"}
	data := "something about all this just doesn't feel right."
	fee := int64(1000)
	numDesiredBlocks := 5
	for _, name := range names {
		amt := fee + int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
		tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
		tx.Sign(state.ChainID, privAccounts[0])

		if err := execTxWithState(state, tx, true); err == nil {
			t.Fatalf("Expected invalid name error from %s", name)
		}
	}

	// try some bad data. these should all fail
	name := "hold_it_chum"
	datas := []string{"cold&warm", "!@#$%^&*()", "<<<>>>>", "because why would you ever need a ~ or a & or even a % in a json file? make your case and we'll talk"}
	for _, data := range datas {
		amt := fee + int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
		tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
		tx.Sign(state.ChainID, privAccounts[0])

		if err := execTxWithState(state, tx, true); err == nil {
			t.Fatalf("Expected invalid data error from %s", data)
		}
	}

	validateEntry := func(t *testing.T, entry *types.NameRegEntry, name, data string, addr []byte, expires int) {

		if entry == nil {
			t.Fatalf("Could not find name %s", name)
		}
		if bytes.Compare(entry.Owner, addr) != 0 {
			t.Fatalf("Wrong owner. Got %X expected %X", entry.Owner, addr)
		}
		if data != entry.Data {
			t.Fatalf("Wrong data. Got %s expected %s", entry.Data, data)
		}
		if name != entry.Name {
			t.Fatalf("Wrong name. Got %s expected %s", entry.Name, name)
		}
		if expires != entry.Expires {
			t.Fatalf("Wrong expiry. Got %d, expected %d", entry.Expires, expires)
		}
	}

	// try a good one, check data, owner, expiry
	name = "@looking_good/karaoke_bar.broadband"
	data = "on this side of neptune there are 1234567890 people: first is OMNIVORE+-3. Or is it. Ok this is pretty restrictive. No exclamations :(. Faces tho :')"
	amt := fee + int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
	tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[0])
	if err := execTxWithState(state, tx, true); err != nil {
		t.Fatal(err)
	}
	entry := state.GetNameRegEntry(name)
	validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks)

	// fail to update it as non-owner, in same block
	tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[1])
	if err := execTxWithState(state, tx, true); err == nil {
		t.Fatal("Expected error")
	}

	// update it as owner, just to increase expiry, in same block
	// NOTE: we have to resend the data or it will clear it (is this what we want?)
	tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[0])
	if err := execTxWithStateNewBlock(state, tx, true); err != nil {
		t.Fatal(err)
	}
	entry = state.GetNameRegEntry(name)
	validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*2)

	// update it as owner, just to increase expiry, in next block
	tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[0])
	if err := execTxWithStateNewBlock(state, tx, true); err != nil {
		t.Fatal(err)
	}
	entry = state.GetNameRegEntry(name)
	validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*3)

	// fail to update it as non-owner
	state.LastBlockHeight = entry.Expires - 1
	tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[1])
	if err := execTxWithState(state, tx, true); err == nil {
		t.Fatal("Expected error")
	}

	// once expires, non-owner succeeds
	state.LastBlockHeight = entry.Expires
	tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[1])
	if err := execTxWithState(state, tx, true); err != nil {
		t.Fatal(err)
	}
	entry = state.GetNameRegEntry(name)
	validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks)

	// update it as new owner, with new data (longer), but keep the expiry!
	data = "In the beginning there was no thing, not even the beginning. It hadn't been here, no there, nor for that matter anywhere, not especially because it had not to even exist, let alone to not. Nothing especially odd about that."
	oldCredit := amt - fee
	numDesiredBlocks = 10
	amt = fee + (int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data) - oldCredit)
	tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[1])
	if err := execTxWithState(state, tx, true); err != nil {
		t.Fatal(err)
	}
	entry = state.GetNameRegEntry(name)
	validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks)

	// test removal
	amt = fee
	data = ""
	tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[1])
	if err := execTxWithStateNewBlock(state, tx, true); err != nil {
		t.Fatal(err)
	}
	entry = state.GetNameRegEntry(name)
	if entry != nil {
		t.Fatal("Expected removed entry to be nil")
	}

	// create entry by key0,
	// test removal by key1 after expiry
	name = "looking_good/karaoke_bar"
	data = "some data"
	amt = fee + int64(numDesiredBlocks)*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
	tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[0])
	if err := execTxWithState(state, tx, true); err != nil {
		t.Fatal(err)
	}
	entry = state.GetNameRegEntry(name)
	validateEntry(t, entry, name, data, privAccounts[0].Address, state.LastBlockHeight+numDesiredBlocks)
	state.LastBlockHeight = entry.Expires

	amt = fee
	data = ""
	tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
	tx.Sign(state.ChainID, privAccounts[1])
	if err := execTxWithStateNewBlock(state, tx, true); err != nil {
		t.Fatal(err)
	}
	entry = state.GetNameRegEntry(name)
	if entry != nil {
		t.Fatal("Expected removed entry to be nil")
	}
}
示例#2
0
func testNameReg(t *testing.T, typ string) {
	client := clients[typ]
	con := newWSCon(t)

	types.MinNameRegistrationPeriod = 1

	// register a new name, check if its there
	// since entries ought to be unique and these run against different clients, we append the typ
	name := "ye_old_domain_name_" + typ
	data := "if not now, when"
	fee := int64(1000)
	numDesiredBlocks := int64(2)
	amt := fee + numDesiredBlocks*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)

	eid := types.EventStringNameReg(name)
	subscribe(t, con, eid)

	tx := makeDefaultNameTx(t, typ, name, data, amt, fee)
	broadcastTx(t, typ, tx)
	// verify the name by both using the event and by checking get_name
	waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error {
		// TODO: unmarshal the response
		tx, err := unmarshalResponseNameReg(b)
		if err != nil {
			return err
		}
		if tx.Name != name {
			t.Fatal(fmt.Sprintf("Err on received event tx.Name: Got %s, expected %s", tx.Name, name))
		}
		if tx.Data != data {
			t.Fatal(fmt.Sprintf("Err on received event tx.Data: Got %s, expected %s", tx.Data, data))
		}
		return nil
	})
	mempoolCount = 0
	entry := getNameRegEntry(t, typ, name)
	if entry.Data != data {
		t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data))
	}
	if bytes.Compare(entry.Owner, user[0].Address) != 0 {
		t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[0].Address))
	}

	unsubscribe(t, con, eid)

	// for the rest we just use new block event
	// since we already tested the namereg event
	eid = types.EventStringNewBlock()
	subscribe(t, con, eid)
	defer func() {
		unsubscribe(t, con, eid)
		con.Close()
	}()

	// update the data as the owner, make sure still there
	numDesiredBlocks = int64(2)
	data = "these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need"
	amt = fee + numDesiredBlocks*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
	tx = makeDefaultNameTx(t, typ, name, data, amt, fee)
	broadcastTx(t, typ, tx)
	// commit block
	waitForEvent(t, con, eid, true, func() {}, doNothing)
	mempoolCount = 0
	entry = getNameRegEntry(t, typ, name)
	if entry.Data != data {
		t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data))
	}

	// try to update as non owner, should fail
	nonce := getNonce(t, typ, user[1].Address)
	data2 := "this is not my beautiful house"
	tx = types.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce+1)
	tx.Sign(chainID, user[1])
	_, err := client.BroadcastTx(tx)
	if err == nil {
		t.Fatal("Expected error on NameTx")
	}

	// commit block
	waitForEvent(t, con, eid, true, func() {}, doNothing)

	// now the entry should be expired, so we can update as non owner
	_, err = client.BroadcastTx(tx)
	waitForEvent(t, con, eid, true, func() {}, doNothing)
	mempoolCount = 0
	entry = getNameRegEntry(t, typ, name)
	if entry.Data != data2 {
		t.Fatal(fmt.Sprintf("Error on entry.Data: Got %s, expected %s", entry.Data, data2))
	}
	if bytes.Compare(entry.Owner, user[1].Address) != 0 {
		t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[1].Address))
	}
}
示例#3
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) (err error) {

	// TODO: do something with fees
	fees := int64(0)
	_s := blockCache.State() // hack to access validators and block height

	// Exec tx
	switch tx := tx.(type) {
	case *types.SendTx:
		accounts, err := getInputs(blockCache, tx.Inputs)
		if err != nil {
			return err
		}

		// ensure all inputs have send permissions
		if !hasSendPermission(blockCache, accounts) {
			return fmt.Errorf("At least one input lacks permission for SendTx")
		}

		// add outputs to accounts map
		// if any outputs don't exist, all inputs must have CreateAccount perm
		accounts, err = getOrMakeOutputs(blockCache, accounts, tx.Outputs)
		if err != nil {
			return err
		}

		signBytes := acm.SignBytes(_s.ChainID, 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), types.EventDataTx{tx, nil, ""})
			}

			for _, o := range tx.Outputs {
				evc.FireEvent(types.EventStringAccOutput(o.Address), types.EventDataTx{tx, nil, ""})
			}
		}
		return nil

	case *types.CallTx:
		var inAcc, outAcc *acm.Account

		// Validate input
		inAcc = blockCache.GetAccount(tx.Input.Address)
		if inAcc == nil {
			log.Info(Fmt("Can't find in account %X", tx.Input.Address))
			return types.ErrTxInvalidAddress
		}

		createContract := len(tx.Address) == 0
		if createContract {
			if !hasCreateContractPermission(blockCache, inAcc) {
				return fmt.Errorf("Account %X does not have CreateContract permission", tx.Input.Address)
			}
		} else {
			if !hasCallPermission(blockCache, inAcc) {
				return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address)
			}
		}

		// pubKey should be present in either "inAcc" or "tx.Input"
		if err := checkInputPubKey(inAcc, tx.Input); err != nil {
			log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
			return err
		}
		signBytes := acm.SignBytes(_s.ChainID, tx)
		err := validateInput(inAcc, signBytes, tx.Input)
		if err != nil {
			log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
			return err
		}
		if tx.Input.Amount < tx.Fee {
			log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
			return types.ErrTxInsufficientFunds
		}

		if !createContract {
			// Validate output
			if len(tx.Address) != 20 {
				log.Info(Fmt("Destination address is not 20 bytes %X", tx.Address))
				return types.ErrTxInvalidAddress
			}
			// check if its a native contract
			if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) {
				return fmt.Errorf("NativeContracts can not be called using CallTx. Use a contract or the appropriate tx type (eg. PermissionsTx, NameTx)")
			}

			// Output account 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.Info(Fmt("Out account: %v", outAcc))

		// Good!
		value := tx.Input.Amount - tx.Fee
		inAcc.Sequence += 1
		inAcc.Balance -= tx.Fee
		blockCache.UpdateAccount(inAcc)

		// The logic in runCall MUST NOT return.
		if runCall {

			// VM call variables
			var (
				gas     int64       = tx.GasLimit
				err     error       = nil
				caller  *vm.Account = toVMAccount(inAcc)
				callee  *vm.Account = nil // initialized below
				code    []byte      = nil
				ret     []byte      = nil
				txCache             = NewTxCache(blockCache)
				params              = vm.Params{
					BlockHeight: int64(_s.LastBlockHeight),
					BlockHash:   LeftPadWord256(_s.LastBlockHash),
					BlockTime:   _s.LastBlockTime.Unix(),
					GasLimit:    _s.GetGasLimit(),
				}
			)

			if !createContract && (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 contract and call with another
				// you have to wait a block to avoid a re-ordering attack
				// that will take your fees
				if outAcc == nil {
					log.Info(Fmt("%X tries to call %X but it does not exist.",
						inAcc.Address, tx.Address))
				} else {
					log.Info(Fmt("%X tries to call %X but code is blank.",
						inAcc.Address, tx.Address))
				}
				err = types.ErrTxInvalidAddress
				goto CALL_COMPLETE
			}

			// get or create callee
			if createContract {
				// We already checked for permission
				callee = txCache.CreateAccount(caller)
				log.Info(Fmt("Created new contract %X", callee.Address))
				code = tx.Data
			} else {
				callee = toVMAccount(outAcc)
				log.Info(Fmt("Calling contract %X with code %X", callee.Address, callee.Code))
				code = callee.Code
			}
			log.Info(Fmt("Code for this contract: %X", code))

			// Run VM call and sync txCache to blockCache.
			{ // Capture scope for goto.
				// Write caller/callee to txCache.
				txCache.UpdateAccount(caller)
				txCache.UpdateAccount(callee)
				vmach := vm.NewVM(txCache, params, caller.Address, types.TxID(_s.ChainID, 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)
				if err != nil {
					// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
					log.Info(Fmt("Error on execution: %v", err))
					goto CALL_COMPLETE
				}

				log.Info("Successful execution")
				if createContract {
					callee.Code = ret
				}
				txCache.Sync()
			}

		CALL_COMPLETE: // err may or may not be nil.

			// Create a receipt from the ret and whether errored.
			log.Notice("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 {
				exception := ""
				if err != nil {
					exception = err.Error()
				}
				evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, ret, exception})
				evc.FireEvent(types.EventStringAccOutput(tx.Address), types.EventDataTx{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 createContract {
				inAcc.Sequence += 1
			}
			blockCache.UpdateAccount(inAcc)
		}

		return nil

	case *types.NameTx:
		var inAcc *acm.Account

		// Validate input
		inAcc = blockCache.GetAccount(tx.Input.Address)
		if inAcc == nil {
			log.Info(Fmt("Can't find in account %X", tx.Input.Address))
			return types.ErrTxInvalidAddress
		}
		// check permission
		if !hasNamePermission(blockCache, inAcc) {
			return fmt.Errorf("Account %X does not have Name permission", tx.Input.Address)
		}
		// pubKey should be present in either "inAcc" or "tx.Input"
		if err := checkInputPubKey(inAcc, tx.Input); err != nil {
			log.Info(Fmt("Can't find pubkey for %X", tx.Input.Address))
			return err
		}
		signBytes := acm.SignBytes(_s.ChainID, tx)
		err := validateInput(inAcc, signBytes, tx.Input)
		if err != nil {
			log.Info(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
			return err
		}
		// fee is in addition to the amount which is used to determine the TTL
		if tx.Input.Amount < tx.Fee {
			log.Info(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
			return types.ErrTxInsufficientFunds
		}

		// validate the input strings
		if err := tx.ValidateStrings(); err != nil {
			return err
		}

		value := tx.Input.Amount - tx.Fee

		// let's say cost of a name for one block is len(data) + 32
		costPerBlock := types.NameCostPerBlock(types.NameBaseCost(tx.Name, tx.Data))
		expiresIn := int(value / costPerBlock)
		lastBlockHeight := _s.LastBlockHeight

		log.Info("New NameTx", "value", value, "costPerBlock", costPerBlock, "expiresIn", expiresIn, "lastBlock", lastBlockHeight)

		// check if the name exists
		entry := blockCache.GetNameRegEntry(tx.Name)

		if entry != nil {
			var expired bool
			// if the entry already exists, and hasn't expired, we must be owner
			if entry.Expires > lastBlockHeight {
				// ensure we are owner
				if bytes.Compare(entry.Owner, tx.Input.Address) != 0 {
					log.Info(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name))
					return types.ErrTxPermissionDenied
				}
			} else {
				expired = true
			}

			// no value and empty data means delete the entry
			if value == 0 && len(tx.Data) == 0 {
				// maybe we reward you for telling us we can delete this crap
				// (owners if not expired, anyone if expired)
				log.Info("Removing namereg entry", "name", entry.Name)
				blockCache.RemoveNameRegEntry(entry.Name)
			} else {
				// update the entry by bumping the expiry
				// and changing the data
				if expired {
					if expiresIn < types.MinNameRegistrationPeriod {
						return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
					}
					entry.Expires = lastBlockHeight + expiresIn
					entry.Owner = tx.Input.Address
					log.Info("An old namereg entry has expired and been reclaimed", "name", entry.Name, "expiresIn", expiresIn, "owner", entry.Owner)
				} else {
					// since the size of the data may have changed
					// we use the total amount of "credit"
					oldCredit := int64(entry.Expires-lastBlockHeight) * types.NameBaseCost(entry.Name, entry.Data)
					credit := oldCredit + value
					expiresIn = int(credit / costPerBlock)
					if expiresIn < types.MinNameRegistrationPeriod {
						return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
					}
					entry.Expires = lastBlockHeight + expiresIn
					log.Info("Updated namereg entry", "name", entry.Name, "expiresIn", expiresIn, "oldCredit", oldCredit, "value", value, "credit", credit)
				}
				entry.Data = tx.Data
				blockCache.UpdateNameRegEntry(entry)
			}
		} else {
			if expiresIn < types.MinNameRegistrationPeriod {
				return errors.New(Fmt("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod))
			}
			// entry does not exist, so create it
			entry = &types.NameRegEntry{
				Name:    tx.Name,
				Owner:   tx.Input.Address,
				Data:    tx.Data,
				Expires: lastBlockHeight + expiresIn,
			}
			log.Info("Creating namereg entry", "name", entry.Name, "expiresIn", expiresIn)
			blockCache.UpdateNameRegEntry(entry)
		}

		// TODO: something with the value sent?

		// Good!
		inAcc.Sequence += 1
		inAcc.Balance -= value
		blockCache.UpdateAccount(inAcc)

		// TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?

		if evc != nil {
			evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, nil, ""})
			evc.FireEvent(types.EventStringNameReg(tx.Name), types.EventDataTx{tx, nil, ""})
		}

		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 := getInputs(blockCache, tx.Inputs)
		if err != nil {
			return err
		}

		// add outputs to accounts map
		// if any outputs don't exist, all inputs must have CreateAccount perm
		// though outputs aren't created until unbonding/release time
		canCreate := hasCreateAccountPermission(blockCache, accounts)
		for _, out := range tx.UnbondTo {
			acc := blockCache.GetAccount(out.Address)
			if acc == nil && !canCreate {
				return fmt.Errorf("At least one input does not have permission to create accounts")
			}
		}

		bondAcc := blockCache.GetAccount(tx.PubKey.Address())
		if !hasBondPermission(blockCache, bondAcc) {
			return fmt.Errorf("The bonder does not have permission to bond")
		}

		if !hasBondOrSendPermission(blockCache, accounts) {
			return fmt.Errorf("At least one input lacks permission to bond")
		}

		signBytes := acm.SignBytes(_s.ChainID, tx)
		inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
		if err != nil {
			return err
		}
		if !tx.PubKey.VerifyBytes(signBytes, tx.Signature) {
			return types.ErrTxInvalidSignature
		}
		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(&types.ValidatorInfo{
			Address:         tx.PubKey.Address(),
			PubKey:          tx.PubKey,
			UnbondTo:        tx.UnbondTo,
			FirstBondHeight: _s.LastBlockHeight + 1,
			FirstBondAmount: outTotal,
		})
		// Add Validator
		added := _s.BondedValidators.Add(&types.Validator{
			Address:     tx.PubKey.Address(),
			PubKey:      tx.PubKey,
			BondHeight:  _s.LastBlockHeight + 1,
			VotingPower: outTotal,
			Accum:       0,
		})
		if !added {
			PanicCrisis("Failed to add validator")
		}
		if evc != nil {
			// TODO: fire for all inputs
			evc.FireEvent(types.EventStringBond(), types.EventDataTx{tx, nil, ""})
		}
		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 := acm.SignBytes(_s.ChainID, 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(), types.EventDataTx{tx, nil, ""})
		}
		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 := acm.SignBytes(_s.ChainID, tx)
		if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
			return types.ErrTxInvalidSignature
		}

		// tx.Height must be in a suitable range
		minRebondHeight := _s.LastBlockHeight - (validatorTimeoutBlocks / 2)
		maxRebondHeight := _s.LastBlockHeight + 2
		if !((minRebondHeight <= tx.Height) && (tx.Height <= maxRebondHeight)) {
			return errors.New(Fmt("Rebond height not in range.  Expected %v <= %v <= %v",
				minRebondHeight, tx.Height, maxRebondHeight))
		}

		// Good!
		_s.rebondValidator(val)
		if evc != nil {
			evc.FireEvent(types.EventStringRebond(), types.EventDataTx{tx, nil, ""})
		}
		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 := acm.SignBytes(_s.ChainID, &tx.VoteA)
		voteBSignBytes := acm.SignBytes(_s.ChainID, &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.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(), types.EventDataTx{tx, nil, ""})
		}
		return nil

	case *types.PermissionsTx:
		var inAcc *acm.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
		}

		permFlag := tx.PermArgs.PermFlag()
		// check permission
		if !HasPermission(blockCache, inAcc, permFlag) {
			return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag)
		}

		// 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 := acm.SignBytes(_s.ChainID, tx)
		err := validateInput(inAcc, signBytes, tx.Input)
		if err != nil {
			log.Debug(Fmt("validateInput failed on %X: %v", tx.Input.Address, err))
			return err
		}

		value := tx.Input.Amount

		log.Debug("New PermissionsTx", "function", ptypes.PermFlagToString(permFlag), "args", tx.PermArgs)

		var permAcc *acm.Account
		switch args := tx.PermArgs.(type) {
		case *ptypes.HasBaseArgs:
			// this one doesn't make sense from txs
			return fmt.Errorf("HasBase is for contracts, not humans. Just look at the blockchain")
		case *ptypes.SetBaseArgs:
			if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
				return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address)
			}
			err = permAcc.Permissions.Base.Set(args.Permission, args.Value)
		case *ptypes.UnsetBaseArgs:
			if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
				return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address)
			}
			err = permAcc.Permissions.Base.Unset(args.Permission)
		case *ptypes.SetGlobalArgs:
			if permAcc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress); permAcc == nil {
				PanicSanity("can't find global permissions account")
			}
			err = permAcc.Permissions.Base.Set(args.Permission, args.Value)
		case *ptypes.HasRoleArgs:
			return fmt.Errorf("HasRole is for contracts, not humans. Just look at the blockchain")
		case *ptypes.AddRoleArgs:
			if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
				return fmt.Errorf("Trying to update roles for unknown account %X", args.Address)
			}
			if !permAcc.Permissions.AddRole(args.Role) {
				return fmt.Errorf("Role (%s) already exists for account %X", args.Role, args.Address)
			}
		case *ptypes.RmRoleArgs:
			if permAcc = blockCache.GetAccount(args.Address); permAcc == nil {
				return fmt.Errorf("Trying to update roles for unknown account %X", args.Address)
			}
			if !permAcc.Permissions.RmRole(args.Role) {
				return fmt.Errorf("Role (%s) does not exist for account %X", args.Role, args.Address)
			}
		default:
			PanicSanity(Fmt("invalid permission function: %s", ptypes.PermFlagToString(permFlag)))
		}

		// TODO: maybe we want to take funds on error and allow txs in that don't do anythingi?
		if err != nil {
			return err
		}

		// Good!
		inAcc.Sequence += 1
		inAcc.Balance -= value
		blockCache.UpdateAccount(inAcc)
		if permAcc != nil {
			blockCache.UpdateAccount(permAcc)
		}

		if evc != nil {
			evc.FireEvent(types.EventStringAccInput(tx.Input.Address), types.EventDataTx{tx, nil, ""})
			evc.FireEvent(types.EventStringPermissions(ptypes.PermFlagToString(permFlag)), types.EventDataTx{tx, nil, ""})
		}

		return nil

	default:
		// binary decoding should not let this happen
		PanicSanity("Unknown Tx type")
		return nil
	}
}