gecko/vms/spdagvm/builder.go

196 lines
5.2 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spdagvm
import (
"errors"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/math"
)
var (
errInputSignerMismatch = errors.New("wrong number of signers for the inputs")
errNilSigner = errors.New("nil signer")
errNilChainID = errors.New("nil chain id")
)
// Builder defines the functionality for building payment objects.
type Builder struct {
NetworkID uint32
ChainID ids.ID
}
// NewInputPayment returns a new input that consumes a UTXO.
func (b Builder) NewInputPayment(sourceID ids.ID, sourceIndex uint32, amount uint64, sigs []*Sig) Input {
SortTxSig(sigs)
return &InputPayment{
sourceID: sourceID,
sourceIndex: sourceIndex,
amount: amount,
sigs: sigs,
}
}
// NewOutputPayment returns a new output that generates a standard UTXO.
func (b Builder) NewOutputPayment(
amount,
locktime uint64,
threshold uint32,
addresses []ids.ShortID,
) Output {
ids.SortShortIDs(addresses)
return &OutputPayment{
amount: amount,
locktime: locktime,
threshold: threshold,
addresses: addresses,
}
}
// NewOutputTakeOrLeave returns a new output that generates a UTXO with all the
// features of the standard UTXO, plus the ability to have a different set of
// addresses spend the asset after the specified time.
func (b Builder) NewOutputTakeOrLeave(
amount,
locktime1 uint64,
threshold1 uint32,
addresses1 []ids.ShortID,
locktime2 uint64,
threshold2 uint32,
addresses2 []ids.ShortID,
) Output {
ids.SortShortIDs(addresses1)
ids.SortShortIDs(addresses2)
return &OutputTakeOrLeave{
amount: amount,
locktime1: locktime1,
threshold1: threshold1,
addresses1: addresses1,
locktime2: locktime2,
threshold2: threshold2,
addresses2: addresses2,
}
}
// NewSig returns a new signature object. This object will specify the address
// in the UTXO that will be used to authorize the wrapping input.
func (b Builder) NewSig(index uint32) *Sig { return &Sig{index: index} }
// NewTxFromUTXOs returns a new transaction where:
// * One of the outputs is an Output with [amount] ava that is controlled by [toAddr].
// * This output can't be spent until at least [locktime].
// * If there is any "change" there is another output controlled by [changeAddr] with the change.
// * The UTXOs consumed to make this transaction are a subset of [utxos].
// * The keys controlling [utxos] are in [keychain]
func (b Builder) NewTxFromUTXOs(keychain *Keychain, utxos []*UTXO, amount, txFee, locktime uint64,
threshold uint32, toAddrs []ids.ShortID, changeAddr ids.ShortID, currentTime uint64) (*Tx, error) {
ins := []Input{} // Consumed by this transaction
signers := []*InputSigner{} // Each corresponds to an input consumed by this transaction
amountPlusTxFee, err := math.Add64(amount, txFee)
if err != nil {
return nil, errAmountOverflow
}
spent := uint64(0) // The sum of the UTXOs consumed in this transaction
for i := 0; i < len(utxos) && amountPlusTxFee > spent; i++ {
utxo := utxos[i]
if in, signer, err := keychain.Spend(utxo, currentTime); err == nil {
ins = append(ins, in)
amount := in.(*InputPayment).Amount()
spent += amount
signers = append(signers, signer)
}
}
if spent < amountPlusTxFee {
return nil, errInsufficientFunds
}
outs := []Output{ // List of outputs
b.NewOutputPayment(amount, locktime, threshold, toAddrs), // The primary output
}
// If there is "change", add another output
if spent > amountPlusTxFee {
outs = append(outs,
b.NewOutputPayment(spent-amountPlusTxFee, 0, 1, []ids.ShortID{changeAddr}),
)
}
return b.NewTx(ins, outs, signers) // Sort, marshal, sign, build the transaction
}
// NewTx returns a new transaction where:
// * The inputs to the Tx are [ins], sorted.
// * The outputs of the Tx are [outs], sorted
// * The ith signer will be used to sign the ith input. This means that len([inputs]) must be == len([signers]).
// TODO: Should the signer be part of the input
func (b Builder) NewTx(ins []Input, outs []Output, signers []*InputSigner) (*Tx, error) {
if b.ChainID.IsZero() {
return nil, errNilChainID
}
if len(ins) != len(signers) {
return nil, errInputSignerMismatch
}
SortOuts(outs)
SortIns(ins, signers)
t := &Tx{
networkID: b.NetworkID,
chainID: b.ChainID,
ins: ins,
outs: outs,
}
c := Codec{}
unsignedBytes, err := c.MarshalUnsignedTx(t)
if err != nil {
return nil, err
}
unsignedHash := hashing.ComputeHash256(unsignedBytes)
for i, rawIn := range t.ins {
switch in := rawIn.(type) {
case *InputPayment:
signer := signers[i]
if signer == nil {
return nil, errNilSigner
}
for j, key := range signer.Keys {
if !crypto.EnableCrypto {
in.sigs[j].sig = make([]byte, crypto.SECP256K1RSigLen)
continue
}
sig, err := key.SignHash(unsignedHash)
if err != nil {
return nil, err
}
in.sigs[j].sig = sig
}
}
}
bytes, err := c.MarshalTx(t)
if err != nil {
return nil, err
}
t.bytes = bytes
t.id = ids.NewID(hashing.ComputeHash256Array(t.bytes))
return t, nil
}