gecko/vms/spchainvm/tx.go

144 lines
3.9 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spchainvm
import (
"errors"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/crypto"
)
var (
errNilTx = errors.New("nil tx")
errTxHasNoValue = errors.New("tx has no value")
errWrongNetworkID = errors.New("tx has wrong network ID")
errWrongChainID = errors.New("tx has wrong chain ID")
)
// Tx is a monetary transfer
type Tx struct {
// The ID of this transaction
id ids.ID
networkID uint32
// The ID of the chain this transaction was issued on.
// Used to prevent replay attacks. Without this field, an attacker could
// re-issue a transaction sent on another chain running the same vm.
chainID ids.ID
// The recipient of the transfered funds
to ids.ShortID
// The nonce of the transaction
nonce uint64
// The amount to be transfered
amount uint64
// The signature on this transaction (namely, on [bytes])
sig []byte
// The public key that authorized this transaction
pubkey crypto.PublicKey
// The byte representation of this transaction
bytes []byte
// Called when this transaction is decided
onDecide func(choices.Status)
startedVerification, finishedVerification bool
verificationErr error
verification chan error
}
// ID of this transaction
func (tx *Tx) ID() ids.ID { return tx.id }
// Nonce is the new nonce of the account this transaction is being sent from
func (tx *Tx) Nonce() uint64 { return tx.nonce }
// Amount is the number of units to transfer to the recipient
func (tx *Tx) Amount() uint64 { return tx.amount }
// To is the address this transaction is sending to
func (tx *Tx) To() ids.ShortID { return tx.to }
// Bytes is the byte representation of this transaction
func (tx *Tx) Bytes() []byte { return tx.bytes }
// Key returns the public key used to authorize this transaction
// This function also sets [tx]'s public key
func (tx *Tx) Key(ctx *snow.Context) crypto.PublicKey { return tx.key(ctx, &crypto.FactorySECP256K1R{}) }
func (tx *Tx) key(ctx *snow.Context, factory *crypto.FactorySECP256K1R) crypto.PublicKey {
tx.verify(ctx, factory) // Sets the public key
return tx.pubkey
}
// Verify that this transaction is well formed
func (tx *Tx) Verify(ctx *snow.Context) error { return tx.verify(ctx, &crypto.FactorySECP256K1R{}) }
func (tx *Tx) verify(ctx *snow.Context, factory *crypto.FactorySECP256K1R) error {
// Check if tx has already been verified
if tx.finishedVerification {
return tx.verificationErr
}
// past this point, we know verification has neither passed nor failed in the past
tx.startVerify(ctx, factory)
// Wait for verification to complete
tx.verificationErr = <-tx.verification
tx.finishedVerification = true
return tx.verificationErr
}
func (tx *Tx) startVerify(ctx *snow.Context, factory *crypto.FactorySECP256K1R) {
// See if verification has been started.
// If not, start verification
if !tx.startedVerification {
tx.startedVerification = true
go func(tx *Tx, ctx *snow.Context, factory *crypto.FactorySECP256K1R) {
tx.verification <- tx.syncVerify(ctx, factory)
}(tx, ctx, factory)
}
}
func (tx *Tx) syncVerify(ctx *snow.Context, factory *crypto.FactorySECP256K1R) error {
switch {
case tx == nil:
return errNilTx
case tx.pubkey != nil:
return nil
case tx.amount == 0:
return errTxHasNoValue
case tx.networkID != ctx.NetworkID:
return errWrongNetworkID
case !tx.chainID.Equals(ctx.ChainID):
return errWrongChainID
}
codec := Codec{}
// The byte repr. of this transaction, unsigned
unsignedBytes, err := codec.MarshalUnsignedTx(tx)
if err != nil {
return err
}
// Using [unsignedBytes] and [tx.sig], derive the public key
// that authorized this transaction
key, err := factory.RecoverPublicKey(unsignedBytes, tx.sig)
if err != nil {
return err
}
tx.pubkey = key
return nil
}