2020-10-27 12:14:06 +00:00
|
|
|
package auditcontract
|
|
|
|
|
|
|
|
import (
|
2020-12-17 15:58:12 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/crypto"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
2020-10-27 12:14:06 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
2021-02-02 17:36:20 +00:00
|
|
|
"github.com/nspcc-dev/neofs-contract/common"
|
2020-10-27 12:14:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
2020-12-17 15:58:12 +00:00
|
|
|
auditHeader struct {
|
|
|
|
epoch int
|
|
|
|
cid []byte
|
|
|
|
from interop.PublicKey
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
// Audit key is a combination of epoch, container ID and public key of node that
|
|
|
|
// executed audit. Together it should be no more than 64 bytes. We can't shrink
|
|
|
|
// epoch and container ID since we iterate over these values. But we can shrink
|
|
|
|
// public key by using first bytes of the hashed value.
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
const maxKeySize = 24 // 24 + 32 (container ID length) + 8 (epoch length) = 64
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func (a auditHeader) ID() []byte {
|
|
|
|
var buf interface{} = a.epoch
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
hashedKey := crypto.SHA256(a.from)
|
|
|
|
shortedKey := hashedKey[:maxKeySize]
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
return append(buf.([]byte), append(a.cid, shortedKey...)...)
|
|
|
|
}
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
const (
|
|
|
|
version = 1
|
|
|
|
|
|
|
|
netmapContractKey = "netmapScriptHash"
|
|
|
|
netmapContractKeyLn = len(netmapContractKey)
|
2020-10-27 12:14:06 +00:00
|
|
|
)
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
var ctx storage.Context
|
|
|
|
|
2020-10-27 12:14:06 +00:00
|
|
|
func init() {
|
|
|
|
if runtime.GetTrigger() != runtime.Application {
|
|
|
|
panic("contract has not been called in application node")
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = storage.GetContext()
|
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func Init(addrNetmap interop.Hash160) {
|
|
|
|
if storage.Get(ctx, netmapContractKey) != nil {
|
2020-10-27 12:14:06 +00:00
|
|
|
panic("init: contract already deployed")
|
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
if len(addrNetmap) != 20 {
|
2020-10-27 12:14:06 +00:00
|
|
|
panic("init: incorrect length of contract script hash")
|
|
|
|
}
|
|
|
|
|
|
|
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
|
|
|
|
|
|
|
runtime.Log("audit contract initialized")
|
|
|
|
}
|
|
|
|
|
|
|
|
func Put(rawAuditResult []byte) bool {
|
2021-02-02 19:20:31 +00:00
|
|
|
innerRing := common.InnerRingListViaStorage(ctx, netmapContractKey)
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
hdr := newAuditHeader(rawAuditResult)
|
|
|
|
presented := false
|
2020-10-27 12:14:06 +00:00
|
|
|
|
|
|
|
for i := range innerRing {
|
|
|
|
ir := innerRing[i]
|
2021-02-02 19:20:31 +00:00
|
|
|
if common.BytesEqual(ir.PublicKey, hdr.from) {
|
2020-10-27 12:14:06 +00:00
|
|
|
presented = true
|
2020-12-17 15:58:12 +00:00
|
|
|
|
2020-10-27 12:14:06 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
if !runtime.CheckWitness(hdr.from) || !presented {
|
|
|
|
panic("audit: put access denied")
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
storage.Put(ctx, hdr.ID(), rawAuditResult)
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
runtime.Log("audit: result has been saved")
|
2020-10-27 12:14:06 +00:00
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func Get(id []byte) []byte {
|
|
|
|
return storage.Get(ctx, id).([]byte)
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func List() [][]byte {
|
2021-02-08 15:23:08 +00:00
|
|
|
it := storage.Find(ctx, []byte{}, storage.KeysOnly)
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
return list(it)
|
|
|
|
}
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func ListByEpoch(epoch int) [][]byte {
|
2021-02-08 15:23:08 +00:00
|
|
|
it := storage.Find(ctx, epoch, storage.KeysOnly)
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
return list(it)
|
|
|
|
}
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func ListByCID(epoch int, cid []byte) [][]byte {
|
|
|
|
var buf interface{} = epoch
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
prefix := append(buf.([]byte), cid...)
|
2021-02-08 15:23:08 +00:00
|
|
|
it := storage.Find(ctx, prefix, storage.KeysOnly)
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
return list(it)
|
|
|
|
}
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func ListByNode(epoch int, cid []byte, key interop.PublicKey) [][]byte {
|
|
|
|
hdr := auditHeader{
|
|
|
|
epoch: epoch,
|
|
|
|
cid: cid,
|
|
|
|
from: key,
|
|
|
|
}
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2021-02-08 15:23:08 +00:00
|
|
|
it := storage.Find(ctx, hdr.ID(), storage.KeysOnly)
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
return list(it)
|
|
|
|
}
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func list(it iterator.Iterator) [][]byte {
|
|
|
|
var result [][]byte
|
2020-10-27 12:14:06 +00:00
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
for iterator.Next(it) {
|
2021-02-08 15:23:08 +00:00
|
|
|
key := iterator.Value(it).([]byte) // iterator MUST BE `storage.KeysOnly`
|
2020-12-17 15:58:12 +00:00
|
|
|
if len(key) == netmapContractKeyLn {
|
|
|
|
continue
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|
2020-12-17 15:58:12 +00:00
|
|
|
|
|
|
|
result = append(result, key)
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
return result
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func Version() int {
|
|
|
|
return version
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
// readNext reads length from first byte and then reads data (max 127 bytes).
|
|
|
|
func readNext(input []byte) ([]byte, int) {
|
|
|
|
var buf interface{} = input[0]
|
|
|
|
ln := buf.(int)
|
|
|
|
|
|
|
|
return input[1 : 1+ln], 1 + ln
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
func newAuditHeader(input []byte) auditHeader {
|
2020-12-21 14:17:17 +00:00
|
|
|
offset := int(input[1])
|
|
|
|
offset = 2 + offset + 1 // version prefix + version len + epoch prefix
|
|
|
|
|
|
|
|
var buf interface{} = input[offset : offset+8] // [ 8 integer bytes ]
|
2020-12-17 15:58:12 +00:00
|
|
|
epoch := buf.(int)
|
|
|
|
|
2020-12-21 14:17:17 +00:00
|
|
|
offset = offset + 8
|
|
|
|
|
2020-12-17 15:58:12 +00:00
|
|
|
// cid is a nested structure with raw bytes
|
|
|
|
// [ cid struct prefix (wireType + len = 2 bytes), cid value wireType (1 byte), ... ]
|
2020-12-21 14:17:17 +00:00
|
|
|
cid, cidOffset := readNext(input[offset+2+1:])
|
2020-12-17 15:58:12 +00:00
|
|
|
|
|
|
|
// key is a raw byte
|
|
|
|
// [ public key wireType (1 byte), ... ]
|
2020-12-21 14:17:17 +00:00
|
|
|
key, _ := readNext(input[offset+2+1+cidOffset+1:])
|
2020-12-17 15:58:12 +00:00
|
|
|
|
|
|
|
return auditHeader{
|
|
|
|
epoch,
|
|
|
|
cid,
|
|
|
|
key,
|
|
|
|
}
|
2020-10-27 12:14:06 +00:00
|
|
|
}
|