frostfs-contract/audit/audit_contract.go
Leonard Lyubich 8ceba2a7c2 [#42] Share InnerRingList function between contracts
Define InnerRingList function that calls "innerRingList" method of
particular smart contract. Define InnerRingListViaStorage function
that reads address of smart contract from the storage by key, and
calls InnerRingList with the result. Reuse these functions in all
contracts.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
2021-02-04 10:31:24 +03:00

179 lines
3.7 KiB
Go

package auditcontract
import (
"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"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
type (
auditHeader struct {
epoch int
cid []byte
from interop.PublicKey
}
)
// 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.
const maxKeySize = 24 // 24 + 32 (container ID length) + 8 (epoch length) = 64
func (a auditHeader) ID() []byte {
var buf interface{} = a.epoch
hashedKey := crypto.SHA256(a.from)
shortedKey := hashedKey[:maxKeySize]
return append(buf.([]byte), append(a.cid, shortedKey...)...)
}
const (
version = 1
netmapContractKey = "netmapScriptHash"
netmapContractKeyLn = len(netmapContractKey)
)
var ctx storage.Context
func init() {
if runtime.GetTrigger() != runtime.Application {
panic("contract has not been called in application node")
}
ctx = storage.GetContext()
}
func Init(addrNetmap interop.Hash160) {
if storage.Get(ctx, netmapContractKey) != nil {
panic("init: contract already deployed")
}
if len(addrNetmap) != 20 {
panic("init: incorrect length of contract script hash")
}
storage.Put(ctx, netmapContractKey, addrNetmap)
runtime.Log("audit contract initialized")
}
func Put(rawAuditResult []byte) bool {
innerRing := common.InnerRingListViaStorage(ctx, netmapContractKey)
hdr := newAuditHeader(rawAuditResult)
presented := false
for i := range innerRing {
ir := innerRing[i]
if common.BytesEqual(ir.PublicKey, hdr.from) {
presented = true
break
}
}
if !runtime.CheckWitness(hdr.from) || !presented {
panic("audit: put access denied")
}
storage.Put(ctx, hdr.ID(), rawAuditResult)
runtime.Log("audit: result has been saved")
return true
}
func Get(id []byte) []byte {
return storage.Get(ctx, id).([]byte)
}
func List() [][]byte {
it := storage.Find(ctx, []byte{})
return list(it)
}
func ListByEpoch(epoch int) [][]byte {
it := storage.Find(ctx, epoch)
return list(it)
}
func ListByCID(epoch int, cid []byte) [][]byte {
var buf interface{} = epoch
prefix := append(buf.([]byte), cid...)
it := storage.Find(ctx, prefix)
return list(it)
}
func ListByNode(epoch int, cid []byte, key interop.PublicKey) [][]byte {
hdr := auditHeader{
epoch: epoch,
cid: cid,
from: key,
}
it := storage.Find(ctx, hdr.ID())
return list(it)
}
func list(it iterator.Iterator) [][]byte {
var result [][]byte
for iterator.Next(it) {
key := iterator.Key(it).([]byte)
if len(key) == netmapContractKeyLn {
continue
}
result = append(result, key)
}
return result
}
func Version() int {
return version
}
// 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
}
func newAuditHeader(input []byte) auditHeader {
offset := int(input[1])
offset = 2 + offset + 1 // version prefix + version len + epoch prefix
var buf interface{} = input[offset : offset+8] // [ 8 integer bytes ]
epoch := buf.(int)
offset = offset + 8
// cid is a nested structure with raw bytes
// [ cid struct prefix (wireType + len = 2 bytes), cid value wireType (1 byte), ... ]
cid, cidOffset := readNext(input[offset+2+1:])
// key is a raw byte
// [ public key wireType (1 byte), ... ]
key, _ := readNext(input[offset+2+1+cidOffset+1:])
return auditHeader{
epoch,
cid,
key,
}
}