core: add duplicate Claim verification check

Refactor HaveInputsDuplicate() out of the core and Blockchain, it doesn't
depend on the Blockchain state. Make it more efficient.
This commit is contained in:
Roman Khimov 2020-02-26 12:30:56 +03:00
parent 9b1ad40273
commit 29586f2aa7
3 changed files with 113 additions and 21 deletions

View file

@ -1121,7 +1121,7 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
if io.GetVarSize(t) > transaction.MaxTransactionSize { 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) 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") return errors.New("invalid transaction's inputs")
} }
if block == nil { 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) return bc.verifyTxWitnesses(t, block)
} }
@ -1223,18 +1230,6 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
return nil 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 { func (bc *Blockchain) verifyOutputs(t *transaction.Transaction) error {
for assetID, outputs := range t.GroupOutputByAssetID() { for assetID, outputs := range t.GroupOutputByAssetID() {
assetState := bc.GetAssetState(assetID) assetState := bc.GetAssetState(assetID)

View file

@ -28,6 +28,23 @@ func (in *Input) EncodeBinary(bw *io.BinWriter) {
bw.WriteU16LE(in.PrevIndex) 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 // GroupInputsByPrevHash groups all TX inputs by their previous hash into
// several slices (which actually are subslices of one new slice with pointers). // several slices (which actually are subslices of one new slice with pointers).
// Each of these slices contains at least one element. // Each of these slices contains at least one element.
@ -36,14 +53,7 @@ func GroupInputsByPrevHash(ins []Input) [][]*Input {
return nil return nil
} }
ptrs := make([]*Input, len(ins)) ptrs := MapInputsToSorted(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
})
var first int var first int
res := make([][]*Input, 0) res := make([][]*Input, 0)
currentHash := ptrs[0].PrevHash currentHash := ptrs[0].PrevHash
@ -58,3 +68,21 @@ func GroupInputsByPrevHash(ins []Input) [][]*Input {
res = append(res, ptrs[first:]) res = append(res, ptrs[first:])
return res 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
}

View file

@ -73,3 +73,72 @@ func TestGroupInputsByPrevHashMany(t *testing.T) {
seen[res[2][i].PrevIndex] = true 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))
}