This repository has been archived by the owner on May 13, 2022. It is now read-only.
/
proposal_context.go
216 lines (182 loc) · 5.85 KB
/
proposal_context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package contexts
import (
"crypto/sha256"
"fmt"
"unicode"
"github.com/hyperledger/burrow/acm/state"
"github.com/hyperledger/burrow/acm/validator"
"github.com/hyperledger/burrow/bcm"
"github.com/hyperledger/burrow/crypto"
"github.com/hyperledger/burrow/execution/errors"
"github.com/hyperledger/burrow/execution/evm/sha3"
"github.com/hyperledger/burrow/execution/exec"
"github.com/hyperledger/burrow/execution/proposal"
"github.com/hyperledger/burrow/logging"
"github.com/hyperledger/burrow/logging/structure"
"github.com/hyperledger/burrow/txs"
"github.com/hyperledger/burrow/txs/payload"
)
type ProposalContext struct {
Tip bcm.BlockchainInfo
StateWriter state.ReaderWriter
ValidatorSet validator.Writer
ProposalReg proposal.ReaderWriter
Logger *logging.Logger
tx *payload.ProposalTx
Contexts map[payload.Type]Context
}
func (ctx *ProposalContext) Execute(txe *exec.TxExecution, p payload.Payload) error {
var ok bool
ctx.tx, ok = p.(*payload.ProposalTx)
if !ok {
return fmt.Errorf("payload must be ProposalTx, but is: %v", txe.Envelope.Tx.Payload)
}
// Validate input
inAcc, err := ctx.StateWriter.GetAccount(ctx.tx.Input.Address)
if err != nil {
return err
}
if inAcc == nil {
ctx.Logger.InfoMsg("Cannot find input account",
"tx_input", ctx.tx.Input)
return errors.ErrorCodeInvalidAddress
}
// check permission
if !hasProposalPermission(ctx.StateWriter, inAcc, ctx.Logger) {
return fmt.Errorf("account %s does not have Proposal permission", ctx.tx.Input.Address)
}
var ballot *payload.Ballot
var proposalHash []byte
if ctx.tx.Proposal == nil {
// voting for existing proposal
if ctx.tx.ProposalHash == nil || ctx.tx.ProposalHash.Size() != sha256.Size {
return errors.ErrorCodeInvalidProposal
}
ballot, err = ctx.ProposalReg.GetProposal(ctx.tx.ProposalHash.Bytes())
if err != nil {
return err
}
} else {
if ctx.tx.ProposalHash != nil || ctx.tx.Proposal.BatchTx == nil ||
len(ctx.tx.Proposal.BatchTx.Txs) == 0 || len(ctx.tx.Proposal.BatchTx.GetInputs()) == 0 {
return errors.ErrorCodeInvalidProposal
}
// validate the input strings
if err := validateProposalStrings(ctx.tx.Proposal); err != nil {
return err
}
bs, err := ctx.tx.Proposal.Encode()
if err != nil {
return err
}
proposalHash = sha3.Sha3(bs)
ballot, err = ctx.ProposalReg.GetProposal(proposalHash)
if ballot == nil && err == nil {
ballot = &payload.Ballot{
Proposal: ctx.tx.Proposal,
ProposalState: payload.Ballot_PROPOSED,
}
}
if err != nil {
return err
}
}
// count votes for proposal
votes := make(map[crypto.Address]int64)
if ballot.Votes == nil {
ballot.Votes = make([]*payload.Vote, 0)
}
for _, v := range ballot.Votes {
acc, err := ctx.StateWriter.GetAccount(v.Address)
if err != nil {
return err
}
// Belt and braces, should have already been checked
if !hasProposalPermission(ctx.StateWriter, acc, ctx.Logger) {
return fmt.Errorf("account %s does not have Proposal permission", ctx.tx.Input.Address)
}
votes[v.Address] = v.VotingWeight
}
for _, i := range ballot.Proposal.BatchTx.GetInputs() {
// Validate input
proposeAcc, err := ctx.StateWriter.GetAccount(i.Address)
if err != nil {
return err
}
if proposeAcc == nil {
ctx.Logger.InfoMsg("Cannot find input account",
"tx_input", ctx.tx.Input)
return errors.ErrorCodeInvalidAddress
}
if !hasBatchPermission(ctx.StateWriter, proposeAcc, ctx.Logger) {
return fmt.Errorf("account %s does not have batch permission", i.Address)
}
if proposeAcc.GetSequence() != i.Sequence {
return errors.ErrorCodeExpiredProposal
}
// Do we have a record of our own vote
if _, ok := votes[i.Address]; !ok {
votes[i.Address] = ctx.tx.VotingWeight
ballot.Votes = append(ballot.Votes, &payload.Vote{Address: i.Address, VotingWeight: ctx.tx.VotingWeight})
}
}
// Count the number of validators; ensure we have at least half the number of validators
// This also means that when running with a single validator, a proposal will run straight away
var power uint64
for _, v := range votes {
if v > 0 {
power++
}
}
for _, step := range ballot.Proposal.BatchTx.Txs {
txE := txs.EnvelopeFromAny("", step)
for _, i := range txE.Tx.GetInputs() {
_, err := ctx.StateWriter.GetAccount(i.Address)
if err != nil {
return err
}
// Do not check sequence numbers of inputs
}
}
if power >= ctx.Tip.GenesisDoc().Params.ProposalThreshold {
ballot.ProposalState = payload.Ballot_EXECUTED
for i, step := range ballot.Proposal.BatchTx.Txs {
txE := txs.EnvelopeFromAny("", step)
txe.PayloadEvent(&exec.PayloadEvent{TxType: txE.Tx.Type(), Index: uint32(i)})
if txExecutor, ok := ctx.Contexts[txE.Tx.Type()]; ok {
err = txExecutor.Execute(txe, txE.Tx.Payload)
if err != nil {
ctx.Logger.InfoMsg("Transaction execution failed", structure.ErrorKey, err)
return err
}
}
if txe.Exception != nil {
ballot.ProposalState = payload.Ballot_FAILED
break
}
}
}
return ctx.ProposalReg.UpdateProposal(proposalHash, ballot)
}
func validateProposalStrings(proposal *payload.Proposal) error {
if len(proposal.Name) == 0 {
return errors.ErrorCodef(errors.ErrorCodeInvalidString, "name must not be empty")
}
if !validateNameRegEntryName(proposal.Name) {
return errors.ErrorCodef(errors.ErrorCodeInvalidString,
"Invalid characters found in Proposal.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed", proposal.Name)
}
if !validateStringPrintable(proposal.Description) {
return errors.ErrorCodef(errors.ErrorCodeInvalidString,
"Invalid characters found in Proposal.Description (%s). Only printable characters are allowed", proposal.Description)
}
return nil
}
func validateStringPrintable(data string) bool {
for _, r := range []rune(data) {
if !unicode.IsPrint(r) {
return false
}
}
return true
}