383 lines
14 KiB
Go
383 lines
14 KiB
Go
// Copyright 2017-2021 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
|
|
|
|
// this file implements necessary structure to SC handling
|
|
|
|
import "fmt"
|
|
|
|
//import "bytes"
|
|
//import "runtime/debug"
|
|
import "encoding/binary"
|
|
import "time"
|
|
import "math/big"
|
|
import "math/rand"
|
|
import "github.com/deroproject/derohe/cryptography/crypto"
|
|
import "github.com/deroproject/derohe/cryptography/bn256"
|
|
|
|
import "golang.org/x/xerrors"
|
|
import "github.com/deroproject/graviton"
|
|
import "github.com/deroproject/derohe/rpc"
|
|
import "github.com/deroproject/derohe/config"
|
|
|
|
//import "github.com/deroproject/derohe/transaction"
|
|
|
|
type Simulator struct {
|
|
ss *graviton.Snapshot
|
|
balance_tree *graviton.Tree
|
|
sc_tree *graviton.Tree
|
|
cache map[crypto.Hash]*graviton.Tree
|
|
height uint64
|
|
Balances map[string]map[string]uint64
|
|
}
|
|
|
|
func SimulatorInitialize(ss *graviton.Snapshot) *Simulator {
|
|
|
|
var s Simulator
|
|
var err error
|
|
|
|
if ss == nil {
|
|
store, err := graviton.NewMemStore()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ss, err = store.LoadSnapshot(0)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
s.ss = ss
|
|
}
|
|
s.ss = ss
|
|
|
|
s.balance_tree, err = ss.GetTree(config.BALANCE_TREE)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
s.sc_tree, err = ss.GetTree(config.SC_META)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
s.cache = map[crypto.Hash]*graviton.Tree{}
|
|
s.Balances = map[string]map[string]uint64{}
|
|
|
|
//w_balance_tree := &dvm.Tree_Wrapper{Tree: balance_tree, Entries: map[string][]byte{}}
|
|
//w_sc_tree := &dvm.Tree_Wrapper{Tree: sc_tree, Entries: map[string][]byte{}}
|
|
|
|
return &s
|
|
}
|
|
|
|
// this is for testing some edge case in simulator
|
|
func (s *Simulator) AccountAddBalance(addr rpc.Address, scid crypto.Hash, balance_value uint64) {
|
|
balance := crypto.ConstructElGamal((*bn256.G1)(addr.PublicKey), crypto.ElGamal_BASE_G) // init zero balance
|
|
nb := crypto.NonceBalance{NonceHeight: 0, Balance: balance}
|
|
s.balance_tree.Put(addr.Compressed(), nb.Serialize())
|
|
}
|
|
|
|
func (s *Simulator) SCInstall(sc_code string, incoming_values map[crypto.Hash]uint64, SCDATA rpc.Arguments, signer_addr *rpc.Address, fees uint64) (scid crypto.Hash, gascompute, gasstorage uint64, err error) {
|
|
var blid crypto.Hash
|
|
rand.Seed(time.Now().Unix())
|
|
rand.Read(scid[:])
|
|
rand.Read(blid[:])
|
|
|
|
var sc SmartContract
|
|
if sc, _, err = ParseSmartContract(sc_code); err != nil {
|
|
//logger.V(2).Error(err, "error Parsing sc", "txid", txhash, "pos", pos)
|
|
return
|
|
}
|
|
|
|
var meta SC_META_DATA
|
|
if _, ok := sc.Functions["InitializePrivate"]; ok {
|
|
meta.Type = 1
|
|
}
|
|
|
|
w_sc_data_tree := Wrapped_tree(s.cache, s.ss, scid)
|
|
w_sc_data_tree.Put(SC_Code_Key(scid), Variable{Type: String, ValueString: sc_code}.MarshalBinaryPanic())
|
|
w_sc_tree := &Tree_Wrapper{Tree: s.sc_tree, Entries: map[string][]byte{}}
|
|
w_sc_tree.Put(SC_Meta_Key(scid), meta.MarshalBinary())
|
|
|
|
entrypoint := "Initialize"
|
|
if meta.Type == 1 { // if its a a private SC
|
|
entrypoint = "InitializePrivate"
|
|
}
|
|
|
|
gascompute, gasstorage, err = s.common(w_sc_tree, w_sc_data_tree, scid, s.height, s.height, uint64(time.Now().UnixMilli()), blid, scid, sc, entrypoint, 1, 0, signer_addr, incoming_values, SCDATA, fees, true)
|
|
return
|
|
}
|
|
|
|
func (s *Simulator) RunSC(incoming_values map[crypto.Hash]uint64, SCDATA rpc.Arguments, signer_addr *rpc.Address, fees uint64) (gascompute, gasstorage uint64, err error) {
|
|
var blid, txid crypto.Hash
|
|
rand.Read(blid[:])
|
|
rand.Read(txid[:])
|
|
|
|
action_code := rpc.SC_ACTION(SCDATA.Value(rpc.SCACTION, rpc.DataUint64).(uint64))
|
|
|
|
switch action_code {
|
|
case rpc.SC_INSTALL: // request to install an SC
|
|
err = fmt.Errorf("cannot install code using this api")
|
|
return
|
|
|
|
case rpc.SC_CALL: // trigger a CALL
|
|
if !SCDATA.Has(rpc.SCID, rpc.DataHash) { // but only if it is present
|
|
err = fmt.Errorf("no scid provided")
|
|
return
|
|
}
|
|
if !SCDATA.Has("entrypoint", rpc.DataString) { // but only if it is present
|
|
err = fmt.Errorf("no entrypoint provided")
|
|
return
|
|
}
|
|
|
|
scid := SCDATA.Value(rpc.SCID, rpc.DataHash).(crypto.Hash)
|
|
|
|
w_sc_tree := &Tree_Wrapper{Tree: s.sc_tree, Entries: map[string][]byte{}}
|
|
if _, err = w_sc_tree.Get(SC_Meta_Key(scid)); err != nil {
|
|
err = fmt.Errorf("scid %s not installed", scid)
|
|
return
|
|
}
|
|
|
|
w_sc_data_tree := Wrapped_tree(s.cache, s.ss, scid)
|
|
entrypoint := SCDATA.Value("entrypoint", rpc.DataString).(string)
|
|
balance, sc, _ := ReadSC(w_sc_tree, w_sc_data_tree, scid)
|
|
|
|
gascompute, gasstorage, err = s.common(w_sc_tree, w_sc_data_tree, scid, s.height, s.height, uint64(time.Now().UnixMilli()), blid, scid, sc, entrypoint, 1, balance, signer_addr, incoming_values, SCDATA, fees, true)
|
|
return
|
|
default:
|
|
err = fmt.Errorf("unknown action_code code %d", action_code)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *Simulator) common(w_sc_tree, w_sc_data_tree *Tree_Wrapper, scid crypto.Hash, bl_height, bl_topoheight, bl_timestamp uint64, blid crypto.Hash, txid crypto.Hash, sc SmartContract, entrypoint string, hard_fork_version_current int64, balance_at_start uint64, signer_addr *rpc.Address, incoming_values map[crypto.Hash]uint64, SCDATA rpc.Arguments, fees uint64, simulator bool) (gascompute, gasstorage uint64, err error) {
|
|
|
|
var signer [33]byte
|
|
if signer_addr != nil {
|
|
copy(signer[:], signer_addr.Compressed())
|
|
}
|
|
|
|
gascompute, gasstorage, err = Execute_sc_function(w_sc_tree, w_sc_data_tree, scid, bl_height, bl_topoheight, uint64(time.Now().UnixMilli()), blid, scid, sc, entrypoint, 1, 0, signer, incoming_values, SCDATA, fees, simulator)
|
|
fmt.Printf("sc execution error %s\n", err)
|
|
|
|
// we must commit all the changes
|
|
// check whether we are not overflowing/underflowing, means SC is not over sending
|
|
if err == nil {
|
|
err = SanityCheckExternalTransfers(w_sc_data_tree, s.balance_tree, scid)
|
|
}
|
|
|
|
if err != nil { // error occured, give everything to SC, since we may not have information to send them back
|
|
var zeroaddress [33]byte
|
|
if signer != zeroaddress { // if we can identify sender, return funds to him
|
|
ErrorRevert(s.ss, s.cache, s.balance_tree, signer, scid, incoming_values)
|
|
} else { // we could not extract signer, give burned funds to SC
|
|
ErrorRevert(s.ss, s.cache, s.balance_tree, signer, scid, incoming_values)
|
|
}
|
|
|
|
return
|
|
}
|
|
ProcessExternal(s.ss, s.cache, s.balance_tree, signer, scid, w_sc_data_tree, w_sc_tree)
|
|
return
|
|
|
|
}
|
|
|
|
// this is core function used to evaluate when we are overflowing/underflowing
|
|
func SanityCheckExternalTransfers(w_sc_data_tree *Tree_Wrapper, balance_tree *graviton.Tree, scid crypto.Hash) (err error) {
|
|
total_per_asset := map[crypto.Hash]uint64{}
|
|
for _, transfer := range w_sc_data_tree.Transfere { // do external tranfer
|
|
if transfer.Amount == 0 {
|
|
continue
|
|
}
|
|
|
|
// an SCID can generate it's token infinitely
|
|
if transfer.Asset != scid && total_per_asset[transfer.Asset]+transfer.Amount <= total_per_asset[transfer.Asset] {
|
|
err = fmt.Errorf("Balance calculation overflow")
|
|
return
|
|
} else {
|
|
total_per_asset[transfer.Asset] = total_per_asset[transfer.Asset] + transfer.Amount
|
|
}
|
|
}
|
|
|
|
if err == nil {
|
|
for asset, value := range total_per_asset {
|
|
stored_value, _ := LoadSCAssetValue(w_sc_data_tree, scid, asset)
|
|
// an SCID can generate it's token infinitely
|
|
if asset != scid && stored_value-value > stored_value {
|
|
err = fmt.Errorf("Balance calculation underflow stored_value %d transferring %d\n", stored_value, value)
|
|
return
|
|
}
|
|
|
|
var new_value [8]byte
|
|
binary.BigEndian.PutUint64(new_value[:], stored_value-value)
|
|
StoreSCValue(w_sc_data_tree, scid, asset[:], new_value[:])
|
|
}
|
|
}
|
|
|
|
//also check whether all destinations are registered
|
|
if err == nil {
|
|
for _, transfer := range w_sc_data_tree.Transfere {
|
|
if _, err = balance_tree.Get([]byte(transfer.Address)); err == nil || xerrors.Is(err, graviton.ErrNotFound) {
|
|
// everything is okay
|
|
} else {
|
|
err = fmt.Errorf("account is unregistered")
|
|
//logger.V(1).Error(err, "account is unregistered", "txhash", txhash, "scid", scid, "address", transfer.Address)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// any error during this will panic
|
|
func ErrorRevert(ss *graviton.Snapshot, cache map[crypto.Hash]*graviton.Tree, balance_tree *graviton.Tree, signer [33]byte, scid crypto.Hash, incoming_values map[crypto.Hash]uint64) {
|
|
var err error
|
|
|
|
for scid_asset, burnvalue := range incoming_values {
|
|
var zeroscid crypto.Hash
|
|
|
|
var curbtree *graviton.Tree
|
|
switch scid_asset {
|
|
case zeroscid: // main dero balance, handle it
|
|
curbtree = balance_tree
|
|
case scid: // this scid balance, handle it
|
|
curbtree = cache[scid]
|
|
default: // any other asset scid
|
|
var ok bool
|
|
if curbtree, ok = cache[scid_asset]; !ok {
|
|
if curbtree, err = ss.GetTree(string(scid_asset[:])); err != nil {
|
|
panic(err)
|
|
}
|
|
cache[scid_asset] = curbtree
|
|
}
|
|
}
|
|
|
|
if curbtree == nil {
|
|
panic("tree cannot be nil at this point in time")
|
|
}
|
|
|
|
if balance_serialized, err1 := curbtree.Get(signer[:]); err1 != nil { // no error can occur
|
|
panic(err1) // only disk corruption can reach here
|
|
} else {
|
|
nb := new(crypto.NonceBalance).Deserialize(balance_serialized)
|
|
nb.Balance = nb.Balance.Plus(new(big.Int).SetUint64(burnvalue)) // add back burn value to users balance homomorphically
|
|
curbtree.Put(signer[:], nb.Serialize()) // reserialize and store
|
|
}
|
|
}
|
|
}
|
|
|
|
func ErrorDeposit(ss *graviton.Snapshot, cache map[crypto.Hash]*graviton.Tree, balance_tree *graviton.Tree, signer [33]byte, scid crypto.Hash, incoming_values map[crypto.Hash]uint64) {
|
|
|
|
for scid_asset, burnvalue := range incoming_values {
|
|
var new_value [8]byte
|
|
|
|
w_sc_data_tree := Wrapped_tree(cache, ss, scid) // get a new tree, discarding everything
|
|
|
|
stored_value, _ := LoadSCAssetValue(w_sc_data_tree, scid, scid_asset)
|
|
binary.BigEndian.PutUint64(new_value[:], stored_value+burnvalue)
|
|
StoreSCValue(w_sc_data_tree, scid, scid_asset[:], new_value[:])
|
|
|
|
for k, v := range w_sc_data_tree.Entries { // commit incoming balances to tree
|
|
if err := w_sc_data_tree.Tree.Put([]byte(k), v); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
cache[scid] = w_sc_data_tree.Tree
|
|
}
|
|
}
|
|
|
|
func ProcessExternal(ss *graviton.Snapshot, cache map[crypto.Hash]*graviton.Tree, balance_tree *graviton.Tree, signer [33]byte, scid crypto.Hash, w_sc_data_tree, w_sc_tree *Tree_Wrapper) {
|
|
var err error
|
|
|
|
// anything below should never give error
|
|
cache[scid] = w_sc_data_tree.Tree
|
|
|
|
for k, v := range w_sc_data_tree.Entries { // commit entire data to tree
|
|
//if _, ok := globals.Arguments["--debug"]; ok && globals.Arguments["--debug"] != nil && chain.simulator {
|
|
// logger.V(1).Info("Writing", "txid", txhash, "scid", scid, "key", fmt.Sprintf("%x", k), "value", fmt.Sprintf("%x", v))
|
|
// }
|
|
if len(v) == 0 {
|
|
if err = w_sc_data_tree.Tree.Delete([]byte(k)); err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
if err = w_sc_data_tree.Tree.Put([]byte(k), v); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
for k, v := range w_sc_tree.Entries {
|
|
if err = w_sc_tree.Tree.Put([]byte(k), v); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
for i, transfer := range w_sc_data_tree.Transfere { // do external tranfer
|
|
if transfer.Amount == 0 {
|
|
continue
|
|
}
|
|
//fmt.Printf("%d sending to external %s %x\n", i,transfer.Asset,transfer.Address)
|
|
var zeroscid crypto.Hash
|
|
|
|
var curbtree *graviton.Tree
|
|
switch transfer.Asset {
|
|
case zeroscid: // main dero balance, handle it
|
|
curbtree = balance_tree
|
|
case scid: // this scid balance, handle it
|
|
curbtree = cache[scid]
|
|
default: // any other asset scid
|
|
var ok bool
|
|
if curbtree, ok = cache[transfer.Asset]; !ok {
|
|
if curbtree, err = ss.GetTree(string(transfer.Asset[:])); err != nil {
|
|
panic(err)
|
|
}
|
|
cache[transfer.Asset] = curbtree
|
|
}
|
|
}
|
|
|
|
if curbtree == nil {
|
|
panic("tree cannot be nil at this point in time")
|
|
}
|
|
|
|
addr_bytes := []byte(transfer.Address)
|
|
if _, err = balance_tree.Get(addr_bytes); err != nil { // first check whether address is registered
|
|
err = fmt.Errorf("sending to non registered account acc %x err %s", addr_bytes, err) // this can only occur, if account no registered or dis corruption
|
|
panic(err)
|
|
}
|
|
|
|
var balance_serialized []byte
|
|
balance_serialized, err = curbtree.Get(addr_bytes)
|
|
if err != nil && xerrors.Is(err, graviton.ErrNotFound) { // if the address is not found, lookup in main tree
|
|
var p bn256.G1
|
|
if err = p.DecodeCompressed(addr_bytes[:]); err != nil {
|
|
panic(fmt.Errorf("key %x could not be decompressed", addr_bytes))
|
|
}
|
|
|
|
balance := crypto.ConstructElGamal(&p, crypto.ElGamal_BASE_G) // init zero balance
|
|
nb := crypto.NonceBalance{NonceHeight: 0, Balance: balance}
|
|
balance_serialized = nb.Serialize()
|
|
} else if err != nil {
|
|
fmt.Printf("%s %d could not transfer %d %+v\n", scid, i, transfer.Amount, addr_bytes)
|
|
panic(err) // only disk corruption can reach here
|
|
}
|
|
|
|
nb := new(crypto.NonceBalance).Deserialize(balance_serialized)
|
|
nb.Balance = nb.Balance.Plus(new(big.Int).SetUint64(transfer.Amount)) // add transfer to users balance homomorphically
|
|
curbtree.Put(addr_bytes, nb.Serialize()) // reserialize and store
|
|
}
|
|
|
|
}
|