diff --git a/chain/chain.go b/chain/chain.go index bd59583f8..c0937b323 100644 --- a/chain/chain.go +++ b/chain/chain.go @@ -25,6 +25,7 @@ import ( "github.com/icon-project/goloop/server/metric" "github.com/icon-project/goloop/service" "github.com/icon-project/goloop/service/eeproxy" + "github.com/icon-project/goloop/service/state" ) type State int @@ -428,7 +429,13 @@ func (c *singleChain) prepareDatabase(chainDir string) error { return errors.Wrapf(err, "UnknownCacheStrategy(%s)", c.cfg.NodeCache) } cacheDir := path.Join(chainDir, DefaultCacheDir) - c.database = cache.AttachManager(cdb, cacheDir, mLevel, fLevel, stores) + cdb = cache.AttachManager(cdb, cacheDir, mLevel, fLevel, stores) + cdb, err = state.AttachAPIInfoCache(cdb, ConfigDefaultAPIInfoCacheSize) + if err != nil { + _ = cdb.Close() + return errors.Wrap(err, "FailToAttachAPIInfoCache") + } + c.database = cdb return nil } diff --git a/chain/config.go b/chain/config.go index 167b99bf4..08d91c630 100644 --- a/chain/config.go +++ b/chain/config.go @@ -20,6 +20,7 @@ const ( ConfigDefaultTxTimeout = 5000 * time.Millisecond ConfigDefaultChildrenLimit = 10 ConfigDefaultNephewLimit = 10 + ConfigDefaultAPIInfoCacheSize = 2048 ) const ( diff --git a/cmd/cli/rpc.go b/cmd/cli/rpc.go index 3e0831315..21da438e2 100644 --- a/cmd/cli/rpc.go +++ b/cmd/cli/rpc.go @@ -1021,7 +1021,7 @@ func NewSendTxCmd(parentCmd *cobra.Command, parentVc *viper.Viper) *cobra.Comman MarkAnnotationRequired(callFlags, "to", "method") deployCmd := &cobra.Command{ - Use: "deploy SCORE_ZIP_FILE", + Use: "deploy SCORE_FILE", Short: "Deploy Transaction", Args: ArgsWithDefaultErrorFunc(cobra.ExactArgs(1)), RunE: func(cmd *cobra.Command, args []string) error { @@ -1043,21 +1043,31 @@ func NewSendTxCmd(parentCmd *cobra.Command, parentVc *viper.Viper) *cobra.Comman param.ToAddress = jsonrpc.Address(to) } dataM := make(map[string]interface{}) - dataM["contentType"] = cmd.Flag("content_type").Value.String() isDir, err := IsDirectory(args[0]) if err != nil { return err } + var contentType string var b []byte if isDir { if b, err = ZipDirectory(args[0], "__pycache__"); err != nil { return fmt.Errorf("fail to zip with directory %s err:%+v", args[0], err) } + contentType = "application/zip" } else { + contentType = cmd.Flag("content_type").Value.String() + if contentType == "" { + if strings.HasSuffix(strings.ToLower(args[0]), ".jar") { + contentType = "application/java" + } else { + contentType = "application/zip" + } + } if b, err = readFile(args[0]); err != nil { return fmt.Errorf("fail to read %s err:%+v", args[0], err) } } + dataM["contentType"] = contentType dataM["content"] = "0x" + hex.EncodeToString(b) if dataParams, err := getParamsFromFlags(cmd.Flags()); err != nil { return err @@ -1078,13 +1088,11 @@ func NewSendTxCmd(parentCmd *cobra.Command, parentVc *viper.Viper) *cobra.Comman rootCmd.AddCommand(deployCmd) deployFlags := deployCmd.Flags() deployFlags.String("to", "cx0000000000000000000000000000000000000000", "ToAddress") - deployFlags.String("content_type", "application/zip", - "Mime-type of the content") + deployFlags.String("content_type", "", "Mime-type of the content") deployFlags.String("params", "", "raw json string or '@' or '-' for stdin for parameter JSON") deployFlags.StringToString("param", nil, "key=value, Function parameters will be delivered to on_install() or on_update()") - MarkAnnotationHidden(deployFlags, "content-type") return rootCmd } diff --git a/cmd/gochain/main.go b/cmd/gochain/main.go index 4ffa0262a..e4d2a01d3 100644 --- a/cmd/gochain/main.go +++ b/cmd/gochain/main.go @@ -89,7 +89,7 @@ var genesisStorage, genesisPath string var keyStoreFile, keyStoreSecret string var saveFile, saveKeyStore string var cfg GoChainConfig -var cpuProfile, memProfile string +var cpuProfile, memProfile, blockProfile string var chainDir string var eeSocket string var modLevels map[string]string @@ -130,6 +130,7 @@ func main() { flag.StringVar(&cfg.KeyStorePass, "key_password", "", "Password for the KeyStore file") flag.StringVar(&cpuProfile, "cpuprofile", "", "CPU Profiling data file") flag.StringVar(&memProfile, "memprofile", "", "Memory Profiling data file") + flag.StringVar(&blockProfile, "blockprofile", "", "Memory Profiling data file") flag.StringVar(&chainDir, "chain_dir", "", "Chain data directory (default: .chain/
/)") flag.IntVar(&cfg.EEInstances, "ee_instances", 1, "Number of execution engines") flag.IntVar(&cfg.ConcurrencyLevel, "concurrency", 1, "Maximum number of executors to be used for concurrency") @@ -448,6 +449,12 @@ func Execute(cmd *cobra.Command, args []string) { } } + if blockProfile != "" { + if err := cli.StartBlockProfile(blockProfile, 0); err != nil { + log.Panicf("Fail to start block profiling err=%+v", err) + } + } + logoLines := []string{ " ____ ___ ____ _ _ _ ___ _ _ ", " / ___|/ _ \\ / ___| | | | / \\ |_ _| \\ | |", diff --git a/cmd/txgen/main.go b/cmd/txgen/main.go index 398956a2a..2e3e3af3d 100644 --- a/cmd/txgen/main.go +++ b/cmd/txgen/main.go @@ -75,7 +75,7 @@ func main() { } var maker TransactionMaker - if len(scorePath) > 0 && len(methodName) > 0 { + if len(scorePath) > 0 && len(params) > 0 { maker = &CallMaker{ NID: nid, SourcePath: scorePath, diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 4d0022b1c..f28fec022 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -7,7 +7,7 @@ import ( "github.com/icon-project/goloop/common" ) -type Create func(string) (interface{}, error) +type Create func([]byte) (interface{}, error) type LRUCache struct { lock sync.Mutex @@ -34,11 +34,11 @@ func (c *LRUCache) putInLock(key string, value interface{}) { } } -func (c *LRUCache) Get(key string) (interface{}, error) { +func (c *LRUCache) Get(key []byte) (interface{}, error) { c.lock.Lock() defer c.lock.Unlock() - e, ok := c.items[key] + e, ok := c.items[string(key)] if ok { c.lru.MoveToBack(e) i := e.Value.(item) @@ -51,7 +51,7 @@ func (c *LRUCache) Get(key string) (interface{}, error) { if err != nil { return nil, err } - c.putInLock(key, value) + c.putInLock(string(key), value) return value, nil } diff --git a/javaee/rt/src/java/a/Array.java b/javaee/rt/src/java/a/Array.java index c2d23dee7..54e32ef4d 100644 --- a/javaee/rt/src/java/a/Array.java +++ b/javaee/rt/src/java/a/Array.java @@ -35,7 +35,7 @@ public Array(Void ignore, int readIndex) { * @param perElementFee energy to be charged per element depending on type. */ static protected void chargeEnergyInitArray(int length, int perElementFee) { - long cost = EnergyCalculator.multiply(length, perElementFee); + long cost = EnergyCalculator.multiply(Math.max(length, 0), perElementFee); IInstrumentation.attachedThreadInstrumentation.get().chargeEnergy(cost); } diff --git a/javaee/rt/src/java/foundation/icon/ee/io/AbstractRLPDataReader.java b/javaee/rt/src/java/foundation/icon/ee/io/AbstractRLPDataReader.java index c2324d7f4..d456d6804 100644 --- a/javaee/rt/src/java/foundation/icon/ee/io/AbstractRLPDataReader.java +++ b/javaee/rt/src/java/foundation/icon/ee/io/AbstractRLPDataReader.java @@ -88,6 +88,9 @@ private void peekRLPString(int b) { ((arr[p + 2] & 0xff) << 16) | ((arr[p + 3] & 0xff) << 8) | (arr[p + 4] & 0xff); + if (l < 0) { + throw new UnsupportedOperationException(); + } } else { throw new UnsupportedOperationException(); } @@ -116,6 +119,9 @@ private void peekRLPListHeader(int b) { ((arr[p + 2] & 0xff) << 16) | ((arr[p + 3] & 0xff) << 8) | (arr[p + 4] & 0xff); + if (l < 0) { + throw new UnsupportedOperationException(); + } } else { throw new UnsupportedOperationException(); } diff --git a/javaee/rt/src/java/foundation/icon/ee/score/Validator.java b/javaee/rt/src/java/foundation/icon/ee/score/Validator.java index ef5df9792..79ea4ea1a 100644 --- a/javaee/rt/src/java/foundation/icon/ee/score/Validator.java +++ b/javaee/rt/src/java/foundation/icon/ee/score/Validator.java @@ -42,7 +42,7 @@ private static ValidationException fail(String fmt, Object... args) throws throw new ValidationException(String.format(fmt, args)); } - private static ValidationException fail(Exception cause, String fmt, Object... args) throws + private static ValidationException fail(Throwable cause, String fmt, Object... args) throws ValidationException { throw new ValidationException(String.format(fmt, args), cause); } @@ -75,8 +75,8 @@ public static Method[] validate(byte[] codeBytes) throws ValidationException, Zi structDB = new StructDB(classMap); eeMethods = MethodUnpacker.readFrom(apisBytes); } catch (IOException e) { - throw fail ("bad APIS format"); - } catch (RuntimeException e) { + throw fail("bad APIS format"); + } catch (Throwable e) { throw fail(e, "malformed class file"); } String cur = jar.mainClassName; diff --git a/javaee/rt/src/java/org/aion/avm/EnergyCalculator.java b/javaee/rt/src/java/org/aion/avm/EnergyCalculator.java index c32f62a30..b784cd786 100644 --- a/javaee/rt/src/java/org/aion/avm/EnergyCalculator.java +++ b/javaee/rt/src/java/org/aion/avm/EnergyCalculator.java @@ -11,7 +11,7 @@ public class EnergyCalculator { * @return base + linearValue * RT_METHOD_FEE_FACTOR_LEVEL_2 */ public static long multiplyLinearValueByMethodFeeLevel2AndAddBase(int base, int linearValue) { - return addAndCheckForOverflow(base, multiplyAndCheckForOverflow(linearValue, RuntimeMethodFeeSchedule.RT_METHOD_FEE_FACTOR_LEVEL_2)); + return add(base, multiply(Math.max(linearValue, 0), RuntimeMethodFeeSchedule.RT_METHOD_FEE_FACTOR_LEVEL_2)); } /** @@ -20,18 +20,14 @@ public static long multiplyLinearValueByMethodFeeLevel2AndAddBase(int base, int * @return base + linearValue * RT_METHOD_FEE_FACTOR_LEVEL_1 */ public static long multiplyLinearValueByMethodFeeLevel1AndAddBase(int base, int linearValue) { - return addAndCheckForOverflow(base, multiplyAndCheckForOverflow(linearValue, RuntimeMethodFeeSchedule.RT_METHOD_FEE_FACTOR_LEVEL_1)); + return add(base, multiply(Math.max(linearValue, 0), RuntimeMethodFeeSchedule.RT_METHOD_FEE_FACTOR_LEVEL_1)); } public static long multiply(int value1, int value2) { - return multiplyAndCheckForOverflow(value1, value2); + return (long) value1 * (long) value2; } - private static long addAndCheckForOverflow(int value1, long value2) { + private static long add(int value1, long value2) { return (long) value1 + value2; } - - private static long multiplyAndCheckForOverflow(int value1, int value2) { - return (long) value1 * (long) value2; - } } diff --git a/javaee/rt/src/java/pi/ObjectReaderImpl.java b/javaee/rt/src/java/pi/ObjectReaderImpl.java index 720b7f968..edff26cb3 100644 --- a/javaee/rt/src/java/pi/ObjectReaderImpl.java +++ b/javaee/rt/src/java/pi/ObjectReaderImpl.java @@ -323,8 +323,10 @@ public void avm_skip() { public void avm_skip(int count) { wrapVoidRead(() -> { - reader.skip(count); - chargeSkip(); + for (var i=0; i 0 && s.info == nil { - bs, err := s.bk.Get(s.hash) - if err != nil { - return nil, errors.CriticalIOError.Wrapf(err, "FailToGetAPIInfo(hash=%x)", s.hash) - } - _, err = codec.UnmarshalFromBytes(bs, &s.info) - if err != nil { - return nil, errors.CriticalFormatError.Wrapf(err, "InvalidAPIInfo(hash=%x)", s.hash) + if info, err := s.bk.Get(s.hash); err != nil { + return nil, err + } else { + s.info = info } - s.bytes = bs } } return s.info, nil @@ -70,7 +58,7 @@ func (s *apiInfoStore) Equal(s2 *apiInfoStore) bool { func (s *apiInfoStore) Flush() error { if s.dirty && s.bk != nil { h := s.getHash() - err := s.bk.Set(h, s.bytes) + err := s.bk.Set(h, s.bytes, s.info) if err != nil { return errors.CriticalIOError.Wrap(err, "FailToSetAPIInfo") } @@ -79,7 +67,7 @@ func (s *apiInfoStore) Flush() error { } func (s *apiInfoStore) ResetDB(b db.Database) error { - if bk, err := b.GetBucket(db.BytesByHash); err != nil { + if bk, err := GetAPIInfoBucket(b); err != nil { return err } else { s.bk = bk @@ -117,10 +105,7 @@ func (s *apiInfoStore) Resolve(bd merkle.Builder) error { bd.RequestData(db.BytesByHash, s.hash, s) return nil } - if _, err = codec.UnmarshalFromBytes(value, &s.info); err != nil { - return err - } - s.bytes = value + s.info = value } return nil } diff --git a/service/state/apiinfocache.go b/service/state/apiinfocache.go new file mode 100644 index 000000000..c05edc03f --- /dev/null +++ b/service/state/apiinfocache.go @@ -0,0 +1,125 @@ +/* + * Copyright 2023 ICON Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package state + +import ( + "github.com/icon-project/goloop/common/cache" + "github.com/icon-project/goloop/common/codec" + "github.com/icon-project/goloop/common/crypto" + "github.com/icon-project/goloop/common/db" + "github.com/icon-project/goloop/common/errors" + "github.com/icon-project/goloop/service/scoreapi" +) + +const APIInfoCache = "cache.api_info" + + +type APIInfoBucket interface { + Get(hash []byte) (*scoreapi.Info, error) + Set(hash, bytes []byte, info *scoreapi.Info) error +} + +type apiInfoCache struct { + bk db.Bucket + cache *cache.LRUCache +} + +func (a *apiInfoCache) Get(hash []byte) (*scoreapi.Info, error) { + obj, err := a.cache.Get(hash) + if err != nil { + return nil, err + } else { + return obj.(*scoreapi.Info), nil + } +} + +func (a *apiInfoCache) Set(hash, bytes []byte, info *scoreapi.Info) error { + if err := a.bk.Set(hash, bytes); err != nil { + return err + } + a.cache.Put(string(hash), info) + return nil +} + +func GetAPIInfoFromBucket(bk db.Bucket, hash []byte) (*scoreapi.Info, error) { + bs, err := bk.Get(hash) + if err != nil { + return nil, errors.CriticalIOError.Wrapf(err, "FailToGetAPIInfo(hash=%x)", hash) + + } + var info scoreapi.Info + _, err = codec.BC.UnmarshalFromBytes(bs, &info) + if err != nil { + return nil, errors.CriticalFormatError.Wrapf(err, "InvalidAPIInfo(hash=%x)", hash) + } + return &info, nil +} + +func MustEncodeAPIInfo(info *scoreapi.Info) ([]byte, []byte) { + bs := codec.BC.MustMarshalToBytes(info) + return crypto.SHA3Sum256(bs), bs +} + +func newAPIInfoCache(dbase db.Database, size int) (*apiInfoCache, error) { + bk, err := dbase.GetBucket(db.BytesByHash) + if err != nil { + return nil, err + } + return &apiInfoCache{ + bk: bk, + cache: cache.NewLRUCache(size, func(hash []byte) (interface{}, error) { + info, err := GetAPIInfoFromBucket(bk, hash) + return info, err + }), + }, nil +} + +func AttachAPIInfoCache(dbase db.Database, size int) (db.Database, error) { + aic, err := newAPIInfoCache(dbase, size) + if err != nil { + return nil, err + } + return db.WithFlags(dbase, db.Flags{ + APIInfoCache: aic, + }), nil +} + +type apiInfoBucket struct { + bk db.Bucket +} + +func (a apiInfoBucket) Get(hash []byte) (*scoreapi.Info, error) { + return GetAPIInfoFromBucket(a.bk, hash) +} + +func (a apiInfoBucket) Set(hash, bytes []byte, _ *scoreapi.Info) error { + return a.bk.Set(hash, bytes) +} + +func GetAPIInfoBucket(dbase db.Database) (APIInfoBucket, error) { + aic := db.GetFlag(dbase, APIInfoCache) + if aic != nil { + return aic.(*apiInfoCache), nil + } + bk, err := dbase.GetBucket(db.BytesByHash) + if err != nil { + return nil, err + } else { + return apiInfoBucket{bk }, nil + } +} \ No newline at end of file diff --git a/service/state/apiinfocache_test.go b/service/state/apiinfocache_test.go new file mode 100644 index 000000000..0f803d89f --- /dev/null +++ b/service/state/apiinfocache_test.go @@ -0,0 +1,123 @@ +/* + * Copyright 2023 ICON Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package state + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/icon-project/goloop/common/db" + "github.com/icon-project/goloop/service/scoreapi" +) + +var apis1 = []*scoreapi.Method{ + { + Type: scoreapi.Function, + Name: "hello", + Flags: scoreapi.FlagExternal, + Indexed: 1, + Inputs: []scoreapi.Parameter{ + { + Name: "name", + Type: scoreapi.String, + }, + }, + Outputs: nil, + }, +} + +var apis2 = []*scoreapi.Method{ + { + Type: scoreapi.Function, + Name: "say", + Flags: scoreapi.FlagExternal, + Indexed: 2, + Inputs: []scoreapi.Parameter{ + { + Name: "name", + Type: scoreapi.String, + }, + { + Name: "msg", + Type: scoreapi.String, + }, + }, + Outputs: nil, + }, + { + Type: scoreapi.Function, + Name: "name", + Flags: scoreapi.FlagExternal|scoreapi.FlagReadOnly, + Indexed: 0, + Inputs: []scoreapi.Parameter{}, + Outputs: []scoreapi.DataType{ scoreapi.String }, + }, +} + +func TestAttachAPIInfoCache(t *testing.T) { + dbase := db.NewMapDB() + ldb := db.NewLayerDB(dbase) + + // record first directly. + info1 := scoreapi.NewInfo(apis1) + hash, bs := MustEncodeAPIInfo(info1) + bk, err := dbase.GetBucket(db.BytesByHash) + assert.NoError(t, err) + assert.NoError(t, bk.Set(hash, bs)) + + // attach cache + cdb, err := AttachAPIInfoCache(ldb, 1024) + assert.NoError(t, err) + + // working with caches + bk1, err := GetAPIInfoBucket(cdb) + assert.NoError(t, err) + info, err := bk1.Get(hash) + assert.NoError(t, err) + assert.EqualValues(t, info1, info) + + // working without cache + bk2, err := GetAPIInfoBucket(ldb) + assert.NoError(t, err) + info, err = bk2.Get(hash) + assert.NoError(t, err) + assert.EqualValues(t, info1, info) + + // add new value + info2 := scoreapi.NewInfo(apis2) + hash, bs = MustEncodeAPIInfo(info2) + assert.NoError(t, bk1.Set(hash, bs, info2)) + + // confirm available through database + info, err = bk2.Get(hash) + assert.NoError(t, err) + assert.EqualValues(t, info2, info) + + // clear database + assert.NoError(t, ldb.Flush(false)) + + // cache should still be valid + info, err = bk1.Get(hash) + assert.NoError(t, err) + assert.EqualValues(t, info2, info) + + // no data in database + info, err = bk2.Get(hash) + assert.Error(t, err) +} diff --git a/service/transitionresultcache.go b/service/transitionresultcache.go index 4201e6054..eff6e7e52 100644 --- a/service/transitionresultcache.go +++ b/service/transitionresultcache.go @@ -44,16 +44,15 @@ type transitionResultCache struct { } func (c *transitionResultCache) GetValidatorSnapshot(vh []byte) (state.ValidatorSnapshot, error) { - vhs := string(vh) - if vs, err := c.vssCache.Get(vhs); err != nil { + if vs, err := c.vssCache.Get(vh); err != nil { return nil, err } else { return vs.(state.ValidatorSnapshot), nil } } -func (c *transitionResultCache) createValidatorSnapshot(vhs string) (interface{}, error) { - vs, err := state.ValidatorSnapshotFromHash(c.database, []byte(vhs)) +func (c *transitionResultCache) createValidatorSnapshot(vh []byte) (interface{}, error) { + vs, err := state.ValidatorSnapshotFromHash(c.database, vh) return vs, err }