diff --git a/vms/avm/unique_tx.go b/vms/avm/unique_tx.go index c47ca442c83d..1fe1daaf9ced 100644 --- a/vms/avm/unique_tx.go +++ b/vms/avm/unique_tx.go @@ -269,8 +269,7 @@ func (tx *UniqueTx) Bytes() []byte { return tx.Tx.Bytes() } -// Verify the validity of this transaction -func (tx *UniqueTx) Verify() error { +func (tx *UniqueTx) verifyWithoutCacheWrites() error { switch status := tx.Status(); status { case choices.Unknown: return errUnknownTx @@ -283,6 +282,17 @@ func (tx *UniqueTx) Verify() error { } } +// Verify the validity of this transaction +func (tx *UniqueTx) Verify() error { + if err := tx.verifyWithoutCacheWrites(); err != nil { + return err + } + + tx.verifiedState = true + tx.vm.pubsub.Publish("verified", tx.ID()) + return nil +} + // SyntacticVerify verifies that this transaction is well formed func (tx *UniqueTx) SyntacticVerify() error { tx.refresh() @@ -310,11 +320,5 @@ func (tx *UniqueTx) SemanticVerify() error { return tx.validity } - if err := tx.Tx.SemanticVerify(tx.vm, tx.UnsignedTx); err != nil { - return err - } - - tx.verifiedState = true - tx.vm.pubsub.Publish("verified", tx.ID()) - return nil + return tx.Tx.SemanticVerify(tx.vm, tx.UnsignedTx) } diff --git a/vms/avm/vm.go b/vms/avm/vm.go index f4fd392b079d..e5d5a0071ac3 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -307,7 +307,7 @@ func (vm *VM) GetTx(txID ids.ID) (snowstorm.Tx, error) { } // Verify must be called in the case the that tx was flushed from the unique // cache. - return tx, tx.Verify() + return tx, tx.verifyWithoutCacheWrites() } /* @@ -328,7 +328,7 @@ func (vm *VM) IssueTx(b []byte) (ids.ID, error) { if err != nil { return ids.ID{}, err } - if err := tx.Verify(); err != nil { + if err := tx.verifyWithoutCacheWrites(); err != nil { return ids.ID{}, err } vm.issueTx(tx) @@ -609,7 +609,7 @@ func (vm *VM) getUTXO(utxoID *avax.UTXOID) (*avax.UTXO, error) { txID: inputTx, } - if err := parent.Verify(); err != nil { + if err := parent.verifyWithoutCacheWrites(); err != nil { return nil, errMissingUTXO } else if status := parent.Status(); status.Decided() { return nil, errMissingUTXO diff --git a/vms/avm/vm_test.go b/vms/avm/vm_test.go index 37ab6b4b7701..c56808d9be21 100644 --- a/vms/avm/vm_test.go +++ b/vms/avm/vm_test.go @@ -521,7 +521,7 @@ type testTxBytes struct{ unsignedBytes []byte } func (tx *testTxBytes) UnsignedBytes() []byte { return tx.unsignedBytes } func TestIssueTx(t *testing.T) { - genesisBytes, issuer, vm , _ := GenesisVM(t) + genesisBytes, issuer, vm, _ := GenesisVM(t) ctx := vm.ctx defer func() { vm.Shutdown() @@ -551,7 +551,7 @@ func TestIssueTx(t *testing.T) { } func TestGenesisGetUTXOs(t *testing.T) { - _, _, vm , _ := GenesisVM(t) + _, _, vm, _ := GenesisVM(t) ctx := vm.ctx defer func() { vm.Shutdown() @@ -575,7 +575,7 @@ func TestGenesisGetUTXOs(t *testing.T) { // Test issuing a transaction that consumes a currently pending UTXO. The // transaction should be issued successfully. func TestIssueDependentTx(t *testing.T) { - genesisBytes, issuer, vm , _ := GenesisVM(t) + genesisBytes, issuer, vm, _ := GenesisVM(t) ctx := vm.ctx defer func() { vm.Shutdown() @@ -968,7 +968,7 @@ func TestIssueProperty(t *testing.T) { } func TestVMFormat(t *testing.T) { - _, _, vm , _ := GenesisVM(t) + _, _, vm, _ := GenesisVM(t) defer func() { vm.Shutdown() vm.ctx.Lock.Unlock() @@ -994,7 +994,7 @@ func TestVMFormat(t *testing.T) { } func TestTxCached(t *testing.T) { - genesisBytes, _, vm , _ := GenesisVM(t) + genesisBytes, _, vm, _ := GenesisVM(t) ctx := vm.ctx defer func() { vm.Shutdown() @@ -1022,7 +1022,7 @@ func TestTxCached(t *testing.T) { } func TestTxNotCached(t *testing.T) { - genesisBytes, _, vm , _ := GenesisVM(t) + genesisBytes, _, vm, _ := GenesisVM(t) ctx := vm.ctx defer func() { vm.Shutdown() @@ -1050,3 +1050,343 @@ func TestTxNotCached(t *testing.T) { assert.NoError(t, err) assert.True(t, *called, "should have called the DB") } + +func TestTxVerifyAfterIssueTx(t *testing.T) { + genesisBytes, issuer, vm, _ := GenesisVM(t) + ctx := vm.ctx + defer func() { + vm.Shutdown() + ctx.Lock.Unlock() + }() + + genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) + key := keys[0] + firstTx := &Tx{UnsignedTx: &BaseTx{BaseTx: avax.BaseTx{ + NetworkID: networkID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: avax.UTXOID{ + TxID: genesisTx.ID(), + OutputIndex: 1, + }, + Asset: avax.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 50000, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }}, + }}} + if err := firstTx.SignSECP256K1Fx(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{key}}); err != nil { + t.Fatal(err) + } + + secondTx := &Tx{UnsignedTx: &BaseTx{BaseTx: avax.BaseTx{ + NetworkID: networkID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: avax.UTXOID{ + TxID: genesisTx.ID(), + OutputIndex: 1, + }, + Asset: avax.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }}, + }}} + if err := secondTx.SignSECP256K1Fx(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{key}}); err != nil { + t.Fatal(err) + } + + parsedSecondTx, err := vm.ParseTx(secondTx.Bytes()) + if err != nil { + t.Fatal(err) + } + if err := parsedSecondTx.Verify(); err != nil { + t.Fatal(err) + } + if _, err := vm.IssueTx(firstTx.Bytes()); err != nil { + t.Fatal(err) + } + if err := parsedSecondTx.Accept(); err != nil { + t.Fatal(err) + } + ctx.Lock.Unlock() + + msg := <-issuer + if msg != common.PendingTxs { + t.Fatalf("Wrong message") + } + ctx.Lock.Lock() + + txs := vm.PendingTxs() + if len(txs) != 1 { + t.Fatalf("Should have returned %d tx(s)", 1) + } + parsedFirstTx := txs[0] + + if err := parsedFirstTx.Verify(); err == nil { + t.Fatalf("Should have errored due to a missing UTXO") + } +} + +func TestTxVerifyAfterGetTx(t *testing.T) { + genesisBytes, _, vm, _ := GenesisVM(t) + ctx := vm.ctx + defer func() { + vm.Shutdown() + ctx.Lock.Unlock() + }() + + genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) + key := keys[0] + firstTx := &Tx{UnsignedTx: &BaseTx{BaseTx: avax.BaseTx{ + NetworkID: networkID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: avax.UTXOID{ + TxID: genesisTx.ID(), + OutputIndex: 1, + }, + Asset: avax.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 50000, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }}, + }}} + if err := firstTx.SignSECP256K1Fx(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{key}}); err != nil { + t.Fatal(err) + } + + secondTx := &Tx{UnsignedTx: &BaseTx{BaseTx: avax.BaseTx{ + NetworkID: networkID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: avax.UTXOID{ + TxID: genesisTx.ID(), + OutputIndex: 1, + }, + Asset: avax.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }}, + }}} + if err := secondTx.SignSECP256K1Fx(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{key}}); err != nil { + t.Fatal(err) + } + + parsedSecondTx, err := vm.ParseTx(secondTx.Bytes()) + if err != nil { + t.Fatal(err) + } + if err := parsedSecondTx.Verify(); err != nil { + t.Fatal(err) + } + if _, err := vm.IssueTx(firstTx.Bytes()); err != nil { + t.Fatal(err) + } + parsedFirstTx, err := vm.GetTx(firstTx.ID()) + if err != nil { + t.Fatal(err) + } + if err := parsedSecondTx.Accept(); err != nil { + t.Fatal(err) + } + if err := parsedFirstTx.Verify(); err == nil { + t.Fatalf("Should have errored due to a missing UTXO") + } +} + +func TestTxVerifyAfterVerifyAncestorTx(t *testing.T) { + genesisBytes, _, vm, _ := GenesisVM(t) + ctx := vm.ctx + defer func() { + vm.Shutdown() + ctx.Lock.Unlock() + }() + + genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) + key := keys[0] + firstTx := &Tx{UnsignedTx: &BaseTx{BaseTx: avax.BaseTx{ + NetworkID: networkID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: avax.UTXOID{ + TxID: genesisTx.ID(), + OutputIndex: 1, + }, + Asset: avax.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 50000, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }}, + }}} + if err := firstTx.SignSECP256K1Fx(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{key}}); err != nil { + t.Fatal(err) + } + + firstTxDescendant := &Tx{UnsignedTx: &BaseTx{BaseTx: avax.BaseTx{ + NetworkID: networkID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: avax.UTXOID{ + TxID: firstTx.ID(), + OutputIndex: 0, + }, + Asset: avax.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 50000, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }}, + }}} + if err := firstTxDescendant.SignSECP256K1Fx(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{key}}); err != nil { + t.Fatal(err) + } + + secondTx := &Tx{UnsignedTx: &BaseTx{BaseTx: avax.BaseTx{ + NetworkID: networkID, + BlockchainID: chainID, + Ins: []*avax.TransferableInput{{ + UTXOID: avax.UTXOID{ + TxID: genesisTx.ID(), + OutputIndex: 1, + }, + Asset: avax.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, + }, + }, + }, + }}, + Outs: []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }}, + }}} + if err := secondTx.SignSECP256K1Fx(vm.codec, [][]*crypto.PrivateKeySECP256K1R{{key}}); err != nil { + t.Fatal(err) + } + + parsedSecondTx, err := vm.ParseTx(secondTx.Bytes()) + if err != nil { + t.Fatal(err) + } + if err := parsedSecondTx.Verify(); err != nil { + t.Fatal(err) + } + if _, err := vm.IssueTx(firstTx.Bytes()); err != nil { + t.Fatal(err) + } + if _, err := vm.IssueTx(firstTxDescendant.Bytes()); err != nil { + t.Fatal(err) + } + parsedFirstTx, err := vm.GetTx(firstTx.ID()) + if err != nil { + t.Fatal(err) + } + if err := parsedSecondTx.Accept(); err != nil { + t.Fatal(err) + } + if err := parsedFirstTx.Verify(); err == nil { + t.Fatalf("Should have errored due to a missing UTXO") + } +}