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") } }
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)) } }
// 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 } }