diff --git a/vms/avm/export_tx.go b/vms/avm/export_tx.go index fce9a99..27d9e40 100644 --- a/vms/avm/export_tx.go +++ b/vms/avm/export_tx.go @@ -4,12 +4,9 @@ package avm 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/snow" "github.com/ava-labs/gecko/snow/choices" "github.com/ava-labs/gecko/vms/components/ava" @@ -17,47 +14,22 @@ import ( "github.com/ava-labs/gecko/vms/components/verify" ) -// ExportTx is a transaction that exports an asset to another blockchain. +// ExportTx is the basis of all transactions. type ExportTx struct { BaseTx `serialize:"true"` - Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction - Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction + ExportOuts []*ava.TransferableOutput `serialize:"true"` // The outputs this transaction is sending to the other chain } -// InputUTXOs track which UTXOs this transaction is consuming. -func (t *ExportTx) InputUTXOs() []*ava.UTXOID { - utxos := t.BaseTx.InputUTXOs() - for _, in := range t.Ins { - utxos = append(utxos, &in.UTXOID) - } - return utxos -} - -// AssetIDs returns the IDs of the assets this transaction depends on -func (t *ExportTx) AssetIDs() ids.Set { - assets := t.BaseTx.AssetIDs() - for _, in := range t.Ins { - assets.Add(in.AssetID()) - } - return assets -} - -var ( - errNoExportInputs = errors.New("no export inputs") -) - // SyntacticVerify that this transaction is well-formed. -func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error { +func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error { switch { case t == nil: return errNilTx - case len(t.Ins) == 0: - return errNoExportInputs - } - - if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil { - return err + case t.NetID != ctx.NetworkID: + return errWrongNetworkID + case !t.BCID.Equals(ctx.ChainID): + return errWrongChainID } fc := ava.NewFlowChecker() @@ -71,6 +43,16 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) return errOutputsNotSorted } + for _, out := range t.ExportOuts { + if err := out.Verify(); err != nil { + return err + } + fc.Produce(out.AssetID(), out.Output().Amount()) + } + if !ava.IsSortedTransferableOutputs(t.ExportOuts, c) { + return errOutputsNotSorted + } + for _, in := range t.Ins { if err := in.Verify(); err != nil { return err @@ -83,18 +65,17 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) // TODO: Add the Tx fee to the produced side - return fc.Verify() -} - -// SemanticVerify that this transaction is well-formed. -func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error { - if err := t.BaseTx.SemanticVerify(vm, uTx, creds); err != nil { + if err := fc.Verify(); err != nil { return err } - offset := len(t.BaseTx.Ins) + return t.Metadata.Verify() +} + +// SemanticVerify that this transaction is valid to be spent. +func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error { for i, in := range t.Ins { - cred := creds[i+offset] + cred := creds[i] fxIndex, err := vm.getFx(cred) if err != nil { @@ -112,9 +93,6 @@ func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab if !utxoAssetID.Equals(inAssetID) { return errAssetIDMismatch } - if !utxoAssetID.Equals(vm.ava) { - return errWrongAssetID - } if !vm.verifyFxUsage(fxIndex, inAssetID) { return errIncompatibleFx @@ -124,6 +102,13 @@ func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab return err } } + + for _, out := range t.ExportOuts { + if !out.AssetID().Equals(vm.ava) { + return errWrongAssetID + } + } + return nil } @@ -137,11 +122,11 @@ func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { vsmDB := versiondb.New(smDB) state := ava.NewPrefixedState(vsmDB, vm.codec) - for i, out := range t.Outs { + for i, out := range t.ExportOuts { utxo := &ava.UTXO{ UTXOID: ava.UTXOID{ TxID: txID, - OutputIndex: uint32(len(t.BaseTx.Outs) + i), + OutputIndex: uint32(len(t.Outs) + i), }, Asset: ava.Asset{ID: out.AssetID()}, Out: out.Out, diff --git a/vms/avm/export_tx_test.go b/vms/avm/export_tx_test.go index 6bcb2c1..008df4f 100644 --- a/vms/avm/export_tx_test.go +++ b/vms/avm/export_tx_test.go @@ -31,10 +31,6 @@ func TestExportTxSerialization(t *testing.T) { 0xdd, 0xdd, 0xdd, 0xdd, 0xcc, 0xcc, 0xcc, 0xcc, 0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa, 0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88, - // number of base outs: - 0x00, 0x00, 0x00, 0x00, - // number of base inputs: - 0x00, 0x00, 0x00, 0x00, // number of outs: 0x00, 0x00, 0x00, 0x00, // number of inputs: @@ -60,18 +56,18 @@ func TestExportTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x01, // sig index[0]: 0x00, 0x00, 0x00, 0x00, + // number of exported outs: + 0x00, 0x00, 0x00, 0x00, } - tx := &Tx{UnsignedTx: &ExportTx{ - BaseTx: BaseTx{ - NetID: 2, - BCID: ids.NewID([32]byte{ - 0xff, 0xff, 0xff, 0xff, 0xee, 0xee, 0xee, 0xee, - 0xdd, 0xdd, 0xdd, 0xdd, 0xcc, 0xcc, 0xcc, 0xcc, - 0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa, - 0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88, - }), - }, + tx := &Tx{UnsignedTx: &ExportTx{BaseTx: BaseTx{ + NetID: 2, + BCID: ids.NewID([32]byte{ + 0xff, 0xff, 0xff, 0xff, 0xee, 0xee, 0xee, 0xee, + 0xdd, 0xdd, 0xdd, 0xdd, 0xcc, 0xcc, 0xcc, 0xcc, + 0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa, + 0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88, + }), Ins: []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: ava.UTXOID{TxID: ids.NewID([32]byte{ 0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee, @@ -90,7 +86,7 @@ func TestExportTxSerialization(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - }} + }}} c := codec.NewDefault() c.RegisterType(&BaseTx{}) @@ -161,19 +157,19 @@ func TestIssueExportTx(t *testing.T) { BaseTx: BaseTx{ NetID: networkID, BCID: chainID, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: avaID, + OutputIndex: 1, + }, + Asset: ava.Asset{ID: avaID}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{SigIndices: []uint32{0}}, + }, + }}, }, - Ins: []*ava.TransferableInput{&ava.TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: avaID, - OutputIndex: 1, - }, - Asset: ava.Asset{ID: avaID}, - In: &secp256k1fx.TransferInput{ - Amt: 50000, - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }}, - Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + ExportOuts: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, @@ -297,19 +293,19 @@ func TestClearForceAcceptedExportTx(t *testing.T) { BaseTx: BaseTx{ NetID: networkID, BCID: chainID, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: avaID, + OutputIndex: 1, + }, + Asset: ava.Asset{ID: avaID}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{SigIndices: []uint32{0}}, + }, + }}, }, - Ins: []*ava.TransferableInput{&ava.TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: avaID, - OutputIndex: 1, - }, - Asset: ava.Asset{ID: avaID}, - In: &secp256k1fx.TransferInput{ - Amt: 50000, - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }}, - Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + ExportOuts: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000,