diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 1e5d9facb..6fdab3311 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1121,7 +1121,7 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e if io.GetVarSize(t) > transaction.MaxTransactionSize { return errors.Errorf("invalid transaction size = %d. It shoud be less then MaxTransactionSize = %d", io.GetVarSize(t), transaction.MaxTransactionSize) } - if ok := bc.verifyInputs(t); !ok { + if transaction.HaveDuplicateInputs(t.Inputs) { return errors.New("invalid transaction's inputs") } if block == nil { @@ -1145,6 +1145,13 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e } } + if t.Type == transaction.ClaimType { + claim := t.Data.(*transaction.ClaimTX) + if transaction.HaveDuplicateInputs(claim.Claims) { + return errors.New("duplicate claims") + } + } + return bc.verifyTxWitnesses(t, block) } @@ -1223,18 +1230,6 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error { return nil } -func (bc *Blockchain) verifyInputs(t *transaction.Transaction) bool { - for i := 1; i < len(t.Inputs); i++ { - for j := 0; j < i; j++ { - if t.Inputs[i].PrevHash == t.Inputs[j].PrevHash && t.Inputs[i].PrevIndex == t.Inputs[j].PrevIndex { - return false - } - } - } - - return true -} - func (bc *Blockchain) verifyOutputs(t *transaction.Transaction) error { for assetID, outputs := range t.GroupOutputByAssetID() { assetState := bc.GetAssetState(assetID) diff --git a/pkg/core/transaction/input.go b/pkg/core/transaction/input.go index b1e6b7694..3b7758ba3 100644 --- a/pkg/core/transaction/input.go +++ b/pkg/core/transaction/input.go @@ -28,6 +28,23 @@ func (in *Input) EncodeBinary(bw *io.BinWriter) { bw.WriteU16LE(in.PrevIndex) } +// MapInputsToSorted maps given slice of inputs into a new slice of pointers +// to inputs sorted by their PrevHash and PrevIndex. +func MapInputsToSorted(ins []Input) []*Input { + ptrs := make([]*Input, len(ins)) + for i := range ins { + ptrs[i] = &ins[i] + } + sort.Slice(ptrs, func(i, j int) bool { + hashcmp := ptrs[i].PrevHash.CompareTo(ptrs[j].PrevHash) + if hashcmp == 0 { + return ptrs[i].PrevIndex < ptrs[j].PrevIndex + } + return hashcmp < 0 + }) + return ptrs +} + // GroupInputsByPrevHash groups all TX inputs by their previous hash into // several slices (which actually are subslices of one new slice with pointers). // Each of these slices contains at least one element. @@ -36,14 +53,7 @@ func GroupInputsByPrevHash(ins []Input) [][]*Input { return nil } - ptrs := make([]*Input, len(ins)) - for i := range ins { - ptrs[i] = &ins[i] - } - sort.Slice(ptrs, func(i, j int) bool { - return ptrs[i].PrevHash.CompareTo(ptrs[j].PrevHash) < 0 - }) - + ptrs := MapInputsToSorted(ins) var first int res := make([][]*Input, 0) currentHash := ptrs[0].PrevHash @@ -58,3 +68,21 @@ func GroupInputsByPrevHash(ins []Input) [][]*Input { res = append(res, ptrs[first:]) return res } + +// HaveDuplicateInputs checks inputs for duplicates and returns true if there are +// any. +func HaveDuplicateInputs(ins []Input) bool { + if len(ins) < 2 { + return false + } + if len(ins) == 2 { + return ins[0] == ins[1] + } + ptrs := MapInputsToSorted(ins) + for i := 1; i < len(ptrs); i++ { + if *ptrs[i] == *ptrs[i-1] { + return true + } + } + return false +} diff --git a/pkg/core/transaction/input_test.go b/pkg/core/transaction/input_test.go index 2b3f595ef..0375a4559 100644 --- a/pkg/core/transaction/input_test.go +++ b/pkg/core/transaction/input_test.go @@ -73,3 +73,72 @@ func TestGroupInputsByPrevHashMany(t *testing.T) { seen[res[2][i].PrevIndex] = true } } + +func TestHaveDuplicateInputs0(t *testing.T) { + inputs := make([]Input, 0) + require.False(t, HaveDuplicateInputs(inputs)) +} + +func TestHaveDuplicateInputs1(t *testing.T) { + inputs := make([]Input, 0) + hash, err := util.Uint256DecodeStringLE("46168f963d6d8168a870405f66cc9e13a235791013b8ee2f90cc20a8293bd1af") + require.NoError(t, err) + inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42}) + require.False(t, HaveDuplicateInputs(inputs)) +} + +func TestHaveDuplicateInputs2True(t *testing.T) { + inputs := make([]Input, 0) + hash, err := util.Uint256DecodeStringLE("46168f963d6d8168a870405f66cc9e13a235791013b8ee2f90cc20a8293bd1af") + require.NoError(t, err) + inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42}) + inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42}) + require.True(t, HaveDuplicateInputs(inputs)) +} + +func TestHaveDuplicateInputs2FalseInd(t *testing.T) { + inputs := make([]Input, 0) + hash, err := util.Uint256DecodeStringLE("46168f963d6d8168a870405f66cc9e13a235791013b8ee2f90cc20a8293bd1af") + require.NoError(t, err) + inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42}) + inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 41}) + require.False(t, HaveDuplicateInputs(inputs)) +} + +func TestHaveDuplicateInputs2FalseHash(t *testing.T) { + inputs := make([]Input, 0) + hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25") + require.NoError(t, err) + hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9") + require.NoError(t, err) + inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: 42}) + inputs = append(inputs, Input{PrevHash: hash2, PrevIndex: 42}) + require.False(t, HaveDuplicateInputs(inputs)) +} + +func TestHaveDuplicateInputsMFalse(t *testing.T) { + inputs := make([]Input, 0) + hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25") + require.NoError(t, err) + hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9") + require.NoError(t, err) + for i := 0; i < 10; i++ { + inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: uint16(i)}) + inputs = append(inputs, Input{PrevHash: hash2, PrevIndex: uint16(i)}) + } + require.False(t, HaveDuplicateInputs(inputs)) +} + +func TestHaveDuplicateInputsMTrue(t *testing.T) { + inputs := make([]Input, 0) + hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25") + require.NoError(t, err) + hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9") + require.NoError(t, err) + for i := 0; i < 10; i++ { + inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: uint16(i)}) + inputs = append(inputs, Input{PrevHash: hash2, PrevIndex: uint16(i)}) + } + inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: 0}) + require.True(t, HaveDuplicateInputs(inputs)) +}