gecko/vms/spdagvm/tx.go

274 lines
6.8 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spdagvm
import (
"errors"
"fmt"
"strings"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/math"
)
var (
errNilTx = errors.New("nil tx")
errNilInput = errors.New("nil input")
errNilOutput = errors.New("nil output")
errNilSig = errors.New("nil signature")
errWrongNetworkID = errors.New("transaction has wrong network ID")
errWrongChainID = errors.New("transaction has wrong chain ID")
errOutputsNotSorted = errors.New("outputs not sorted")
errInputsNotSortedUnique = errors.New("inputs not sorted and unique")
errSigsNotSortedUnique = errors.New("sigs not sorted and unique")
errAddrsNotSortedUnique = errors.New("addresses not sorted and unique")
errTimesNotSortedUnique = errors.New("times not sorted and unique")
errUnknownInputType = errors.New("unknown input type")
errUnknownOutputType = errors.New("unknown output type")
errInputHasNoValue = errors.New("input has no value")
errOutputHasNoValue = errors.New("output has no value")
errOutputUnspendable = errors.New("output is unspendable")
errOutputUnoptimized = errors.New("output could be optimized")
errInputOverflow = errors.New("inputs overflowed uint64")
errOutputOverflow = errors.New("outputs (plus transaction fee) overflowed uint64")
errInvalidAmount = errors.New("amount mismatch")
errInsufficientFunds = errors.New("insufficient funds (includes transaction fee)")
errTimelocked = errors.New("time locked")
errTypeMismatch = errors.New("input and output types don't match")
errSpendFailed = errors.New("input does not satisfy output's spend requirements")
errInvalidSigLen = errors.New("signature is not the correct length")
)
// Tx is the core operation that can be performed. The tx uses the UTXO model.
// That is, a tx's inputs will consume previous tx's outputs. A tx will be
// valid if the inputs have the authority to consume the outputs they are
// attempting to consume and the inputs consume sufficient state to produce the
// outputs.
type Tx struct {
id ids.ID // ID of this transaction
networkID uint32 // The network this transaction was issued to
chainID ids.ID // ID of the chain this transaction exists on
ins []Input // Inputs to this transaction
outs []Output // Outputs of this transaction
bytes []byte // Byte representation of this transaction
}
// ID of this transaction
func (t *Tx) ID() ids.ID { return t.id }
// Ins returns the ins of this tx
func (t *Tx) Ins() []Input { return t.ins }
// Outs returns the outs of this tx
func (t *Tx) Outs() []Output { return t.outs }
// UTXOs returns the UTXOs that this transaction will produce if accepted.
func (t *Tx) UTXOs() []*UTXO {
txID := t.ID()
utxos := []*UTXO(nil)
c := Codec{}
for i, out := range t.outs {
utxo := &UTXO{
sourceID: txID,
sourceIndex: uint32(i),
id: txID.Prefix(uint64(i)),
out: out,
}
b, _ := c.MarshalUTXO(utxo)
utxo.bytes = b
utxos = append(utxos, utxo)
}
return utxos
}
// Bytes of this transaction
func (t *Tx) Bytes() []byte { return t.bytes }
// Verify that this transaction is well formed
func (t *Tx) Verify(ctx *snow.Context, txFee uint64) error {
switch {
case t == nil:
return errNilTx
case ctx.NetworkID != t.networkID:
return errWrongNetworkID
case !ctx.ChainID.Equals(t.chainID):
return errWrongChainID
}
if err := t.verifyIns(); err != nil {
return err
}
if err := t.verifyOuts(); err != nil {
return err
}
if err := t.verifyFunds(txFee); err != nil {
return err
}
if err := t.verifySigs(); err != nil {
return err
}
return nil
}
// verify that inputs are well-formed
func (t *Tx) verifyIns() error {
for _, in := range t.ins {
if in == nil {
return errNilInput
}
if err := in.Verify(); err != nil {
return err
}
}
if !isSortedAndUniqueIns(t.ins) {
return errInputsNotSortedUnique
}
return nil
}
// verify outputs are well-formed
func (t *Tx) verifyOuts() error {
for _, out := range t.outs {
if out == nil {
return errNilOutput
}
if err := out.Verify(); err != nil {
return err
}
}
if !isSortedOuts(t.outs) {
return errOutputsNotSorted
}
return nil
}
// Ensure that the sum of the input amounts
// is at least:
// [the sum of the output amounts] + [txFee]
func (t *Tx) verifyFunds(txFee uint64) error {
inFunds := uint64(0)
for _, in := range t.ins {
err := error(nil)
switch i := in.(type) {
case *InputPayment:
inFunds, err = math.Add64(inFunds, i.amount)
default:
return errUnknownInputType
}
if err != nil {
return errInputOverflow
}
}
outFunds := uint64(0)
for _, out := range t.outs {
err := error(nil)
switch o := out.(type) {
case *OutputPayment:
outFunds, err = math.Add64(outFunds, o.amount)
case *OutputTakeOrLeave:
outFunds, err = math.Add64(outFunds, o.amount)
default:
return errUnknownOutputType
}
if err != nil {
return errOutputOverflow
}
}
outFundsPlusFee, err := math.Add64(outFunds, txFee)
if err != nil {
return errOutputOverflow
}
if outFundsPlusFee > inFunds {
return errInsufficientFunds
}
return nil
}
// verify the signatures
func (t *Tx) verifySigs() error {
if !crypto.EnableCrypto {
return nil
}
c := Codec{}
txBytes, err := c.MarshalUnsignedTx(t)
if err != nil {
return err
}
txHash := hashing.ComputeHash256(txBytes)
factory := crypto.FactorySECP256K1R{}
for _, in := range t.ins {
switch i := in.(type) {
case *InputPayment:
for _, sig := range i.sigs {
key, err := factory.RecoverHashPublicKey(txHash, sig.sig)
if err != nil {
return err
}
sig.parsedPubKey = key.Bytes()
}
}
}
return nil
}
// PrefixedString converts this tx to a string representation with a prefix for
// each newline
func (t *Tx) PrefixedString(prefix string) string {
s := strings.Builder{}
nestedPrefix := fmt.Sprintf("%s ", prefix)
ins := t.Ins()
s.WriteString(fmt.Sprintf("Tx(\n"+
"%s ID = %s\n"+
"%s NumIns = %d\n",
prefix, t.ID(),
prefix, len(ins)))
inFormat := fmt.Sprintf("%%s In[%s]: %%s\n",
formatting.IntFormat(len(ins)-1))
for i, in := range ins {
s.WriteString(fmt.Sprintf(inFormat,
prefix, i,
in.PrefixedString(nestedPrefix),
))
}
outs := t.Outs()
s.WriteString(fmt.Sprintf("%s NumOuts = %d\n",
prefix, len(outs)))
outFormat := fmt.Sprintf("%%s Out[%s]: %%s\n",
formatting.IntFormat(len(outs)-1))
for i, out := range outs {
s.WriteString(fmt.Sprintf(outFormat,
prefix, i,
out.PrefixedString(nestedPrefix),
))
}
s.WriteString(fmt.Sprintf("%s)", prefix))
return s.String()
}
func (t *Tx) String() string { return t.PrefixedString("") }