forked from TrueCloudLab/neoneo-go
rpcclient: fix WS-client unsubscription process
Do not block subscribers until the unsubscription request to RPC server is completed. Otherwise, another notification may be received from the RPC server which will block the unsubscription process. At the same time, fix event-based waiter. We must not block the receiver channel during unsubscription because there's a chance that subsequent event will be sent by the server. We need to read this event in order not to block the WSClient's readloop.
This commit is contained in:
parent
ddaba9e74d
commit
6dbae7edc4
9 changed files with 225 additions and 86 deletions
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -273,6 +274,11 @@ func Init(t *testing.T, rootpath string, e *neotest.Executor) {
|
|||
storageCfg := filepath.Join(testDataPrefix, "storage", "storage_contract.yml")
|
||||
_, _, _ = deployContractFromPriv0(t, storagePath, "Storage", storageCfg, StorageContractID)
|
||||
|
||||
// Block #23: add FAULTed transaction to check WSClient waitloops.
|
||||
faultedInvoker := e.NewInvoker(cHash, acc0)
|
||||
faultedH := faultedInvoker.InvokeScriptCheckFAULT(t, []byte{byte(opcode.ABORT)}, []neotest.Signer{acc0}, "at instruction 0 (ABORT): ABORT")
|
||||
t.Logf("FAULTed transaction:\n\thash LE: %s\n\tblock index: %d", faultedH.StringLE(), e.Chain.BlockHeight())
|
||||
|
||||
// Compile contract to test `invokescript` RPC call
|
||||
invokePath := filepath.Join(testDataPrefix, "invoke", "invokescript_contract.go")
|
||||
invokeCfg := filepath.Join(testDataPrefix, "invoke", "invoke.yml")
|
||||
|
|
|
@ -307,7 +307,6 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) {
|
|||
e.AddNewBlock(t)
|
||||
e.AddNewBlock(t) // This block is stateSyncPoint-th block.
|
||||
e.AddNewBlock(t)
|
||||
e.AddNewBlock(t)
|
||||
require.Equal(t, stateSyncPoint+2, int(bcSpout.BlockHeight()))
|
||||
|
||||
boltCfg := func(c *config.ProtocolConfiguration) {
|
||||
|
|
|
@ -200,9 +200,10 @@ func (e *Executor) InvokeScriptCheckHALT(t testing.TB, script []byte, signers []
|
|||
|
||||
// InvokeScriptCheckFAULT adds a transaction with the specified script to the
|
||||
// chain and checks if it's FAULTed with the specified error.
|
||||
func (e *Executor) InvokeScriptCheckFAULT(t testing.TB, script []byte, signers []Signer, errMessage string) {
|
||||
func (e *Executor) InvokeScriptCheckFAULT(t testing.TB, script []byte, signers []Signer, errMessage string) util.Uint256 {
|
||||
hash := e.InvokeScript(t, script, signers)
|
||||
e.CheckFault(t, hash, errMessage)
|
||||
return hash
|
||||
}
|
||||
|
||||
// CheckHalt checks that the transaction is persisted with HALT state.
|
||||
|
|
|
@ -217,26 +217,47 @@ func (w *EventWaiter) Wait(h util.Uint256, vub uint32, err error) (res *state.Ap
|
|||
|
||||
// WaitAny implements Waiter interface.
|
||||
func (w *EventWaiter) WaitAny(ctx context.Context, vub uint32, hashes ...util.Uint256) (res *state.AppExecResult, waitErr error) {
|
||||
var wsWaitErr error
|
||||
var (
|
||||
wsWaitErr error
|
||||
bRcvr = make(chan *block.Block)
|
||||
aerRcvr = make(chan *state.AppExecResult)
|
||||
unsubErrs = make(chan error)
|
||||
waitersActive int
|
||||
)
|
||||
|
||||
// Rollback to a poll-based waiter if needed.
|
||||
defer func() {
|
||||
if wsWaitErr != nil {
|
||||
res, waitErr = w.polling.WaitAny(ctx, vub, hashes...)
|
||||
if waitErr != nil {
|
||||
waitErr = fmt.Errorf("WS waiter error: %w; simple waiter error: %v", wsWaitErr, waitErr)
|
||||
// Wrap the poll-based error, it's more important.
|
||||
waitErr = fmt.Errorf("event-based error: %v; poll-based waiter error: %w", wsWaitErr, waitErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
bRcvr := make(chan *block.Block)
|
||||
aerRcvr := make(chan *state.AppExecResult)
|
||||
|
||||
// Drain receivers to avoid other notification receivers blocking.
|
||||
defer func() {
|
||||
drainLoop:
|
||||
// Drain receivers to avoid other notification receivers blocking.
|
||||
for {
|
||||
select {
|
||||
case <-bRcvr:
|
||||
case <-aerRcvr:
|
||||
default:
|
||||
break drainLoop
|
||||
case unsubErr := <-unsubErrs:
|
||||
if unsubErr != nil {
|
||||
errFmt := "unsubscription error: %v"
|
||||
errArgs := []interface{}{unsubErr}
|
||||
if waitErr != nil {
|
||||
errFmt = "%w; " + errFmt
|
||||
errArgs = append([]interface{}{waitErr}, errArgs...)
|
||||
}
|
||||
waitErr = fmt.Errorf(errFmt, errArgs...)
|
||||
}
|
||||
waitersActive--
|
||||
// Wait until all receiver channels finish their work.
|
||||
if waitersActive == 0 {
|
||||
break drainLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
if wsWaitErr == nil || !errors.Is(wsWaitErr, ErrMissedEvent) {
|
||||
|
@ -244,24 +265,24 @@ func (w *EventWaiter) WaitAny(ctx context.Context, vub uint32, hashes ...util.Ui
|
|||
close(aerRcvr)
|
||||
}
|
||||
}()
|
||||
// Execution event precedes the block event, thus wait until the VUB-th block to be sure.
|
||||
|
||||
// Execution event preceded the block event, thus wait until the VUB-th block to be sure.
|
||||
since := vub
|
||||
blocksID, err := w.ws.ReceiveBlocks(&neorpc.BlockFilter{Since: &since}, bRcvr)
|
||||
if err != nil {
|
||||
wsWaitErr = fmt.Errorf("failed to subscribe for new blocks: %w", err)
|
||||
return
|
||||
}
|
||||
waitersActive++
|
||||
defer func() {
|
||||
err = w.ws.Unsubscribe(blocksID)
|
||||
if err != nil {
|
||||
errFmt := "failed to unsubscribe from blocks (id: %s): %v"
|
||||
errArgs := []interface{}{blocksID, err}
|
||||
if waitErr != nil {
|
||||
errFmt += "; wait error: %w"
|
||||
errArgs = append(errArgs, waitErr)
|
||||
go func() {
|
||||
err = w.ws.Unsubscribe(blocksID)
|
||||
if err != nil {
|
||||
unsubErrs <- fmt.Errorf("failed to unsubscribe from blocks (id: %s): %w", blocksID, err)
|
||||
return
|
||||
}
|
||||
waitErr = fmt.Errorf(errFmt, errArgs...)
|
||||
}
|
||||
unsubErrs <- nil
|
||||
}()
|
||||
}()
|
||||
for _, h := range hashes {
|
||||
txsID, err := w.ws.ReceiveExecutions(&neorpc.ExecutionFilter{Container: &h}, aerRcvr)
|
||||
|
@ -269,17 +290,16 @@ func (w *EventWaiter) WaitAny(ctx context.Context, vub uint32, hashes ...util.Ui
|
|||
wsWaitErr = fmt.Errorf("failed to subscribe for execution results: %w", err)
|
||||
return
|
||||
}
|
||||
waitersActive++
|
||||
defer func() {
|
||||
err = w.ws.Unsubscribe(txsID)
|
||||
if err != nil {
|
||||
errFmt := "failed to unsubscribe from transactions (id: %s): %v"
|
||||
errArgs := []interface{}{txsID, err}
|
||||
if waitErr != nil {
|
||||
errFmt += "; wait error: %w"
|
||||
errArgs = append(errArgs, waitErr)
|
||||
go func() {
|
||||
err = w.ws.Unsubscribe(txsID)
|
||||
if err != nil {
|
||||
unsubErrs <- fmt.Errorf("failed to unsubscribe from transactions (id: %s): %w", txsID, err)
|
||||
return
|
||||
}
|
||||
waitErr = fmt.Errorf(errFmt, errArgs...)
|
||||
}
|
||||
unsubErrs <- nil
|
||||
}()
|
||||
}()
|
||||
}
|
||||
|
||||
|
|
|
@ -625,16 +625,6 @@ func (c *WSClient) performSubscription(params []interface{}, rcvr notificationRe
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *WSClient) performUnsubscription(id string) error {
|
||||
c.subscriptionsLock.Lock()
|
||||
defer c.subscriptionsLock.Unlock()
|
||||
|
||||
if _, ok := c.subscriptions[id]; !ok {
|
||||
return errors.New("no subscription with this ID")
|
||||
}
|
||||
return c.removeSubscription(id)
|
||||
}
|
||||
|
||||
// SubscribeForNewBlocks adds subscription for new block events to this instance
|
||||
// of the client. It can be filtered by primary consensus node index, nil value doesn't
|
||||
// add any filters.
|
||||
|
@ -876,29 +866,55 @@ func (c *WSClient) ReceiveNotaryRequests(flt *neorpc.TxFilter, rcvr chan<- *resu
|
|||
return c.performSubscription(params, r)
|
||||
}
|
||||
|
||||
// Unsubscribe removes subscription for the given event stream.
|
||||
// Unsubscribe removes subscription for the given event stream. It will return an
|
||||
// error in case if there's no subscription with the provided ID. Call to Unsubscribe
|
||||
// doesn't block notifications receive process for given subscriber, thus, ensure
|
||||
// that subscriber channel is properly drained while unsubscription is being
|
||||
// performed. You may probably need to run unsubscription process in a separate
|
||||
// routine (in parallel with notification receiver routine) to avoid Client's
|
||||
// notification dispatcher blocking.
|
||||
func (c *WSClient) Unsubscribe(id string) error {
|
||||
return c.performUnsubscription(id)
|
||||
}
|
||||
|
||||
// UnsubscribeAll removes all active subscriptions of the current client.
|
||||
// UnsubscribeAll removes all active subscriptions of the current client. It copies
|
||||
// the list of subscribers in order not to hold the lock for the whole execution
|
||||
// time and tries to unsubscribe from us many feeds as possible returning the
|
||||
// chunk of unsubscription errors afterwards. Call to UnsubscribeAll doesn't block
|
||||
// notifications receive process for given subscribers, thus, ensure that subscribers
|
||||
// channels are properly drained while unsubscription is being performed. You may
|
||||
// probably need to run unsubscription process in a separate routine (in parallel
|
||||
// with notification receiver routines) to avoid Client's notification dispatcher
|
||||
// blocking.
|
||||
func (c *WSClient) UnsubscribeAll() error {
|
||||
c.subscriptionsLock.Lock()
|
||||
defer c.subscriptionsLock.Unlock()
|
||||
|
||||
subs := make([]string, 0, len(c.subscriptions))
|
||||
for id := range c.subscriptions {
|
||||
err := c.removeSubscription(id)
|
||||
subs = append(subs, id)
|
||||
}
|
||||
c.subscriptionsLock.Unlock()
|
||||
|
||||
var resErr error
|
||||
for _, id := range subs {
|
||||
err := c.performUnsubscription(id)
|
||||
if err != nil {
|
||||
return err
|
||||
errFmt := "failed to unsubscribe from feed %d: %v"
|
||||
errArgs := []interface{}{err}
|
||||
if resErr != nil {
|
||||
errFmt = "%w; " + errFmt
|
||||
errArgs = append([]interface{}{resErr}, errArgs...)
|
||||
}
|
||||
resErr = fmt.Errorf(errFmt, errArgs...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return resErr
|
||||
}
|
||||
|
||||
// removeSubscription is internal method that removes subscription with the given
|
||||
// ID from the list of subscriptions and receivers. It must be performed under
|
||||
// subscriptions lock.
|
||||
func (c *WSClient) removeSubscription(id string) error {
|
||||
// performUnsubscription is internal method that removes subscription with the given
|
||||
// ID from the list of subscriptions and receivers. It takes the subscriptions lock
|
||||
// after WS RPC unsubscription request is completed. Until then the subscriber channel
|
||||
// may still receive WS notifications.
|
||||
func (c *WSClient) performUnsubscription(id string) error {
|
||||
var resp bool
|
||||
if err := c.performRequest("unsubscribe", []interface{}{id}, &resp); err != nil {
|
||||
return err
|
||||
|
@ -906,7 +922,14 @@ func (c *WSClient) removeSubscription(id string) error {
|
|||
if !resp {
|
||||
return errors.New("unsubscribe method returned false result")
|
||||
}
|
||||
rcvr := c.subscriptions[id]
|
||||
|
||||
c.subscriptionsLock.Lock()
|
||||
defer c.subscriptionsLock.Unlock()
|
||||
|
||||
rcvr, ok := c.subscriptions[id]
|
||||
if !ok {
|
||||
return errors.New("no subscription with this ID")
|
||||
}
|
||||
ch := rcvr.Receiver()
|
||||
ids := c.receivers[ch]
|
||||
for i, rcvrID := range ids {
|
||||
|
|
|
@ -19,8 +19,10 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"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/crypto/keys"
|
||||
|
@ -1419,6 +1421,7 @@ func TestClient_NEP11_ND(t *testing.T) {
|
|||
expected := stackitem.NewMap()
|
||||
expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("neo.com")))
|
||||
expected.Add(stackitem.Make([]byte("expiration")), stackitem.Make(blockRegisterDomain.Timestamp+365*24*3600*1000)) // expiration formula
|
||||
expected.Add(stackitem.Make([]byte("admin")), stackitem.Null{})
|
||||
require.EqualValues(t, expected, p)
|
||||
})
|
||||
t.Run("Transfer", func(t *testing.T) {
|
||||
|
@ -2001,3 +2004,72 @@ func TestClient_Wait(t *testing.T) {
|
|||
// Wait for transaction that hasn't been persisted and VUB block has been persisted.
|
||||
check(t, util.Uint256{1, 2, 3}, chain.BlockHeight()-1, true)
|
||||
}
|
||||
|
||||
func TestWSClient_Wait(t *testing.T) {
|
||||
chain, rpcSrv, httpSrv := initClearServerWithServices(t, false, false, true)
|
||||
defer chain.Close()
|
||||
defer rpcSrv.Shutdown()
|
||||
|
||||
url := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/ws"
|
||||
c, err := rpcclient.NewWS(context.Background(), url, rpcclient.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.Init())
|
||||
acc, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
act, err := actor.New(c, []actor.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: acc.ScriptHash(),
|
||||
},
|
||||
Account: acc,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rcvr := make(chan *state.AppExecResult)
|
||||
check := func(t *testing.T, b *block.Block, h util.Uint256, vub uint32) {
|
||||
go func() {
|
||||
aer, err := act.Wait(h, vub, nil)
|
||||
require.NoError(t, err, b.Index)
|
||||
rcvr <- aer
|
||||
}()
|
||||
go func() {
|
||||
require.Eventually(t, func() bool {
|
||||
rpcSrv.subsLock.Lock()
|
||||
defer rpcSrv.subsLock.Unlock()
|
||||
return len(rpcSrv.subscribers) == 1
|
||||
}, time.Second, 100*time.Millisecond)
|
||||
require.NoError(t, chain.AddBlock(b))
|
||||
}()
|
||||
waitloop:
|
||||
for {
|
||||
select {
|
||||
case aer := <-rcvr:
|
||||
require.Equal(t, h, aer.Container)
|
||||
require.Equal(t, trigger.Application, aer.Trigger)
|
||||
if h.StringLE() == faultedTxHashLE {
|
||||
require.Equal(t, vmstate.Fault, aer.VMState)
|
||||
} else {
|
||||
require.Equal(t, vmstate.Halt, aer.VMState)
|
||||
}
|
||||
break waitloop
|
||||
case <-time.NewTimer(time.Duration(chain.GetConfig().SecondsPerBlock) * time.Second).C:
|
||||
t.Fatalf("transaction from block %d failed to be awaited: deadline exceeded", b.Index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var faultedChecked bool
|
||||
for _, b := range getTestBlocks(t) {
|
||||
if len(b.Transactions) > 0 {
|
||||
tx := b.Transactions[0]
|
||||
check(t, b, tx.Hash(), tx.ValidUntilBlock)
|
||||
if tx.Hash().StringLE() == faultedTxHashLE {
|
||||
faultedChecked = true
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, chain.AddBlock(b))
|
||||
}
|
||||
}
|
||||
require.True(t, faultedChecked, "FAULTed transaction wasn't checked")
|
||||
}
|
||||
|
|
|
@ -67,20 +67,22 @@ type rpcTestCase struct {
|
|||
}
|
||||
|
||||
const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4"
|
||||
const testContractHash = "2db7d679c538ace5f00495c9e9d8ea95f1e0f5a5"
|
||||
const deploymentTxHash = "496bccb5cb0a008ef9b7a32c459e508ef24fbb0830f82bac9162afa4ca804839"
|
||||
const testContractHash = "565cff9508ebc75aadd7fe59f38dac610ab6093c"
|
||||
const deploymentTxHash = "a14390941cc3a1d87393eff720a722e9cd350bd6ed233c5fe2001326c80eb68e"
|
||||
|
||||
const (
|
||||
verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c"
|
||||
verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A="
|
||||
verifyWithArgsContractHash = "0dce75f52adb1a4c5c6eaa6a34eb26db2e5b3781"
|
||||
nnsContractHash = "bdbfe1a280a0e23ca5b569c8f5845169bd93cb06"
|
||||
nnsToken1ID = "6e656f2e636f6d"
|
||||
nfsoContractHash = "0e15ca0df00669a2cd5dcb03bfd3e2b3849c2969"
|
||||
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
||||
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
||||
block20StateRootLE = "f1380226a217b5e35ea968d42c50e20b9af7ab83b91416c8fb85536c61004332"
|
||||
storageContractHash = "ebc0c16a76c808cd4dde6bcc063f09e45e331ec7"
|
||||
verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c"
|
||||
verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A="
|
||||
verifyWithArgsContractHash = "4dc916254efd2947c93b11207e8ffc0bb56161c5"
|
||||
nnsContractHash = "892429fcd47c30f8451781acc627e8b20e0d64f3"
|
||||
nnsToken1ID = "6e656f2e636f6d"
|
||||
nfsoContractHash = "730ebe719ab8e3b69d11dafc95cdb9bf409db179"
|
||||
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
||||
storageContractHash = "ebc0c16a76c808cd4dde6bcc063f09e45e331ec7"
|
||||
faultedTxHashLE = "82279bfe9bada282ca0f8cb8e0bb124b921af36f00c69a518320322c6f4fef60"
|
||||
faultedTxBlock uint32 = 23
|
||||
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
||||
block20StateRootLE = "b49a045246bf3bb90248ed538dd21e67d782a9242c52f31dfdef3da65ecd87c1"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -287,6 +289,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
return &map[string]interface{}{
|
||||
"name": "neo.com",
|
||||
"expiration": "lhbLRl0B",
|
||||
"admin": nil,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -817,7 +820,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
require.True(t, ok)
|
||||
expected := result.UnclaimedGas{
|
||||
Address: testchain.MultisigScriptHash(),
|
||||
Unclaimed: *big.NewInt(11000),
|
||||
Unclaimed: *big.NewInt(11500),
|
||||
}
|
||||
assert.Equal(t, expected, *actual)
|
||||
},
|
||||
|
@ -905,7 +908,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
script = append(script, 0x41, 0x62, 0x7d, 0x5b, 0x52)
|
||||
return &result.Invoke{
|
||||
State: "HALT",
|
||||
GasConsumed: 32414250,
|
||||
GasConsumed: 31922970,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{stackitem.Make(true)},
|
||||
Notifications: []state.NotificationEvent{{
|
||||
|
@ -935,19 +938,19 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
chg := []dboper.Operation{{
|
||||
State: "Changed",
|
||||
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb},
|
||||
Value: []byte{0xf6, 0x8b, 0x4e, 0x9d, 0x51, 0x79, 0x12},
|
||||
Value: []byte{0x54, 0xb2, 0xd2, 0xa3, 0x51, 0x79, 0x12},
|
||||
}, {
|
||||
State: "Added",
|
||||
Key: []byte{0xfb, 0xff, 0xff, 0xff, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb},
|
||||
Value: []byte{0x41, 0x03, 0x21, 0x01, 0x01, 0x21, 0x01, 0x17, 0},
|
||||
Value: []byte{0x41, 0x03, 0x21, 0x01, 0x01, 0x21, 0x01, 0x18, 0},
|
||||
}, {
|
||||
State: "Changed",
|
||||
Key: []byte{0xfb, 0xff, 0xff, 0xff, 0x14, 0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x8, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2},
|
||||
Value: []byte{0x41, 0x03, 0x21, 0x04, 0x2f, 0xd9, 0xf5, 0x05, 0x21, 0x01, 0x17, 0},
|
||||
Value: []byte{0x41, 0x03, 0x21, 0x04, 0x2f, 0xd9, 0xf5, 0x05, 0x21, 0x01, 0x18, 0},
|
||||
}, {
|
||||
State: "Changed",
|
||||
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0x14, 0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x8, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2},
|
||||
Value: []byte{0x41, 0x01, 0x21, 0x05, 0xe4, 0x74, 0xef, 0xdb, 0x08},
|
||||
Value: []byte{0x41, 0x01, 0x21, 0x05, 0x0c, 0x76, 0x4f, 0xdf, 0x08},
|
||||
}}
|
||||
// Can be returned in any order.
|
||||
assert.ElementsMatch(t, chg, res.Diagnostics.Changes)
|
||||
|
@ -963,7 +966,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||
return &result.Invoke{
|
||||
State: "HALT",
|
||||
GasConsumed: 15928320,
|
||||
GasConsumed: 13970250,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
||||
Notifications: []state.NotificationEvent{},
|
||||
|
@ -1052,7 +1055,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
script = append(script, 0x41, 0x62, 0x7d, 0x5b, 0x52)
|
||||
return &result.Invoke{
|
||||
State: "HALT",
|
||||
GasConsumed: 32414250,
|
||||
GasConsumed: 31922970,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{stackitem.Make(true)},
|
||||
Notifications: []state.NotificationEvent{{
|
||||
|
@ -1078,7 +1081,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||
return &result.Invoke{
|
||||
State: "HALT",
|
||||
GasConsumed: 15928320,
|
||||
GasConsumed: 13970250,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
||||
Notifications: []state.NotificationEvent{},
|
||||
|
@ -1605,12 +1608,12 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
"sendrawtransaction": {
|
||||
{
|
||||
name: "positive",
|
||||
params: `["ABwAAACWP5gAAAAAAEDaEgAAAAAAFwAAAAHunqIsJ+NL0BSPxBCOCPdOj1BIsoAAXgsDAOh2SBcAAAAMFBEmW7QXJQBBvgTo+iQOOPV8HlabDBTunqIsJ+NL0BSPxBCOCPdOj1BIshTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1IBQgxAEh2U53FB2sU+eeLwTAUqMM5518nsDGil4Oi5IoBiMM7hvl6lKGoYIEaVkf7cS6x4MX1RmSHcoOabKFTyuEXI3SgMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwkFW57Mn"]`,
|
||||
params: `["AB0AAACWP5gAAAAAAEDaEgAAAAAAGAAAAAHunqIsJ+NL0BSPxBCOCPdOj1BIsoAAXgsDAOh2SBcAAAAMFBEmW7QXJQBBvgTo+iQOOPV8HlabDBTunqIsJ+NL0BSPxBCOCPdOj1BIshTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1IBQgxAJ6norhWoZxp+Hj1JFhi+Z3qI9DUkLSbfsbaLSaJIqxTfdmPbNFDVK1G+oa+LWmpRp/bj9+QZM7yC+S6HXUI7rigMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwkFW57Mn"]`,
|
||||
result: func(e *executor) interface{} { return &result.RelayResult{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.RelayResult)
|
||||
require.True(t, ok)
|
||||
expectedHash := "e4418a8bdad8cdf401aabb277c7bec279d0b0113812c09607039c4ad87204d90"
|
||||
expectedHash := "c11861dec1dd0f188608b725095041fcfc90abe51eea044993f122f22472753e"
|
||||
assert.Equal(t, expectedHash, res.Hash.StringLE())
|
||||
},
|
||||
},
|
||||
|
@ -2232,7 +2235,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
require.NoErrorf(t, err, "could not parse response: %s", txOut)
|
||||
|
||||
assert.Equal(t, *block.Transactions[0], actual.Transaction)
|
||||
assert.Equal(t, 23, actual.Confirmations)
|
||||
assert.Equal(t, 24, actual.Confirmations)
|
||||
assert.Equal(t, TXHash, actual.Transaction.Hash())
|
||||
})
|
||||
|
||||
|
@ -2345,12 +2348,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
require.NoError(t, json.Unmarshal(res, actual))
|
||||
checkNep17TransfersAux(t, e, actual, sent, rcvd)
|
||||
}
|
||||
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{18, 19, 20, 21}, []int{3, 4}) })
|
||||
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{19, 20, 21, 22}, []int{3, 4}) })
|
||||
t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) })
|
||||
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{15, 16}, []int{2}) })
|
||||
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{18}, []int{3}) })
|
||||
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{17, 18}, []int{3}) })
|
||||
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{19, 20}, []int{4}) })
|
||||
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{16, 17}, []int{2}) })
|
||||
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{19}, []int{3}) })
|
||||
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{18, 19}, []int{3}) })
|
||||
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{20, 21}, []int{4}) })
|
||||
})
|
||||
|
||||
prepareIteratorSession := func(t *testing.T) (uuid.UUID, uuid.UUID) {
|
||||
|
@ -2557,7 +2560,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
t.Run("contract-based verification with parameters", func(t *testing.T) {
|
||||
verAcc, err := util.Uint160DecodeStringLE(verifyWithArgsContractHash)
|
||||
require.NoError(t, err)
|
||||
checkContract(t, verAcc, []byte{}, 490890) // No C# match, but we believe it's OK and it differs from the one above.
|
||||
checkContract(t, verAcc, []byte{}, 245250) // No C# match, but we believe it's OK and it differs from the one above.
|
||||
})
|
||||
t.Run("contract-based verification with invocation script", func(t *testing.T) {
|
||||
verAcc, err := util.Uint160DecodeStringLE(verifyWithArgsContractHash)
|
||||
|
@ -2567,7 +2570,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
emit.Int(invocWriter.BinWriter, 5)
|
||||
emit.String(invocWriter.BinWriter, "")
|
||||
invocScript := invocWriter.Bytes()
|
||||
checkContract(t, verAcc, invocScript, 393720) // No C# match, but we believe it's OK and it has a specific invocation script overriding anything server-side.
|
||||
checkContract(t, verAcc, invocScript, 148080) // No C# match, but we believe it's OK and it has a specific invocation script overriding anything server-side.
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -2719,8 +2722,8 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
},
|
||||
{
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Amount: "37099660700",
|
||||
LastUpdated: 22,
|
||||
Amount: "37106285100",
|
||||
LastUpdated: 23,
|
||||
Decimals: 8,
|
||||
Name: "GasToken",
|
||||
Symbol: "GAS",
|
||||
|
@ -2834,7 +2837,7 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
|
|||
}
|
||||
|
||||
func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) {
|
||||
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}, []int{0, 1, 2, 3, 4, 5, 6, 7, 8})
|
||||
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}, []int{0, 1, 2, 3, 4, 5, 6, 7, 8})
|
||||
}
|
||||
|
||||
func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) {
|
||||
|
@ -2843,6 +2846,11 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
|
|||
rublesHash, err := util.Uint160DecodeStringLE(testContractHash)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockWithFAULTedTx, err := e.chain.GetBlock(e.chain.GetHeaderHash(int(faultedTxBlock))) // Transaction with ABORT inside.
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockWithFAULTedTx.Transactions))
|
||||
txFAULTed := blockWithFAULTedTx.Transactions[0]
|
||||
|
||||
blockDeploy6, err := e.chain.GetBlock(e.chain.GetHeaderHash(22)) // deploy Storage contract (storage_contract.go)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockDeploy6.Transactions))
|
||||
|
@ -2947,6 +2955,14 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
|
|||
// duplicate the Server method.
|
||||
expected := result.NEP17Transfers{
|
||||
Sent: []result.NEP17Transfer{
|
||||
{
|
||||
Timestamp: blockWithFAULTedTx.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "", // burn
|
||||
Amount: big.NewInt(txFAULTed.SystemFee + txFAULTed.NetworkFee).String(),
|
||||
Index: 23,
|
||||
TxHash: blockWithFAULTedTx.Hash(),
|
||||
},
|
||||
{
|
||||
Timestamp: blockDeploy6.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
@ -270,8 +271,9 @@ func TestFilteredSubscriptions(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"execution non-matching": {
|
||||
params: `["transaction_executed", {"state":"FAULT"}]`,
|
||||
check: func(t *testing.T, _ *neorpc.Notification) {
|
||||
// We have single FAULTed transaction in chain, this, use the wrong hash for this test instead of FAULT state.
|
||||
params: `["transaction_executed", {"container":"0x` + util.Uint256{}.StringLE() + `"}]`,
|
||||
check: func(t *testing.T, n *neorpc.Notification) {
|
||||
t.Fatal("unexpected match for faulted execution")
|
||||
},
|
||||
},
|
||||
|
|
BIN
pkg/services/rpcsrv/testdata/testblocks.acc
vendored
BIN
pkg/services/rpcsrv/testdata/testblocks.acc
vendored
Binary file not shown.
Loading…
Reference in a new issue