mirror of https://github.com/poanetwork/gecko.git
144 lines
3.9 KiB
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
|
||
|
}
|