Unverified Commit 876a76b2 authored by 33cn's avatar 33cn Committed by GitHub

Merge pull request #194 from vipwzw/js

support big number
parents 86fae6a8 3fb4cb6d
This diff is collapsed.
......@@ -8,18 +8,32 @@ import (
"github.com/33cn/chain33/types"
ptypes "github.com/33cn/plugin/plugin/dapp/js/types"
"github.com/33cn/plugin/plugin/dapp/js/types/jsproto"
lru "github.com/hashicorp/golang-lru"
"github.com/robertkrimen/otto"
)
var driverName = ptypes.JsX
var basevm *otto.Otto
var codecache *lru.Cache
func init() {
ety := types.LoadExecutorType(driverName)
ety.InitFuncList(types.ListMethod(&js{}))
basevm = otto.New()
_, err := basevm.Run(callcode)
if err != nil {
panic(err)
}
}
//Init 插件初始化
func Init(name string, sub []byte) {
//最新的64个code做cache
var err error
codecache, err = lru.New(512)
if err != nil {
panic(err)
}
drivers.Register(GetName(), newjs, 0)
}
......@@ -47,21 +61,24 @@ func (u *js) GetDriverName() string {
func (u *js) callVM(prefix string, payload *jsproto.Call, tx *types.Transaction,
index int, receiptData *types.ReceiptData) (*otto.Object, error) {
vm, err := u.createVM(payload.Name, tx, index)
if payload.Args != "" {
newjson, err := rewriteJSON([]byte(payload.Args))
if err != nil {
return nil, err
}
db := u.GetStateDB()
code, err := db.Get(calcCodeKey(payload.Name))
payload.Args = string(newjson)
} else {
payload.Args = "{}"
}
loglist, err := jslogs(receiptData)
if err != nil {
return nil, err
}
loglist, err := jslogs(receiptData)
vm, err := u.createVM(payload.Name, tx, index)
if err != nil {
return nil, err
}
vm.Set("loglist", loglist)
vm.Set("code", code)
if prefix == "init" {
vm.Set("f", "init")
} else {
......@@ -69,7 +86,7 @@ func (u *js) callVM(prefix string, payload *jsproto.Call, tx *types.Transaction,
}
vm.Set("args", payload.Args)
callfunc := "callcode(context, f, args, loglist)"
jsvalue, err := vm.Run(callcode + string(code) + "\n" + callfunc)
jsvalue, err := vm.Run(callfunc)
if err != nil {
return nil, err
}
......@@ -186,7 +203,20 @@ func (u *js) createVM(name string, tx *types.Transaction, index int) (*otto.Otto
if err != nil {
return nil, err
}
vm := otto.New()
var vm *otto.Otto
if vmitem, ok := codecache.Get(name); ok {
vm = vmitem.(*otto.Otto).Copy()
} else {
code, err := u.GetStateDB().Get(calcCodeKey(name))
if err != nil {
return nil, err
}
//cache 合约代码部分,不会cache 具体执行
cachevm := basevm.Copy()
cachevm.Run(code)
codecache.Add(name, cachevm)
vm = cachevm.Copy()
}
vm.Set("context", string(data))
u.getstatedbFunc(vm, name)
u.getlocaldbFunc(vm, name)
......
package executor
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/33cn/chain33/types"
ptypes "github.com/33cn/plugin/plugin/dapp/js/types"
......@@ -107,3 +110,50 @@ func parseKV(data *otto.Object) (kv *types.KeyValue, err error) {
}
return &types.KeyValue{Key: []byte(key), Value: []byte(value)}, nil
}
func rewriteJSON(data []byte) ([]byte, error) {
dat := make(map[string]interface{})
d := json.NewDecoder(bytes.NewBuffer(data))
d.UseNumber()
if err := d.Decode(&dat); err != nil {
return nil, err
}
dat = rewriteString(dat)
return json.Marshal(dat)
}
func rewriteString(dat map[string]interface{}) map[string]interface{} {
for k, v := range dat {
if n, ok := v.(json.Number); ok {
dat[k] = jssafe(n)
} else if arr, ok := v.([]interface{}); ok {
for i := 0; i < len(arr); i++ {
v := arr[i]
if n, ok := v.(json.Number); ok {
arr[i] = jssafe(n)
}
}
dat[k] = arr
} else if d, ok := v.(map[string]interface{}); ok {
dat[k] = rewriteString(d)
} else {
dat[k] = v
}
}
return dat
}
func jssafe(n json.Number) interface{} {
if strings.Contains(string(n), ".") { //float
return n
}
i, err := n.Int64()
if err != nil {
return n
}
//javascript can not parse
if i >= 9007199254740991 || i <= -9007199254740991 {
return string(n)
}
return n
}
......@@ -2,6 +2,9 @@ package executor
import (
"encoding/json"
"fmt"
"math"
"runtime"
"strings"
"testing"
"time"
......@@ -67,12 +70,15 @@ Query.prototype.hello = function(args) {
}
`
func initExec(ldb db.DB, kvdb db.KVDB, t *testing.T) *js {
func init() {
Init("js", nil)
}
func initExec(ldb db.DB, kvdb db.KVDB, t assert.TestingT) *js {
e := newjs().(*js)
e.SetEnv(1, time.Now().Unix(), 1)
e.SetLocalDB(kvdb)
e.SetStateDB(kvdb)
c, tx := createCodeTx("test", jscode)
receipt, err := e.Exec_Create(c, tx, 0)
assert.Nil(t, err)
......@@ -158,8 +164,8 @@ func TestCallError(t *testing.T) {
call, tx := callCodeTx("test", "hello", `{hello":"world"}`)
_, err := e.callVM("exec", call, tx, 0, nil)
_, ok := err.(*otto.Error)
assert.Equal(t, true, ok)
assert.Equal(t, true, strings.Contains(err.Error(), "SyntaxError"))
assert.Equal(t, false, ok)
assert.Equal(t, true, strings.Contains(err.Error(), "invalid character 'h'"))
call, tx = callCodeTx("test", "hello", `{"hello":"world"}`)
_, err = e.callVM("hello", call, tx, 0, nil)
......@@ -174,9 +180,81 @@ func TestCallError(t *testing.T) {
assert.Equal(t, true, strings.Contains(err.Error(), ptypes.ErrFuncNotFound.Error()))
}
//数字非常大的数字的处理
func TestBigInt(t *testing.T) {
dir, ldb, kvdb := util.CreateTestDB()
defer util.CloseTestDB(dir, ldb)
e := initExec(ldb, kvdb, t)
//test call error(invalid json input)
s := fmt.Sprintf(`{"balance":%d,"balance1":%d,"balance2":%d,"balance3":%d}`, math.MaxInt64, math.MinInt64, 9007199254740990, -9007199254740990)
call, tx := callCodeTx("test", "hello", s)
data, err := e.callVM("exec", call, tx, 0, nil)
assert.Nil(t, err)
kvs, _, err := parseJsReturn(data)
assert.Nil(t, err)
assert.Equal(t, `{"balance":"9223372036854775807","balance1":"-9223372036854775808","balance2":9007199254740990,"balance3":-9007199254740990}`, string(kvs[0].Value))
}
func BenchmarkBigInt(b *testing.B) {
dir, ldb, kvdb := util.CreateTestDB()
defer util.CloseTestDB(dir, ldb)
e := initExec(ldb, kvdb, b)
//test call error(invalid json input)
s := fmt.Sprintf(`{"balance":%d,"balance1":%d,"balance2":%d,"balance3":%d}`, math.MaxInt64, math.MinInt64, 9007199254740990, -9007199254740990)
call, tx := callCodeTx("test", "hello", s)
b.StartTimer()
for i := 0; i < b.N; i++ {
_, err := e.callVM("exec", call, tx, 0, nil)
assert.Nil(b, err)
}
}
func TestRewriteJSON(t *testing.T) {
s := fmt.Sprintf(`{"balance":%d,"balance1":%d,"balance2":%d,"balance3":%d}`, math.MaxInt64, math.MinInt64, 9007199254740990, -9007199254740990)
quota := fmt.Sprintf(`{"balance":"%d","balance1":"%d","balance2":%d,"balance3":%d}`, math.MaxInt64, math.MinInt64, 9007199254740990, -9007199254740990)
data, err := rewriteJSON([]byte(s))
assert.Nil(t, err)
assert.Equal(t, quota, string(data))
data2 := make(map[string]interface{})
data2["ints"] = []int64{math.MaxInt64, math.MinInt64, 9007199254740990, -9007199254740990, 1, 0}
data2["float"] = []float64{1.1, 1000000000000000000000000000, 10000000000000000}
json1, err := json.Marshal(data2)
assert.Nil(t, err)
//assert.Equal(t, `{"float":[1.1,1100000000000000000000,-1100000000000000000000],"ints":[9223372036854775807,-9223372036854775808,9007199254740990,-9007199254740990,1,0]}`, string(json1))
json2, err := rewriteJSON(json1)
assert.Nil(t, err)
assert.Equal(t, string(json2), `{"float":[1.1,1e+27,"10000000000000000"],"ints":["9223372036854775807","-9223372036854775808",9007199254740990,-9007199254740990,1,0]}`)
}
func TestCalcLocalPrefix(t *testing.T) {
assert.Equal(t, calcLocalPrefix([]byte("a")), []byte("LODB-a-"))
assert.Equal(t, calcStatePrefix([]byte("a")), []byte("mavl-a-"))
assert.Equal(t, calcCodeKey("a"), []byte("mavl-js-code-a"))
assert.Equal(t, calcRollbackKey([]byte("a")), []byte("LODB-js-rollback-a"))
}
func TestCacheMemUsage(t *testing.T) {
dir, ldb, kvdb := util.CreateTestDB()
defer util.CloseTestDB(dir, ldb)
e := initExec(ldb, kvdb, t)
vm, err := e.createVM("test", nil, 0)
assert.Nil(t, err)
vms := make([]*otto.Otto, 1024)
for i := 0; i < 1024; i++ {
vms[i] = vm.Copy()
}
printMemUsage()
}
func printMemUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment