gecko/vms/spchainvm/account.go

156 lines
4.6 KiB
Go

// (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())
}