2020-10-27 12:14:06 +00:00
|
|
|
package auditcontract
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/binary"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
irNode struct {
|
|
|
|
key []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
CheckedNode struct {
|
|
|
|
Key []byte // 33 bytes
|
|
|
|
Pair int // 2 bytes
|
|
|
|
Reward int // ? up to 32 byte
|
|
|
|
}
|
|
|
|
|
|
|
|
AuditResult struct {
|
|
|
|
InnerRingNode []byte // 33 bytes
|
|
|
|
Epoch int // 8 bytes
|
|
|
|
ContainerID []byte // 32 bytes
|
|
|
|
StorageGroupID []byte // 16 bytes
|
|
|
|
PoR bool // 1 byte
|
|
|
|
PDP bool // 1 byte
|
|
|
|
// --- 91 bytes -- //
|
|
|
|
// --- 2 more bytes to size of the []CheckedNode //
|
|
|
|
Nodes []CheckedNode // <= 67 bytes per node
|
|
|
|
// about 1400 nodes may be presented in container
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
version = 1
|
|
|
|
|
|
|
|
// 1E-8 GAS in precision of balance container.
|
|
|
|
// This value may be calculated in runtime based on decimal value of
|
|
|
|
// balance contract. We can also provide methods to change fee
|
|
|
|
// in runtime.
|
|
|
|
auditFee = 1 * 100_000_000
|
|
|
|
|
|
|
|
ownerIDLength = 25
|
|
|
|
|
|
|
|
journalKey = "auditJournal"
|
|
|
|
balanceContractKey = "balanceScriptHash"
|
|
|
|
containerContractKey = "containerScriptHash"
|
|
|
|
netmapContractKey = "netmapScriptHash"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
auditFeeTransferMsg = []byte("audit execution fee")
|
|
|
|
auditRewardTransferMsg = []byte("data audit reward")
|
|
|
|
|
|
|
|
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, addrBalance, addrContainer []byte) {
|
|
|
|
if storage.Get(ctx, netmapContractKey) != nil &&
|
|
|
|
storage.Get(ctx, balanceContractKey) != nil &&
|
|
|
|
storage.Get(ctx, containerContractKey) != nil {
|
|
|
|
panic("init: contract already deployed")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(addrNetmap) != 20 || len(addrBalance) != 20 || len(addrContainer) != 20 {
|
|
|
|
panic("init: incorrect length of contract script hash")
|
|
|
|
}
|
|
|
|
|
|
|
|
storage.Put(ctx, netmapContractKey, addrNetmap)
|
|
|
|
storage.Put(ctx, balanceContractKey, addrBalance)
|
|
|
|
storage.Put(ctx, containerContractKey, addrContainer)
|
|
|
|
|
|
|
|
setSerialized(ctx, journalKey, []AuditResult{})
|
|
|
|
|
|
|
|
runtime.Log("audit contract initialized")
|
|
|
|
}
|
|
|
|
|
|
|
|
func Put(rawAuditResult []byte) bool {
|
|
|
|
netmapContractAddr := storage.Get(ctx, netmapContractKey).([]byte)
|
2020-12-10 14:45:45 +00:00
|
|
|
innerRing := contract.Call(netmapContractAddr, "innerRingList").([]irNode)
|
2020-10-27 12:14:06 +00:00
|
|
|
|
|
|
|
auditResult, err := newAuditResult(rawAuditResult)
|
|
|
|
if err {
|
|
|
|
panic("put: can't parse audit result")
|
|
|
|
}
|
|
|
|
|
|
|
|
var presented = false
|
|
|
|
|
|
|
|
for i := range innerRing {
|
|
|
|
ir := innerRing[i]
|
|
|
|
if bytesEqual(ir.key, auditResult.InnerRingNode) {
|
|
|
|
presented = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !runtime.CheckWitness(auditResult.InnerRingNode) || !presented {
|
|
|
|
panic("put: access denied")
|
|
|
|
}
|
|
|
|
|
|
|
|
// todo: limit size of the audit journal:
|
|
|
|
// history will be stored in chain (args or notifies)
|
|
|
|
// contract storage will be used as a cache if needed
|
|
|
|
journal := getAuditResult(ctx)
|
|
|
|
journal = append(journal, auditResult)
|
|
|
|
|
|
|
|
setSerialized(ctx, journalKey, journal)
|
|
|
|
|
|
|
|
if auditResult.PDP && auditResult.PoR {
|
|
|
|
// find who is the ownerID
|
|
|
|
containerContract := storage.Get(ctx, containerContractKey).([]byte)
|
|
|
|
|
|
|
|
// todo: implement easy way to get owner from the container id
|
2020-12-10 14:45:45 +00:00
|
|
|
ownerID := contract.Call(containerContract, "owner", auditResult.ContainerID).([]byte)
|
2020-10-27 12:14:06 +00:00
|
|
|
if len(ownerID) != ownerIDLength {
|
|
|
|
runtime.Log("put: can't get owner id of the container")
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
ownerScriptHash := walletToScripHash(ownerID)
|
|
|
|
|
|
|
|
// transfer fee to the inner ring node
|
|
|
|
balanceContract := storage.Get(ctx, balanceContractKey).([]byte)
|
|
|
|
irScriptHash := contract.CreateStandardAccount(auditResult.InnerRingNode)
|
|
|
|
|
2020-12-10 14:45:45 +00:00
|
|
|
tx := contract.Call(balanceContract, "transferX",
|
2020-10-27 12:14:06 +00:00
|
|
|
ownerScriptHash,
|
|
|
|
irScriptHash,
|
|
|
|
auditFee,
|
|
|
|
auditFeeTransferMsg, // todo: add epoch, container and storage group info
|
|
|
|
)
|
|
|
|
if !tx.(bool) {
|
|
|
|
panic("put: can't transfer inner ring fee")
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(auditResult.Nodes); i++ {
|
|
|
|
node := auditResult.Nodes[i]
|
|
|
|
nodeScriptHash := contract.CreateStandardAccount(node.Key)
|
|
|
|
|
2020-12-10 14:45:45 +00:00
|
|
|
tx := contract.Call(balanceContract, "transferX",
|
2020-10-27 12:14:06 +00:00
|
|
|
ownerScriptHash,
|
|
|
|
nodeScriptHash,
|
|
|
|
node.Reward,
|
|
|
|
auditRewardTransferMsg, // todo: add epoch, container and storage group info
|
|
|
|
)
|
|
|
|
if !tx.(bool) {
|
|
|
|
runtime.Log("put: can't transfer storage payment")
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func Version() int {
|
|
|
|
return version
|
|
|
|
}
|
|
|
|
|
|
|
|
func newAuditResult(data []byte) (AuditResult, bool) {
|
|
|
|
var (
|
|
|
|
tmp interface{}
|
|
|
|
ln = len(data)
|
|
|
|
result = AuditResult{
|
|
|
|
InnerRingNode: nil, // neo-go#949
|
|
|
|
ContainerID: nil,
|
|
|
|
StorageGroupID: nil,
|
|
|
|
Nodes: []CheckedNode{},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if len(data) < 91 { // all required headers
|
|
|
|
runtime.Log("newAuditResult: can't parse audit result header")
|
|
|
|
return result, true
|
|
|
|
}
|
|
|
|
|
|
|
|
result.InnerRingNode = data[0:33]
|
|
|
|
|
|
|
|
epoch := data[33:41]
|
|
|
|
tmp = epoch
|
|
|
|
result.Epoch = tmp.(int)
|
|
|
|
|
|
|
|
result.ContainerID = data[41:73]
|
|
|
|
result.StorageGroupID = data[73:89]
|
|
|
|
result.PoR = util.Equals(data[90], 0x01)
|
|
|
|
result.PDP = util.Equals(data[91], 0x01)
|
|
|
|
|
|
|
|
// if there are nodes, that were checked
|
|
|
|
if len(data) > 93 {
|
|
|
|
rawCounter := data[91:93]
|
|
|
|
tmp = rawCounter
|
|
|
|
counter := tmp.(int)
|
|
|
|
|
|
|
|
ptr := 93
|
|
|
|
|
|
|
|
for i := 0; i < counter; i++ {
|
|
|
|
if ptr+33+2+32 > ln {
|
|
|
|
runtime.Log("newAuditResult: broken node")
|
|
|
|
return result, false
|
|
|
|
}
|
|
|
|
|
|
|
|
node := CheckedNode{
|
|
|
|
Key: nil, // neo-go#949
|
|
|
|
}
|
|
|
|
node.Key = data[ptr : ptr+33]
|
|
|
|
|
|
|
|
pair := data[ptr+33 : ptr+35]
|
|
|
|
tmp = pair
|
|
|
|
node.Pair = tmp.(int)
|
|
|
|
|
|
|
|
reward := data[ptr+35 : ptr+67]
|
|
|
|
tmp = reward
|
|
|
|
node.Reward = tmp.(int)
|
|
|
|
|
|
|
|
result.Nodes = append(result.Nodes, node)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, false
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAuditResult(ctx storage.Context) []AuditResult {
|
|
|
|
data := storage.Get(ctx, journalKey)
|
|
|
|
if data != nil {
|
|
|
|
return binary.Deserialize(data.([]byte)).([]AuditResult)
|
|
|
|
}
|
|
|
|
|
|
|
|
return []AuditResult{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setSerialized(ctx storage.Context, key interface{}, value interface{}) {
|
|
|
|
data := binary.Serialize(value)
|
|
|
|
storage.Put(ctx, key, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func walletToScripHash(wallet []byte) []byte {
|
|
|
|
return wallet[1 : len(wallet)-4]
|
|
|
|
}
|
|
|
|
|
|
|
|
// neo-go#1176
|
|
|
|
func bytesEqual(a []byte, b []byte) bool {
|
|
|
|
return util.Equals(string(a), string(b))
|
|
|
|
}
|