diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index ea3f05b87e..8988f7aeae 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -29,6 +29,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/execpool" @@ -60,6 +61,23 @@ func verifyTxn(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext) er return batchVerifier.Verify() } +type DummyLedgerForSignature struct { +} + +func (d *DummyLedgerForSignature) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) { + return bookkeeping.BlockHeader{ + Round: 50, + GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: protocol.ConsensusCurrentVersion, + }, + RewardsState: bookkeeping.RewardsState{ + FeeSink: feeSink, + RewardsPool: poolAddr, + }, + }, nil +} + func keypair() *crypto.SignatureSecrets { var seed crypto.Seed crypto.RandBytes(seed[:]) @@ -67,18 +85,98 @@ func keypair() *crypto.SignatureSecrets { return s } -func generateTestObjects(numTxs, numAccs int, blockRound basics.Round) ([]transactions.Transaction, []transactions.SignedTxn, []*crypto.SignatureSecrets, []basics.Address) { +func generateMultiSigTxn(numTxs, numAccs int, blockRound basics.Round, t *testing.T) ([]transactions.Transaction, []transactions.SignedTxn, []*crypto.SignatureSecrets, []basics.Address) { + secrets, addresses, pks, multiAddress := generateMultiSigAccounts(t, numAccs) + + numMultiSigAcct := len(multiAddress) txs := make([]transactions.Transaction, numTxs) signed := make([]transactions.SignedTxn, numTxs) + + var iss, exp int + u := uint64(0) + + for i := 0; i < numTxs; i++ { + s := rand.Intn(numMultiSigAcct) + r := rand.Intn(numMultiSigAcct) + a := rand.Intn(1000) + f := config.Consensus[protocol.ConsensusCurrentVersion].MinTxnFee + uint64(rand.Intn(10)) + u + if blockRound == 0 { + iss = 50 + rand.Intn(30) + exp = iss + 10 + } else { + iss = int(blockRound) / 2 + exp = int(blockRound) + rand.Intn(30) + } + + txs[i] = transactions.Transaction{ + Type: protocol.PaymentTx, + Header: transactions.Header{ + Sender: multiAddress[s], + Fee: basics.MicroAlgos{Raw: f}, + FirstValid: basics.Round(iss), + LastValid: basics.Round(exp), + GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), + }, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: multiAddress[r], + Amount: basics.MicroAlgos{Raw: uint64(a)}, + }, + } + signed[i].Txn = txs[i] + + // create multi sig that 2 out of 3 has signed the txn + var sigs [2]crypto.MultisigSig + for j := 0; j < 2; j++ { + msig, err := crypto.MultisigSign(txs[i], crypto.Digest(multiAddress[s]), 1, 2, pks[3*s:3*s+3], *secrets[3*s+j]) + require.NoError(t, err) + sigs[j] = msig + } + msig, err := crypto.MultisigAssemble(sigs[:]) + require.NoError(t, err) + signed[i].Msig = msig + u += 100 + } + + return txs, signed, secrets, addresses +} + +func generateMultiSigAccounts(t *testing.T, numAccs int) ([]*crypto.SignatureSecrets, []basics.Address, []crypto.PublicKey, []basics.Address) { + require.Equal(t, numAccs%3, 0, "numAccs should be multiple of 3 to create multiaccounts") + + numMultiSigAcct := numAccs / 3 + secrets, addresses, pks := generateAccounts(numAccs) + + multiAddress := make([]basics.Address, numMultiSigAcct) + + // create multiAccounts + for i := 0; i < numAccs; i += 3 { + multiSigAdd, err := crypto.MultisigAddrGen(1, 2, pks[i:i+3]) + require.NoError(t, err) + multiAddress[i/3] = basics.Address(multiSigAdd) + } + return secrets, addresses, pks, multiAddress +} + +func generateAccounts(numAccs int) ([]*crypto.SignatureSecrets, []basics.Address, []crypto.PublicKey) { secrets := make([]*crypto.SignatureSecrets, numAccs) addresses := make([]basics.Address, numAccs) + pks := make([]crypto.PublicKey, numAccs) for i := 0; i < numAccs; i++ { secret := keypair() addr := basics.Address(secret.SignatureVerifier) secrets[i] = secret addresses[i] = addr + pks[i] = secret.SignatureVerifier } + return secrets, addresses, pks +} + +func generateTestObjects(numTxs, numAccs int, blockRound basics.Round) ([]transactions.Transaction, []transactions.SignedTxn, []*crypto.SignatureSecrets, []basics.Address) { + txs := make([]transactions.Transaction, numTxs) + signed := make([]transactions.SignedTxn, numTxs) + secrets, addresses, _ := generateAccounts(numAccs) + var iss, exp int u := uint64(0) for i := 0; i < numTxs; i++ { @@ -275,17 +373,7 @@ func TestPaysetGroups(t *testing.T) { partitiontest.PartitionTest(t) _, signedTxn, secrets, addrs := generateTestObjects(10000, 20, 50) - blkHdr := bookkeeping.BlockHeader{ - Round: 50, - GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: protocol.ConsensusCurrentVersion, - }, - RewardsState: bookkeeping.RewardsState{ - FeeSink: feeSink, - RewardsPool: poolAddr, - }, - } + blkHdr := createDummyBlockHeader() execPool := execpool.MakePool(t) verificationPool := execpool.MakeBacklog(execPool, 64, execpool.LowPriority, t) @@ -355,17 +443,7 @@ func BenchmarkPaysetGroups(b *testing.B) { b.N = 2000 } _, signedTxn, secrets, addrs := generateTestObjects(b.N, 20, 50) - blkHdr := bookkeeping.BlockHeader{ - Round: 50, - GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: protocol.ConsensusCurrentVersion, - }, - RewardsState: bookkeeping.RewardsState{ - FeeSink: feeSink, - RewardsPool: poolAddr, - }, - } + blkHdr := createDummyBlockHeader() execPool := execpool.MakePool(b) verificationPool := execpool.MakeBacklog(execPool, 64, execpool.LowPriority, b) @@ -380,6 +458,82 @@ func BenchmarkPaysetGroups(b *testing.B) { b.StopTimer() } +func TestTxnGroupMixedSignatures(t *testing.T) { + partitiontest.PartitionTest(t) + + _, signedTxn, secrets, addrs := generateTestObjects(1, 20, 50) + blkHdr := createDummyBlockHeader() + + // add a simple logic that verifies this condition: + // sha256(arg0) == base64decode(5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=) + op, err := logic.AssembleString(`arg 0 +sha256 +byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= +==`) + require.NoError(t, err) + + txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + + dummyLedger := DummyLedgerForSignature{} + _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + require.NoError(t, err) + + ///// no sig + tmpSig := txnGroups[0][0].Sig + txnGroups[0][0].Sig = crypto.Signature{} + _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + require.Error(t, err) + require.Contains(t, err.Error(), "has no sig") + txnGroups[0][0].Sig = tmpSig + + ///// Sig + multiSig + txnGroups[0][0].Msig.Subsigs = make([]crypto.MultisigSubsig, 1) + txnGroups[0][0].Msig.Subsigs[0] = crypto.MultisigSubsig{ + Key: crypto.PublicKey{0x1}, + Sig: crypto.Signature{0x2}, + } + _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + require.Error(t, err) + require.Contains(t, err.Error(), "should only have one of Sig or Msig or LogicSig") + txnGroups[0][0].Msig.Subsigs = nil + + ///// Sig + logic + txnGroups[0][0].Lsig.Logic = op.Program + _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + require.Error(t, err) + require.Contains(t, err.Error(), "should only have one of Sig or Msig or LogicSig") + txnGroups[0][0].Lsig.Logic = []byte{} + + ///// MultiSig + logic + txnGroups[0][0].Sig = crypto.Signature{} + txnGroups[0][0].Lsig.Logic = op.Program + txnGroups[0][0].Msig.Subsigs = make([]crypto.MultisigSubsig, 1) + txnGroups[0][0].Msig.Subsigs[0] = crypto.MultisigSubsig{ + Key: crypto.PublicKey{0x1}, + Sig: crypto.Signature{0x2}, + } + _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + require.Error(t, err) + require.Contains(t, err.Error(), "should only have one of Sig or Msig or LogicSig") + txnGroups[0][0].Lsig.Logic = []byte{} + txnGroups[0][0].Sig = tmpSig + txnGroups[0][0].Msig.Subsigs = nil + + ///// logic with sig and multi sig + txnGroups[0][0].Sig = crypto.Signature{} + txnGroups[0][0].Lsig.Logic = op.Program + txnGroups[0][0].Lsig.Sig = tmpSig + txnGroups[0][0].Lsig.Msig.Subsigs = make([]crypto.MultisigSubsig, 1) + txnGroups[0][0].Lsig.Msig.Subsigs[0] = crypto.MultisigSubsig{ + Key: crypto.PublicKey{0x1}, + Sig: crypto.Signature{0x2}, + } + _, err = TxnGroup(txnGroups[0], blkHdr, nil, &dummyLedger) + require.Error(t, err) + require.Contains(t, err.Error(), "should only have one of Sig or Msig") + +} + func generateTransactionGroups(signedTxns []transactions.SignedTxn, secrets []*crypto.SignatureSecrets, addrs []basics.Address) [][]transactions.SignedTxn { addrToSecret := make(map[basics.Address]*crypto.SignatureSecrets) for i, addr := range addrs { @@ -409,46 +563,221 @@ func generateTransactionGroups(signedTxns []transactions.SignedTxn, secrets []*c return txnGroups } -func BenchmarkTxn(b *testing.B) { - if b.N < 2000 { - b.N = 2000 +func TestTxnGroupCacheUpdate(t *testing.T) { + partitiontest.PartitionTest(t) + + _, signedTxn, secrets, addrs := generateTestObjects(100, 20, 50) + blkHdr := createDummyBlockHeader() + + txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + breakSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Sig[0]++ } - _, signedTxn, secrets, addrs := generateTestObjects(b.N, 20, 50) - blk := bookkeeping.Block{ - BlockHeader: bookkeeping.BlockHeader{ - Round: 50, - GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: protocol.ConsensusCurrentVersion, - }, - RewardsState: bookkeeping.RewardsState{ - FeeSink: feeSink, - RewardsPool: poolAddr, - }, - }, + restoreSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Sig[0]-- } - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchVerificationFailed.Error()) +} - b.ResetTimer() - for _, txnGroup := range txnGroups { - groupCtx, err := PrepareGroupContext(txnGroup, blk.BlockHeader, nil) - require.NoError(b, err) - for i, txn := range txnGroup { - err := verifyTxn(&txn, i, groupCtx) - require.NoError(b, err) - } +// TestTxnGroupCacheUpdateMultiSig makes sure that a payment transaction signed with multisig +// is valid (and added to the cache) only if all signatures in the multisig are correct +func TestTxnGroupCacheUpdateMultiSig(t *testing.T) { + partitiontest.PartitionTest(t) + + _, signedTxn, _, _ := generateMultiSigTxn(100, 30, 50, t) + blkHdr := createDummyBlockHeader() + + txnGroups := make([][]transactions.SignedTxn, len(signedTxn)) + for i := 0; i < len(txnGroups); i++ { + txnGroups[i] = make([]transactions.SignedTxn, 1) + txnGroups[i][0] = signedTxn[i] } - b.StopTimer() + breakSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Msig.Subsigs[0].Sig[0]++ + } + restoreSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Msig.Subsigs[0].Sig[0]-- + } + verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchVerificationFailed.Error()) } -// TestTxnGroupCacheUpdate uses TxnGroup to verify txns and add them to the -// cache. Then makes sure that only the valid txns are verified and added to -// the cache. -func TestTxnGroupCacheUpdate(t *testing.T) { +// TestTxnGroupCacheUpdateFailLogic test makes sure that a payment transaction contains a logic (and no signature) +// is valid (and added to the cache) only if logic passes +func TestTxnGroupCacheUpdateFailLogic(t *testing.T) { partitiontest.PartitionTest(t) - _, signedTxn, secrets, addrs := generateTestObjects(100, 20, 50) - blkHdr := bookkeeping.BlockHeader{ + _, signedTxn, _, _ := generateTestObjects(100, 20, 50) + blkHdr := createDummyBlockHeader() + + // sign the transcation with logic + for i := 0; i < len(signedTxn); i++ { + // add a simple logic that verifies this condition: + // sha256(arg0) == base64decode(5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=) + op, err := logic.AssembleString(`arg 0 +sha256 +byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= +==`) + require.NoError(t, err) + signedTxn[i].Lsig.Logic = op.Program + program := logic.Program(op.Program) + signedTxn[i].Txn.Sender = basics.Address(crypto.HashObj(&program)) + signedTxn[i].Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} + signedTxn[i].Sig = crypto.Signature{} + } + + txnGroups := make([][]transactions.SignedTxn, len(signedTxn)) + for i := 0; i < len(txnGroups); i++ { + txnGroups[i] = make([]transactions.SignedTxn, 1) + txnGroups[i][0] = signedTxn[i] + } + + breakSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Lsig.Args[0][0]++ + } + restoreSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Lsig.Args[0][0]-- + } + verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") + +} + +// TestTxnGroupCacheUpdateLogicWithSig makes sure that a payment transaction contains logicsig signed with single signature is valid (and added to the cache) only +// if the logic passes and the signature is correct. +// for this, we will break the signature and make sure that txn verification fails. +func TestTxnGroupCacheUpdateLogicWithSig(t *testing.T) { + partitiontest.PartitionTest(t) + + _, signedTxn, secrets, addresses := generateTestObjects(100, 20, 50) + blkHdr := createDummyBlockHeader() + + for i := 0; i < len(signedTxn); i++ { + // add a simple logic that verifies this condition: + // sha256(arg0) == base64decode(5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=) + op, err := logic.AssembleString(`arg 0 +sha256 +byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= +==`) + require.NoError(t, err) + + s := rand.Intn(len(secrets)) + signedTxn[i].Sig = crypto.Signature{} + signedTxn[i].Txn.Sender = addresses[s] + signedTxn[i].Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} + signedTxn[i].Lsig.Logic = op.Program + program := logic.Program(op.Program) + signedTxn[i].Lsig.Sig = secrets[s].Sign(program) + + } + + txnGroups := make([][]transactions.SignedTxn, len(signedTxn)) + for i := 0; i < len(txnGroups); i++ { + txnGroups[i] = make([]transactions.SignedTxn, 1) + txnGroups[i][0] = signedTxn[i] + } + + breakSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Lsig.Sig[0]++ + } + restoreSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Lsig.Sig[0]-- + } + verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchVerificationFailed.Error()) + + // signature is correct and logic fails + breakSignatureFunc = func(txn *transactions.SignedTxn) { + txn.Lsig.Args[0][0]++ + } + restoreSignatureFunc = func(txn *transactions.SignedTxn) { + txn.Lsig.Args[0][0]-- + } + verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") +} + +// TestTxnGroupCacheUpdateLogicWithMultiSig makes sure that a payment transaction contains logicsig signed with multisig is valid +// if the logic passes and the multisig is correct. +// for this, we will break one of the multisig and the logic and make sure that txn verification fails. +func TestTxnGroupCacheUpdateLogicWithMultiSig(t *testing.T) { + partitiontest.PartitionTest(t) + + secrets, _, pks, multiAddress := generateMultiSigAccounts(t, 30) + blkHdr := createDummyBlockHeader() + + const numOfTxn = 20 + signedTxn := make([]transactions.SignedTxn, numOfTxn) + + numMultiSigAcct := len(multiAddress) + for i := 0; i < numOfTxn; i++ { + s := rand.Intn(numMultiSigAcct) + r := rand.Intn(numMultiSigAcct) + a := rand.Intn(1000) + f := config.Consensus[protocol.ConsensusCurrentVersion].MinTxnFee + uint64(rand.Intn(10)) + + signedTxn[i].Txn = transactions.Transaction{ + Type: protocol.PaymentTx, + Header: transactions.Header{ + Sender: multiAddress[s], + Fee: basics.MicroAlgos{Raw: f}, + FirstValid: basics.Round(1), + LastValid: basics.Round(100), + GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), + }, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: multiAddress[r], + Amount: basics.MicroAlgos{Raw: uint64(a)}, + }, + } + // add a simple logic that verifies this condition: + // sha256(arg0) == base64decode(5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=) + op, err := logic.AssembleString(`arg 0 +sha256 +byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= +==`) + require.NoError(t, err) + + signedTxn[i].Sig = crypto.Signature{} + signedTxn[i].Txn.Sender = multiAddress[s] + signedTxn[i].Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")} + signedTxn[i].Lsig.Logic = op.Program + program := logic.Program(op.Program) + + // create multi sig that 2 out of 3 has signed the txn + var sigs [2]crypto.MultisigSig + for j := 0; j < 2; j++ { + msig, err := crypto.MultisigSign(program, crypto.Digest(multiAddress[s]), 1, 2, pks[3*s:3*s+3], *secrets[3*s+j]) + require.NoError(t, err) + sigs[j] = msig + } + msig, err := crypto.MultisigAssemble(sigs[:]) + require.NoError(t, err) + signedTxn[i].Lsig.Msig = msig + } + + txnGroups := make([][]transactions.SignedTxn, len(signedTxn)) + for i := 0; i < len(txnGroups); i++ { + txnGroups[i] = make([]transactions.SignedTxn, 1) + txnGroups[i][0] = signedTxn[i] + } + + breakSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Lsig.Msig.Subsigs[0].Sig[0]++ + } + restoreSignatureFunc := func(txn *transactions.SignedTxn) { + txn.Lsig.Msig.Subsigs[0].Sig[0]-- + } + + verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, crypto.ErrBatchVerificationFailed.Error()) + // signature is correct and logic fails + breakSignatureFunc = func(txn *transactions.SignedTxn) { + txn.Lsig.Args[0][0]++ + } + restoreSignatureFunc = func(txn *transactions.SignedTxn) { + txn.Lsig.Args[0][0]-- + } + verifyGroup(t, txnGroups, blkHdr, breakSignatureFunc, restoreSignatureFunc, "rejected by logic") +} + +func createDummyBlockHeader() bookkeeping.BlockHeader { + return bookkeeping.BlockHeader{ Round: 50, GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), UpgradeState: bookkeeping.UpgradeState{ @@ -459,15 +788,20 @@ func TestTxnGroupCacheUpdate(t *testing.T) { RewardsPool: poolAddr, }, } +} - txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) +// verifyGroup uses TxnGroup to verify txns and add them to the +// cache. Then makes sure that only the valid txns are verified and added to +// the cache. +func verifyGroup(t *testing.T, txnGroups [][]transactions.SignedTxn, blkHdr bookkeeping.BlockHeader, breakSig func(txn *transactions.SignedTxn), restoreSig func(txn *transactions.SignedTxn), errorString string) { cache := MakeVerifiedTransactionCache(1000) - // break the signature and see if it fails. - txnGroups[0][0].Sig[0] = txnGroups[0][0].Sig[0] + 1 + breakSig(&txnGroups[0][0]) - _, err := TxnGroup(txnGroups[0], blkHdr, cache, nil) + dummeyLedger := DummyLedgerForSignature{} + _, err := TxnGroup(txnGroups[0], blkHdr, cache, &dummeyLedger) require.Error(t, err) + require.Contains(t, err.Error(), errorString) // The txns should not be in the cache unverifiedGroups := cache.GetUnverifiedTransactionGroups(txnGroups[:1], spec, protocol.ConsensusCurrentVersion) @@ -476,42 +810,77 @@ func TestTxnGroupCacheUpdate(t *testing.T) { unverifiedGroups = cache.GetUnverifiedTransactionGroups(txnGroups[:2], spec, protocol.ConsensusCurrentVersion) require.Len(t, unverifiedGroups, 2) - _, err = TxnGroup(txnGroups[1], blkHdr, cache, nil) + _, err = TxnGroup(txnGroups[1], blkHdr, cache, &dummeyLedger) require.NoError(t, err) // Only the second txn should be in the cache unverifiedGroups = cache.GetUnverifiedTransactionGroups(txnGroups[:2], spec, protocol.ConsensusCurrentVersion) require.Len(t, unverifiedGroups, 1) - // Fix the signature - txnGroups[0][0].Sig[0] = txnGroups[0][0].Sig[0] - 1 + restoreSig(&txnGroups[0][0]) - _, err = TxnGroup(txnGroups[0], blkHdr, cache, nil) + _, err = TxnGroup(txnGroups[0], blkHdr, cache, &dummeyLedger) require.NoError(t, err) // Both transactions should be in the cache unverifiedGroups = cache.GetUnverifiedTransactionGroups(txnGroups[:2], spec, protocol.ConsensusCurrentVersion) require.Len(t, unverifiedGroups, 0) + cache = MakeVerifiedTransactionCache(1000) // Break a random signature txgIdx := rand.Intn(len(txnGroups)) txIdx := rand.Intn(len(txnGroups[txgIdx])) - txnGroups[txgIdx][txIdx].Sig[0] = txnGroups[0][0].Sig[0] + 1 + breakSig(&txnGroups[txgIdx][txIdx]) numFailed := 0 // Add them to the cache by verifying them for _, txng := range txnGroups { - _, err = TxnGroup(txng, blkHdr, cache, nil) + _, err = TxnGroup(txng, blkHdr, cache, &dummeyLedger) if err != nil { + require.Error(t, err) + require.Contains(t, err.Error(), errorString) numFailed++ } } require.Equal(t, 1, numFailed) - // Onle one transaction should not be in cache + // Only one transaction should not be in cache unverifiedGroups = cache.GetUnverifiedTransactionGroups(txnGroups, spec, protocol.ConsensusCurrentVersion) require.Len(t, unverifiedGroups, 1) require.Equal(t, unverifiedGroups[0], txnGroups[txgIdx]) + restoreSig(&txnGroups[txgIdx][txIdx]) +} + +func BenchmarkTxn(b *testing.B) { + if b.N < 2000 { + b.N = 2000 + } + _, signedTxn, secrets, addrs := generateTestObjects(b.N, 20, 50) + blk := bookkeeping.Block{ + BlockHeader: bookkeeping.BlockHeader{ + Round: 50, + GenesisHash: crypto.Hash([]byte{1, 2, 3, 4, 5}), + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: protocol.ConsensusCurrentVersion, + }, + RewardsState: bookkeeping.RewardsState{ + FeeSink: feeSink, + RewardsPool: poolAddr, + }, + }, + } + txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) + + b.ResetTimer() + for _, txnGroup := range txnGroups { + groupCtx, err := PrepareGroupContext(txnGroup, blk.BlockHeader, nil) + require.NoError(b, err) + for i, txn := range txnGroup { + err := verifyTxn(&txn, i, groupCtx) + require.NoError(b, err) + } + } + b.StopTimer() }