native: add committee change events

Port neo-project/neo#3158.

Close #3326

Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
This commit is contained in:
Ekaterina Pavlova 2024-03-15 14:46:50 +03:00
parent a81dc5c795
commit 270515de6a
4 changed files with 101 additions and 0 deletions

View file

@ -265,6 +265,10 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO {
manifest.NewParameter("to", smartcontract.PublicKeyType), manifest.NewParameter("to", smartcontract.PublicKeyType),
manifest.NewParameter("amount", smartcontract.IntegerType), manifest.NewParameter("amount", smartcontract.IntegerType),
) )
n.AddEvent("CommitteeChanged",
manifest.NewParameter("old", smartcontract.ArrayType),
manifest.NewParameter("new", smartcontract.ArrayType),
)
return n return n
} }
@ -425,6 +429,16 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache) cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
// Cached newEpoch* values always have proper value set (either by PostPersist // Cached newEpoch* values always have proper value set (either by PostPersist
// during the last epoch block handling or by initialization code). // 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.nextValidators = cache.newEpochNextValidators
cache.committee = cache.newEpochCommittee cache.committee = cache.newEpochCommittee
cache.committeeHash = cache.newEpochCommitteeHash cache.committeeHash = cache.newEpochCommitteeHash
@ -432,6 +446,12 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
// We need to put in storage anyway, as it affects dumps // We need to put in storage anyway, as it affects dumps
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cache.committee.Bytes(ic.DAO.GetItemCtx())) 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 return nil
} }

View file

@ -28,6 +28,7 @@ import (
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -118,6 +119,70 @@ func TestNEO_CandidateEvents(t *testing.T) {
require.Equal(t, 0, len(aer.Events)) 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) { func TestNEO_Vote(t *testing.T) {
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000) neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator) neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)

View file

@ -52,6 +52,16 @@ func (k keysWithVotes) toStackItem() stackitem.Item {
return stackitem.NewArray(arr) 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 { func (k *keysWithVotes) fromStackItem(item stackitem.Item) error {
arr, ok := item.Value().([]stackitem.Item) arr, ok := item.Value().([]stackitem.Item)
if !ok { if !ok {

View file

@ -75,6 +75,12 @@ type CandidateStateEvent struct {
Votes *big.Int Votes *big.Int
} }
// CommitteeChangedEvent represents a CommitteeChanged NEO event.
type CommitteeChangedEvent struct {
Old []keys.PublicKey
New []keys.PublicKey
}
// VoteEvent represents a Vote NEO event. // VoteEvent represents a Vote NEO event.
type VoteEvent struct { type VoteEvent struct {
Account util.Uint160 Account util.Uint160