• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vocdoni / vocdoni-node / 11856589598

15 Nov 2024 12:49PM UTC coverage: 62.207% (-0.08%) from 62.286%
11856589598

push

github

altergui
Merge branch 'origin/main' into release-lts-1

29 of 56 new or added lines in 10 files covered. (51.79%)

6 existing lines in 3 files now uncovered.

16837 of 27066 relevant lines covered (62.21%)

38382.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.57
/vochain/transaction/transaction.go
1
package transaction
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "encoding/hex"
7
        "fmt"
8

9
        cometCrypto256k1 "github.com/cometbft/cometbft/crypto/secp256k1"
10
        "github.com/ethereum/go-ethereum/common"
11
        "go.vocdoni.io/dvote/crypto/ethereum"
12
        "go.vocdoni.io/dvote/log"
13
        "go.vocdoni.io/dvote/vochain/ist"
14
        vstate "go.vocdoni.io/dvote/vochain/state"
15
        "go.vocdoni.io/dvote/vochain/transaction/vochaintx"
16
        "go.vocdoni.io/proto/build/go/models"
17
        "google.golang.org/protobuf/proto"
18
)
19

20
const (
21
        // newValidatorPower is the default power of a new validator
22
        newValidatorPower = 5
23
)
24

25
var (
26
        // ErrNilTx is returned if the transaction is nil.
27
        ErrNilTx = fmt.Errorf("nil transaction")
28
        // ErrInvalidURILength is returned if the entity URI length is invalid.
29
        ErrInvalidURILength = fmt.Errorf("invalid URI length")
30
        // ErrorAlreadyExistInCache is returned if the transaction has been already processed
31
        // and stored in the vote cache.
32
        ErrorAlreadyExistInCache = fmt.Errorf("transaction already exist in cache")
33
)
34

35
// TransactionResponse is the response of a transaction check.
36
type TransactionResponse struct {
37
        TxHash []byte
38
        Data   []byte
39
        Log    string
40
}
41

42
// TransactionHandler holds the methods for checking the correctness of a transaction.
43
type TransactionHandler struct {
44
        // state is the state of the vochain
45
        state *vstate.State
46
        // istc is the internal state transition controller
47
        istc *ist.Controller
48
}
49

50
// NewTransactionHandler creates a new TransactionHandler.
51
func NewTransactionHandler(state *vstate.State, istc *ist.Controller) *TransactionHandler {
74✔
52
        return &TransactionHandler{
74✔
53
                state: state,
74✔
54
                istc:  istc,
74✔
55
        }
74✔
56
}
74✔
57

58
// CheckTx check the validity of a transaction and adds it to the state if forCommit=true.
59
// It returns a bytes value which depends on the transaction type:
60
//
61
//        Tx_Vote: vote nullifier
62
//        default: []byte{}
63
func (t *TransactionHandler) CheckTx(vtx *vochaintx.Tx, forCommit bool) (*TransactionResponse, error) {
5,711✔
64
        if vtx.Tx == nil || vtx.Tx.Payload == nil {
5,711✔
65
                return nil, fmt.Errorf("transaction is empty")
×
66
        }
×
67
        response := &TransactionResponse{
5,711✔
68
                TxHash: vtx.TxID[:],
5,711✔
69
        }
5,711✔
70
        if forCommit {
8,812✔
71
                if err := t.checkAccountNonce(vtx); err != nil {
3,106✔
72
                        return nil, fmt.Errorf("checkAccountNonce: %w", err)
5✔
73
                }
5✔
74
        }
75
        switch vtx.Tx.Payload.(type) {
5,706✔
76
        case *models.Tx_Vote:
3,847✔
77
                v, err := t.VoteTxCheck(vtx, forCommit)
3,847✔
78
                if err != nil || v == nil {
3,914✔
79
                        return nil, fmt.Errorf("voteTx: %w", err)
67✔
80
                }
67✔
81
                response.Data = v.Nullifier
3,780✔
82
                if forCommit {
5,823✔
83
                        return response, t.state.AddVote(v)
2,043✔
84
                }
2,043✔
85

86
        case *models.Tx_Admin:
224✔
87
                _, err := t.AdminTxCheck(vtx)
224✔
88
                if err != nil {
224✔
UNCOV
89
                        return nil, fmt.Errorf("adminTx: %w", err)
×
UNCOV
90
                }
×
91
                if forCommit {
344✔
92
                        tx := vtx.Tx.GetAdmin()
120✔
93
                        switch tx.Txtype {
120✔
94
                        // TODO: @jordipainan No cost applied, no nonce increased
95
                        case models.TxType_ADD_PROCESS_KEYS:
60✔
96
                                if err := t.state.AddProcessKeys(tx); err != nil {
60✔
97
                                        return nil, fmt.Errorf("addProcessKeys: %w", err)
×
98
                                }
×
99
                        // TODO: @jordipainan No cost applied, no nonce increased
100
                        case models.TxType_REVEAL_PROCESS_KEYS:
60✔
101
                                if err := t.state.RevealProcessKeys(tx); err != nil {
60✔
102
                                        return nil, fmt.Errorf("revealProcessKeys: %w", err)
×
103
                                }
×
104
                        default:
×
105
                                return nil, fmt.Errorf("tx not supported")
×
106
                        }
107
                }
108

109
        case *models.Tx_NewProcess:
328✔
110
                p, txSender, err := t.NewProcessTxCheck(vtx)
328✔
111
                if err != nil {
335✔
112
                        return nil, fmt.Errorf("newProcessTx: %w", err)
7✔
113
                }
7✔
114
                response.Data = p.ProcessId
321✔
115
                if forCommit {
503✔
116
                        tx := vtx.Tx.GetNewProcess()
182✔
117
                        if tx.Process == nil {
182✔
118
                                return nil, fmt.Errorf("newProcess process is empty")
×
119
                        }
×
120
                        if err := t.state.AddProcess(p); err != nil {
182✔
121
                                return nil, fmt.Errorf("newProcessTx: addProcess: %w", err)
×
122
                        }
×
123
                        entityAddr := common.BytesToAddress(p.EntityId)
182✔
124
                        if err := t.state.IncrementAccountProcessIndex(entityAddr); err != nil {
182✔
125
                                return nil, fmt.Errorf("newProcessTx: cannot increment process index: %w", err)
×
126
                        }
×
127
                        // schedule end process on the ISTC
128
                        if err := t.istc.Schedule(ist.Action{
182✔
129
                                TypeID:     ist.ActionEndProcess,
182✔
130
                                ElectionID: p.ProcessId,
182✔
131
                                ID:         p.ProcessId,
182✔
132
                                TimeStamp:  p.StartTime + p.Duration,
182✔
133
                        }); err != nil {
182✔
134
                                return nil, fmt.Errorf("newProcessTx: cannot schedule end process: %w", err)
×
135
                        }
×
136

137
                        cost := t.txElectionCostFromProcess(p)
182✔
138

182✔
139
                        // check for a faucet package and transfer amount to sender account
182✔
140
                        sender := common.Address(txSender)
182✔
141
                        if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, cost, sender, vtx.TxID[:], true); err != nil {
182✔
142
                                return nil, err
×
143
                        }
×
144

145
                        return response, t.state.BurnTxCostIncrementNonce(
182✔
146
                                sender,
182✔
147
                                models.TxType_NEW_PROCESS,
182✔
148
                                cost,
182✔
149
                                hex.EncodeToString(p.GetProcessId()),
182✔
150
                        )
182✔
151
                }
152

153
        case *models.Tx_SetProcess:
338✔
154
                cost := uint64(0)
338✔
155
                txSender, err := t.SetProcessTxCheck(vtx)
338✔
156
                if err != nil {
360✔
157
                        return nil, fmt.Errorf("setProcessTx: %w", err)
22✔
158
                }
22✔
159
                if forCommit {
485✔
160
                        tx := vtx.Tx.GetSetProcess()
169✔
161
                        switch tx.Txtype {
169✔
162
                        case models.TxType_SET_PROCESS_STATUS:
150✔
163
                                if tx.GetStatus() == models.ProcessStatus_PROCESS_UNKNOWN {
150✔
164
                                        return nil, fmt.Errorf("setProcessStatus: status unknown")
×
165
                                }
×
166
                                if err := t.state.SetProcessStatus(tx.ProcessId, tx.GetStatus(), true); err != nil {
150✔
167
                                        return nil, fmt.Errorf("setProcessStatus: %s", err)
×
168
                                }
×
169
                                if tx.GetStatus() == models.ProcessStatus_ENDED {
268✔
170
                                        // purge RegisterSIKTx counter if it exists
118✔
171
                                        if err := t.state.PurgeRegisterSIK(tx.ProcessId); err != nil {
118✔
172
                                                return nil, fmt.Errorf("setProcessStatus: cannot purge RegisterSIKTx counter: %w", err)
×
173
                                        }
×
174
                                        // schedule results computations on the ISTC
175
                                        if err := t.istc.Remove(tx.ProcessId); err != nil {
118✔
176
                                                log.Errorw(err, "setProcessStatus: cannot remove IST action")
×
177
                                        }
×
178
                                        if err := t.istc.Schedule(ist.Action{
118✔
179
                                                TypeID:     ist.ActionCommitResults,
118✔
180
                                                ElectionID: tx.ProcessId,
118✔
181
                                                ID:         tx.ProcessId,
118✔
182
                                                Height:     t.state.CurrentHeight() + 1,
118✔
183
                                        }); err != nil {
118✔
184
                                                return nil, fmt.Errorf("setProcessStatus: cannot schedule commit results: %w", err)
×
185
                                        }
×
186
                                }
187
                        case models.TxType_SET_PROCESS_CENSUS:
16✔
188
                                // if census size is increased, cost must be applied
16✔
189
                                if tx.GetCensusSize() > 0 {
22✔
190
                                        process, err := t.state.Process(tx.ProcessId, false)
6✔
191
                                        if err != nil {
6✔
192
                                                return nil, fmt.Errorf("setProcessCensus: %s", err)
×
193
                                        }
×
194
                                        cost = t.txCostIncreaseCensusSize(process, tx.GetCensusSize())
6✔
195
                                }
196
                                // update process census on state
197
                                if err := t.state.SetProcessCensus(tx.ProcessId, tx.CensusRoot, tx.GetCensusURI(), tx.GetCensusSize(), true); err != nil {
16✔
198
                                        return nil, fmt.Errorf("setProcessCensus: %s", err)
×
199
                                }
×
200
                        case models.TxType_SET_PROCESS_DURATION:
3✔
201
                                if tx.GetDuration() == 0 {
3✔
202
                                        return nil, fmt.Errorf("setProcessDuration: duration cannot be 0")
×
203
                                }
×
204
                                // if duration is increased, cost must be applied
205
                                process, err := t.state.Process(tx.ProcessId, false)
3✔
206
                                if err != nil {
3✔
207
                                        return nil, fmt.Errorf("setProcessDuration: %s", err)
×
208
                                }
×
209
                                cost = t.txCostIncreaseDuration(process, tx.GetDuration())
3✔
210
                                // update process duration on state
3✔
211
                                if err := t.state.SetProcessDuration(tx.ProcessId, tx.GetDuration(), true); err != nil {
3✔
212
                                        return nil, fmt.Errorf("setProcessCensus: %s", err)
×
213
                                }
×
214
                        default:
×
215
                                return nil, fmt.Errorf("unknown set process tx type")
×
216
                        }
217

218
                        // check for a faucet package and transfer amount to sender account
219
                        sender := common.Address(txSender)
169✔
220
                        if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, cost, sender, vtx.TxID[:], true); err != nil {
169✔
221
                                return nil, err
×
222
                        }
×
223

224
                        return response, t.state.BurnTxCostIncrementNonce(sender, tx.Txtype, cost, hex.EncodeToString(tx.ProcessId))
169✔
225
                }
226

227
        case *models.Tx_SetAccount:
722✔
228
                tx := vtx.Tx.GetSetAccount()
722✔
229

722✔
230
                switch tx.Txtype {
722✔
231
                case models.TxType_CREATE_ACCOUNT:
685✔
232
                        if err := t.CreateAccountTxCheck(vtx); err != nil {
694✔
233
                                return nil, fmt.Errorf("createAccountTx: %w", err)
9✔
234
                        }
9✔
235

236
                case models.TxType_SET_ACCOUNT_INFO_URI:
30✔
237
                        if err := t.SetAccountInfoTxCheck(vtx); err != nil {
35✔
238
                                return nil, fmt.Errorf("setAccountInfoTx: %w", err)
5✔
239
                        }
5✔
240

241
                case models.TxType_ADD_DELEGATE_FOR_ACCOUNT, models.TxType_DEL_DELEGATE_FOR_ACCOUNT:
7✔
242
                        if err := t.SetAccountDelegateTxCheck(vtx); err != nil {
10✔
243
                                return nil, fmt.Errorf("setAccountDelegateTx: %w", err)
3✔
244
                        }
3✔
245
                case models.TxType_SET_ACCOUNT_VALIDATOR:
×
246
                        if err := t.SetAccountValidatorTxCheck(vtx); err != nil {
×
247
                                return nil, fmt.Errorf("setAccountValidatorTx: %w", err)
×
248
                        }
×
249
                default:
×
250
                        return nil, fmt.Errorf("setAccount: invalid transaction type")
×
251
                }
252

253
                if forCommit {
1,095✔
254
                        switch tx.Txtype {
390✔
255
                        case models.TxType_CREATE_ACCOUNT:
376✔
256
                                txSenderAddress, err := ethereum.AddrFromSignature(vtx.SignedBody, vtx.Signature)
376✔
257
                                if err != nil {
376✔
258
                                        return nil, fmt.Errorf("createAccountTx: txSenderAddress %w", err)
×
259
                                }
×
260
                                if err := t.state.CreateAccount(
376✔
261
                                        txSenderAddress,
376✔
262
                                        tx.GetInfoURI(),
376✔
263
                                        tx.GetDelegates(),
376✔
264
                                        0,
376✔
265
                                ); err != nil {
376✔
266
                                        return nil, fmt.Errorf("setAccountTx: createAccount %w", err)
×
267
                                }
×
268
                                // if the tx includes a sik try to persist it in the state
269
                                if sik := tx.GetSIK(); sik != nil {
752✔
270
                                        if err := t.state.SetAddressSIK(txSenderAddress, sik); err != nil {
376✔
271
                                                return nil, fmt.Errorf("setAccountTx: SetAddressSIK %w", err)
×
272
                                        }
×
273
                                }
274
                                txCost, err := t.state.TxBaseCost(models.TxType_CREATE_ACCOUNT, false)
376✔
275
                                if err != nil {
376✔
276
                                        return nil, fmt.Errorf("createAccountTx: txCost %w", err)
×
277
                                }
×
278

279
                                // transfer balance from faucet package issuer to created account
280
                                canPayWithFaucet, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true)
376✔
281
                                if err != nil {
376✔
282
                                        return nil, fmt.Errorf("could not verify the faucet package: %w", err)
×
283
                                }
×
284
                                if !canPayWithFaucet {
376✔
285
                                        return nil, fmt.Errorf("faucet package is not enough for paying for the transaction cost")
×
286
                                }
×
287
                                // burn the cost of the transaction from the txSender account
288
                                if err := t.state.BurnTxCost(txSenderAddress, txCost); err != nil {
376✔
289
                                        return nil, err
×
290
                                }
×
291

292
                                return response, nil
376✔
293

294
                        case models.TxType_SET_ACCOUNT_INFO_URI:
12✔
295
                                txSenderAddress, err := ethereum.AddrFromSignature(vtx.SignedBody, vtx.Signature)
12✔
296
                                if err != nil {
12✔
297
                                        return nil, fmt.Errorf("setAccountInfo: txSenderAddress %w", err)
×
298
                                }
×
299
                                txCost, err := t.state.TxBaseCost(models.TxType_SET_ACCOUNT_INFO_URI, false)
12✔
300
                                if err != nil {
12✔
301
                                        return nil, fmt.Errorf("setAccountInfoUriTx: txCost: %w", err)
×
302
                                }
×
303
                                // check for a faucet package and if exist, transfer the amount to the tx sender
304
                                if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true); err != nil {
12✔
305
                                        return nil, err
×
306
                                }
×
307
                                // consume cost for setAccount
308
                                if err := t.state.BurnTxCostIncrementNonce(
12✔
309
                                        txSenderAddress,
12✔
310
                                        models.TxType_SET_ACCOUNT_INFO_URI,
12✔
311
                                        txCost,
12✔
312
                                        tx.GetInfoURI(),
12✔
313
                                ); err != nil {
12✔
314
                                        return nil, fmt.Errorf("setAccountInfo: burnCostIncrementNonce %w", err)
×
315
                                }
×
316
                                txAccount := common.BytesToAddress(tx.GetAccount())
12✔
317
                                if txAccount != (common.Address{}) {
23✔
318
                                        return response, t.state.SetAccountInfoURI(
11✔
319
                                                txAccount,
11✔
320
                                                tx.GetInfoURI(),
11✔
321
                                        )
11✔
322
                                }
11✔
323
                                return response, t.state.SetAccountInfoURI(
1✔
324
                                        txSenderAddress,
1✔
325
                                        tx.GetInfoURI(),
1✔
326
                                )
1✔
327

328
                        case models.TxType_ADD_DELEGATE_FOR_ACCOUNT:
1✔
329
                                txSenderAddress, err := ethereum.AddrFromSignature(vtx.SignedBody, vtx.Signature)
1✔
330
                                if err != nil {
1✔
331
                                        return nil, fmt.Errorf("addDelegate: txSenderAddress %w", err)
×
332
                                }
×
333
                                txCost, err := t.state.TxBaseCost(models.TxType_ADD_DELEGATE_FOR_ACCOUNT, false)
1✔
334
                                if err != nil {
1✔
335
                                        return nil, fmt.Errorf("addDelegate: txCost: %w", err)
×
336
                                }
×
337
                                // check for a faucet package and if exist, transfer the amount to the tx sender
338
                                if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true); err != nil {
1✔
339
                                        return nil, err
×
340
                                }
×
341
                                if err := t.state.BurnTxCostIncrementNonce(
1✔
342
                                        txSenderAddress,
1✔
343
                                        models.TxType_ADD_DELEGATE_FOR_ACCOUNT,
1✔
344
                                        txCost,
1✔
345
                                        "",
1✔
346
                                ); err != nil {
1✔
347
                                        return nil, fmt.Errorf("addDelegate: burnTxCostIncrementNonce %w", err)
×
348
                                }
×
349
                                if err := t.state.SetAccountDelegate(
1✔
350
                                        txSenderAddress,
1✔
351
                                        tx.Delegates,
1✔
352
                                        models.TxType_ADD_DELEGATE_FOR_ACCOUNT,
1✔
353
                                ); err != nil {
1✔
354
                                        return nil, fmt.Errorf("addDelegate: %w", err)
×
355
                                }
×
356
                        case models.TxType_DEL_DELEGATE_FOR_ACCOUNT:
1✔
357
                                txSenderAddress, err := ethereum.AddrFromSignature(vtx.SignedBody, vtx.Signature)
1✔
358
                                if err != nil {
1✔
359
                                        return nil, fmt.Errorf("delDelegate: txSenderAddress %w", err)
×
360
                                }
×
361
                                txCost, err := t.state.TxBaseCost(models.TxType_DEL_DELEGATE_FOR_ACCOUNT, false)
1✔
362
                                if err != nil {
1✔
363
                                        return nil, fmt.Errorf("delDelegate: txCost: %w", err)
×
364
                                }
×
365
                                // check for a faucet package and if exist, transfer the amount to the tx sender
366
                                if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true); err != nil {
1✔
367
                                        return nil, err
×
368
                                }
×
369
                                if err := t.state.BurnTxCostIncrementNonce(
1✔
370
                                        txSenderAddress,
1✔
371
                                        models.TxType_DEL_DELEGATE_FOR_ACCOUNT,
1✔
372
                                        txCost,
1✔
373
                                        "",
1✔
374
                                ); err != nil {
1✔
375
                                        return nil, fmt.Errorf("delDelegate: burnTxCostIncrementNonce %w", err)
×
376
                                }
×
377
                                if err := t.state.SetAccountDelegate(
1✔
378
                                        txSenderAddress,
1✔
379
                                        tx.Delegates,
1✔
380
                                        models.TxType_DEL_DELEGATE_FOR_ACCOUNT,
1✔
381
                                ); err != nil {
1✔
382
                                        return nil, fmt.Errorf("delDelegate: %w", err)
×
383
                                }
×
384
                        case models.TxType_SET_ACCOUNT_VALIDATOR:
×
385
                                txSenderAddress, err := ethereum.AddrFromSignature(vtx.SignedBody, vtx.Signature)
×
386
                                if err != nil {
×
387
                                        return nil, fmt.Errorf("setValidator: txSenderAddress %w", err)
×
388
                                }
×
389
                                txCost, err := t.state.TxBaseCost(models.TxType_SET_ACCOUNT_VALIDATOR, false)
×
390
                                if err != nil {
×
391
                                        return nil, fmt.Errorf("setValidator: txCost: %w", err)
×
392
                                }
×
393
                                // check for a faucet package and if exist, transfer the amount to the tx sender
394
                                if _, err := t.checkFaucetPackageAndTransfer(tx.FaucetPackage, txCost, txSenderAddress, vtx.TxID[:], true); err != nil {
×
395
                                        return nil, err
×
396
                                }
×
397
                                validatorAddr, err := ethereum.AddrFromPublicKey(tx.GetPublicKey())
×
398
                                if err != nil {
×
399
                                        return nil, fmt.Errorf("setValidator: %w", err)
×
400
                                }
×
401
                                if err := t.state.BurnTxCostIncrementNonce(
×
402
                                        txSenderAddress,
×
403
                                        models.TxType_SET_ACCOUNT_VALIDATOR,
×
404
                                        txCost,
×
405
                                        validatorAddr.Hex(),
×
406
                                ); err != nil {
×
407
                                        return nil, fmt.Errorf("setValidator: burnTxCostIncrementNonce %w", err)
×
408
                                }
×
409
                                if err := t.state.AddValidator(&models.Validator{
×
410
                                        Address:          validatorAddr.Bytes(),
×
411
                                        PubKey:           tx.GetPublicKey(),
×
412
                                        Name:             tx.GetName(),
×
413
                                        Power:            newValidatorPower,
×
414
                                        ValidatorAddress: cometCrypto256k1.PubKey(tx.GetPublicKey()).Address().Bytes(),
×
415
                                        Height:           uint64(t.state.CurrentHeight()),
×
416
                                }); err != nil {
×
417
                                        return nil, fmt.Errorf("setValidator: %w", err)
×
418
                                }
×
419
                        default:
×
420
                                return nil, fmt.Errorf("setAccount: invalid transaction type")
×
421
                        }
422
                }
423

424
        case *models.Tx_SendTokens:
164✔
425
                err := t.SendTokensTxCheck(vtx)
164✔
426
                if err != nil {
167✔
427
                        return nil, fmt.Errorf("sendTokensTx: %w", err)
3✔
428
                }
3✔
429
                if forCommit {
296✔
430
                        tx := vtx.Tx.GetSendTokens()
135✔
431
                        from, to := common.BytesToAddress(tx.From), common.BytesToAddress(tx.To)
135✔
432
                        err := t.state.BurnTxCostIncrementNonce(from, models.TxType_SEND_TOKENS, 0, to.Hex())
135✔
433
                        if err != nil {
135✔
434
                                return nil, fmt.Errorf("sendTokensTx: burnTxCostIncrementNonce %w", err)
×
435
                        }
×
436
                        if err := t.state.TransferBalance(&vochaintx.TokenTransfer{
135✔
437
                                FromAddress: from,
135✔
438
                                ToAddress:   to,
135✔
439
                                Amount:      tx.Value,
135✔
440
                                TxHash:      vtx.TxID[:],
135✔
441
                        }, false); err != nil {
135✔
442
                                return nil, fmt.Errorf("sendTokensTx: %w", err)
×
443
                        }
×
444
                        return response, nil
135✔
445
                }
446

447
        case *models.Tx_CollectFaucet:
8✔
448
                err := t.CollectFaucetTxCheck(vtx)
8✔
449
                if err != nil {
14✔
450
                        return nil, fmt.Errorf("collectFaucetTxCheck: %w", err)
6✔
451
                }
6✔
452
                if forCommit {
3✔
453
                        tx := vtx.Tx.GetCollectFaucet()
1✔
454
                        issuerAddress, err := ethereum.AddrFromSignature(tx.FaucetPackage.Payload, tx.FaucetPackage.Signature)
1✔
455
                        if err != nil {
1✔
456
                                return nil, fmt.Errorf("collectFaucetTx: cannot get issuerAddress %w", err)
×
457
                        }
×
458
                        if err := t.state.BurnTxCostIncrementNonce(issuerAddress, models.TxType_COLLECT_FAUCET, 0, ""); err != nil {
1✔
459
                                return nil, fmt.Errorf("collectFaucetTx: burnTxCost %w", err)
×
460
                        }
×
461
                        faucetPayload := &models.FaucetPayload{}
1✔
462
                        if err := proto.Unmarshal(tx.FaucetPackage.Payload, faucetPayload); err != nil {
1✔
463
                                return nil, fmt.Errorf("could not unmarshal faucet package: %w", err)
×
464
                        }
×
465
                        if err := t.state.ConsumeFaucetPayload(
1✔
466
                                issuerAddress,
1✔
467
                                &models.FaucetPayload{
1✔
468
                                        Identifier: faucetPayload.Identifier,
1✔
469
                                        To:         faucetPayload.To,
1✔
470
                                        Amount:     faucetPayload.Amount,
1✔
471
                                },
1✔
472
                        ); err != nil {
1✔
473
                                return nil, fmt.Errorf("collectFaucetTx: %w", err)
×
474
                        }
×
475
                        if err := t.state.TransferBalance(&vochaintx.TokenTransfer{
1✔
476
                                FromAddress: issuerAddress,
1✔
477
                                ToAddress:   common.BytesToAddress(faucetPayload.To),
1✔
478
                                Amount:      faucetPayload.Amount,
1✔
479
                                TxHash:      vtx.TxID[:],
1✔
480
                        }, false); err != nil {
1✔
481
                                return nil, fmt.Errorf("collectFaucetTx: %w", err)
×
482
                        }
×
483
                        return response, nil
1✔
484
                }
485

486
        case *models.Tx_SetSIK:
×
487
                txAddress, newSIK, err := t.SetSIKTxCheck(vtx)
×
488
                if err != nil {
×
489
                        return nil, fmt.Errorf("setSIKTx: %w", err)
×
490
                }
×
491
                if forCommit {
×
492
                        txCost, err := t.state.TxBaseCost(models.TxType_SET_ACCOUNT_SIK, false)
×
493
                        if err != nil {
×
494
                                return nil, fmt.Errorf("setAccountInfoUriTx: txCost: %w", err)
×
495
                        }
×
496
                        // check for a faucet package and if exist, transfer the amount to the tx sender
497
                        if _, err := t.checkFaucetPackageAndTransfer(vtx.Tx.GetSetSIK().FaucetPackage, txCost, txAddress, vtx.TxID[:], true); err != nil {
×
498
                                return nil, err
×
499
                        }
×
500
                        if err := t.state.BurnTxCostIncrementNonce(
×
501
                                txAddress,
×
502
                                models.TxType_SET_ACCOUNT_SIK,
×
503
                                txCost,
×
504
                                newSIK.String(),
×
505
                        ); err != nil {
×
506
                                return nil, fmt.Errorf("setSIKTx: burnTxCostIncrementNonce %w", err)
×
507
                        }
×
508
                        if err := t.state.SetAddressSIK(txAddress, newSIK); err != nil {
×
509
                                return nil, fmt.Errorf("setSIKTx: %w", err)
×
510
                        }
×
511
                }
512
                return response, nil
×
513

514
        case *models.Tx_DelSIK:
×
515
                txAddress, err := t.DelSIKTxCheck(vtx)
×
516
                if err != nil {
×
517
                        return nil, fmt.Errorf("delSIKTx: %w", err)
×
518
                }
×
519
                if forCommit {
×
520
                        txCost, err := t.state.TxBaseCost(models.TxType_DEL_ACCOUNT_SIK, false)
×
521
                        if err != nil {
×
522
                                return nil, fmt.Errorf("delSIKTx: txCost: %w", err)
×
523
                        }
×
524
                        // check for a faucet package and if exist, transfer the amount to the tx sender
525
                        if _, err := t.checkFaucetPackageAndTransfer(vtx.Tx.GetSetSIK().FaucetPackage, txCost, txAddress, vtx.TxID[:], true); err != nil {
×
526
                                return nil, err
×
527
                        }
×
528
                        if err := t.state.BurnTxCostIncrementNonce(
×
529
                                txAddress,
×
530
                                models.TxType_DEL_ACCOUNT_SIK,
×
531
                                txCost,
×
532
                                "",
×
533
                        ); err != nil {
×
534
                                return nil, fmt.Errorf("delSIKTx: burnTxCostIncrementNonce %w", err)
×
535
                        }
×
536
                        if err := t.state.InvalidateSIK(txAddress); err != nil {
×
537
                                return nil, fmt.Errorf("delSIKTx: %w", err)
×
538
                        }
×
539
                }
540
                return response, nil
×
541

542
        case *models.Tx_RegisterSIK:
75✔
543
                txAddress, SIK, pid, tempSIKs, err := t.RegisterSIKTxCheck(vtx)
75✔
544
                if err != nil {
75✔
545
                        return nil, fmt.Errorf("registerSIKTx: %w", err)
×
546
                }
×
547
                if forCommit {
115✔
548
                        // register the SIK
40✔
549
                        if err := t.state.SetAddressSIK(txAddress, SIK); err != nil {
40✔
550
                                return nil, fmt.Errorf("registerSIKTx: %w", err)
×
551
                        }
×
552
                        // increase the RegisterSIKTx counter
553
                        if err := t.state.IncreaseRegisterSIKCounter(pid); err != nil {
40✔
554
                                return nil, fmt.Errorf("registerSIKTx: %w", err)
×
555
                        }
×
556
                        if tempSIKs {
80✔
557
                                log.Infow("registering tempSIK", "address", txAddress.String())
40✔
558
                                if err := t.state.AssignSIKToElection(pid, txAddress); err != nil {
40✔
559
                                        return nil, fmt.Errorf("registerSIKTx: %w", err)
×
560
                                }
×
561
                        }
562
                }
563
                return response, nil
75✔
564

565
        default:
×
566
                return nil, fmt.Errorf("invalid transaction type")
×
567
        }
568

569
        return response, nil
2,591✔
570
}
571

572
// checkAccountCanPayCost checks if the account can pay the cost of the transaction.
573
// It returns the account and the address of the sender.
574
// It also checks if a faucet package is available in the transaction and can pay for it.
575
func (t *TransactionHandler) checkAccountCanPayCost(txType models.TxType, vtx *vochaintx.Tx) (*vstate.Account, *common.Address, error) {
37✔
576
        // extract sender address from signature
37✔
577
        pubKey, err := ethereum.PubKeyFromSignature(vtx.SignedBody, vtx.Signature)
37✔
578
        if err != nil {
37✔
579
                return nil, nil, fmt.Errorf("cannot extract public key from vtx.Signature: %w", err)
×
580
        }
×
581
        txSenderAddress, err := ethereum.AddrFromPublicKey(pubKey)
37✔
582
        if err != nil {
37✔
583
                return nil, nil, fmt.Errorf("cannot extract address from public key: %w", err)
×
584
        }
×
585
        txSenderAcc, err := t.state.GetAccount(txSenderAddress, false)
37✔
586
        if err != nil {
37✔
587
                return nil, nil, fmt.Errorf("cannot get account: %w", err)
×
588
        }
×
589
        if txSenderAcc == nil {
37✔
590
                return nil, nil, vstate.ErrAccountNotExist
×
591
        }
×
592
        // get setAccount tx cost
593
        cost, err := t.state.TxBaseCost(txType, false)
37✔
594
        if err != nil {
37✔
595
                return nil, nil, fmt.Errorf("cannot get tx cost for %s: %w", txType.String(), err)
×
596
        }
×
597

598
        if cost > 0 {
74✔
599
                // check if faucet package can pay for the transaction
37✔
600
                canFaucetPackagePay, err := t.checkFaucetPackageAndTransfer(vtx.GetFaucetPackage(), cost, txSenderAddress, vtx.TxID[:], false)
37✔
601
                if err != nil {
39✔
602
                        return nil, nil, err
2✔
603
                }
2✔
604

605
                // if faucet cannot pay for it, check tx sender balance
606
                if !canFaucetPackagePay {
62✔
607
                        if txSenderAcc.Balance < cost {
27✔
608
                                return nil, nil, fmt.Errorf("unauthorized: %s", vstate.ErrNotEnoughBalance)
×
609
                        }
×
610
                }
611
        }
612
        return txSenderAcc, &txSenderAddress, nil
35✔
613
}
614

615
// checkFaucetPackageAndTransfer checks if the txFaucetPackage is a valid Faucet package issued for txSenderAddress and the account signing the faucet package has
616
// enough funds for paying for the txCost.
617
// If forCommit is true, the faucet package balance is transferred to the txSender account.
618
// Returns false and no error if the faucet package does not exist. If it exists but there is an issue, returns false and the error.
619
func (t *TransactionHandler) checkFaucetPackageAndTransfer(txFaucetPackage *models.FaucetPackage, txCost uint64, txSenderAddress common.Address, txHash []byte, forCommit bool) (bool, error) {
1,463✔
620
        if txFaucetPackage == nil {
1,860✔
621
                if txCost == 0 {
564✔
622
                        return true, nil
167✔
623
                }
167✔
624
                return false, nil
230✔
625
        }
626
        if txFaucetPackage.Payload == nil {
1,069✔
627
                return false, fmt.Errorf("invalid faucet package payload")
3✔
628
        }
3✔
629
        faucetPayload := &models.FaucetPayload{}
1,063✔
630
        if err := proto.Unmarshal(txFaucetPackage.Payload, faucetPayload); err != nil {
1,063✔
631
                return false, fmt.Errorf("could not unmarshal faucet package: %w", err)
×
632
        }
×
633
        if faucetPayload.Amount == 0 {
1,064✔
634
                return false, fmt.Errorf("invalid faucet payload amount provided")
1✔
635
        }
1✔
636
        if faucetPayload.To == nil {
1,062✔
637
                return false, fmt.Errorf("invalid to address provided")
×
638
        }
×
639
        if !bytes.Equal(faucetPayload.To, txSenderAddress.Bytes()) {
1,063✔
640
                return false, fmt.Errorf("payload to and tx sender missmatch (%x != %x)",
1✔
641
                        faucetPayload.To, txSenderAddress.Bytes())
1✔
642
        }
1✔
643
        issuerAddress, err := ethereum.AddrFromSignature(txFaucetPackage.Payload, txFaucetPackage.Signature)
1,061✔
644
        if err != nil {
1,061✔
645
                return false, fmt.Errorf("cannot extract issuer address from faucet package vtx.Signature: %w", err)
×
646
        }
×
647
        issuerBalance, err := t.state.AccountBalance(issuerAddress, false)
1,061✔
648
        if err != nil {
1,061✔
649
                return false, fmt.Errorf("cannot get faucet issuer balance: %w", err)
×
650
        }
×
651
        b := make([]byte, 8)
1,061✔
652
        binary.LittleEndian.PutUint64(b, faucetPayload.Identifier)
1,061✔
653
        keyHash := ethereum.HashRaw(append(issuerAddress.Bytes(), b...))
1,061✔
654
        used, err := t.state.FaucetNonce(keyHash, false)
1,061✔
655
        if err != nil {
1,061✔
656
                return false, fmt.Errorf("cannot check if faucet payload already used: %w", err)
×
657
        }
×
658
        if used {
1,061✔
659
                return false, fmt.Errorf("faucet payload %x already used", keyHash)
×
660
        }
×
661
        if issuerBalance < faucetPayload.Amount {
1,065✔
662
                return false, fmt.Errorf(
4✔
663
                        "issuer address does not have enough balance %d, required %d",
4✔
664
                        issuerBalance,
4✔
665
                        faucetPayload.Amount,
4✔
666
                )
4✔
667
        }
4✔
668

669
        balance, err := t.state.AccountBalance(txSenderAddress, false)
1,057✔
670
        if err != nil {
1,057✔
671
                return false, fmt.Errorf("cannot get tx sender balance: %w", err)
×
672
        }
×
673
        if balance+faucetPayload.Amount < txCost {
1,058✔
674
                return false, fmt.Errorf(
1✔
675
                        "account balance (%d) + faucet amount (%d) is not enough to pay the tx cost %d",
1✔
676
                        balance, faucetPayload.Amount, txCost,
1✔
677
                )
1✔
678
        }
1✔
679

680
        // if forCommit, then the cost of the transaction is consumed from the faucet
681
        if forCommit {
1,433✔
682
                if err := t.state.ConsumeFaucetPayload(
377✔
683
                        issuerAddress,
377✔
684
                        faucetPayload,
377✔
685
                ); err != nil {
377✔
686
                        return false, fmt.Errorf("consumeFaucet: %w", err)
×
687
                }
×
688
                if err := t.state.TransferBalance(&vochaintx.TokenTransfer{
377✔
689
                        FromAddress: issuerAddress,
377✔
690
                        ToAddress:   txSenderAddress,
377✔
691
                        Amount:      faucetPayload.Amount,
377✔
692
                        TxHash:      txHash,
377✔
693
                }, false); err != nil {
377✔
694
                        return false, fmt.Errorf("consumeFaucet: %w", err)
×
695
                }
×
696
        }
697

698
        return true, nil
1,056✔
699
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc