325 lines
9.4 KiB
Go
325 lines
9.4 KiB
Go
|
// Copyright 2017-2018 DERO Project. All rights reserved.
|
||
|
// Use of this source code in any form is governed by RESEARCH license.
|
||
|
// license can be found in the LICENSE file.
|
||
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
||
|
//
|
||
|
//
|
||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
package dvm
|
||
|
|
||
|
import "fmt"
|
||
|
import "encoding/binary"
|
||
|
import "github.com/deroproject/derohe/cryptography/crypto"
|
||
|
|
||
|
const DVAL = "DERO_BALANCE" // DERO Values are stored in this variable
|
||
|
const CHANGELOG = "CHANGELOG"
|
||
|
|
||
|
// this package exports an interface which is used by blockchain to persist/query data
|
||
|
|
||
|
type DataKey struct {
|
||
|
SCID crypto.Hash // tx which created the the contract or contract ID
|
||
|
Key Variable
|
||
|
}
|
||
|
|
||
|
type DataAtom struct {
|
||
|
Key DataKey
|
||
|
|
||
|
Prev_Value Variable // previous Value if any
|
||
|
Value Variable // current value if any
|
||
|
}
|
||
|
|
||
|
type TransferInternal struct {
|
||
|
Received []uint64
|
||
|
Sent []uint64
|
||
|
}
|
||
|
|
||
|
// any external tranfers
|
||
|
type TransferExternal struct {
|
||
|
Address string `cbor:"A,omitempty" json:"A,omitempty"` // transfer to this blob
|
||
|
Amount uint64 `cbor:"V,omitempty" json:"V,omitempty"` // Amount in Atomic units
|
||
|
}
|
||
|
|
||
|
type SC_Transfers struct {
|
||
|
BalanceAtStart uint64 // value at start
|
||
|
TransferI TransferInternal // all internal transfers, SC to other SC
|
||
|
TransferE []TransferExternal // all external transfers, SC to external wallets
|
||
|
}
|
||
|
|
||
|
// all SC load and store operations will go though this
|
||
|
type TX_Storage struct {
|
||
|
DiskLoader func(DataKey, *uint64) Variable
|
||
|
DiskLoaderRaw func([]byte) ([]byte, bool)
|
||
|
SCID crypto.Hash
|
||
|
BalanceAtStart uint64 // at runtime this will be fed balance
|
||
|
Keys map[DataKey]Variable // this keeps the in-transit DB updates, just in case we have to discard instantly
|
||
|
RawKeys map[string][]byte
|
||
|
|
||
|
Transfers map[crypto.Hash]SC_Transfers // all transfers ( internal/external )
|
||
|
}
|
||
|
|
||
|
var DVM_STORAGE_BACKEND DVM_Storage_Loader // this variable can be hijacked at runtime to offer different stores such as RAM/file/DB etc
|
||
|
|
||
|
type DVM_Storage_Loader interface {
|
||
|
Load(DataKey, *uint64) Variable
|
||
|
Store(DataKey, Variable)
|
||
|
RawLoad([]byte) ([]byte, bool)
|
||
|
RawStore([]byte, []byte)
|
||
|
}
|
||
|
|
||
|
// initialize tx store
|
||
|
func Initialize_TX_store() (tx_store *TX_Storage) {
|
||
|
tx_store = &TX_Storage{Keys: map[DataKey]Variable{}, RawKeys: map[string][]byte{}, Transfers: map[crypto.Hash]SC_Transfers{}}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (tx_store *TX_Storage) RawLoad(key []byte) (value []byte, found bool) {
|
||
|
value, found = tx_store.RawKeys[string(key)]
|
||
|
|
||
|
if !found {
|
||
|
if tx_store.DiskLoaderRaw == nil {
|
||
|
return
|
||
|
}
|
||
|
value, found = tx_store.DiskLoaderRaw(key)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (tx_store *TX_Storage) RawStore(key []byte, value []byte) {
|
||
|
tx_store.RawKeys[string(key)] = value
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// this will load the variable, and if the key is found
|
||
|
func (tx_store *TX_Storage) Load(dkey DataKey, found_value *uint64) (value Variable) {
|
||
|
|
||
|
//fmt.Printf("Loading %+v \n", dkey)
|
||
|
|
||
|
*found_value = 0
|
||
|
if result, ok := tx_store.Keys[dkey]; ok { // if it was modified in current TX, use it
|
||
|
*found_value = 1
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
if tx_store.DiskLoader == nil {
|
||
|
panic("DVM_STORAGE_BACKEND is not ready")
|
||
|
}
|
||
|
|
||
|
value = tx_store.DiskLoader(dkey, found_value)
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// store variable
|
||
|
func (tx_store *TX_Storage) Store(dkey DataKey, v Variable) {
|
||
|
//fmt.Printf("Storing request %+v : %+v\n", dkey, v)
|
||
|
|
||
|
tx_store.Keys[dkey] = v
|
||
|
}
|
||
|
|
||
|
// store variable
|
||
|
func (tx_store *TX_Storage) SendExternal(sender_scid crypto.Hash, addr_str string, amount uint64) {
|
||
|
|
||
|
//fmt.Printf("Transfer to external address : %+v\n", addr_str)
|
||
|
|
||
|
tx_store.Balance(sender_scid) // load from disk if required
|
||
|
transfer := tx_store.Transfers[sender_scid]
|
||
|
transfer.TransferE = append(transfer.TransferE, TransferExternal{Address: addr_str, Amount: amount})
|
||
|
tx_store.Transfers[sender_scid] = transfer
|
||
|
tx_store.Balance(sender_scid) // recalculate balance panic if any issues
|
||
|
|
||
|
}
|
||
|
|
||
|
// if TXID is not already loaded, load it
|
||
|
func (tx_store *TX_Storage) ReceiveInternal(scid crypto.Hash, amount uint64) {
|
||
|
|
||
|
tx_store.Balance(scid) // load from disk if required
|
||
|
transfer := tx_store.Transfers[scid]
|
||
|
transfer.TransferI.Received = append(transfer.TransferI.Received, amount)
|
||
|
tx_store.Transfers[scid] = transfer
|
||
|
tx_store.Balance(scid) // recalculate balance panic if any issues
|
||
|
}
|
||
|
|
||
|
func (tx_store *TX_Storage) SendInternal(sender_scid crypto.Hash, receiver_scid crypto.Hash, amount uint64) {
|
||
|
|
||
|
//sender side
|
||
|
{
|
||
|
tx_store.Balance(sender_scid) // load from disk if required
|
||
|
transfer := tx_store.Transfers[sender_scid]
|
||
|
transfer.TransferI.Sent = append(transfer.TransferI.Sent, amount)
|
||
|
tx_store.Transfers[sender_scid] = transfer
|
||
|
tx_store.Balance(sender_scid) // recalculate balance panic if any issues
|
||
|
}
|
||
|
|
||
|
{
|
||
|
tx_store.Balance(receiver_scid) // load from disk if required
|
||
|
transfer := tx_store.Transfers[receiver_scid]
|
||
|
transfer.TransferI.Received = append(transfer.TransferI.Received, amount)
|
||
|
tx_store.Transfers[receiver_scid] = transfer
|
||
|
tx_store.Balance(receiver_scid) // recalculate balance panic if any issues
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func GetBalanceKey(scid crypto.Hash) (x DataKey) {
|
||
|
x.SCID = scid
|
||
|
x.Key = Variable{Type: String, Value: DVAL}
|
||
|
return x
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
func GetNormalKey(scid crypto.Key, v Variable) (x DataKey) {
|
||
|
x.SCID = scid
|
||
|
x.Key = Variable {Type:v.Type, Value: v.Value}
|
||
|
return x
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
// this will give the balance, will load the balance from disk
|
||
|
func (tx_store *TX_Storage) Balance(scid crypto.Hash) uint64 {
|
||
|
|
||
|
if scid != tx_store.SCID {
|
||
|
fmt.Printf("scid %s SCID %s\n", scid, tx_store.SCID)
|
||
|
fmt.Printf("tx_store internal %+v\n", tx_store)
|
||
|
panic("cross SC balance calls are not supported")
|
||
|
}
|
||
|
if _, ok := tx_store.Transfers[scid]; !ok {
|
||
|
|
||
|
var transfer SC_Transfers
|
||
|
/*
|
||
|
found_value := uint64(0)
|
||
|
value := tx_store.Load(GetBalanceKey(scid), &found_value)
|
||
|
|
||
|
if found_value == 0 {
|
||
|
panic(fmt.Sprintf("SCID %s is not loaded", scid)) // we must load it from disk
|
||
|
}
|
||
|
|
||
|
if value.Type != Uint64 {
|
||
|
panic(fmt.Sprintf("SCID %s balance is not uint64, HOW ??", scid)) // we must load it from disk
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
transfer.BalanceAtStart = tx_store.BalanceAtStart
|
||
|
tx_store.Transfers[scid] = transfer
|
||
|
}
|
||
|
|
||
|
transfers := tx_store.Transfers[scid]
|
||
|
balance := transfers.BalanceAtStart
|
||
|
|
||
|
// replay all receives/sends
|
||
|
|
||
|
// handle all internal receives
|
||
|
for _, amt_received := range transfers.TransferI.Received {
|
||
|
c := balance + amt_received
|
||
|
|
||
|
if c >= balance {
|
||
|
balance = c
|
||
|
} else {
|
||
|
panic("uint64 overflow wraparound attack")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// handle all internal sends
|
||
|
for _, amt_sent := range transfers.TransferI.Sent {
|
||
|
if amt_sent >= balance {
|
||
|
panic("uint64 underflow wraparound attack")
|
||
|
}
|
||
|
balance = balance - amt_sent
|
||
|
}
|
||
|
|
||
|
// handle all external sends
|
||
|
for _, trans := range transfers.TransferE {
|
||
|
if trans.Amount >= balance {
|
||
|
panic("uint64 underflow wraparound attack")
|
||
|
}
|
||
|
balance = balance - trans.Amount
|
||
|
}
|
||
|
|
||
|
return balance
|
||
|
}
|
||
|
|
||
|
// whether the scid has enough balance
|
||
|
func (tx_store *TX_Storage) HasBalance(scid crypto.Key, amount uint64) {
|
||
|
|
||
|
}
|
||
|
|
||
|
// why should we not hash the return value to return a hash value
|
||
|
// using entire key could be useful, if DB can somehow link between them in the form of buckets and all
|
||
|
func (dkey DataKey) MarshalBinary() (ser []byte, err error) {
|
||
|
ser, err = dkey.Key.MarshalBinary()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (dkey DataKey) MarshalBinaryPanic() (ser []byte) {
|
||
|
var err error
|
||
|
if ser, err = dkey.Key.MarshalBinary(); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// these are used by lowest layers
|
||
|
func (v Variable) MarshalBinary() (data []byte, err error) {
|
||
|
data = append(data, byte(v.Type)) // add object type
|
||
|
switch v.Type {
|
||
|
case Invalid:
|
||
|
return
|
||
|
case Uint64:
|
||
|
var buf [binary.MaxVarintLen64]byte
|
||
|
done := binary.PutUvarint(buf[:], v.Value.(uint64)) // uint64 data type
|
||
|
data = append(data, buf[:done]...)
|
||
|
case Blob:
|
||
|
panic("not implemented")
|
||
|
case String:
|
||
|
data = append(data, ([]byte(v.Value.(string)))...) // string
|
||
|
default:
|
||
|
panic("unknown variable type not implemented")
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
func (v Variable) MarshalBinaryPanic() (ser []byte) {
|
||
|
var err error
|
||
|
if ser, err = v.MarshalBinary(); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (v *Variable) UnmarshalBinary(buf []byte) (err error) {
|
||
|
if len(buf) < 1 || Vtype(buf[0]) == Invalid {
|
||
|
return fmt.Errorf("invalid, probably corruption")
|
||
|
}
|
||
|
|
||
|
switch Vtype(buf[0]) {
|
||
|
case Invalid:
|
||
|
return fmt.Errorf("Invalid cannot be deserialized")
|
||
|
case Uint64:
|
||
|
v.Type = Uint64
|
||
|
var n int
|
||
|
v.Value, n = binary.Uvarint(buf[1:]) // uint64 data type
|
||
|
if n <= 0 {
|
||
|
panic("corruption in DB")
|
||
|
return fmt.Errorf("corruption in DB")
|
||
|
}
|
||
|
case String:
|
||
|
v.Type = String
|
||
|
v.Value = string(buf[1:])
|
||
|
return nil
|
||
|
case Blob:
|
||
|
panic("blob not implemented") // an encrypted blob, used to add data to blockchain without knowing address
|
||
|
|
||
|
default:
|
||
|
panic("unknown variable type not implemented")
|
||
|
|
||
|
}
|
||
|
return
|
||
|
}
|