-
Notifications
You must be signed in to change notification settings - Fork 669
/
account.go
155 lines (136 loc) · 4.57 KB
/
account.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
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spchainvm
import (
"errors"
"fmt"
"math"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/utils/crypto"
)
var (
errOutOfSpends = errors.New("ran out of spends")
errInsufficientFunds = errors.New("insufficient funds")
errOverflow = errors.New("math overflowed")
errInvalidID = errors.New("invalid ID")
errInvalidAddress = errors.New("invalid address")
)
// Account represents the balance and nonce of a user's funds
type Account struct {
id ids.ShortID
nonce, balance uint64
}
// ID of this account
func (a Account) ID() ids.ShortID { return a.id }
// Balance contained in this account
func (a Account) Balance() uint64 { return a.balance }
// Nonce this account was last spent with
func (a Account) Nonce() uint64 { return a.nonce }
// CreateTx creates a transaction from this account
// that sends [amount] to the address [destination]
func (a Account) CreateTx(amount uint64, destination ids.ShortID, ctx *snow.Context, key *crypto.PrivateKeySECP256K1R) (*Tx, Account, error) {
builder := Builder{
NetworkID: ctx.NetworkID,
ChainID: ctx.ChainID,
}
// If nonce overflows, Send will return an error
tx, err := builder.NewTx(key, a.nonce+1, amount, destination)
if err != nil {
return nil, a, err
}
newAccount, err := a.Send(tx, ctx)
return tx, newAccount, err
}
// Send generates a new account state from sending the transaction
func (a Account) Send(tx *Tx, ctx *snow.Context) (Account, error) {
return a.send(tx, ctx, &crypto.FactorySECP256K1R{})
}
// send generates the new account state from sending the transaction
func (a Account) send(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) (Account, error) {
return Account{
id: a.id,
// guaranteed not to overflow due to VerifySend
nonce: a.nonce + 1,
// guaranteed not to underflow due to VerifySend
balance: a.balance - tx.amount,
}, a.verifySend(tx, ctx, factory)
}
// VerifySend returns if the provided transaction can send this transaction
func (a Account) VerifySend(tx *Tx, ctx *snow.Context) error {
return a.verifySend(tx, ctx, &crypto.FactorySECP256K1R{})
}
func (a Account) verifySend(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) error {
// Verify the account is in a valid state and the transaction is valid
if err := a.Verify(); err != nil {
return err
}
if err := tx.verify(ctx, factory); err != nil {
return err
}
switch {
case a.nonce == math.MaxUint64:
// For this error to occur, a user would need to be issuing transactions
// at 10k tps for ~ 80 million years
return errOutOfSpends
case a.nonce+1 != tx.nonce:
return fmt.Errorf("wrong tx nonce used, %d != %d", a.nonce+1, tx.nonce)
case a.balance < tx.amount:
return fmt.Errorf("%s %d < %d", errInsufficientFunds, a.balance, tx.amount)
case a.nonce+1 == math.MaxUint64 && a.balance != tx.amount:
return errOutOfSpends
case !a.id.Equals(tx.key(ctx, factory).Address()):
return errInvalidAddress
default:
return nil
}
}
// Receive generates a new account state from receiving the transaction
func (a Account) Receive(tx *Tx, ctx *snow.Context) (Account, error) {
return a.receive(tx, ctx, &crypto.FactorySECP256K1R{})
}
func (a Account) receive(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) (Account, error) {
return Account{
id: a.id,
nonce: a.nonce,
// guaranteed not to overflow due to VerifyReceive
balance: a.balance + tx.amount,
}, a.verifyReceive(tx, ctx, factory)
}
// VerifyReceive returns if the provided transaction can receive this
// transaction
func (a Account) VerifyReceive(tx *Tx, ctx *snow.Context) error {
return a.verifyReceive(tx, ctx, &crypto.FactorySECP256K1R{})
}
func (a Account) verifyReceive(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) error {
if err := a.Verify(); err != nil {
return err
}
if err := tx.verify(ctx, factory); err != nil {
return err
}
switch {
case a.nonce == math.MaxUint64:
// For this error to occur, a user would need to be issuing transactions
// at 10k tps for ~ 80 million years
return errOutOfSpends
case a.balance > math.MaxUint64-tx.amount:
return errOverflow
case !a.id.Equals(tx.to):
return errInvalidID
default:
return nil
}
}
// Verify that this account is well formed
func (a Account) Verify() error {
switch {
case a.id.IsZero():
return errInvalidID
default:
return nil
}
}
func (a Account) String() string {
return fmt.Sprintf("Account[%s]: Balance=%d, Nonce=%d", a.ID(), a.Balance(), a.Nonce())
}