diff --git a/rpc/test/client_ws_test.go b/rpc/test/client_ws_test.go index c61d08a76..a5d0880c7 100644 --- a/rpc/test/client_ws_test.go +++ b/rpc/test/client_ws_test.go @@ -9,6 +9,8 @@ import ( "github.com/tendermint/tendermint/types" ) +var wsTyp = "JSONRPC" + //-------------------------------------------------------------------------------- // Test the websocket service @@ -52,7 +54,7 @@ func TestWSSend(t *testing.T) { amt := uint64(100) con := newWSCon(t) - eidInput := types.EventStringAccInput(userByteAddr) + eidInput := types.EventStringAccInput(user[0].Address) eidOutput := types.EventStringAccOutput(toAddr) subscribe(t, con, eidInput) subscribe(t, con, eidOutput) @@ -62,7 +64,8 @@ func TestWSSend(t *testing.T) { con.Close() }() waitForEvent(t, con, eidInput, true, func() { - broadcastTx(t, "JSONRPC", userByteAddr, toAddr, nil, userBytePriv, amt, 0, 0) + tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt) + broadcastTx(t, wsTyp, tx) }, unmarshalValidateSend(amt, toAddr)) waitForEvent(t, con, eidOutput, true, func() {}, unmarshalValidateSend(amt, toAddr)) } @@ -70,7 +73,7 @@ func TestWSSend(t *testing.T) { // ensure events are only fired once for a given transaction func TestWSDoubleFire(t *testing.T) { con := newWSCon(t) - eid := types.EventStringAccInput(userByteAddr) + eid := types.EventStringAccInput(user[0].Address) subscribe(t, con, eid) defer func() { unsubscribe(t, con, eid) @@ -80,7 +83,8 @@ func TestWSDoubleFire(t *testing.T) { toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54} // broadcast the transaction, wait to hear about it waitForEvent(t, con, eid, true, func() { - broadcastTx(t, "JSONRPC", userByteAddr, toAddr, nil, userBytePriv, amt, 0, 0) + tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt) + broadcastTx(t, wsTyp, tx) }, func(eid string, b []byte) error { return nil }) @@ -94,18 +98,19 @@ func TestWSDoubleFire(t *testing.T) { // create a contract, wait for the event, and send it a msg, validate the return func TestWSCallWait(t *testing.T) { con := newWSCon(t) - eid1 := types.EventStringAccInput(userByteAddr) + eid1 := types.EventStringAccInput(user[0].Address) subscribe(t, con, eid1) defer func() { unsubscribe(t, con, eid1) con.Close() }() - amt := uint64(10000) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, returnCode, returnVal := simpleContract() var contractAddr []byte // wait for the contract to be created waitForEvent(t, con, eid1, true, func() { - _, receipt := broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) + tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) + receipt := broadcastTx(t, wsTyp, tx) contractAddr = receipt.ContractAddr }, unmarshalValidateCall(amt, returnCode)) @@ -117,9 +122,11 @@ func TestWSCallWait(t *testing.T) { unsubscribe(t, con, eid2) }() // get the return value from a call - data := []byte{0x1} // just needs to be non empty for this to be a CallTx + data := []byte{0x1} waitForEvent(t, con, eid2, true, func() { - broadcastTx(t, "JSONRPC", userByteAddr, contractAddr, data, userBytePriv, amt, 1000, 1000) + tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee) + receipt := broadcastTx(t, wsTyp, tx) + contractAddr = receipt.ContractAddr }, unmarshalValidateCall(amt, returnVal)) } @@ -127,10 +134,11 @@ func TestWSCallWait(t *testing.T) { // and validate return func TestWSCallNoWait(t *testing.T) { con := newWSCon(t) - amt := uint64(10000) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, _, returnVal := simpleContract() - _, receipt := broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) + tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) + receipt := broadcastTx(t, wsTyp, tx) contractAddr := receipt.ContractAddr // susbscribe to the new contract @@ -142,24 +150,28 @@ func TestWSCallNoWait(t *testing.T) { con.Close() }() // get the return value from a call - data := []byte{0x1} // just needs to be non empty for this to be a CallTx + data := []byte{0x1} waitForEvent(t, con, eid, true, func() { - broadcastTx(t, "JSONRPC", userByteAddr, contractAddr, data, userBytePriv, amt, 1000, 1000) + tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee) + broadcastTx(t, wsTyp, tx) }, unmarshalValidateCall(amt, returnVal)) } // create two contracts, one of which calls the other func TestWSCallCall(t *testing.T) { con := newWSCon(t) - amt := uint64(10000) + amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) code, _, returnVal := simpleContract() txid := new([]byte) // deploy the two contracts - _, receipt := broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) + tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) + receipt := broadcastTx(t, wsTyp, tx) contractAddr1 := receipt.ContractAddr + code, _, _ = simpleCallContract(contractAddr1) - _, receipt = broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) + tx = makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) + receipt = broadcastTx(t, wsTyp, tx) contractAddr2 := receipt.ContractAddr // susbscribe to the new contracts @@ -171,7 +183,6 @@ func TestWSCallCall(t *testing.T) { 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() { @@ -180,7 +191,8 @@ func TestWSCallCall(t *testing.T) { }) // call it waitForEvent(t, con, eid1, true, func() { - tx, _ := broadcastTx(t, "JSONRPC", userByteAddr, contractAddr2, data, userBytePriv, amt, 1000, 1000) + tx := makeDefaultCallTx(t, wsTyp, contractAddr2, nil, amt, gasLim, fee) + broadcastTx(t, wsTyp, tx) *txid = account.HashSignBytes(tx) - }, unmarshalValidateCallCall(userByteAddr, returnVal, txid)) + }, unmarshalValidateCallCall(user[0].Address, returnVal, txid)) } diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index a8cd2b1cb..8602c0bf5 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -3,6 +3,10 @@ package rpctest import ( "bytes" "encoding/hex" + "strconv" + "testing" + "time" + "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/consensus" @@ -12,8 +16,6 @@ import ( cclient "github.com/tendermint/tendermint/rpc/core_client" "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" - "testing" - "time" ) // global variables for use across all tests @@ -26,10 +28,9 @@ var ( mempoolCount = 0 - userAddr = "1D7A91CB32F758A02EBB9BE1FB6F8DEE56F90D42" - userPriv = "C453604BD6480D5538B4C6FD2E3E314B5BCE518D75ADE4DA3DA85AB8ADFD819606FBAC4E285285D1D91FCBC7E91C780ADA11516F67462340B3980CE2B94940E8" - userPub = "06FBAC4E285285D1D91FCBC7E91C780ADA11516F67462340B3980CE2B94940E8" - userByteAddr, userBytePriv = initUserBytes() + // make keys + userPriv = "C453604BD6480D5538B4C6FD2E3E314B5BCE518D75ADE4DA3DA85AB8ADFD819606FBAC4E285285D1D91FCBC7E91C780ADA11516F67462340B3980CE2B94940E8" + user = makeUsers(2) clients = map[string]cclient.Client{ "JSONRPC": cclient.NewClient(requestAddr, "JSONRPC"), @@ -37,22 +38,21 @@ var ( } ) -// returns byte versions of address and private key -// type [64]byte needed by account.GenPrivAccountFromKey -func initUserBytes() ([]byte, [64]byte) { - byteAddr, _ := hex.DecodeString(userAddr) +func makeUsers(n int) []*account.PrivAccount { + accounts := []*account.PrivAccount{} + for i := 0; i < n; i++ { + secret := []byte("mysecret" + strconv.Itoa(i)) + user := account.GenPrivAccountFromSecret(secret) + accounts = append(accounts, user) + } + + // include our validator var byteKey [64]byte userPrivByteSlice, _ := hex.DecodeString(userPriv) copy(byteKey[:], userPrivByteSlice) - return byteAddr, byteKey -} - -func decodeHex(hexStr string) []byte { - bytes, err := hex.DecodeString(hexStr) - if err != nil { - panic(err) - } - return bytes + privAcc := account.GenPrivAccountFromKey(byteKey) + accounts[0] = privAcc + return accounts } // create a new node and sleep forever @@ -76,9 +76,9 @@ func newNode(ready chan struct{}) { func init() { // Save new priv_validator file. priv := &state.PrivValidator{ - Address: decodeHex(userAddr), - PubKey: account.PubKeyEd25519(decodeHex(userPub)), - PrivKey: account.PrivKeyEd25519(decodeHex(userPriv)), + Address: user[0].Address, + PubKey: account.PubKeyEd25519(user[0].PubKey.(account.PubKeyEd25519)), + PrivKey: account.PrivKeyEd25519(user[0].PrivKey.(account.PrivKeyEd25519)), } priv.SetFile(config.GetString("priv_validator_file")) priv.Save() @@ -93,71 +93,45 @@ func init() { } //------------------------------------------------------------------------------- -// make transactions - -// make a send tx (uses get account to figure out the nonce) -func makeSendTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.SendTx { - acc := getAccount(t, typ, from) - nonce := 0 - if acc != nil { - nonce = int(acc.Sequence) + 1 - } - bytePub, err := hex.DecodeString(userPub) - if err != nil { - t.Fatal(err) - } - tx := &types.SendTx{ - Inputs: []*types.TxInput{ - &types.TxInput{ - Address: from, - Amount: amt, - Sequence: uint(nonce), - Signature: account.SignatureEd25519{}, - PubKey: account.PubKeyEd25519(bytePub), - }, - }, - Outputs: []*types.TxOutput{ - &types.TxOutput{ - Address: to, - Amount: amt, - }, - }, - } +// some default transaction functions + +func makeDefaultSendTx(t *testing.T, typ string, addr []byte, amt uint64) *types.SendTx { + nonce := getNonce(t, typ, user[0].Address) + tx := types.NewSendTx() + tx.AddInputWithNonce(user[0].PubKey, amt, nonce) + tx.AddOutput(addr, amt) return tx } -// make a call tx (uses get account to figure out the nonce) -func makeCallTx(t *testing.T, typ string, from, to, data []byte, amt, gaslim, fee uint64) *types.CallTx { - acc := getAccount(t, typ, from) - nonce := 0 - if acc != nil { - nonce = int(acc.Sequence) + 1 - } +func makeDefaultSendTxSigned(t *testing.T, typ string, addr []byte, amt uint64) *types.SendTx { + tx := makeDefaultSendTx(t, typ, addr, amt) + tx.SignInput(0, user[0]) + return tx +} - bytePub, err := hex.DecodeString(userPub) +func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, fee uint64) *types.CallTx { + nonce := getNonce(t, typ, user[0].Address) + tx := types.NewCallTxWithNonce(user[0].PubKey, addr, code, amt, gasLim, fee, nonce) + tx.Sign(user[0]) + return tx +} + +//------------------------------------------------------------------------------- +// rpc call wrappers (fail on err) + +// get an account's nonce +func getNonce(t *testing.T, typ string, addr []byte) uint64 { + client := clients[typ] + ac, err := client.GetAccount(addr) if err != nil { t.Fatal(err) } - tx := &types.CallTx{ - Input: &types.TxInput{ - Address: from, - Amount: amt, - Sequence: uint(nonce), - Signature: account.SignatureEd25519{}, - PubKey: account.PubKeyEd25519(bytePub), - }, - Address: to, - GasLimit: gaslim, - Fee: fee, - Data: data, + if ac.Account == nil { + return 0 } - return tx + return uint64(ac.Account.Sequence) } -// make transactions -//------------------------------------------------------------------------------- -// rpc call wrappers - // get the account func getAccount(t *testing.T, typ string, addr []byte) *account.Account { client := clients[typ] @@ -168,38 +142,25 @@ func getAccount(t *testing.T, typ string, addr []byte) *account.Account { return ac.Account } -// make and sign transaction -func signTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [64]byte, amt, gaslim, fee uint64) (types.Tx, *account.PrivAccount) { - var tx types.Tx - if data == nil { - tx = makeSendTx(t, typ, fromAddr, toAddr, amt) - } else { - tx = makeCallTx(t, typ, fromAddr, toAddr, data, amt, gaslim, fee) - } - - privAcc := account.GenPrivAccountFromKey(key) - if bytes.Compare(privAcc.PubKey.Address(), fromAddr) != 0 { - t.Fatal("Failed to generate correct priv acc") - } - +// sign transaction +func signTx(t *testing.T, typ string, tx types.Tx, privAcc *account.PrivAccount) types.Tx { client := clients[typ] resp, err := client.SignTx(tx, []*account.PrivAccount{privAcc}) if err != nil { t.Fatal(err) } - return resp.Tx, privAcc + return resp.Tx } -// create, sign, and broadcast a transaction -func broadcastTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [64]byte, amt, gaslim, fee uint64) (types.Tx, ctypes.Receipt) { - tx, _ := signTx(t, typ, fromAddr, toAddr, data, key, amt, gaslim, fee) +// broadcast transaction +func broadcastTx(t *testing.T, typ string, tx types.Tx) ctypes.Receipt { client := clients[typ] resp, err := client.BroadcastTx(tx) if err != nil { t.Fatal(err) } mempoolCount += 1 - return tx, resp.Receipt + return resp.Receipt } // dump all storage for an account. currently unused diff --git a/rpc/test/tests.go b/rpc/test/tests.go index 06cd62e8e..40819e0b1 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -3,6 +3,7 @@ package rpctest import ( "bytes" "fmt" + "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/types" "testing" @@ -32,34 +33,47 @@ func testGenPriv(t *testing.T, typ string) { } func testGetAccount(t *testing.T, typ string) { - acc := getAccount(t, typ, userByteAddr) + acc := getAccount(t, typ, user[0].Address) if acc == nil { t.Fatalf("Account was nil") } - if bytes.Compare(acc.Address, userByteAddr) != 0 { - t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, userByteAddr) + if bytes.Compare(acc.Address, user[0].Address) != 0 { + t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, user[0].Address) } } func testSignedTx(t *testing.T, typ string) { amt := uint64(100) toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54} - tx, priv := signTx(t, typ, userByteAddr, toAddr, nil, userBytePriv, amt, 0, 0) - checkTx(t, userByteAddr, priv, tx.(*types.SendTx)) + testOneSignTx(t, typ, toAddr, amt) toAddr = []byte{20, 143, 24, 63, 16, 17, 83, 29, 90, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54} - tx, priv = signTx(t, typ, userByteAddr, toAddr, nil, userBytePriv, amt, 0, 0) - checkTx(t, userByteAddr, priv, tx.(*types.SendTx)) + testOneSignTx(t, typ, toAddr, amt) toAddr = []byte{0, 0, 4, 0, 0, 4, 0, 0, 4, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54} - tx, priv = signTx(t, typ, userByteAddr, toAddr, nil, userBytePriv, amt, 0, 0) - checkTx(t, userByteAddr, priv, tx.(*types.SendTx)) + testOneSignTx(t, typ, toAddr, amt) +} + +func testOneSignTx(t *testing.T, typ string, addr []byte, amt uint64) { + tx := makeDefaultSendTx(t, typ, addr, amt) + tx2 := signTx(t, typ, tx, user[0]) + tx2hash := account.HashSignBytes(tx2) + tx.SignInput(0, user[0]) + txhash := account.HashSignBytes(tx) + if bytes.Compare(txhash, tx2hash) != 0 { + t.Fatal("Got different signatures for signing via rpc vs tx_utils") + } + + tx_ := signTx(t, typ, tx, user[0]) + tx = tx_.(*types.SendTx) + checkTx(t, user[0].Address, user[0], tx) } func testBroadcastTx(t *testing.T, typ string) { amt := uint64(100) toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54} - tx, receipt := broadcastTx(t, typ, userByteAddr, toAddr, nil, userBytePriv, amt, 0, 0) + tx := makeDefaultSendTxSigned(t, typ, toAddr, amt) + receipt := broadcastTx(t, typ, tx) if receipt.CreatesContract > 0 { t.Fatal("This tx does not create a contract") } @@ -90,9 +104,10 @@ func testGetStorage(t *testing.T, typ string) { con.Close() }() - amt := uint64(1100) + amt, gasLim, fee := uint64(1100), uint64(1000), uint64(1000) code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} - _, receipt := broadcastTx(t, typ, userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) + tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee) + receipt := broadcastTx(t, typ, tx) if receipt.CreatesContract == 0 { t.Fatal("This tx creates a contract") } @@ -147,9 +162,11 @@ func testCall(t *testing.T, typ string) { client := clients[typ] // create the contract - amt := uint64(6969) + amt, gasLim, fee := uint64(6969), uint64(1000), uint64(1000) code, _, _ := simpleContract() - _, receipt := broadcastTx(t, typ, userByteAddr, nil, code, userBytePriv, amt, 1000, 1000) + tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee) + receipt := broadcastTx(t, typ, tx) + if receipt.CreatesContract == 0 { t.Fatal("This tx creates a contract") } diff --git a/rpc/test/ws_helpers.go b/rpc/test/ws_helpers.go index a7e6edf0d..d9a581646 100644 --- a/rpc/test/ws_helpers.go +++ b/rpc/test/ws_helpers.go @@ -10,8 +10,8 @@ import ( "github.com/gorilla/websocket" "github.com/tendermint/tendermint/binary" - "github.com/tendermint/tendermint/rpc/types" _ "github.com/tendermint/tendermint/config/tendermint_test" + "github.com/tendermint/tendermint/rpc/types" "github.com/tendermint/tendermint/types" ) @@ -173,14 +173,14 @@ func unmarshalValidateSend(amt uint64, toAddr []byte) func(string, []byte) error return fmt.Errorf("Eventid is not correct. Got %s, expected %s", response.Event, eid) } tx := response.Data - if bytes.Compare(tx.Inputs[0].Address, userByteAddr) != 0 { - return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, userByteAddr) + if bytes.Compare(tx.Inputs[0].Address, user[0].Address) != 0 { + return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, user[0].Address) } if tx.Inputs[0].Amount != amt { return fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt) } if bytes.Compare(tx.Outputs[0].Address, toAddr) != 0 { - return fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, userByteAddr) + return fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, user[0].Address) } return nil } @@ -210,8 +210,8 @@ func unmarshalValidateCall(amt uint64, returnCode []byte) func(string, []byte) e return fmt.Errorf(response.Data.Exception) } tx := response.Data.Tx - if bytes.Compare(tx.Input.Address, userByteAddr) != 0 { - return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Input.Address, userByteAddr) + if bytes.Compare(tx.Input.Address, user[0].Address) != 0 { + return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Input.Address, user[0].Address) } if tx.Input.Amount != amt { return fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Input.Amount, amt) diff --git a/types/tx_utils.go b/types/tx_utils.go index b307c88b4..29a7b3ab3 100644 --- a/types/tx_utils.go +++ b/types/tx_utils.go @@ -36,6 +36,18 @@ func (tx *SendTx) AddInput(st AccountGetter, pubkey account.PubKey, amt uint64) return nil } +func (tx *SendTx) AddInputWithNonce(pubkey account.PubKey, amt, nonce uint64) error { + addr := pubkey.Address() + tx.Inputs = append(tx.Inputs, &TxInput{ + Address: addr, + Amount: amt, + Sequence: uint(nonce) + 1, + Signature: account.SignatureEd25519{}, + PubKey: pubkey, + }) + return nil +} + func (tx *SendTx) AddOutput(addr []byte, amt uint64) error { tx.Outputs = append(tx.Outputs, &TxOutput{ Address: addr, @@ -63,10 +75,16 @@ func NewCallTx(st AccountGetter, from account.PubKey, to, data []byte, amt, gasL return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from) } + nonce := uint64(acc.Sequence) + return NewCallTxWithNonce(from, to, data, amt, gasLimit, fee, nonce), nil +} + +func NewCallTxWithNonce(from account.PubKey, to, data []byte, amt, gasLimit, fee, nonce uint64) *CallTx { + addr := from.Address() input := &TxInput{ Address: addr, Amount: amt, - Sequence: uint(acc.Sequence) + 1, + Sequence: uint(nonce) + 1, Signature: account.SignatureEd25519{}, PubKey: from, } @@ -77,7 +95,7 @@ func NewCallTx(st AccountGetter, from account.PubKey, to, data []byte, amt, gasL GasLimit: gasLimit, Fee: fee, Data: data, - }, nil + } } func (tx *CallTx) Sign(privAccount *account.PrivAccount) { @@ -117,6 +135,18 @@ func (tx *BondTx) AddInput(st AccountGetter, pubkey account.PubKey, amt uint64) return nil } +func (tx *BondTx) AddInputWithNonce(pubkey account.PubKey, amt, nonce uint64) error { + addr := pubkey.Address() + tx.Inputs = append(tx.Inputs, &TxInput{ + Address: addr, + Amount: amt, + Sequence: uint(nonce) + 1, + Signature: account.SignatureEd25519{}, + PubKey: pubkey, + }) + return nil +} + func (tx *BondTx) AddOutput(addr []byte, amt uint64) error { tx.UnbondTo = append(tx.UnbondTo, &TxOutput{ Address: addr,