forked from TrueCloudLab/neoneo-go
Merge pull request #1573 from nspcc-dev/doubleresponse
mempool: correctly handle tx with oracle response
This commit is contained in:
commit
795b2bd713
3 changed files with 78 additions and 1 deletions
|
@ -30,6 +30,9 @@ var (
|
||||||
// ErrConflictsAttribute is returned when transaction conflicts with other transactions
|
// ErrConflictsAttribute is returned when transaction conflicts with other transactions
|
||||||
// due to its (or theirs) Conflicts attributes.
|
// due to its (or theirs) Conflicts attributes.
|
||||||
ErrConflictsAttribute = errors.New("conflicts with memory pool due to Conflicts attribute")
|
ErrConflictsAttribute = errors.New("conflicts with memory pool due to Conflicts attribute")
|
||||||
|
// ErrOracleResponse is returned when mempool already contains transaction
|
||||||
|
// with the same oracle response ID and higher network fee.
|
||||||
|
ErrOracleResponse = errors.New("conflicts with memory pool due to OracleResponse attribute")
|
||||||
)
|
)
|
||||||
|
|
||||||
// item represents a transaction in the the Memory pool.
|
// item represents a transaction in the the Memory pool.
|
||||||
|
@ -56,6 +59,8 @@ type Pool struct {
|
||||||
fees map[util.Uint160]utilityBalanceAndFees
|
fees map[util.Uint160]utilityBalanceAndFees
|
||||||
// conflicts is a map of hashes of transactions which are conflicting with the mempooled ones.
|
// conflicts is a map of hashes of transactions which are conflicting with the mempooled ones.
|
||||||
conflicts map[util.Uint256][]util.Uint256
|
conflicts map[util.Uint256][]util.Uint256
|
||||||
|
// oracleResp contains ids of oracle responses for tx in pool.
|
||||||
|
oracleResp map[uint64]util.Uint256
|
||||||
|
|
||||||
capacity int
|
capacity int
|
||||||
feePerByte int64
|
feePerByte int64
|
||||||
|
@ -192,6 +197,18 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
|
||||||
mp.lock.Unlock()
|
mp.lock.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if attrs := t.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
|
||||||
|
id := attrs[0].Value.(*transaction.OracleResponse).ID
|
||||||
|
h, ok := mp.oracleResp[id]
|
||||||
|
if ok {
|
||||||
|
if mp.verifiedMap[h].NetworkFee >= t.NetworkFee {
|
||||||
|
mp.lock.Unlock()
|
||||||
|
return ErrOracleResponse
|
||||||
|
}
|
||||||
|
mp.removeInternal(h, fee)
|
||||||
|
}
|
||||||
|
mp.oracleResp[id] = t.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
mp.verifiedMap[t.Hash()] = t
|
mp.verifiedMap[t.Hash()] = t
|
||||||
if fee.P2PSigExtensionsEnabled() {
|
if fee.P2PSigExtensionsEnabled() {
|
||||||
|
@ -276,6 +293,9 @@ func (mp *Pool) removeInternal(hash util.Uint256, feer Feer) {
|
||||||
// remove all conflicting hashes from mp.conflicts list
|
// remove all conflicting hashes from mp.conflicts list
|
||||||
mp.removeConflictsOf(tx)
|
mp.removeConflictsOf(tx)
|
||||||
}
|
}
|
||||||
|
if attrs := tx.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
|
||||||
|
delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateMempoolMetrics(len(mp.verifiedTxes))
|
updateMempoolMetrics(len(mp.verifiedTxes))
|
||||||
}
|
}
|
||||||
|
@ -314,6 +334,9 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delete(mp.verifiedMap, itm.txn.Hash())
|
delete(mp.verifiedMap, itm.txn.Hash())
|
||||||
|
if attrs := itm.txn.GetAttributes(transaction.OracleResponseT); len(attrs) != 0 {
|
||||||
|
delete(mp.oracleResp, attrs[0].Value.(*transaction.OracleResponse).ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(staleTxs) != 0 {
|
if len(staleTxs) != 0 {
|
||||||
|
@ -350,6 +373,7 @@ func New(capacity int) *Pool {
|
||||||
capacity: capacity,
|
capacity: capacity,
|
||||||
fees: make(map[util.Uint160]utilityBalanceAndFees),
|
fees: make(map[util.Uint160]utilityBalanceAndFees),
|
||||||
conflicts: make(map[util.Uint256][]util.Uint256),
|
conflicts: make(map[util.Uint256][]util.Uint256),
|
||||||
|
oracleResp: make(map[uint64]util.Uint256),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -351,6 +351,59 @@ func TestMempoolItemsOrder(t *testing.T) {
|
||||||
require.True(t, item4.CompareTo(item3) < 0)
|
require.True(t, item4.CompareTo(item3) < 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMempoolAddRemoveOracleResponse(t *testing.T) {
|
||||||
|
mp := New(5)
|
||||||
|
nonce := uint32(0)
|
||||||
|
fs := &FeerStub{}
|
||||||
|
newTx := func(netFee int64, id uint64) *transaction.Transaction {
|
||||||
|
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||||
|
tx.NetworkFee = netFee
|
||||||
|
tx.Nonce = nonce
|
||||||
|
nonce++
|
||||||
|
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}}
|
||||||
|
tx.Attributes = []transaction.Attribute{{
|
||||||
|
Type: transaction.OracleResponseT,
|
||||||
|
Value: &transaction.OracleResponse{ID: id},
|
||||||
|
}}
|
||||||
|
// sanity check
|
||||||
|
_, ok := mp.TryGetValue(tx.Hash())
|
||||||
|
require.False(t, ok)
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
tx1 := newTx(10, 1)
|
||||||
|
require.NoError(t, mp.Add(tx1, fs))
|
||||||
|
|
||||||
|
// smaller network fee
|
||||||
|
tx2 := newTx(5, 1)
|
||||||
|
err := mp.Add(tx2, fs)
|
||||||
|
require.True(t, errors.Is(err, ErrOracleResponse))
|
||||||
|
|
||||||
|
// ok if old tx is removed
|
||||||
|
mp.Remove(tx1.Hash(), fs)
|
||||||
|
require.NoError(t, mp.Add(tx2, fs))
|
||||||
|
|
||||||
|
// higher network fee
|
||||||
|
tx3 := newTx(6, 1)
|
||||||
|
require.NoError(t, mp.Add(tx3, fs))
|
||||||
|
_, ok := mp.TryGetValue(tx2.Hash())
|
||||||
|
require.False(t, ok)
|
||||||
|
_, ok = mp.TryGetValue(tx3.Hash())
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
// another oracle response ID
|
||||||
|
tx4 := newTx(4, 2)
|
||||||
|
require.NoError(t, mp.Add(tx4, fs))
|
||||||
|
|
||||||
|
mp.RemoveStale(func(tx *transaction.Transaction) bool {
|
||||||
|
return tx.Hash() != tx4.Hash()
|
||||||
|
}, fs)
|
||||||
|
|
||||||
|
// check that oracle id was removed.
|
||||||
|
tx5 := newTx(3, 2)
|
||||||
|
require.NoError(t, mp.Add(tx5, fs))
|
||||||
|
}
|
||||||
|
|
||||||
func TestMempoolAddRemoveConflicts(t *testing.T) {
|
func TestMempoolAddRemoveConflicts(t *testing.T) {
|
||||||
capacity := 6
|
capacity := 6
|
||||||
mp := New(capacity)
|
mp := New(capacity)
|
||||||
|
|
|
@ -153,7 +153,7 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
|
||||||
reqKey := makeRequestKey(resp.ID)
|
reqKey := makeRequestKey(resp.ID)
|
||||||
req := new(state.OracleRequest)
|
req := new(state.OracleRequest)
|
||||||
if err := o.getSerializableFromDAO(ic.DAO, reqKey, req); err != nil {
|
if err := o.getSerializableFromDAO(ic.DAO, reqKey, req); err != nil {
|
||||||
return err
|
continue
|
||||||
}
|
}
|
||||||
if err := ic.DAO.DeleteStorageItem(o.ContractID, reqKey); err != nil {
|
if err := ic.DAO.DeleteStorageItem(o.ContractID, reqKey); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in a new issue