Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions pkg/core/native/native_neo.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO {
manifest.NewParameter("to", smartcontract.PublicKeyType),
manifest.NewParameter("amount", smartcontract.IntegerType),
)
n.AddEvent("CommitteeChanged",
manifest.NewParameter("old", smartcontract.ArrayType),
manifest.NewParameter("new", smartcontract.ArrayType),
)

return n
}
Expand Down Expand Up @@ -425,13 +429,29 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
// Cached newEpoch* values always have proper value set (either by PostPersist
// during the last epoch block handling or by initialization code).

var oldCommittee, newCommittee stackitem.Item
for i := 0; i < len(cache.committee); i++ {
if cache.newEpochCommittee[i].Key != cache.committee[i].Key ||
(i == 0 && len(cache.newEpochCommittee) != len(cache.committee)) {
oldCommittee, newCommittee = cache.committee.toNotificationItem(), cache.newEpochCommittee.toNotificationItem()
break
}
}

cache.nextValidators = cache.newEpochNextValidators
cache.committee = cache.newEpochCommittee
cache.committeeHash = cache.newEpochCommitteeHash
cache.votesChanged = false

// We need to put in storage anyway, as it affects dumps
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes(ic.DAO.GetItemCtx()))

if oldCommittee != nil {
ic.AddNotification(n.Hash, "CommitteeChanged", stackitem.NewArray([]stackitem.Item{
oldCommittee, newCommittee,
}))
}
}
return nil
}
Expand Down
65 changes: 65 additions & 0 deletions pkg/core/native/native_test/neo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"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/stackitem"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -118,6 +119,70 @@ func TestNEO_CandidateEvents(t *testing.T) {
require.Equal(t, 0, len(aer.Events))
}

func TestNEO_CommitteeEvents(t *testing.T) {
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
e := neoCommitteeInvoker.Executor

cfg := e.Chain.GetConfig()
committeeSize := cfg.GetCommitteeSize(0)

voters := make([]neotest.Signer, committeeSize)
candidates := make([]neotest.Signer, committeeSize)
for i := 0; i < committeeSize; i++ {
voters[i] = e.NewAccount(t, 10_0000_0000)
candidates[i] = e.NewAccount(t, 2000_0000_0000) // enough for one registration
}
txes := make([]*transaction.Transaction, 0, committeeSize*3)
for i := 0; i < committeeSize; i++ {
transferTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(committeeSize-i)*1000000, nil)
txes = append(txes, transferTx)

registerTx := neoValidatorsInvoker.WithSigners(candidates[i]).PrepareInvoke(t, "registerCandidate", candidates[i].(neotest.SingleSigner).Account().PublicKey().Bytes())
txes = append(txes, registerTx)

voteTx := neoValidatorsInvoker.WithSigners(voters[i]).PrepareInvoke(t, "vote", voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[i].(neotest.SingleSigner).Account().PublicKey().Bytes())
txes = append(txes, voteTx)
}
block := neoValidatorsInvoker.AddNewBlock(t, txes...)
for _, tx := range txes {
e.CheckHalt(t, tx.Hash(), stackitem.Make(true))
}

// Advance the chain to trigger committee recalculation and potential change.
for (block.Index)%uint32(committeeSize) != 0 {
block = neoCommitteeInvoker.AddNewBlock(t)
}

// Check for CommitteeChanged event in the last persisted block's AER.
blockHash := e.Chain.CurrentBlockHash()
aer, err := e.Chain.GetAppExecResults(blockHash, trigger.OnPersist)
require.NoError(t, err)
require.Equal(t, 1, len(aer))

require.Equal(t, aer[0].Events[0].Name, "CommitteeChanged")
require.Equal(t, 2, len(aer[0].Events[0].Item.Value().([]stackitem.Item)))

expectedOldCommitteePublicKeys, err := keys.NewPublicKeysFromStrings(cfg.StandbyCommittee)
require.NoError(t, err)
expectedOldCommitteeStackItems := make([]stackitem.Item, len(expectedOldCommitteePublicKeys))
for i, pubKey := range expectedOldCommitteePublicKeys {
expectedOldCommitteeStackItems[i] = stackitem.NewByteArray(pubKey.Bytes())
}
oldCommitteeStackItem := aer[0].Events[0].Item.Value().([]stackitem.Item)[0].(*stackitem.Array)
for i, item := range oldCommitteeStackItem.Value().([]stackitem.Item) {
assert.Equal(t, expectedOldCommitteeStackItems[i].(*stackitem.ByteArray).Value().([]byte), item.Value().([]byte))
}
expectedNewCommitteeStackItems := make([]stackitem.Item, 0, committeeSize)
for _, candidate := range candidates {
expectedNewCommitteeStackItems = append(expectedNewCommitteeStackItems, stackitem.NewByteArray(candidate.(neotest.SingleSigner).Account().PublicKey().Bytes()))
}
newCommitteeStackItem := aer[0].Events[0].Item.Value().([]stackitem.Item)[1].(*stackitem.Array)
for i, item := range newCommitteeStackItem.Value().([]stackitem.Item) {
assert.Equal(t, expectedNewCommitteeStackItems[i].(*stackitem.ByteArray).Value().([]byte), item.Value().([]byte))
}
}

func TestNEO_Vote(t *testing.T) {
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
Expand Down
10 changes: 10 additions & 0 deletions pkg/core/native/neo_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ func (k keysWithVotes) toStackItem() stackitem.Item {
return stackitem.NewArray(arr)
}

// toNotificationItem converts keysWithVotes to a stackitem.Item suitable for use in a notification,
// including public keys only.
func (k keysWithVotes) toNotificationItem() stackitem.Item {
arr := make([]stackitem.Item, len(k))
for i := range k {
arr[i] = stackitem.NewByteArray([]byte(k[i].Key))
}
return stackitem.NewArray(arr)
}

func (k *keysWithVotes) fromStackItem(item stackitem.Item) error {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
Expand Down
6 changes: 6 additions & 0 deletions pkg/rpcclient/neo/neo.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ type CandidateStateEvent struct {
Votes *big.Int
}

// CommitteeChangedEvent represents a CommitteeChanged NEO event.
type CommitteeChangedEvent struct {
Old []keys.PublicKey
New []keys.PublicKey
}

// VoteEvent represents a Vote NEO event.
type VoteEvent struct {
Account util.Uint160
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/rpcsrv/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const (
faultedTxHashLE = "82279bfe9bada282ca0f8cb8e0bb124b921af36f00c69a518320322c6f4fef60"
faultedTxBlock uint32 = 23
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
block20StateRootLE = "858c873539d6d24a70f2be13f9dafc61aef2b63c2aa16bb440676de6e44e3cf1"
block20StateRootLE = "397c69adbc0201d59623fa913bfff4a2da25c792c484d1d278c061709f2c21cf"
)

var (
Expand Down