From 10601cb3756ff1f22ed5c4f06d10a70fac20716b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 12 Mar 2020 18:06:26 +0300 Subject: [PATCH 1/3] core: add issuer hashes into the verification list for Issue TX As it should be done. --- pkg/core/blockchain.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index f4a4f4ab5..85a439d67 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1942,6 +1942,16 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([ case transaction.EnrollmentType: etx := t.Data.(*transaction.EnrollmentTX) hashes[etx.PublicKey.GetScriptHash()] = true + case transaction.IssueType: + for _, res := range refsAndOutsToResults(references, t.Outputs) { + if res.Amount < 0 { + asset, err := bc.dao.GetAssetState(res.AssetID) + if asset == nil || err != nil { + return nil, errors.New("invalid asset in issue tx") + } + hashes[asset.Issuer] = true + } + } case transaction.RegisterType: reg := t.Data.(*transaction.RegisterTX) hashes[reg.Owner.GetScriptHash()] = true From d5d0479671979ad7e5c64a7eac3ed13d7a8888c7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 16 Mar 2020 18:43:02 +0300 Subject: [PATCH 2/3] core: verify results of issue transaction It shouldn't try to issue more tokens than there is available. --- pkg/core/blockchain.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 85a439d67..e5a7a5519 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1660,6 +1660,13 @@ func (bc *Blockchain) verifyResults(t *transaction.Transaction, results []*trans if r.AssetID == UtilityTokenID() { return errors.New("issue tx issues utility tokens") } + asset, err := bc.dao.GetAssetState(r.AssetID) + if asset == nil || err != nil { + return errors.New("invalid asset in issue tx") + } + if asset.Available < r.Amount { + return errors.New("trying to issue more than available") + } } break default: From ec76a0bf15286c1daee8e335bb3b1539d0b5bed1 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 16 Mar 2020 19:07:23 +0300 Subject: [PATCH 3/3] mempool: disallow more than one issue tx at once Technically they could conflict for available asset amount, but as they're very rare (and even can be considered obsolete) we can simplify this check. --- pkg/core/mempool/mem_pool.go | 41 +++++++++++++++++++++---------- pkg/core/mempool/mem_pool_test.go | 25 +++++++++++++++++++ 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/pkg/core/mempool/mem_pool.go b/pkg/core/mempool/mem_pool.go index 5f9fe82ed..7dc38e2d4 100644 --- a/pkg/core/mempool/mem_pool.go +++ b/pkg/core/mempool/mem_pool.go @@ -168,7 +168,7 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error { } pItem.isLowPrio = fee.IsLowPriority(pItem.netFee) mp.lock.Lock() - if !mp.verifyInputs(t) { + if !mp.checkTxConflicts(t) { mp.lock.Unlock() return ErrConflict } @@ -329,24 +329,39 @@ func (mp *Pool) GetVerifiedTransactions() []TxWithFee { return t } -// verifyInputs is an internal unprotected version of Verify. -func (mp *Pool) verifyInputs(tx *transaction.Transaction) bool { - for i := range tx.Inputs { - n := findIndexForInput(mp.inputs, &tx.Inputs[i]) - if n < len(mp.inputs) && *mp.inputs[n] == tx.Inputs[i] { - return false +// areInputsInPool tries to find inputs in a given sorted pool and returns true +// if it finds any. +func areInputsInPool(inputs []transaction.Input, pool []*transaction.Input) bool { + for i := range inputs { + n := findIndexForInput(pool, &inputs[i]) + if n < len(pool) && *pool[n] == inputs[i] { + return true } } - if tx.Type == transaction.ClaimType { + return false +} + +// checkTxConflicts is an internal unprotected version of Verify. +func (mp *Pool) checkTxConflicts(tx *transaction.Transaction) bool { + if areInputsInPool(tx.Inputs, mp.inputs) { + return false + } + switch tx.Type { + case transaction.ClaimType: claim := tx.Data.(*transaction.ClaimTX) - for i := range claim.Claims { - n := findIndexForInput(mp.claims, &claim.Claims[i]) - if n < len(mp.claims) && *mp.claims[n] == claim.Claims[i] { + if areInputsInPool(claim.Claims, mp.claims) { + return false + } + case transaction.IssueType: + // It's a hack, because technically we could check for + // available asset amount, but these transactions are so rare + // that no one really cares about this restriction. + for i := range mp.verifiedTxes { + if mp.verifiedTxes[i].txn.Type == transaction.IssueType { return false } } } - return true } @@ -356,5 +371,5 @@ func (mp *Pool) verifyInputs(tx *transaction.Transaction) bool { func (mp *Pool) Verify(tx *transaction.Transaction) bool { mp.lock.RLock() defer mp.lock.RUnlock() - return mp.verifyInputs(tx) + return mp.checkTxConflicts(tx) } diff --git a/pkg/core/mempool/mem_pool_test.go b/pkg/core/mempool/mem_pool_test.go index 315361bb3..212a623f0 100644 --- a/pkg/core/mempool/mem_pool_test.go +++ b/pkg/core/mempool/mem_pool_test.go @@ -177,6 +177,31 @@ func TestMemPoolVerifyClaims(t *testing.T) { require.Error(t, mp.Add(tx3, &FeerStub{})) } +func TestMemPoolVerifyIssue(t *testing.T) { + mp := NewMemPool(50) + tx1 := newIssueTX() + require.Equal(t, true, mp.Verify(tx1)) + require.NoError(t, mp.Add(tx1, &FeerStub{})) + + tx2 := newIssueTX() + require.Equal(t, false, mp.Verify(tx2)) + require.Error(t, mp.Add(tx2, &FeerStub{})) +} + +func newIssueTX() *transaction.Transaction { + return &transaction.Transaction{ + Type: transaction.IssueType, + Data: &transaction.IssueTX{}, + Outputs: []transaction.Output{ + transaction.Output{ + AssetID: random.Uint256(), + Amount: util.Fixed8FromInt64(42), + ScriptHash: random.Uint160(), + }, + }, + } +} + func newMinerTX(i uint32) *transaction.Transaction { return &transaction.Transaction{ Type: transaction.MinerType,