gecko/vms/platformvm/export_tx.go

194 lines
4.7 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package platformvm
import (
"errors"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"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"
"github.com/ava-labs/gecko/vms/components/ava"
)
var (
errNoExportOutputs = errors.New("no export outputs")
errOutputsNotSorted = errors.New("outputs not sorted")
)
// UnsignedExportTx is an unsigned ExportTx
type UnsignedExportTx struct {
// ID of the network this blockchain exists on
NetworkID uint32 `serialize:"true"`
// Next unused nonce of account paying for this transaction.
Nonce uint64 `serialize:"true"`
Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction
}
// ExportTx exports funds to the AVM
type ExportTx struct {
UnsignedExportTx `serialize:"true"`
Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
vm *VM
id ids.ID
key crypto.PublicKey // public key of transaction signer
bytes []byte
}
func (tx *ExportTx) initialize(vm *VM) error {
tx.vm = vm
txBytes, err := Codec.Marshal(tx) // byte repr. of the signed tx
tx.bytes = txBytes
tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes))
return err
}
// ID of this transaction
func (tx *ExportTx) ID() ids.ID { return tx.id }
// Key returns the public key of the signer of this transaction
// Precondition: tx.Verify() has been called and returned nil
func (tx *ExportTx) Key() crypto.PublicKey { return tx.key }
// Bytes returns the byte representation of an ExportTx
func (tx *ExportTx) Bytes() []byte { return tx.bytes }
// InputUTXOs returns an empty set
func (tx *ExportTx) InputUTXOs() ids.Set { return ids.Set{} }
// SyntacticVerify this transaction is well-formed
// Also populates [tx.Key] with the public key that signed this transaction
func (tx *ExportTx) SyntacticVerify() error {
switch {
case tx == nil:
return errNilTx
case tx.key != nil:
return nil // Only verify the transaction once
case tx.NetworkID != tx.vm.Ctx.NetworkID: // verify the transaction is on this network
return errWrongNetworkID
case tx.id.IsZero():
return errInvalidID
case len(tx.Outs) == 0:
return errNoExportOutputs
}
for _, out := range tx.Outs {
if err := out.Verify(); err != nil {
return err
}
if !out.AssetID().Equals(tx.vm.ava) {
return errUnknownAsset
}
}
if !ava.IsSortedTransferableOutputs(tx.Outs, Codec) {
return errOutputsNotSorted
}
unsignedIntf := interface{}(&tx.UnsignedExportTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr of unsigned tx
if err != nil {
return err
}
key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:])
if err != nil {
return err
}
tx.key = key
return nil
}
// SemanticVerify this transaction is valid.
func (tx *ExportTx) SemanticVerify(db database.Database) error {
if err := tx.SyntacticVerify(); err != nil {
return err
}
amount := uint64(0)
for _, out := range tx.Outs {
newAmount, err := math.Add64(out.Out.Amount(), amount)
if err != nil {
return err
}
amount = newAmount
}
accountID := tx.key.Address()
account, err := tx.vm.getAccount(db, accountID)
if err != nil {
return errDBAccount
}
account, err = account.Remove(amount, tx.Nonce)
if err != nil {
return err
}
return tx.vm.putAccount(db, account)
}
// Accept this transaction.
func (tx *ExportTx) Accept(batch database.Batch) error {
txID := tx.ID()
smDB := tx.vm.Ctx.SharedMemory.GetDatabase(tx.vm.avm)
defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(tx.vm.avm)
vsmDB := versiondb.New(smDB)
state := ava.NewPrefixedState(vsmDB, Codec)
for i, out := range tx.Outs {
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: txID,
OutputIndex: uint32(i),
},
Asset: ava.Asset{ID: out.AssetID()},
Out: out.Out,
}
if err := state.FundPlatformUTXO(utxo); err != nil {
return err
}
}
sharedBatch, err := vsmDB.CommitBatch()
if err != nil {
return err
}
return atomic.WriteAll(batch, sharedBatch)
}
func (vm *VM) newExportTx(nonce uint64, networkID uint32, outs []*ava.TransferableOutput, from *crypto.PrivateKeySECP256K1R) (*ExportTx, error) {
ava.SortTransferableOutputs(outs, Codec)
tx := &ExportTx{UnsignedExportTx: UnsignedExportTx{
NetworkID: networkID,
Nonce: nonce,
Outs: outs,
}}
unsignedIntf := interface{}(&tx.UnsignedExportTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction
if err != nil {
return nil, err
}
sig, err := from.Sign(unsignedBytes)
if err != nil {
return nil, err
}
copy(tx.Sig[:], sig)
return tx, tx.initialize(vm)
}