forked from TrueCloudLab/neoneo-go
native: support postPersist
method
It should be called for NEO contract to distribute committee bounties.
This commit is contained in:
parent
e8eb177c64
commit
c5cdaae87a
10 changed files with 143 additions and 35 deletions
|
@ -365,6 +365,19 @@ func (bc *Blockchain) notificationDispatcher() {
|
||||||
ch <- tx
|
ch <- tx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aer = event.appExecResults[aerIdx]
|
||||||
|
if !aer.TxHash.Equals(event.block.Hash()) {
|
||||||
|
panic("inconsistent application execution results")
|
||||||
|
}
|
||||||
|
for ch := range executionFeed {
|
||||||
|
ch <- aer
|
||||||
|
}
|
||||||
|
for i := range aer.Events {
|
||||||
|
for ch := range notificationFeed {
|
||||||
|
ch <- &aer.Events[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for ch := range blockFeed {
|
for ch := range blockFeed {
|
||||||
ch <- event.block
|
ch <- event.block
|
||||||
|
@ -528,7 +541,7 @@ func (bc *Blockchain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
|
||||||
func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error {
|
func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error {
|
||||||
cache := dao.NewCached(bc.dao)
|
cache := dao.NewCached(bc.dao)
|
||||||
writeBuf := io.NewBufBinWriter()
|
writeBuf := io.NewBufBinWriter()
|
||||||
appExecResults := make([]*state.AppExecResult, 0, 1+len(block.Transactions))
|
appExecResults := make([]*state.AppExecResult, 0, 2+len(block.Transactions))
|
||||||
if err := cache.StoreAsBlock(block, writeBuf); err != nil {
|
if err := cache.StoreAsBlock(block, writeBuf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -540,28 +553,12 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
writeBuf.Reset()
|
writeBuf.Reset()
|
||||||
|
|
||||||
if block.Index > 0 {
|
if block.Index > 0 {
|
||||||
systemInterop := bc.newInteropContext(trigger.System, cache, block, nil)
|
aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache)
|
||||||
v := systemInterop.SpawnVM()
|
if err != nil {
|
||||||
v.LoadScriptWithFlags(bc.contracts.GetPersistScript(), smartcontract.AllowModifyStates|smartcontract.AllowCall)
|
return fmt.Errorf("onPersist failed: %w", err)
|
||||||
v.SetPriceGetter(getPrice)
|
|
||||||
if err := v.Run(); err != nil {
|
|
||||||
return fmt.Errorf("onPersist run failed: %w", err)
|
|
||||||
} else if _, err := systemInterop.DAO.Persist(); err != nil {
|
|
||||||
return fmt.Errorf("can't save onPersist changes: %w", err)
|
|
||||||
}
|
|
||||||
for i := range systemInterop.Notifications {
|
|
||||||
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
|
|
||||||
}
|
|
||||||
aer := &state.AppExecResult{
|
|
||||||
TxHash: block.Hash(), // application logs can be retrieved by block hash
|
|
||||||
Trigger: trigger.System,
|
|
||||||
VMState: v.State(),
|
|
||||||
GasConsumed: v.GasConsumed(),
|
|
||||||
Stack: v.Estack().ToArray(),
|
|
||||||
Events: systemInterop.Notifications,
|
|
||||||
}
|
}
|
||||||
appExecResults = append(appExecResults, aer)
|
appExecResults = append(appExecResults, aer)
|
||||||
err := cache.PutAppExecResult(aer, writeBuf)
|
err = cache.PutAppExecResult(aer, writeBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to store onPersist exec result: %w", err)
|
return fmt.Errorf("failed to store onPersist exec result: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -611,6 +608,17 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
writeBuf.Reset()
|
writeBuf.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aer, err := bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("postPersist failed: %w", err)
|
||||||
|
}
|
||||||
|
appExecResults = append(appExecResults, aer)
|
||||||
|
err = cache.PutAppExecResult(aer, writeBuf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to store postPersist exec result: %w", err)
|
||||||
|
}
|
||||||
|
writeBuf.Reset()
|
||||||
|
|
||||||
root := bc.dao.MPT.StateRoot()
|
root := bc.dao.MPT.StateRoot()
|
||||||
var prevHash util.Uint256
|
var prevHash util.Uint256
|
||||||
if block.Index > 0 {
|
if block.Index > 0 {
|
||||||
|
@ -620,7 +628,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
}
|
}
|
||||||
prevHash = hash.DoubleSha256(prev.GetSignedPart())
|
prevHash = hash.DoubleSha256(prev.GetSignedPart())
|
||||||
}
|
}
|
||||||
err := bc.AddStateRoot(&state.MPTRoot{
|
err = bc.AddStateRoot(&state.MPTRoot{
|
||||||
MPTRootBase: state.MPTRootBase{
|
MPTRootBase: state.MPTRootBase{
|
||||||
Index: block.Index,
|
Index: block.Index,
|
||||||
PrevHash: prevHash,
|
PrevHash: prevHash,
|
||||||
|
@ -664,6 +672,29 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Cached) (*state.AppExecResult, error) {
|
||||||
|
systemInterop := bc.newInteropContext(trigger.System, cache, block, nil)
|
||||||
|
v := systemInterop.SpawnVM()
|
||||||
|
v.LoadScriptWithFlags(script, smartcontract.AllowModifyStates|smartcontract.AllowCall)
|
||||||
|
v.SetPriceGetter(getPrice)
|
||||||
|
if err := v.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("VM has failed: %w", err)
|
||||||
|
} else if _, err := systemInterop.DAO.Persist(); err != nil {
|
||||||
|
return nil, fmt.Errorf("can't save changes: %w", err)
|
||||||
|
}
|
||||||
|
for i := range systemInterop.Notifications {
|
||||||
|
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
|
||||||
|
}
|
||||||
|
return &state.AppExecResult{
|
||||||
|
TxHash: block.Hash(), // application logs can be retrieved by block hash
|
||||||
|
Trigger: trigger.System,
|
||||||
|
VMState: v.State(),
|
||||||
|
GasConsumed: v.GasConsumed(),
|
||||||
|
Stack: v.Estack().ToArray(),
|
||||||
|
Events: systemInterop.Notifications,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) {
|
func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) {
|
||||||
if note.Name != "transfer" && note.Name != "Transfer" {
|
if note.Name != "transfer" && note.Name != "Transfer" {
|
||||||
return
|
return
|
||||||
|
|
|
@ -588,7 +588,7 @@ func TestSubscriptions(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond)
|
require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond)
|
||||||
assert.Len(t, notificationCh, 1) // validator bounty
|
assert.Len(t, notificationCh, 1) // validator bounty
|
||||||
assert.Len(t, executionCh, 1)
|
assert.Len(t, executionCh, 2)
|
||||||
assert.Empty(t, txCh)
|
assert.Empty(t, txCh)
|
||||||
|
|
||||||
b := <-blockCh
|
b := <-blockCh
|
||||||
|
@ -597,6 +597,8 @@ func TestSubscriptions(t *testing.T) {
|
||||||
|
|
||||||
aer := <-executionCh
|
aer := <-executionCh
|
||||||
assert.Equal(t, b.Hash(), aer.TxHash)
|
assert.Equal(t, b.Hash(), aer.TxHash)
|
||||||
|
aer = <-executionCh
|
||||||
|
assert.Equal(t, b.Hash(), aer.TxHash)
|
||||||
|
|
||||||
notif := <-notificationCh
|
notif := <-notificationCh
|
||||||
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
||||||
|
@ -669,11 +671,15 @@ func TestSubscriptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Empty(t, txCh)
|
assert.Empty(t, txCh)
|
||||||
assert.Len(t, notificationCh, 1)
|
assert.Len(t, notificationCh, 1)
|
||||||
assert.Empty(t, executionCh)
|
assert.Len(t, executionCh, 1)
|
||||||
|
|
||||||
notif = <-notificationCh
|
notif = <-notificationCh
|
||||||
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
||||||
|
|
||||||
|
exec = <-executionCh
|
||||||
|
require.Equal(t, b.Hash(), exec.TxHash)
|
||||||
|
require.Equal(t, exec.VMState, vm.HaltState)
|
||||||
|
|
||||||
bc.UnsubscribeFromBlocks(blockCh)
|
bc.UnsubscribeFromBlocks(blockCh)
|
||||||
bc.UnsubscribeFromTransactions(txCh)
|
bc.UnsubscribeFromTransactions(txCh)
|
||||||
bc.UnsubscribeFromNotifications(notificationCh)
|
bc.UnsubscribeFromNotifications(notificationCh)
|
||||||
|
|
|
@ -177,7 +177,7 @@ func TestCreateBasicChain(t *testing.T) {
|
||||||
priv0 := testchain.PrivateKeyByID(0)
|
priv0 := testchain.PrivateKeyByID(0)
|
||||||
priv0ScriptHash := priv0.GetScriptHash()
|
priv0ScriptHash := priv0.GetScriptHash()
|
||||||
|
|
||||||
require.Equal(t, big.NewInt(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
|
require.Equal(t, big.NewInt(2500_0000), bc.GetUtilityTokenBalance(priv0ScriptHash)) // gas bounty
|
||||||
// Move some NEO to one simple account.
|
// Move some NEO to one simple account.
|
||||||
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
|
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
|
||||||
txMoveNeo.ValidUntilBlock = validUntilBlock
|
txMoveNeo.ValidUntilBlock = validUntilBlock
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
@ -16,6 +19,8 @@ type Contracts struct {
|
||||||
Contracts []interop.Contract
|
Contracts []interop.Contract
|
||||||
// persistScript is vm script which executes "onPersist" method of every native contract.
|
// persistScript is vm script which executes "onPersist" method of every native contract.
|
||||||
persistScript []byte
|
persistScript []byte
|
||||||
|
// postPersistScript is vm script which executes "postPersist" method of every native contract.
|
||||||
|
postPersistScript []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByHash returns native contract with the specified hash.
|
// ByHash returns native contract with the specified hash.
|
||||||
|
@ -71,3 +76,33 @@ func (cs *Contracts) GetPersistScript() []byte {
|
||||||
cs.persistScript = w.Bytes()
|
cs.persistScript = w.Bytes()
|
||||||
return cs.persistScript
|
return cs.persistScript
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPostPersistScript returns VM script calling "postPersist" method of some native contracts.
|
||||||
|
func (cs *Contracts) GetPostPersistScript() []byte {
|
||||||
|
if cs.postPersistScript != nil {
|
||||||
|
return cs.postPersistScript
|
||||||
|
}
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
for i := range cs.Contracts {
|
||||||
|
md := cs.Contracts[i].Metadata()
|
||||||
|
// Not every contract is persisted:
|
||||||
|
// https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L103
|
||||||
|
if md.ContractID == policyContractID || md.ContractID == gasContractID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
emit.Int(w.BinWriter, 0)
|
||||||
|
emit.Opcode(w.BinWriter, opcode.NEWARRAY)
|
||||||
|
emit.String(w.BinWriter, "postPersist")
|
||||||
|
emit.AppCall(w.BinWriter, md.Hash)
|
||||||
|
emit.Opcode(w.BinWriter, opcode.DROP)
|
||||||
|
}
|
||||||
|
cs.postPersistScript = w.Bytes()
|
||||||
|
return cs.postPersistScript
|
||||||
|
}
|
||||||
|
|
||||||
|
func postPersistBase(ic *interop.Context) error {
|
||||||
|
if ic.Trigger != trigger.System {
|
||||||
|
return errors.New("'postPersist' should be trigered by system")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -90,6 +90,7 @@ func NewNEO() *NEO {
|
||||||
nep5.decimals = 0
|
nep5.decimals = 0
|
||||||
nep5.factor = 1
|
nep5.factor = 1
|
||||||
nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist)
|
nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist)
|
||||||
|
nep5.postPersist = chainOnPersist(nep5.postPersist, n.PostPersist)
|
||||||
nep5.incBalance = n.increaseBalance
|
nep5.incBalance = n.increaseBalance
|
||||||
nep5.ContractID = neoContractID
|
nep5.ContractID = neoContractID
|
||||||
|
|
||||||
|
@ -103,6 +104,10 @@ func NewNEO() *NEO {
|
||||||
onp.Func = getOnPersistWrapper(n.onPersist)
|
onp.Func = getOnPersistWrapper(n.onPersist)
|
||||||
n.Methods["onPersist"] = onp
|
n.Methods["onPersist"] = onp
|
||||||
|
|
||||||
|
pp := n.Methods["postPersist"]
|
||||||
|
pp.Func = getOnPersistWrapper(n.postPersist)
|
||||||
|
n.Methods["postPersist"] = pp
|
||||||
|
|
||||||
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
||||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||||
manifest.NewParameter("end", smartcontract.IntegerType))
|
manifest.NewParameter("end", smartcontract.IntegerType))
|
||||||
|
@ -226,7 +231,11 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostPersist implements Contract interface.
|
||||||
|
func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||||
gas, err := n.GetGASPerBlock(ic, ic.Block.Index)
|
gas, err := n.GetGASPerBlock(ic, ic.Block.Index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -35,6 +35,7 @@ type nep5TokenNative struct {
|
||||||
decimals int64
|
decimals int64
|
||||||
factor int64
|
factor int64
|
||||||
onPersist func(*interop.Context) error
|
onPersist func(*interop.Context) error
|
||||||
|
postPersist func(*interop.Context) error
|
||||||
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +85,10 @@ func newNEP5Native(name string) *nep5TokenNative {
|
||||||
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||||
n.AddMethod(md, desc, false)
|
n.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
desc = newDescriptor("postPersist", smartcontract.VoidType)
|
||||||
|
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
|
||||||
|
n.AddMethod(md, desc, false)
|
||||||
|
|
||||||
n.AddEvent("Transfer", desc.Parameters...)
|
n.AddEvent("Transfer", desc.Parameters...)
|
||||||
|
|
||||||
return n
|
return n
|
||||||
|
|
|
@ -124,6 +124,10 @@ func newPolicy() *Policy {
|
||||||
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
||||||
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||||
p.AddMethod(md, desc, false)
|
p.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
desc = newDescriptor("postPersist", smartcontract.VoidType)
|
||||||
|
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
|
||||||
|
p.AddMethod(md, desc, false)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -212,16 +212,16 @@ func TestNEO_CommitteeBountyOnPersist(t *testing.T) {
|
||||||
hs[i] = testchain.PrivateKeyByID(i).GetScriptHash()
|
hs[i] = testchain.PrivateKeyByID(i).GetScriptHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
bs := make(map[int]int64)
|
const singleBounty = 25000000
|
||||||
|
bs := map[int]int64{0: singleBounty}
|
||||||
checkBalances := func() {
|
checkBalances := func() {
|
||||||
for i := 0; i < testchain.CommitteeSize(); i++ {
|
for i := 0; i < testchain.CommitteeSize(); i++ {
|
||||||
require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64())
|
require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64(), i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < testchain.CommitteeSize()*2; i++ {
|
for i := 0; i < testchain.CommitteeSize()*2; i++ {
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
bs[(i+1)%testchain.CommitteeSize()] += 25000000
|
bs[(i+1)%testchain.CommitteeSize()] += singleBounty
|
||||||
checkBalances()
|
checkBalances()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1075,7 +1075,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Asset: e.chain.UtilityTokenHash(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Amount: "799.34495030",
|
Amount: "799.59495030",
|
||||||
LastUpdated: 7,
|
LastUpdated: 7,
|
||||||
}},
|
}},
|
||||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
||||||
|
@ -1132,6 +1132,9 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
||||||
txReceiveNEO := blockReceiveGAS.Transactions[0]
|
txReceiveNEO := blockReceiveGAS.Transactions[0]
|
||||||
txReceiveGAS := blockReceiveGAS.Transactions[1]
|
txReceiveGAS := blockReceiveGAS.Transactions[1]
|
||||||
|
|
||||||
|
blockGASBounty0, err := e.chain.GetBlock(e.chain.GetHeaderHash(0))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// These are laid out here explicitly for 2 purposes:
|
// These are laid out here explicitly for 2 purposes:
|
||||||
// * to be able to reference any particular event for paging
|
// * to be able to reference any particular event for paging
|
||||||
// * to check chain events consistency
|
// * to check chain events consistency
|
||||||
|
@ -1260,6 +1263,14 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
||||||
NotifyIndex: 0,
|
NotifyIndex: 0,
|
||||||
TxHash: txReceiveNEO.Hash(),
|
TxHash: txReceiveNEO.Hash(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Timestamp: blockGASBounty0.Timestamp,
|
||||||
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
|
Address: "",
|
||||||
|
Amount: "0.25000000",
|
||||||
|
Index: 0,
|
||||||
|
TxHash: blockGASBounty0.Hash(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Address: testchain.PrivateKeyByID(0).Address(),
|
Address: testchain.PrivateKeyByID(0).Address(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,13 @@ func TestSubscriptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp = getNotification(t, respMsgs)
|
resp = getNotification(t, respMsgs)
|
||||||
|
require.Equal(t, response.ExecutionEventID, resp.Event)
|
||||||
|
for {
|
||||||
|
resp = getNotification(t, respMsgs)
|
||||||
|
if resp.Event != response.NotificationEventID {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
require.Equal(t, response.BlockEventID, resp.Event)
|
require.Equal(t, response.BlockEventID, resp.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue