core: add Blockchain event subscription mechanism
A deep internal part of #895. Blockchainer interface is also extended for various uses of these methods.
This commit is contained in:
parent
2e58a14978
commit
1ac4f8528d
5 changed files with 333 additions and 0 deletions
|
@ -2,13 +2,19 @@ package core
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -229,3 +235,108 @@ func TestClose(t *testing.T) {
|
|||
// This should never be executed.
|
||||
assert.Nil(t, t)
|
||||
}
|
||||
|
||||
func TestSubscriptions(t *testing.T) {
|
||||
// We use buffering here as a substitute for reader goroutines, events
|
||||
// get queued up and we read them one by one here.
|
||||
const chBufSize = 16
|
||||
blockCh := make(chan *block.Block, chBufSize)
|
||||
txCh := make(chan *transaction.Transaction, chBufSize)
|
||||
notificationCh := make(chan *state.NotificationEvent, chBufSize)
|
||||
executionCh := make(chan *state.AppExecResult, chBufSize)
|
||||
|
||||
bc := newTestChain(t)
|
||||
bc.SubscribeForBlocks(blockCh)
|
||||
bc.SubscribeForTransactions(txCh)
|
||||
bc.SubscribeForNotifications(notificationCh)
|
||||
bc.SubscribeForExecutions(executionCh)
|
||||
|
||||
assert.Empty(t, notificationCh)
|
||||
assert.Empty(t, executionCh)
|
||||
assert.Empty(t, blockCh)
|
||||
assert.Empty(t, txCh)
|
||||
|
||||
blocks, err := bc.genBlocks(1)
|
||||
require.NoError(t, err)
|
||||
assert.Eventually(t, func() bool { return len(blockCh) != 0 && len(txCh) != 0 }, time.Second, 10*time.Millisecond)
|
||||
assert.Empty(t, notificationCh)
|
||||
assert.Empty(t, executionCh)
|
||||
|
||||
b := <-blockCh
|
||||
tx := <-txCh
|
||||
assert.Equal(t, blocks[0], b)
|
||||
assert.Equal(t, blocks[0].Transactions[0], tx)
|
||||
assert.Empty(t, blockCh)
|
||||
assert.Empty(t, txCh)
|
||||
|
||||
acc0, err := wallet.NewAccountFromWIF(privNetKeys[0])
|
||||
require.NoError(t, err)
|
||||
addr0, err := address.StringToUint160(acc0.Address)
|
||||
require.NoError(t, err)
|
||||
|
||||
script := io.NewBufBinWriter()
|
||||
emit.Bytes(script.BinWriter, []byte("yay!"))
|
||||
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
|
||||
require.NoError(t, script.Err)
|
||||
txGood1 := transaction.NewInvocationTX(script.Bytes(), 0)
|
||||
txGood1.AddVerificationHash(addr0)
|
||||
require.NoError(t, acc0.SignTx(txGood1))
|
||||
|
||||
// Reset() reuses the script buffer and we need to keep scripts.
|
||||
script = io.NewBufBinWriter()
|
||||
emit.Bytes(script.BinWriter, []byte("nay!"))
|
||||
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
|
||||
emit.Opcode(script.BinWriter, opcode.THROW)
|
||||
require.NoError(t, script.Err)
|
||||
txBad := transaction.NewInvocationTX(script.Bytes(), 0)
|
||||
txBad.AddVerificationHash(addr0)
|
||||
require.NoError(t, acc0.SignTx(txBad))
|
||||
|
||||
script = io.NewBufBinWriter()
|
||||
emit.Bytes(script.BinWriter, []byte("yay! yay! yay!"))
|
||||
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
|
||||
require.NoError(t, script.Err)
|
||||
txGood2 := transaction.NewInvocationTX(script.Bytes(), 0)
|
||||
txGood2.AddVerificationHash(addr0)
|
||||
require.NoError(t, acc0.SignTx(txGood2))
|
||||
|
||||
txMiner := newMinerTX()
|
||||
invBlock := newBlock(bc.config, bc.BlockHeight()+1, bc.CurrentHeaderHash(), txMiner, txGood1, txBad, txGood2)
|
||||
require.NoError(t, bc.AddBlock(invBlock))
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
return len(blockCh) != 0 && len(txCh) != 0 &&
|
||||
len(notificationCh) != 0 && len(executionCh) != 0
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
|
||||
b = <-blockCh
|
||||
require.Equal(t, invBlock, b)
|
||||
assert.Empty(t, blockCh)
|
||||
|
||||
// Follow in-block transaction order.
|
||||
for _, txExpected := range invBlock.Transactions {
|
||||
tx = <-txCh
|
||||
require.Equal(t, txExpected, tx)
|
||||
if txExpected.Type == transaction.InvocationType {
|
||||
exec := <-executionCh
|
||||
require.Equal(t, tx.Hash(), exec.TxHash)
|
||||
if exec.VMState == "HALT" {
|
||||
notif := <-notificationCh
|
||||
inv := tx.Data.(*transaction.InvocationTX)
|
||||
require.Equal(t, hash.Hash160(inv.Script), notif.ScriptHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Empty(t, txCh)
|
||||
assert.Empty(t, notificationCh)
|
||||
assert.Empty(t, executionCh)
|
||||
|
||||
bc.UnsubscribeFromBlocks(blockCh)
|
||||
bc.UnsubscribeFromTransactions(txCh)
|
||||
bc.UnsubscribeFromNotifications(notificationCh)
|
||||
bc.UnsubscribeFromExecutions(executionCh)
|
||||
|
||||
// Ensure that new blocks are processed correctly after unsubscription.
|
||||
_, err = bc.genBlocks(2 * chBufSize)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue