forked from TrueCloudLab/frostfs-contract
[#101] reputation: allow Put
in multiple tx per block
`listByEpoch` now returns only peer identifiers. Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
da7f7eb88f
commit
c4212e7d2f
2 changed files with 128 additions and 27 deletions
|
@ -3,6 +3,7 @@ package reputation
|
||||||
import (
|
import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
"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/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/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||||
|
@ -12,19 +13,39 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
notaryDisabledKey = "notary"
|
notaryDisabledKey = "notary"
|
||||||
|
reputationValuePrefix = 'r'
|
||||||
|
reputationCountPrefix = 'c'
|
||||||
)
|
)
|
||||||
|
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
|
||||||
if isUpdate {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
args := data.([]interface{})
|
args := data.([]interface{})
|
||||||
notaryDisabled := args[0].(bool)
|
notaryDisabled := args[0].(bool)
|
||||||
|
|
||||||
ctx := storage.GetContext()
|
|
||||||
|
|
||||||
// initialize the way to collect signatures
|
// initialize the way to collect signatures
|
||||||
storage.Put(ctx, notaryDisabledKey, notaryDisabled)
|
storage.Put(ctx, notaryDisabledKey, notaryDisabled)
|
||||||
if notaryDisabled {
|
if notaryDisabled {
|
||||||
|
@ -78,11 +99,19 @@ func Put(epoch int, peerID []byte, value []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
id := storageID(epoch, peerID)
|
id := storageID(epoch, peerID)
|
||||||
|
key := getReputationKey(reputationCountPrefix, id)
|
||||||
|
|
||||||
reputationValues := GetByID(id)
|
rawCnt := storage.Get(ctx, key)
|
||||||
reputationValues = append(reputationValues, value)
|
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 {
|
if notaryDisabled {
|
||||||
threshold := len(alphabet)*2/3 + 1
|
threshold := len(alphabet)*2/3 + 1
|
||||||
|
@ -94,8 +123,6 @@ func Put(epoch int, peerID []byte, value []byte) {
|
||||||
|
|
||||||
common.RemoveVotes(ctx, id)
|
common.RemoveVotes(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.Put(ctx, id, rawValues)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get method returns list of all stable marshaled DataAuditResult structures
|
// 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 {
|
func GetByID(id []byte) [][]byte {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
|
||||||
data := storage.Get(ctx, id)
|
var data [][]byte
|
||||||
if data == nil {
|
|
||||||
return [][]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
|
// ListByEpoch returns list of IDs that may be used to get reputation data
|
||||||
// with GetByID method.
|
// with GetByID method.
|
||||||
func ListByEpoch(epoch int) [][]byte {
|
func ListByEpoch(epoch int) [][]byte {
|
||||||
ctx := storage.GetReadOnlyContext()
|
ctx := storage.GetReadOnlyContext()
|
||||||
var buf interface{} = epoch
|
key := getReputationKey(reputationCountPrefix, convert.ToBytes(epoch))
|
||||||
it := storage.Find(ctx, buf.([]byte), storage.KeysOnly)
|
it := storage.Find(ctx, key, storage.KeysOnly)
|
||||||
|
|
||||||
var result [][]byte
|
var result [][]byte
|
||||||
|
|
||||||
ignore := [][]byte{
|
|
||||||
[]byte(notaryDisabledKey),
|
|
||||||
}
|
|
||||||
|
|
||||||
loop:
|
|
||||||
for iterator.Next(it) {
|
for iterator.Next(it) {
|
||||||
key := iterator.Value(it).([]byte) // iterator MUST BE `storage.KeysOnly`
|
key := iterator.Value(it).([]byte) // iterator MUST BE `storage.KeysOnly`
|
||||||
for _, ignoreKey := range ignore {
|
result = append(result, key[1:])
|
||||||
if common.BytesEqual(key, ignoreKey) {
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
80
tests/reputation_test.go
Normal file
80
tests/reputation_test.go
Normal file
|
@ -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))
|
||||||
|
}
|
Loading…
Reference in a new issue