Skip to content

Commit

Permalink
restructure, address all PR comments frey/jae, besides returning unsp…
Browse files Browse the repository at this point in the history
…ent funds
  • Loading branch information
rigelrozanski committed Jan 31, 2017
1 parent bb1daa8 commit 9337405
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 143 deletions.
2 changes: 1 addition & 1 deletion cmd/paytovote/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
255 changes: 148 additions & 107 deletions plugins/paytovote/paytovote.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 9337405

Please sign in to comment.