From c4212e7d2f909e9b46ca12b76c79b86e4bba7e64 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 15 Nov 2021 16:12:53 +0300 Subject: [PATCH] [#101] reputation: allow `Put` in multiple tx per block `listByEpoch` now returns only peer identifiers. Signed-off-by: Evgenii Stratonikov --- reputation/reputation_contract.go | 75 ++++++++++++++++++----------- tests/reputation_test.go | 80 +++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 27 deletions(-) create mode 100644 tests/reputation_test.go diff --git a/reputation/reputation_contract.go b/reputation/reputation_contract.go index 77f8af0..ca9f3ac 100644 --- a/reputation/reputation_contract.go +++ b/reputation/reputation_contract.go @@ -3,6 +3,7 @@ package reputation import ( "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/convert" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/std" @@ -12,19 +13,39 @@ import ( ) const ( - notaryDisabledKey = "notary" + notaryDisabledKey = "notary" + reputationValuePrefix = 'r' + reputationCountPrefix = 'c' ) func _deploy(data interface{}, isUpdate bool) { + ctx := storage.GetContext() + if isUpdate { + // Storage migration. + it := storage.Find(ctx, []byte{}, storage.None) + for iterator.Next(it) { + kv := iterator.Value(it).([][]byte) + if string(kv[0]) == notaryDisabledKey { + continue + } + + rawValues := std.Deserialize(kv[1]).([][]byte) + key := getReputationKey(reputationCountPrefix, kv[0]) + storage.Put(ctx, key, len(rawValues)) + + key[0] = reputationValuePrefix + for i := range rawValues { + newKey := append(key, convert.ToBytes(i)...) + storage.Put(ctx, newKey, rawValues[i]) + } + } return } args := data.([]interface{}) notaryDisabled := args[0].(bool) - ctx := storage.GetContext() - // initialize the way to collect signatures storage.Put(ctx, notaryDisabledKey, notaryDisabled) if notaryDisabled { @@ -78,11 +99,19 @@ func Put(epoch int, peerID []byte, value []byte) { } id := storageID(epoch, peerID) + key := getReputationKey(reputationCountPrefix, id) - reputationValues := GetByID(id) - reputationValues = append(reputationValues, value) + rawCnt := storage.Get(ctx, key) + cnt := 0 + if rawCnt != nil { + cnt = rawCnt.(int) + } + cnt++ + storage.Put(ctx, key, cnt) - rawValues := std.Serialize(reputationValues) + key[0] = reputationValuePrefix + key = append(key, convert.ToBytes(cnt)...) + storage.Put(ctx, key, value) if notaryDisabled { threshold := len(alphabet)*2/3 + 1 @@ -94,8 +123,6 @@ func Put(epoch int, peerID []byte, value []byte) { common.RemoveVotes(ctx, id) } - - storage.Put(ctx, id, rawValues) } // Get method returns list of all stable marshaled DataAuditResult structures @@ -110,37 +137,31 @@ func Get(epoch int, peerID []byte) [][]byte { func GetByID(id []byte) [][]byte { ctx := storage.GetReadOnlyContext() - data := storage.Get(ctx, id) - if data == nil { - return [][]byte{} - } + var data [][]byte - return std.Deserialize(data.([]byte)).([][]byte) + it := storage.Find(ctx, getReputationKey(reputationValuePrefix, id), storage.ValuesOnly) + for iterator.Next(it) { + data = append(data, iterator.Value(it).([]byte)) + } + return data +} + +func getReputationKey(prefix byte, id []byte) []byte { + return append([]byte{prefix}, id...) } // ListByEpoch returns list of IDs that may be used to get reputation data // with GetByID method. func ListByEpoch(epoch int) [][]byte { ctx := storage.GetReadOnlyContext() - var buf interface{} = epoch - it := storage.Find(ctx, buf.([]byte), storage.KeysOnly) + key := getReputationKey(reputationCountPrefix, convert.ToBytes(epoch)) + it := storage.Find(ctx, key, storage.KeysOnly) var result [][]byte - ignore := [][]byte{ - []byte(notaryDisabledKey), - } - -loop: for iterator.Next(it) { key := iterator.Value(it).([]byte) // iterator MUST BE `storage.KeysOnly` - for _, ignoreKey := range ignore { - if common.BytesEqual(key, ignoreKey) { - continue loop - } - } - - result = append(result, key) + result = append(result, key[1:]) } return result diff --git a/tests/reputation_test.go b/tests/reputation_test.go new file mode 100644 index 0000000..8b09a25 --- /dev/null +++ b/tests/reputation_test.go @@ -0,0 +1,80 @@ +package tests + +import ( + "path" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +const reputationPath = "../reputation" + +func deployReputationContract(t *testing.T, e *neotest.Executor) util.Uint160 { + c := neotest.CompileFile(t, e.CommitteeHash, reputationPath, + path.Join(reputationPath, "config.yml")) + + args := make([]interface{}, 1) + args[0] = false + + e.DeployContract(t, c, args) + return c.Hash +} + +func newReputationInvoker(t *testing.T) *neotest.ContractInvoker { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + h := deployReputationContract(t, e) + return e.CommitteeInvoker(h) +} + +func TestReputation_Put(t *testing.T) { + e := newReputationInvoker(t) + + peerID := []byte{1, 2, 3} + e.Invoke(t, stackitem.Null{}, "put", int64(1), peerID, []byte{4}) + + t.Run("concurrent invocations", func(t *testing.T) { + repValue1 := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + repValue2 := []byte{10, 20, 30, 40, 50, 60, 70, 80} + tx1 := e.PrepareInvoke(t, "put", int64(1), peerID, repValue1) + tx2 := e.PrepareInvoke(t, "put", int64(1), peerID, repValue2) + + e.AddNewBlock(t, tx1, tx2) + e.CheckHalt(t, tx1.Hash(), stackitem.Null{}) + e.CheckHalt(t, tx2.Hash(), stackitem.Null{}) + + t.Run("get all", func(t *testing.T) { + result := stackitem.NewArray([]stackitem.Item{ + stackitem.NewBuffer([]byte{4}), + stackitem.NewBuffer(repValue1), + stackitem.NewBuffer(repValue2), + }) + e.Invoke(t, result, "get", int64(1), peerID) + }) + }) +} + +func TestReputation_ListByEpoch(t *testing.T) { + e := newReputationInvoker(t) + + peerIDs := []string{"peer1", "peer2"} + e.Invoke(t, stackitem.Null{}, "put", int64(1), peerIDs[0], []byte{1}) + e.Invoke(t, stackitem.Null{}, "put", int64(1), peerIDs[0], []byte{2}) + e.Invoke(t, stackitem.Null{}, "put", int64(2), peerIDs[1], []byte{3}) + e.Invoke(t, stackitem.Null{}, "put", int64(2), peerIDs[0], []byte{4}) + e.Invoke(t, stackitem.Null{}, "put", int64(2), peerIDs[1], []byte{5}) + + result := stackitem.NewArray([]stackitem.Item{ + stackitem.NewBuffer(append([]byte{1}, peerIDs[0]...)), + }) + e.Invoke(t, result, "listByEpoch", int64(1)) + + result = stackitem.NewArray([]stackitem.Item{ + stackitem.NewBuffer(append([]byte{2}, peerIDs[0]...)), + stackitem.NewBuffer(append([]byte{2}, peerIDs[1]...)), + }) + e.Invoke(t, result, "listByEpoch", int64(2)) +}