From 9337405d1a8462c944cc5d1d227dc95489d4ab19 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 31 Jan 2017 05:27:24 -0500 Subject: [PATCH] restructure, address all PR comments frey/jae, besides returning unspent funds --- cmd/paytovote/main.go | 2 +- plugins/paytovote/paytovote.go | 255 ++++++++++++++++------------ plugins/paytovote/paytovote_test.go | 56 +++--- 3 files changed, 170 insertions(+), 143 deletions(-) diff --git a/cmd/paytovote/main.go b/cmd/paytovote/main.go index 149f0ef51f91..aa78454cd8ff 100644 --- a/cmd/paytovote/main.go +++ b/cmd/paytovote/main.go @@ -28,7 +28,7 @@ func main() { // create/add plugins counter := counter.New("counter") - paytovote := paytovote.New("p2v") + paytovote := paytovote.New() app.RegisterPlugin(counter) app.RegisterPlugin(paytovote) diff --git a/plugins/paytovote/paytovote.go b/plugins/paytovote/paytovote.go index 3dfb96d745a3..1169c18ff24e 100644 --- a/plugins/paytovote/paytovote.go +++ b/plugins/paytovote/paytovote.go @@ -8,168 +8,209 @@ import ( "github.com/tendermint/go-wire" ) +type P2VPlugin struct { + name string +} + +func New() *P2VPlugin { + return &P2VPlugin{ + name: "paytovote", + } +} + +/////////////////////////////////////////////////// + const ( - TypeByteCreateIssue byte = 0x00 + TypeByteTxCreate byte = 0x01 + TypeByteTxVote byte = 0x02 + TypeByteVoteFor byte = 0x01 TypeByteVoteAgainst byte = 0x02 - TypeByteVoteSpoiled byte = 0x03 ) -type P2VPluginState struct { - TotalCost types.Coins - Issue string - votesFor int - votesAgainst int - votesSpoiled int +type createIssueTx struct { + Issue string //Issue to be created + FeePerVote types.Coins //Cost to vote for the issue + Fee2CreateIssue types.Coins //Cost to create a new issue } -type P2VTx struct { - Valid bool - Issue string //Issue being voted for - ActionTypeByte byte //How is the vote being cast - Cost2Vote types.Coins //Cost to vote - Cost2CreateIssue types.Coins //Cost to create a new issue +type voteTx struct { + Issue string //Issue being voted for + VoteTypeByte byte //How is the vote being cast } -//-------------------------------------------------------------------------------- - -type P2VPlugin struct { - name string +func NewCreateIssueTxBytes(issue string, feePerVote, fee2CreateIssue types.Coins) []byte { + data := wire.BinaryBytes( + createIssueTx{ + Issue: issue, + FeePerVote: feePerVote, + Fee2CreateIssue: fee2CreateIssue, + }) + data = append([]byte{TypeByteTxCreate}, data...) + return data } -func (p2v *P2VPlugin) Name() string { - return p2v.name +func NewVoteTxBytes(issue string, voteTypeByte byte) []byte { + data := wire.BinaryBytes( + voteTx{ + Issue: issue, + VoteTypeByte: voteTypeByte, + }) + data = append([]byte{TypeByteTxVote}, data...) + return data } -func (p2v *P2VPlugin) StateKey(issue string) []byte { - return []byte(fmt.Sprintf("P2VPlugin{name=%v,issue=%v}.State", p2v.name, issue)) -} +/////////////////////////////////////////////////// -func New(name string) *P2VPlugin { - return &P2VPlugin{ - name: name, - } +type P2VIssue struct { + Issue string + FeePerVote types.Coins + votesFor int + votesAgainst int } -func newState(issue string) P2VPluginState { - return P2VPluginState{ - TotalCost: types.Coins{}, +func newP2VIssue(issue string, feePerVote types.Coins) P2VIssue { + return P2VIssue{ Issue: issue, + FeePerVote: feePerVote, votesFor: 0, votesAgainst: 0, - votesSpoiled: 0, } } +func (p2v *P2VPlugin) IssueKey(issue string) []byte { + //The state key is defined as only being affected by effected issue + // aka. if multiple paytovote plugins are initialized + // then all will have access to the same issue vote counts + return []byte(fmt.Sprintf("P2VPlugin{issue=%v}.State", issue)) +} + +/////////////////////////////////////////////////// + +func (p2v *P2VPlugin) Name() string { + return p2v.name +} + func (p2v *P2VPlugin) SetOption(store types.KVStore, key string, value string) (log string) { return "" } func (p2v *P2VPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { + defer func() { + //TODO return the ctx coins to the wallet if there is an error + }() + + //Determine the transaction type and then send to the appropriate transaction function + if len(txBytes) < 1 { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: no tx bytes") + } + + //Note that the zero position of txBytes contains the type-byte for the tx type + switch txBytes[0] { + case TypeByteTxCreate: + return p2v.runTxCreateIssue(store, ctx, txBytes[1:]) + case TypeByteTxVote: + return p2v.runTxVote(store, ctx, txBytes[1:]) + default: + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: bad prepended bytes") + } +} + +func chargeFee(ctx types.CallContext, fee types.Coins) { + leftoverCoins := ctx.Coins.Minus(fee) + if !leftoverCoins.IsZero() { + // TODO If there are any funds left over, return funds. + // ctx.CallerAccount is synced w/ store, so just modify that and store it. + } +} + +func (p2v *P2VPlugin) runTxCreateIssue(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { // Decode tx - var tx P2VTx + var tx createIssueTx err := wire.ReadBinaryBytes(txBytes, &tx) if err != nil { return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) } - // Validate tx - if !tx.Valid { - return abci.ErrInternalError.AppendLog("P2VTx.Valid must be true") - } - - if len(tx.Issue) == 0 { + //Validate Tx + switch { + case len(tx.Issue) == 0: return abci.ErrInternalError.AppendLog("P2VTx.Issue must have a length greater than 0") + case !tx.FeePerVote.IsValid(): + return abci.ErrInternalError.AppendLog("P2VTx.Fee2Vote is not sorted or has zero amounts") + case !tx.FeePerVote.IsNonnegative(): + return abci.ErrInternalError.AppendLog("P2VTx.Fee2Vote must be nonnegative") + case !tx.Fee2CreateIssue.IsValid(): + return abci.ErrInternalError.AppendLog("P2VTx.Fee2CreateIssue is not sorted or has zero amounts") + case !tx.Fee2CreateIssue.IsNonnegative(): + return abci.ErrInternalError.AppendLog("P2VTx.Fee2CreateIssue must be nonnegative") + case !ctx.Coins.IsGTE(tx.Fee2CreateIssue): // Did the caller provide enough coins? + return abci.ErrInsufficientFunds.AppendLog("Tx Funds insufficient for creating a new issue") } - if !tx.Cost2Vote.IsValid() { - return abci.ErrInternalError.AppendLog("P2VTx.Cost2Vote is not sorted or has zero amounts") - } + // Load P2VIssue + var p2vIssue P2VIssue + p2vIssueBytes := store.Get(p2v.IssueKey(tx.Issue)) - if !tx.Cost2Vote.IsNonnegative() { - return abci.ErrInternalError.AppendLog("P2VTx.Cost2Vote must be nonnegative") + //Return if the issue already exists + if len(p2vIssueBytes) > 0 { + return abci.ErrInsufficientFunds.AppendLog("Cannot create an already existing issue") } - if !tx.Cost2CreateIssue.IsValid() { - return abci.ErrInternalError.AppendLog("P2VTx.Cost2CreateIssue is not sorted or has zero amounts") - } + // Create and Save P2VIssue, charge fee, return + newP2VIssue := newP2VIssue(tx.Issue, tx.FeePerVote) + store.Set(p2v.IssueKey(tx.Issue), wire.BinaryBytes(newP2VIssue)) + chargeFee(ctx, tx.Fee2CreateIssue) + return abci.NewResultOK(wire.BinaryBytes(p2vIssue), "") +} - if !tx.Cost2CreateIssue.IsNonnegative() { - return abci.ErrInternalError.AppendLog("P2VTx.Cost2CreateIssue must be nonnegative") +func (p2v *P2VPlugin) runTxVote(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { + // Decode tx + var tx voteTx + err := wire.ReadBinaryBytes(txBytes, &tx) + if err != nil { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) } - // Load P2VPluginState - var p2vState P2VPluginState - p2vStateBytes := store.Get(p2v.StateKey(tx.Issue)) + //Validate Tx + if len(tx.Issue) == 0 { + return abci.ErrInternalError.AppendLog("transaction issue must have a length greater than 0") + } - //Determine if the issue already exists - issueExists := true + // Load P2VIssue + var p2vIssue P2VIssue + p2vIssueBytes := store.Get(p2v.IssueKey(tx.Issue)) - if len(p2vStateBytes) > 0 { //is there a record of the issue existing? - err = wire.ReadBinaryBytes(p2vStateBytes, &p2vState) + //Determine if the issue already exists and load + if len(p2vIssueBytes) > 0 { //is there a record of the issue existing? + err = wire.ReadBinaryBytes(p2vIssueBytes, &p2vIssue) if err != nil { return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error()) } } else { - issueExists = false + return abci.ErrInsufficientFunds.AppendLog("Tx Issue not found") } - returnLeftover := func(cost types.Coins) { - leftoverCoins := ctx.Coins.Minus(cost) - if !leftoverCoins.IsZero() { - // TODO If there are any funds left over, return funds. - // ctx.CallerAccount is synced w/ store, so just modify that and store it. - } + // Did the caller provide enough coins? + if !ctx.Coins.IsGTE(p2vIssue.FeePerVote) { + return abci.ErrInsufficientFunds.AppendLog("Tx Funds insufficient for voting") } - switch { - case tx.ActionTypeByte == TypeByteCreateIssue && issueExists: - return abci.ErrInsufficientFunds.AppendLog("Cannot create an already existing issue") - case tx.ActionTypeByte != TypeByteCreateIssue && !issueExists: - return abci.ErrInsufficientFunds.AppendLog("Tx Issue not found") - case tx.ActionTypeByte == TypeByteCreateIssue && !issueExists: - // Did the caller provide enough coins? - if !ctx.Coins.IsGTE(tx.Cost2CreateIssue) { - return abci.ErrInsufficientFunds.AppendLog("Tx Funds insufficient for creating a new issue") - } - - // Update P2VPluginState - newP2VState := newState(tx.Issue) - newP2VState.TotalCost = newP2VState.TotalCost.Plus(tx.Cost2Vote) - - // Save P2VPluginState - store.Set(p2v.StateKey(tx.Issue), wire.BinaryBytes(newP2VState)) - - returnLeftover(tx.Cost2CreateIssue) - - case tx.ActionTypeByte != TypeByteCreateIssue && issueExists: - // Did the caller provide enough coins? - if !ctx.Coins.IsGTE(tx.Cost2Vote) { - return abci.ErrInsufficientFunds.AppendLog("Tx Funds insufficient for voting") - } - - switch tx.ActionTypeByte { - case TypeByteVoteFor: - p2vState.votesFor += 1 - case TypeByteVoteAgainst: - p2vState.votesAgainst += 1 - case TypeByteVoteSpoiled: - p2vState.votesSpoiled += 1 - default: - return abci.ErrInternalError.AppendLog("P2VTx.ActionTypeByte was not recognized") - } - - // Update P2VPluginState - p2vState.TotalCost = p2vState.TotalCost.Plus(tx.Cost2Vote) - - // Save P2VPluginState - store.Set(p2v.StateKey(tx.Issue), wire.BinaryBytes(p2vState)) - - returnLeftover(tx.Cost2CreateIssue) + //Transaction Logic + switch tx.VoteTypeByte { + case TypeByteVoteFor: + p2vIssue.votesFor += 1 + case TypeByteVoteAgainst: + p2vIssue.votesAgainst += 1 + default: + return abci.ErrInternalError.AppendLog("P2VTx.ActionTypeByte was not recognized") } - return abci.NewResultOK(wire.BinaryBytes(p2vState), "") + // Save P2VIssue, charge fee, return + store.Set(p2v.IssueKey(tx.Issue), wire.BinaryBytes(p2vIssue)) + chargeFee(ctx, p2vIssue.FeePerVote) + return abci.NewResultOK(wire.BinaryBytes(p2vIssue), "") } func (p2v *P2VPlugin) InitChain(store types.KVStore, vals []*abci.Validator) { diff --git a/plugins/paytovote/paytovote_test.go b/plugins/paytovote/paytovote_test.go index 87c87a4173cb..cab3b630e1a4 100644 --- a/plugins/paytovote/paytovote_test.go +++ b/plugins/paytovote/paytovote_test.go @@ -22,8 +22,7 @@ func TestP2VPlugin(t *testing.T) { t.Log(bcApp.Info()) // Add Counter plugin - P2VPluginName := "testP2V" - P2VPlugin := New(P2VPluginName) + P2VPlugin := New() bcApp.RegisterPlugin(P2VPlugin) // Account initialization @@ -34,29 +33,19 @@ func TestP2VPlugin(t *testing.T) { test1Acc.Balance = types.Coins{{"", 1000}, {"issueToken", 1000}, {"voteToken", 1000}} bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc))) - DeliverTx := func(gas int64, + deliverTx := func(gas int64, fee types.Coin, inputCoins types.Coins, inputSequence int, - issue string, - actionTypeByte byte, - cost2Vote, - cost2CreateIssue types.Coins) abci.Result { + txData []byte) abci.Result { // Construct an AppTx signature tx := &types.AppTx{ Gas: gas, Fee: fee, - Name: P2VPluginName, + Name: P2VPlugin.Name(), Input: types.NewTxInput(test1Acc.PubKey, inputCoins, inputSequence), - Data: wire.BinaryBytes( - P2VTx{ - Valid: true, - Issue: issue, - ActionTypeByte: actionTypeByte, - Cost2Vote: cost2Vote, - Cost2CreateIssue: cost2CreateIssue, - }), + Data: txData, } // Sign request @@ -82,46 +71,43 @@ func TestP2VPlugin(t *testing.T) { t.Log(len(query)) return bcApp.Query(query) }*/ - // REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, issue, action, cost2Vote, cost2CreateIssue) + // REF: deliverTx(gas, fee, inputCoins, inputSequence, NewVoteTxBytes(issue, voteTypeByte)) + // REF: deliverTx(gas, fee, inputCoins, inputSequence, NewCreateIssueTxBytes(issue, feePerVote, fee2CreateIssue)) issue1 := "free internet" issue2 := "commutate foobar" // Test a basic issue generation - res := DeliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 1, - issue1, TypeByteCreateIssue, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 1}}) + res := deliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 2}}, 1, + NewCreateIssueTxBytes(issue1, types.Coins{{"voteToken", 2}}, types.Coins{{"issueToken", 1}})) assert.True(t, res.IsOK(), res.String()) // Test a basic votes - res = DeliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 2, - issue1, TypeByteVoteFor, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 1}}) + res = deliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 2}}, 2, + NewVoteTxBytes(issue1, TypeByteVoteFor)) assert.True(t, res.IsOK(), res.String()) - res = DeliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 3, - issue1, TypeByteVoteAgainst, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 1}}) - assert.True(t, res.IsOK(), res.String()) - - res = DeliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 4, - issue1, TypeByteVoteSpoiled, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 1}}) + res = deliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 2}}, 3, + NewVoteTxBytes(issue1, TypeByteVoteAgainst)) assert.True(t, res.IsOK(), res.String()) // Test prevented voting on non-existent issue - res = DeliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 5, - issue2, TypeByteVoteFor, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 1}}) + res = deliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 2}}, 5, + NewVoteTxBytes(issue2, TypeByteVoteFor)) assert.True(t, res.IsErr(), res.String()) // Test prevented duplicate issue generation - res = DeliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 5, - issue1, TypeByteCreateIssue, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 1}}) + res = deliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 2}}, 5, + NewCreateIssueTxBytes(issue1, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 1}})) assert.True(t, res.IsErr(), res.String()) // Test prevented issue generation from insufficient funds - res = DeliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 5, - issue2, TypeByteCreateIssue, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 2}}) + res = deliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 2}}, 5, + NewCreateIssueTxBytes(issue2, types.Coins{{"voteToken", 1}}, types.Coins{{"issueToken", 2}})) assert.True(t, res.IsErr(), res.String()) // Test prevented voting from insufficient funds - res = DeliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 5, - issue1, TypeByteVoteFor, types.Coins{{"voteToken", 2}}, types.Coins{{"issueToken", 1}}) + res = deliverTx(0, types.Coin{}, types.Coins{{"", 1}, {"issueToken", 1}, {"voteToken", 1}}, 5, + NewVoteTxBytes(issue1, TypeByteVoteFor)) assert.True(t, res.IsErr(), res.String()) }