diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ca0189666..f574667110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ FEATURES: * Context now has access to the application-configured logger * Add (non-proof) subspace query helper functions * Add more staking query functions: candidates, delegator-bonds +* Bank module now tags transactions with sender/recipient for indexing & later retrieval +* Stake module now tags transactions with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy BUG FIXES * Gaia now uses stake, ported from github.com/cosmos/gaia diff --git a/examples/democoin/x/cool/handler.go b/examples/democoin/x/cool/handler.go index c82fa4ae43..b4375c5dcd 100644 --- a/examples/democoin/x/cool/handler.go +++ b/examples/democoin/x/cool/handler.go @@ -53,7 +53,7 @@ func handleMsgQuiz(ctx sdk.Context, k Keeper, msg MsgQuiz) sdk.Result { bonusCoins := sdk.Coins{{msg.CoolAnswer, 69}} - _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins) + _, _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins) if err != nil { return err.Result() } diff --git a/examples/democoin/x/pow/keeper.go b/examples/democoin/x/pow/keeper.go index 931e41a320..35fccf7424 100644 --- a/examples/democoin/x/pow/keeper.go +++ b/examples/democoin/x/pow/keeper.go @@ -125,7 +125,7 @@ func (k Keeper) CheckValid(ctx sdk.Context, difficulty uint64, count uint64) (ui // Add some coins for a POW well done func (k Keeper) ApplyValid(ctx sdk.Context, sender sdk.Address, newDifficulty uint64, newCount uint64) sdk.Error { - _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.Coin{k.config.Denomination, k.config.Reward}}) + _, _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.Coin{k.config.Denomination, k.config.Reward}}) if ckErr != nil { return ckErr } diff --git a/examples/democoin/x/simplestake/keeper.go b/examples/democoin/x/simplestake/keeper.go index 7b61c36236..5bd2639610 100644 --- a/examples/democoin/x/simplestake/keeper.go +++ b/examples/democoin/x/simplestake/keeper.go @@ -66,7 +66,7 @@ func (k Keeper) Bond(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, st return 0, ErrIncorrectStakingToken(k.codespace) } - _, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake}) + _, _, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake}) if err != nil { return 0, err } @@ -95,7 +95,7 @@ func (k Keeper) Unbond(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, returnedBond := sdk.Coin{stakingToken, bi.Power} - _, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond}) + _, _, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond}) if err != nil { return bi.PubKey, bi.Power, err } diff --git a/types/result.go b/types/result.go index f4f7454e2c..65f87400d2 100644 --- a/types/result.go +++ b/types/result.go @@ -2,7 +2,6 @@ package types import ( abci "github.com/tendermint/abci/types" - cmn "github.com/tendermint/tmlibs/common" ) // Result is the union of ResponseDeliverTx and ResponseCheckTx. @@ -31,7 +30,7 @@ type Result struct { ValidatorUpdates []abci.Validator // Tags are used for transaction indexing and pubsub. - Tags []cmn.KVPair + Tags Tags } // TODO: In the future, more codes may be OK. diff --git a/types/tags.go b/types/tags.go new file mode 100644 index 0000000000..95a826fd78 --- /dev/null +++ b/types/tags.go @@ -0,0 +1,48 @@ +package types + +import ( + cmn "github.com/tendermint/tmlibs/common" +) + +// Type synonym for convenience +type Tag = cmn.KVPair + +// Type synonym for convenience +type Tags cmn.KVPairs + +// New empty tags +func EmptyTags() Tags { + return make(Tags, 0) +} + +// Append a single tag +func (t Tags) AppendTag(k string, v []byte) Tags { + return append(t, MakeTag(k, v)) +} + +// Append two lists of tags +func (t Tags) AppendTags(a Tags) Tags { + return append(t, a...) +} + +// New variadic tags, must be k string, v []byte repeating +func NewTags(tags ...interface{}) Tags { + var ret Tags + if len(tags)%2 != 0 { + panic("must specify key-value pairs as varargs") + } + i := 0 + for { + if i == len(tags) { + break + } + ret = append(ret, Tag{Key: []byte(tags[i].(string)), Value: tags[i+1].([]byte)}) + i += 2 + } + return ret +} + +// Make a tag from a key and a value +func MakeTag(k string, v []byte) Tag { + return Tag{Key: []byte(k), Value: v} +} diff --git a/types/tags_test.go b/types/tags_test.go new file mode 100644 index 0000000000..4ef5561240 --- /dev/null +++ b/types/tags_test.go @@ -0,0 +1,24 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAppendTags(t *testing.T) { + a := NewTags("a", []byte("1")) + b := NewTags("b", []byte("2")) + c := a.AppendTags(b) + require.Equal(t, c, Tags{MakeTag("a", []byte("1")), MakeTag("b", []byte("2"))}) +} + +func TestEmptyTags(t *testing.T) { + a := EmptyTags() + require.Equal(t, a, Tags{}) +} + +func TestNewTags(t *testing.T) { + b := NewTags("a", []byte("1")) + require.Equal(t, b, Tags{MakeTag("a", []byte("1"))}) +} diff --git a/x/bank/handler.go b/x/bank/handler.go index a50b0afcf3..ec56d05b42 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -25,13 +25,14 @@ func NewHandler(k Keeper) sdk.Handler { func handleMsgSend(ctx sdk.Context, k Keeper, msg MsgSend) sdk.Result { // NOTE: totalIn == totalOut should already have been checked - err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) + tags, err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) if err != nil { return err.Result() } - // TODO: add some tags so we can search it! - return sdk.Result{} // TODO + return sdk.Result{ + Tags: tags, + } } // Handle MsgIssue. diff --git a/x/bank/keeper.go b/x/bank/keeper.go index d1fdeaea06..d23167c3c5 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -32,22 +32,22 @@ func (keeper Keeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) } // SubtractCoins subtracts amt from the coins at the addr. -func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { +func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { return subtractCoins(ctx, keeper.am, addr, amt) } // AddCoins adds amt to the coins at the addr. -func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { +func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { return addCoins(ctx, keeper.am, addr, amt) } // SendCoins moves coins from one account to another -func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error { +func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt) } // InputOutputCoins handles a list of inputs and outputs -func (keeper Keeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) sdk.Error { +func (keeper Keeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { return inputOutputCoins(ctx, keeper.am, inputs, outputs) } @@ -74,12 +74,12 @@ func (keeper SendKeeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coi } // SendCoins moves coins from one account to another -func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error { +func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt) } // InputOutputCoins handles a list of inputs and outputs -func (keeper SendKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) sdk.Error { +func (keeper SendKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { return inputOutputCoins(ctx, keeper.am, inputs, outputs) } @@ -131,59 +131,65 @@ func hasCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.C } // SubtractCoins subtracts amt from the coins at the addr. -func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { +func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Minus(amt) if !newCoins.IsNotNegative() { - return amt, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) + return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } err := setCoins(ctx, am, addr, newCoins) - return newCoins, err + tags := sdk.NewTags("sender", addr.Bytes()) + return newCoins, tags, err } // AddCoins adds amt to the coins at the addr. -func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { +func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) { oldCoins := getCoins(ctx, am, addr) newCoins := oldCoins.Plus(amt) if !newCoins.IsNotNegative() { - return amt, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) + return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } err := setCoins(ctx, am, addr, newCoins) - return newCoins, err + tags := sdk.NewTags("recipient", addr.Bytes()) + return newCoins, tags, err } // SendCoins moves coins from one account to another // NOTE: Make sure to revert state changes from tx on error -func sendCoins(ctx sdk.Context, am sdk.AccountMapper, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error { - _, err := subtractCoins(ctx, am, fromAddr, amt) +func sendCoins(ctx sdk.Context, am sdk.AccountMapper, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) { + _, subTags, err := subtractCoins(ctx, am, fromAddr, amt) if err != nil { - return err + return nil, err } - _, err = addCoins(ctx, am, toAddr, amt) + _, addTags, err := addCoins(ctx, am, toAddr, amt) if err != nil { - return err + return nil, err } - return nil + return subTags.AppendTags(addTags), nil } // InputOutputCoins handles a list of inputs and outputs // NOTE: Make sure to revert state changes from tx on error -func inputOutputCoins(ctx sdk.Context, am sdk.AccountMapper, inputs []Input, outputs []Output) sdk.Error { +func inputOutputCoins(ctx sdk.Context, am sdk.AccountMapper, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) { + allTags := sdk.EmptyTags() + for _, in := range inputs { - _, err := subtractCoins(ctx, am, in.Address, in.Coins) + _, tags, err := subtractCoins(ctx, am, in.Address, in.Coins) if err != nil { - return err + return nil, err } + allTags = allTags.AppendTags(tags) } for _, out := range outputs { - _, err := addCoins(ctx, am, out.Address, out.Coins) + _, tags, err := addCoins(ctx, am, out.Address, out.Coins) if err != nil { - return err + return nil, err } + allTags = allTags.AppendTags(tags) } - return nil + return allTags, nil } diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 4394392dde..3db16c5f92 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -65,7 +65,7 @@ func TestKeeper(t *testing.T) { coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 5}}) assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}})) - _, err := coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}}) + _, _, err := coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}}) assert.Implements(t, (*sdk.Error)(nil), err) assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}})) @@ -78,7 +78,7 @@ func TestKeeper(t *testing.T) { assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) - err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) + _, err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) assert.Implements(t, (*sdk.Error)(nil), err2) assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) @@ -147,7 +147,7 @@ func TestSendKeeper(t *testing.T) { assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) - err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) + _, err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}}) assert.Implements(t, (*sdk.Error)(nil), err2) assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}})) assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}})) diff --git a/x/ibc/handler.go b/x/ibc/handler.go index 46cbf1e309..1f334166bf 100644 --- a/x/ibc/handler.go +++ b/x/ibc/handler.go @@ -25,7 +25,7 @@ func NewHandler(ibcm Mapper, ck bank.Keeper) sdk.Handler { func handleIBCTransferMsg(ctx sdk.Context, ibcm Mapper, ck bank.Keeper, msg IBCTransferMsg) sdk.Result { packet := msg.IBCPacket - _, err := ck.SubtractCoins(ctx, packet.SrcAddr, packet.Coins) + _, _, err := ck.SubtractCoins(ctx, packet.SrcAddr, packet.Coins) if err != nil { return err.Result() } @@ -47,7 +47,7 @@ func handleIBCReceiveMsg(ctx sdk.Context, ibcm Mapper, ck bank.Keeper, msg IBCRe return ErrInvalidSequence(ibcm.codespace).Result() } - _, err := ck.AddCoins(ctx, packet.DestAddr, packet.Coins) + _, _, err := ck.AddCoins(ctx, packet.DestAddr, packet.Coins) if err != nil { return err.Result() } diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index d0019002fd..60cc59bad9 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -34,7 +34,8 @@ func newAddress() crypto.Address { func getCoins(ck bank.Keeper, ctx sdk.Context, addr crypto.Address) (sdk.Coins, sdk.Error) { zero := sdk.Coins(nil) - return ck.AddCoins(ctx, addr, zero) + coins, _, err := ck.AddCoins(ctx, addr, zero) + return coins, err } func makeCodec() *wire.Codec { @@ -70,7 +71,7 @@ func TestIBC(t *testing.T) { zero := sdk.Coins(nil) mycoins := sdk.Coins{sdk.Coin{"mycoin", 10}} - coins, err := ck.AddCoins(ctx, src, mycoins) + coins, _, err := ck.AddCoins(ctx, src, mycoins) assert.Nil(t, err) assert.Equal(t, mycoins, coins) diff --git a/x/stake/handler.go b/x/stake/handler.go index 1408c5bb13..8d3bbb8b80 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -97,14 +97,18 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe candidate := NewCandidate(msg.CandidateAddr, msg.PubKey, msg.Description) k.setCandidate(ctx, candidate) + tags := sdk.NewTags("action", []byte("declareCandidacy"), "candidate", msg.CandidateAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity)) // move coins from the msg.Address account to a (self-bond) delegator account // the candidate account and global shares are updated within here - err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate) + delegateTags, err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate) if err != nil { return err.Result() } - return sdk.Result{} + tags = tags.AppendTags(delegateTags) + return sdk.Result{ + Tags: tags, + } } func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result { @@ -128,7 +132,10 @@ func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk candidate.Description.Details = msg.Description.Details k.setCandidate(ctx, candidate) - return sdk.Result{} + tags := sdk.NewTags("action", []byte("editCandidacy"), "candidate", msg.CandidateAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity)) + return sdk.Result{ + Tags: tags, + } } func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { @@ -148,16 +155,18 @@ func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { GasUsed: GasDelegate, } } - err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate) + tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate) if err != nil { return err.Result() } - return sdk.Result{} + return sdk.Result{ + Tags: tags, + } } // common functionality between handlers func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, - bondAmt sdk.Coin, candidate Candidate) sdk.Error { + bondAmt sdk.Coin, candidate Candidate) (sdk.Tags, sdk.Error) { // Get or create the delegator bond bond, found := k.GetDelegatorBond(ctx, delegatorAddr, candidate.Address) @@ -171,9 +180,9 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, // Account new shares, save pool := k.GetPool(ctx) - _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) + _, _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) if err != nil { - return err + return nil, err } pool, candidate, newShares := pool.candidateAddTokens(candidate, bondAmt.Amount) bond.Shares = bond.Shares.Add(newShares) @@ -184,7 +193,8 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, k.setDelegatorBond(ctx, bond) k.setCandidate(ctx, candidate) k.setPool(ctx, pool) - return nil + tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "candidate", candidate.Address.Bytes()) + return tags, nil } func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { @@ -281,5 +291,8 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { k.setCandidate(ctx, candidate) } k.setPool(ctx, p) - return sdk.Result{} + tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "candidate", msg.CandidateAddr.Bytes()) + return sdk.Result{ + Tags: tags, + } }