mirror of
https://github.com/luxfi/oraclevm.git
synced 2026-10-30 15:23:15 +00:00
426 lines
9.0 KiB
Go
426 lines
9.0 KiB
Go
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package storage
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/ava-labs/avalanchego/database"
|
|
"github.com/ava-labs/avalanchego/ids"
|
|
smath "github.com/ava-labs/avalanchego/utils/math"
|
|
"github.com/ava-labs/hypersdk/chain"
|
|
"github.com/ava-labs/hypersdk/consts"
|
|
"github.com/ava-labs/hypersdk/crypto"
|
|
|
|
"github.com/bianyuanop/oraclevm/utils"
|
|
)
|
|
|
|
type ReadState func(context.Context, [][]byte) ([][]byte, []error)
|
|
|
|
// Metadata
|
|
// 0x0/ (tx)
|
|
// -> [txID] => timestamp
|
|
//
|
|
// State
|
|
// / (height) => store in root
|
|
// -> [heightPrefix] => height
|
|
// 0x0/ (balance)
|
|
// -> [owner] => balance
|
|
// 0x1/ (hypersdk-incoming warp)
|
|
// 0x2/ (hypersdk-outgoing warp)
|
|
|
|
const (
|
|
txPrefix = 0x0
|
|
|
|
balancePrefix = 0x0
|
|
incomingWarpPrefix = 0x1
|
|
outgoingWarpPrefix = 0x2
|
|
|
|
// store entity uploading history
|
|
entityPrefix = 0x3
|
|
// store entity aggregation result
|
|
entityAggregationResultPrefix = 0x4
|
|
entityAggregationCachePrefix = 0x5
|
|
)
|
|
|
|
var (
|
|
failureByte = byte(0x0)
|
|
successByte = byte(0x1)
|
|
heightKey = []byte{}
|
|
|
|
// TODO: extend to other types
|
|
balancePrefixPool = sync.Pool{
|
|
New: func() any {
|
|
return make([]byte, 1+crypto.PublicKeyLen)
|
|
},
|
|
}
|
|
)
|
|
|
|
// [txPrefix] + [txID]
|
|
func PrefixTxKey(id ids.ID) (k []byte) {
|
|
// TODO: use packer?
|
|
k = make([]byte, 1+consts.IDLen)
|
|
k[0] = txPrefix
|
|
copy(k[1:], id[:])
|
|
return
|
|
}
|
|
|
|
func StoreTransaction(
|
|
_ context.Context,
|
|
db database.KeyValueWriter,
|
|
id ids.ID,
|
|
t int64,
|
|
success bool,
|
|
units uint64,
|
|
) error {
|
|
k := PrefixTxKey(id)
|
|
v := make([]byte, consts.Uint64Len+1+consts.Uint64Len)
|
|
binary.BigEndian.PutUint64(v, uint64(t))
|
|
if success {
|
|
v[consts.Uint64Len] = successByte
|
|
} else {
|
|
v[consts.Uint64Len] = failureByte
|
|
}
|
|
binary.BigEndian.PutUint64(v[consts.Uint64Len+1:], units)
|
|
return db.Put(k, v)
|
|
}
|
|
|
|
func GetTransaction(
|
|
_ context.Context,
|
|
db database.KeyValueReader,
|
|
id ids.ID,
|
|
) (bool, int64, bool, uint64, error) {
|
|
k := PrefixTxKey(id)
|
|
v, err := db.Get(k)
|
|
if errors.Is(err, database.ErrNotFound) {
|
|
return false, 0, false, 0, nil
|
|
}
|
|
if err != nil {
|
|
return false, 0, false, 0, err
|
|
}
|
|
t := int64(binary.BigEndian.Uint64(v))
|
|
success := true
|
|
if v[consts.Uint64Len] == failureByte {
|
|
success = false
|
|
}
|
|
units := binary.BigEndian.Uint64(v[consts.Uint64Len+1:])
|
|
return true, t, success, units, nil
|
|
}
|
|
|
|
// [accountPrefix] + [address]
|
|
func PrefixBalanceKey(pk crypto.PublicKey) (k []byte) {
|
|
k = balancePrefixPool.Get().([]byte)
|
|
k[0] = balancePrefix
|
|
copy(k[1:], pk[:])
|
|
return
|
|
}
|
|
|
|
// If locked is 0, then account does not exist
|
|
func GetBalance(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
pk crypto.PublicKey,
|
|
) (uint64, error) {
|
|
dbKey, bal, err := getBalance(ctx, db, pk)
|
|
balancePrefixPool.Put(dbKey)
|
|
return bal, err
|
|
}
|
|
|
|
func getBalance(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
pk crypto.PublicKey,
|
|
) ([]byte, uint64, error) {
|
|
k := PrefixBalanceKey(pk)
|
|
bal, err := innerGetBalance(db.GetValue(ctx, k))
|
|
return k, bal, err
|
|
}
|
|
|
|
// Used to serve RPC queries
|
|
func GetBalanceFromState(
|
|
ctx context.Context,
|
|
f ReadState,
|
|
pk crypto.PublicKey,
|
|
) (uint64, error) {
|
|
k := PrefixBalanceKey(pk)
|
|
values, errs := f(ctx, [][]byte{k})
|
|
bal, err := innerGetBalance(values[0], errs[0])
|
|
balancePrefixPool.Put(k)
|
|
return bal, err
|
|
}
|
|
|
|
func innerGetBalance(
|
|
v []byte,
|
|
err error,
|
|
) (uint64, error) {
|
|
if errors.Is(err, database.ErrNotFound) {
|
|
return 0, nil
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return binary.BigEndian.Uint64(v), nil
|
|
}
|
|
|
|
func SetBalance(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
pk crypto.PublicKey,
|
|
balance uint64,
|
|
) error {
|
|
k := PrefixBalanceKey(pk)
|
|
return setBalance(ctx, db, k, balance)
|
|
}
|
|
|
|
func setBalance(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
dbKey []byte,
|
|
balance uint64,
|
|
) error {
|
|
return db.Insert(ctx, dbKey, binary.BigEndian.AppendUint64(nil, balance))
|
|
}
|
|
|
|
func AddBalance(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
pk crypto.PublicKey,
|
|
amount uint64,
|
|
) error {
|
|
dbKey, bal, err := getBalance(ctx, db, pk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nbal, err := smath.Add64(bal, amount)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"%w: could not add balance (bal=%d, addr=%v, amount=%d)",
|
|
ErrInvalidBalance,
|
|
bal,
|
|
utils.Address(pk),
|
|
amount,
|
|
)
|
|
}
|
|
return setBalance(ctx, db, dbKey, nbal)
|
|
}
|
|
|
|
func SubBalance(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
pk crypto.PublicKey,
|
|
amount uint64,
|
|
) error {
|
|
dbKey, bal, err := getBalance(ctx, db, pk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nbal, err := smath.Sub(bal, amount)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"%w: could not subtract balance (bal=%d, addr=%v, amount=%d)",
|
|
ErrInvalidBalance,
|
|
bal,
|
|
utils.Address(pk),
|
|
amount,
|
|
)
|
|
}
|
|
if nbal == 0 {
|
|
// If there is no balance left, we should delete the record instead of
|
|
// setting it to 0.
|
|
return db.Remove(ctx, dbKey)
|
|
}
|
|
return setBalance(ctx, db, dbKey, nbal)
|
|
}
|
|
|
|
func HeightKey() (k []byte) {
|
|
return heightKey
|
|
}
|
|
|
|
func IncomingWarpKeyPrefix(sourceChainID ids.ID, msgID ids.ID) (k []byte) {
|
|
k = make([]byte, 1+consts.IDLen*2)
|
|
k[0] = incomingWarpPrefix
|
|
copy(k[1:], sourceChainID[:])
|
|
copy(k[1+consts.IDLen:], msgID[:])
|
|
return k
|
|
}
|
|
|
|
func OutgoingWarpKeyPrefix(txID ids.ID) (k []byte) {
|
|
k = make([]byte, 1+consts.IDLen)
|
|
k[0] = outgoingWarpPrefix
|
|
copy(k[1:], txID[:])
|
|
return k
|
|
}
|
|
|
|
// [entityPrefix] + [txID] + [entityIndex]
|
|
func PrefixEntityKey(txID ids.ID) (k []byte) {
|
|
k = make([]byte, 1+consts.IDLen)
|
|
k[0] = entityPrefix
|
|
copy(k[1:], txID[:])
|
|
|
|
return
|
|
}
|
|
|
|
func PackEntity(entityIndex uint64, entityType uint64, tick int64, publisher crypto.PublicKey, payload []byte) (v []byte) {
|
|
v = make([]byte, consts.Uint64Len*3+crypto.PublicKeyLen+len(payload))
|
|
|
|
binary.BigEndian.PutUint64(v, entityIndex)
|
|
binary.BigEndian.PutUint64(v[consts.Uint64Len:], entityType)
|
|
binary.BigEndian.PutUint64(v[consts.Uint64Len*2:], uint64(tick))
|
|
copy(v[consts.Uint64Len*3:], publisher[:])
|
|
copy(v[consts.Uint64Len*3+crypto.PublicKeyLen:], payload)
|
|
|
|
return
|
|
}
|
|
|
|
func UnpackEntity(v []byte) (
|
|
entityIndex uint64,
|
|
entityType uint64,
|
|
tick int64,
|
|
publisher crypto.PublicKey,
|
|
payload []byte,
|
|
) {
|
|
entityIndex = binary.BigEndian.Uint64(v)
|
|
entityType = binary.BigEndian.Uint64(v[consts.Uint64Len:])
|
|
tick = int64(binary.BigEndian.Uint64(v[consts.Uint64Len*2:]))
|
|
publisher = crypto.PublicKey(v[consts.Uint64Len*3:])
|
|
|
|
payload = make([]byte, len(v)-consts.Uint64Len*3+crypto.PublicKeyLen)
|
|
|
|
copy(payload, v[consts.Uint64Len*3+crypto.PublicKeyLen:])
|
|
|
|
return
|
|
}
|
|
|
|
func StoreEntity(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
txID ids.ID,
|
|
entityType uint64,
|
|
entityIndex uint64,
|
|
tick int64,
|
|
publisher crypto.PublicKey,
|
|
payload []byte,
|
|
) error {
|
|
k := PrefixEntityKey(txID)
|
|
v := PackEntity(entityIndex, entityType, tick, publisher, payload)
|
|
|
|
return db.Insert(ctx, k, v)
|
|
}
|
|
|
|
func GetEntity(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
txID ids.ID,
|
|
) (
|
|
exists bool,
|
|
tick int64,
|
|
entityIndex uint64,
|
|
entityType uint64,
|
|
publisher crypto.PublicKey,
|
|
payload []byte,
|
|
e error,
|
|
) {
|
|
k := PrefixEntityKey(txID)
|
|
|
|
v, err := db.GetValue(ctx, k)
|
|
|
|
if errors.Is(err, database.ErrNotFound) {
|
|
return false, 0, 0, 0, crypto.EmptyPublicKey, make([]byte, 0), nil
|
|
}
|
|
|
|
if err != nil {
|
|
return false, 0, 0, 0, crypto.EmptyPublicKey, make([]byte, 0), err
|
|
}
|
|
|
|
entityIndex, entityType, tick, publisher, payload = UnpackEntity(v)
|
|
|
|
return
|
|
}
|
|
|
|
func PrefixAggregationResult(tick int64, entityIndex uint64) (k []byte) {
|
|
k = make([]byte, 1+consts.Uint64Len*2)
|
|
k[0] = entityAggregationResultPrefix
|
|
binary.BigEndian.PutUint64(k[1:], uint64(tick))
|
|
binary.BigEndian.PutUint64(k[1+consts.Uint64Len:], entityIndex)
|
|
|
|
return
|
|
}
|
|
|
|
func StoreAggregationResult(
|
|
ctx context.Context,
|
|
db database.KeyValueWriter,
|
|
entityType uint64,
|
|
entityIndex uint64,
|
|
tick int64,
|
|
payload []byte,
|
|
) error {
|
|
k := PrefixAggregationResult(tick, entityIndex)
|
|
|
|
v := PackEntity(entityIndex, entityType, tick, crypto.EmptyPublicKey, payload)
|
|
|
|
return db.Put(k, v)
|
|
}
|
|
|
|
func GetAggregationResult(
|
|
ctx context.Context,
|
|
db database.KeyValueReader,
|
|
entityIndex uint64,
|
|
tick int64, // same with block timestamp
|
|
) (entityType uint64, payload []byte, e error) {
|
|
k := PrefixAggregationResult(tick, entityIndex)
|
|
|
|
v, err := db.Get(k)
|
|
|
|
if err != nil {
|
|
return 0, make([]byte, 0), err
|
|
}
|
|
|
|
_, entityType, _, _, payload = UnpackEntity(v)
|
|
|
|
return
|
|
}
|
|
|
|
func PrefixAggregationCacheResult(entityIndex uint64) (k []byte) {
|
|
k = make([]byte, 1+consts.Uint64Len)
|
|
k[0] = entityAggregationCachePrefix
|
|
binary.BigEndian.PutUint64(k[1:], entityIndex)
|
|
|
|
return
|
|
}
|
|
|
|
func CacheAggregationResult(
|
|
ctx context.Context,
|
|
db database.KeyValueWriter,
|
|
entityType uint64,
|
|
entityIndex uint64,
|
|
tick int64,
|
|
payload []byte,
|
|
) error {
|
|
k := PrefixAggregationCacheResult(entityIndex)
|
|
v := PackEntity(entityIndex, entityType, tick, crypto.EmptyPublicKey, payload)
|
|
|
|
return db.Put(k, v)
|
|
}
|
|
|
|
func GetCachedAggregationResult(
|
|
ctx context.Context,
|
|
db chain.Database,
|
|
entityIndex uint64,
|
|
) (entityType uint64, payload []byte, e error) {
|
|
k := PrefixAggregationCacheResult(entityIndex)
|
|
|
|
v, err := db.GetValue(ctx, k)
|
|
if err != nil {
|
|
return 0, make([]byte, 0), err
|
|
}
|
|
|
|
_, entityType, _, _, payload = UnpackEntity(v)
|
|
|
|
return
|
|
}
|