mirror of https://github.com/poanetwork/gecko.git
274 lines
6.8 KiB
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("") }
|