diff --git a/.gitmodules b/.gitmodules index bf888d3b03..1b67e6e615 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,12 +2,9 @@ path = packages/composer-runtime-hlf/vendor/github.com/hyperledger/fabric url = https://github.com/hyperledger/fabric.git branch = v0.6 -[submodule "packages/composer-runtime-hlf/vendor/gopkg.in/sourcemap.v1"] - path = packages/composer-runtime-hlf/vendor/gopkg.in/sourcemap.v1 - url = https://gopkg.in/sourcemap.v1.git -[submodule "packages/composer-runtime-hlf/vendor/github.com/robertkrimen/otto"] - path = packages/composer-runtime-hlf/vendor/github.com/robertkrimen/otto - url = https://github.com/robertkrimen/otto.git [submodule "packages/composer-runtime-hlfv1/vendor/gopkg.in/olebedev/go-duktape.v3"] path = packages/composer-runtime-hlfv1/vendor/gopkg.in/olebedev/go-duktape.v3 url = https://gopkg.in/olebedev/go-duktape.v3.git +[submodule "packages/composer-runtime-hlf/vendor/gopkg.in/olebedev/go-duktape.v3"] + path = packages/composer-runtime-hlf/vendor/gopkg.in/olebedev/go-duktape.v3 + url = https://gopkg.in/olebedev/go-duktape.v3.git diff --git a/.travis/before-install.sh b/.travis/before-install.sh index 29e40a7e71..8f9f56ca19 100755 --- a/.travis/before-install.sh +++ b/.travis/before-install.sh @@ -34,26 +34,16 @@ npm install -g @alrra/travis-scripts echo "ABORT_BUILD=false" > ${DIR}/build.cfg echo "ABORT_CODE=0" >> ${DIR}/build.cfg -if [ "${SYSTEST}" = "hlf" ] && [ "${SYSTEST_HLF}" = "ibm" ]; then - - if [ "${TRAVIS_EVENT_TYPE}" = "cron" ]; then - echo Valid to run hlf with ibm systest as CRON build - else - echo "ABORT_BUILD=true" > ${DIR}/build.cfg - echo "ABORT_CODE=0" >> ${DIR}/build.cfg - echo Not running as a PR or merge build - exit 0 - fi -fi - # Abort the systest if this is a merge build # Check for the FC_TASK that is set in travis.yml, also the pull request is false => merge build # and that the TRAVIS_TAG is empty meaning this is not a release build if [ "${FC_TASK}" = "systest" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ -z "${TRAVIS_TAG}" ]; then - echo "ABORT_BUILD=true" > ${DIR}/build.cfg - echo "ABORT_CODE=0" >> ${DIR}/build.cfg - echo Merge build from non release PR: ergo not running systest - exit 0 + if [[ "${TRAVIS_REPO_SLUG}" = hyperledger* ]]; then + echo "ABORT_BUILD=true" > ${DIR}/build.cfg + echo "ABORT_CODE=0" >> ${DIR}/build.cfg + echo Merge build from non release PR: ergo not running systest + exit 0 + fi fi # diff --git a/packages/composer-connector-hlfv1/lib/hlfconnection.js b/packages/composer-connector-hlfv1/lib/hlfconnection.js index 8c2ca4acd9..6e8c367991 100644 --- a/packages/composer-connector-hlfv1/lib/hlfconnection.js +++ b/packages/composer-connector-hlfv1/lib/hlfconnection.js @@ -233,11 +233,7 @@ class HLFConnection extends Connection { let ccid = HLFConnection.generateCcid(this.businessNetworkIdentifier); LOG.debug(method, 'registerChaincodeEvent', ccid, 'composer'); let ccEvent = this.eventHubs[0].registerChaincodeEvent(ccid, 'composer', (event) => { - //let evt = Buffer.from(event.payload, 'hex').toString('utf8'); - // Remove the first set of "" around the event so it can be parsed first time let evt = event.payload.toString('utf8'); - evt = evt.replace(/^"(.*)"$/, '$1'); // Remove end quotes - evt = evt.replace(/\\/g, ''); evt = JSON.parse(evt); this.emit('events', evt); }); diff --git a/packages/composer-connector-hlfv1/test/hlfconnection.js b/packages/composer-connector-hlfv1/test/hlfconnection.js index 2a30b17e3c..d1454ac249 100644 --- a/packages/composer-connector-hlfv1/test/hlfconnection.js +++ b/packages/composer-connector-hlfv1/test/hlfconnection.js @@ -198,7 +198,7 @@ describe('HLFConnection', () => { const events = { payload: { toString: () => { - return '"{"event":"event"}"'; + return '{"event":"event"}'; } } }; @@ -207,6 +207,7 @@ describe('HLFConnection', () => { sinon.assert.calledOnce(mockEventHub.registerChaincodeEvent); sinon.assert.calledWith(mockEventHub.registerChaincodeEvent, 'org-acme-biznet', 'composer', sinon.match.func); sinon.assert.calledOnce(connection.emit); + sinon.assert.calledWith(connection.emit, 'events', {'event':'event'}); }); it('should not register any listeners for chaincode events if no business network is specified', () => { diff --git a/packages/composer-runtime-embedded/lib/embeddedeventservice.js b/packages/composer-runtime-embedded/lib/embeddedeventservice.js index eb5076c956..7131fab488 100644 --- a/packages/composer-runtime-embedded/lib/embeddedeventservice.js +++ b/packages/composer-runtime-embedded/lib/embeddedeventservice.js @@ -44,7 +44,7 @@ class EmbeddedEventService extends EventService { transactionCommit() { return super.transactionCommit() .then(() => { - const jsonEvent = JSON.parse(this.serializeBuffer()); + const jsonEvent = this.getEvents(); this.eventSink.emit('events', jsonEvent); }); } diff --git a/packages/composer-runtime-embedded/lib/embeddedqueryservice.js b/packages/composer-runtime-embedded/lib/embeddedqueryservice.js index b7de1a6846..b9796c0e7a 100644 --- a/packages/composer-runtime-embedded/lib/embeddedqueryservice.js +++ b/packages/composer-runtime-embedded/lib/embeddedqueryservice.js @@ -37,16 +37,12 @@ class EmbeddedQueryService extends QueryService { /** * Query the underlying world-state store using a store native query string. + * @abstract * @param {string} queryString - the native query string * @return {Promise} A promise that will be resolved with a JS object containing the results of the query */ queryNative(queryString) { - const method = 'queryNative'; - LOG.entry(method, queryString); - this.queryString = queryString; - LOG.debug(method, queryString); - // TODO (DCS) - we need an implementation! - return Promise.resolve({data: 'not implemented'}); + throw new Error('The native query functionality is not available on this Blockchain platform'); } } diff --git a/packages/composer-runtime-embedded/test/embeddedeventservice.js b/packages/composer-runtime-embedded/test/embeddedeventservice.js index 033fd41a67..52f81197a5 100644 --- a/packages/composer-runtime-embedded/test/embeddedeventservice.js +++ b/packages/composer-runtime-embedded/test/embeddedeventservice.js @@ -48,10 +48,10 @@ describe('EmbeddedEventService', () => { describe('#transactionCommit', () => { it ('should emit a list of events', () => { - sinon.stub(eventService, 'serializeBuffer').returns('[{"event":"event"}]'); + sinon.stub(eventService, 'getEvents').returns([{'event':'event'}]); return eventService.transactionCommit() .then(() => { - sinon.assert.calledOnce(eventService.serializeBuffer); + sinon.assert.calledOnce(eventService.getEvents); sinon.assert.calledOnce(mockEventEmitter.emit); sinon.assert.calledWith(mockEventEmitter.emit, 'events', [{'event':'event'}]); }); diff --git a/packages/composer-runtime-embedded/test/embeddedqueryservice.js b/packages/composer-runtime-embedded/test/embeddedqueryservice.js index 5a673b966d..ca29cc9704 100644 --- a/packages/composer-runtime-embedded/test/embeddedqueryservice.js +++ b/packages/composer-runtime-embedded/test/embeddedqueryservice.js @@ -38,11 +38,10 @@ describe('EmbeddedQueryService', () => { describe('#queryNative', () => { - it ('should return the query string', () => { - return queryService.queryNative('dummyString') - .then((result) => { - result.should.deep.equal({data: 'not implemented'}); - }); + it ('should throw as not supported on this runtime', () => { + (() => { + queryService.queryNative('dummyString'); + }).should.throw(/not available on this Blockchain platform/); }); }); }); diff --git a/packages/composer-runtime-hlf/.gitignore b/packages/composer-runtime-hlf/.gitignore index fa6feeb823..d95047f157 100644 --- a/packages/composer-runtime-hlf/.gitignore +++ b/packages/composer-runtime-hlf/.gitignore @@ -45,8 +45,8 @@ out *.swp # Build generated files should be ignored by git, but not by npm. -concerto -concerto.js.go -concerto.js -concerto.js.map -concerto.min.js +composer +composer.js.go +composer.js +composer.js.map +composer.min.js diff --git a/packages/composer-runtime-hlf/.npmignore b/packages/composer-runtime-hlf/.npmignore index 02392bfa60..cdf7be0ab8 100644 --- a/packages/composer-runtime-hlf/.npmignore +++ b/packages/composer-runtime-hlf/.npmignore @@ -45,8 +45,8 @@ out *.swp # Build generated files should be ignored by git, but not by npm. -concerto -# concerto.js.go -concerto.js -concerto.js.map -concerto.min.js +composer +# composer.js.go +composer.js +composer.js.map +composer.min.js diff --git a/packages/composer-runtime-hlf/chaincode.go b/packages/composer-runtime-hlf/chaincode.go index 0e87554a0a..530e5d7605 100644 --- a/packages/composer-runtime-hlf/chaincode.go +++ b/packages/composer-runtime-hlf/chaincode.go @@ -19,7 +19,7 @@ import "github.com/hyperledger/fabric/core/chaincode/shim" // Chaincode is the chaincode class. It is an implementation of the // Chaincode interface. type Chaincode struct { - ConcertoPool *ConcertoPool + ComposerPool *ComposerPool } // NewChaincode creates a new instance of the Chaincode chaincode class. @@ -28,39 +28,39 @@ func NewChaincode() (result *Chaincode) { defer func() { logger.Debug("Exiting NewChaincode", result) }() return &Chaincode{ - ConcertoPool: NewConcertoPool(8), + ComposerPool: NewComposerPool(8), } } // Init is called by the Hyperledger Fabric when the chaincode is deployed. // Init can read from and write to the world state. func (chaincode *Chaincode) Init(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { - logger.Debug("Entering Chaincode.Init", stub, function, arguments) + logger.Debug("Entering Chaincode.Init", &stub, function, arguments) defer func() { logger.Debug("Exiting Chaincode.Init", string(result), err) }() - concerto := chaincode.ConcertoPool.Get() - defer chaincode.ConcertoPool.Put(concerto) - return concerto.Init(stub, function, arguments) + composer := chaincode.ComposerPool.Get() + defer chaincode.ComposerPool.Put(composer) + return composer.Init(stub, function, arguments) } // Invoke is called by the Hyperledger Fabric when the chaincode is invoked. // Invoke can read from and write to the world state. func (chaincode *Chaincode) Invoke(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { - logger.Debug("Entering Chaincode.Invoke", stub, function, arguments) + logger.Debug("Entering Chaincode.Invoke", &stub, function, arguments) defer func() { logger.Debug("Exiting Chaincode.Invoke", string(result), err) }() - concerto := chaincode.ConcertoPool.Get() - defer chaincode.ConcertoPool.Put(concerto) - return concerto.Invoke(stub, function, arguments) + composer := chaincode.ComposerPool.Get() + defer chaincode.ComposerPool.Put(composer) + return composer.Invoke(stub, function, arguments) } // Query is called by the Hyperledger Fabric when the chaincode is queried. // Query can read from, but not write to the world state. func (chaincode *Chaincode) Query(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { - logger.Debug("Entering Chaincode.Query", stub, function, arguments) + logger.Debug("Entering Chaincode.Query", &stub, function, arguments) defer func() { logger.Debug("Exiting Chaincode.Query", string(result), err) }() - concerto := chaincode.ConcertoPool.Get() - defer chaincode.ConcertoPool.Put(concerto) - return concerto.Query(stub, function, arguments) + composer := chaincode.ComposerPool.Get() + defer chaincode.ComposerPool.Put(composer) + return composer.Query(stub, function, arguments) } diff --git a/packages/composer-runtime-hlf/chaincode_test.go b/packages/composer-runtime-hlf/chaincode_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/chaincode_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/composer.go b/packages/composer-runtime-hlf/composer.go new file mode 100644 index 0000000000..7e8284dfd2 --- /dev/null +++ b/packages/composer-runtime-hlf/composer.go @@ -0,0 +1,209 @@ +/* + * 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 main + +import ( + "errors" + "strings" + + "github.com/hyperledger/fabric/core/chaincode/shim" + duktape "gopkg.in/olebedev/go-duktape.v3" +) + +// Composer is the chaincode class. It is an implementation of the +// Chaincode interface. +type Composer struct { + VM *duktape.Context + Container *Container + Engine *Engine +} + +// NewComposer creates a new instance of the Composer chaincode class. +func NewComposer() (result *Composer) { + logger.Debug("Entering NewComposer") + defer func() { logger.Debug("Exiting NewComposer", result) }() + + // Create the JavaScript engine. + result = &Composer{} + result.createJavaScript() + + // Create the container and engine objects. + result.Container = NewContainer(result.VM, nil) + result.Engine = NewEngine(result.VM, result.Container) + + return result +} + +// createJavaScript creates a new JavaScript virtual machine with the JavaScript code loaded. +func (composer *Composer) createJavaScript() { + logger.Debug("Entering Composer.createJavaScript") + defer func() { logger.Debug("Exiting Composer.createJavaScript") }() + + // Create a new JavaScript virtual machine. + vm := duktape.New() + if vm == nil { + panic("Failed to create JavaScript virtual machine") + } + composer.VM = vm + + // Register event loop functions. + vm.PushTimers() + + // Install the global object, and the window alias to it. + err := vm.PevalString(` + if (typeof global === 'undefined') { + (function () { + var global = new Function('return this;')(); + Object.defineProperty(global, 'global', { + value: global, + writable: true, + enumerable: false, + configurable: true + }); + Object.defineProperty(global, 'window', { + value: global, + writable: true, + enumerable: false, + configurable: true + }); + Object.defineProperty(global, 'document', { + value: undefined, + writable: true, + enumerable: false, + configurable: true + }); + Object.defineProperty(global, 'navigator', { + value: undefined, + writable: true, + enumerable: false, + configurable: true + }); + })(); + } + `) + if err != nil { + panic(err) + } + + // Execute the Babel Polyfill JavaScript source inside the JavaScript virtual machine. + err = vm.PevalString(babelPolyfillJavaScript) + if err != nil { + panic(err) + } + + // Execute the Composer JavaScript source inside the JavaScript virtual machine. + // We trim any trailing newlines as this is required for Otto to find the source maps. + err = vm.PevalString(strings.TrimRight(composerJavaScript, "\n")) + if err != nil { + panic(err) + } + +} + +// Init is called by the Hyperledger Fabric when the chaincode is deployed. +// Init can read from and write to the world state. +func (composer *Composer) Init(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { + logger.Debug("Entering Composer.Init", &stub, function, arguments) + defer func() { logger.Debug("Exiting Composer.Init", string(result), err) }() + + // Start a scope for locking the JavaScript virtual machine. + var channel chan EngineCallback + func() { + + // Lock the JavaScript virtual machine. + vm := composer.VM + vm.Lock() + defer vm.Unlock() + + // Create all required objects. + context := NewContext(composer.VM, composer.Engine, stub) + + // Defer to the JavaScript function. + channel = composer.Engine.Init(context, function, arguments) + + }() + + // Now read from the channel. This will be triggered when the JavaScript + // code calls the callback function. + data, ok := <-channel + if !ok { + return nil, errors.New("Failed to receive callback from JavaScript function") + } + return data.Result, data.Error +} + +// Invoke is called by the Hyperledger Fabric when the chaincode is invoked. +// Invoke can read from and write to the world state. +func (composer *Composer) Invoke(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { + logger.Debug("Entering Composer.Invoke", &stub, function, arguments) + defer func() { logger.Debug("Exiting Composer.Invoke", string(result), err) }() + + // Start a scope for locking the JavaScript virtual machine. + var channel chan EngineCallback + func() { + + // Lock the JavaScript virtual machine. + vm := composer.VM + vm.Lock() + defer vm.Unlock() + + // Create all required objects. + context := NewContext(composer.VM, composer.Engine, stub) + + // Defer to the JavaScript function. + channel = composer.Engine.Invoke(context, function, arguments) + + }() + + // Now read from the channel. This will be triggered when the JavaScript + // code calls the callback function. + data, ok := <-channel + if !ok { + return nil, errors.New("Failed to receive callback from JavaScript function") + } + return data.Result, data.Error +} + +// Query is called by the Hyperledger Fabric when the chaincode is queried. +// Query can read from the world state. +func (composer *Composer) Query(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { + logger.Debug("Entering Composer.Query", &stub, function, arguments) + defer func() { logger.Debug("Exiting Composer.Query", string(result), err) }() + + // Start a scope for locking the JavaScript virtual machine. + var channel chan EngineCallback + func() { + + // Lock the JavaScript virtual machine. + vm := composer.VM + vm.Lock() + defer vm.Unlock() + + // Create all required objects. + context := NewContext(composer.VM, composer.Engine, stub) + + // Defer to the JavaScript function. + channel = composer.Engine.Query(context, function, arguments) + + }() + + // Now read from the channel. This will be triggered when the JavaScript + // code calls the callback function. + data, ok := <-channel + if !ok { + return nil, errors.New("Failed to receive callback from JavaScript function") + } + return data.Result, data.Error +} diff --git a/packages/composer-runtime-hlf/concerto_test.go b/packages/composer-runtime-hlf/composer_test.go similarity index 82% rename from packages/composer-runtime-hlf/concerto_test.go rename to packages/composer-runtime-hlf/composer_test.go index 2d448e8563..7d386ad025 100644 --- a/packages/composer-runtime-hlf/concerto_test.go +++ b/packages/composer-runtime-hlf/composer_test.go @@ -16,9 +16,9 @@ package main import "testing" -func TestNewConcerto(t *testing.T) { - concerto := NewConcerto() - if concerto == nil { - t.Fatal("NewConcerto returned nil") +func TestNewComposer(t *testing.T) { + composer := NewComposer() + if composer == nil { + t.Fatal("NewComposer returned nil") } } diff --git a/packages/composer-runtime-hlf/composerpool.go b/packages/composer-runtime-hlf/composerpool.go new file mode 100644 index 0000000000..ad82e96d3e --- /dev/null +++ b/packages/composer-runtime-hlf/composerpool.go @@ -0,0 +1,58 @@ +/* + * 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 main + +// ComposerPool holds a pool of Composer objects. +type ComposerPool struct { + Pool chan *Composer +} + +// NewComposerPool creates a new pool of Composer objects. +func NewComposerPool(max int) (result *ComposerPool) { + logger.Debug("Entering NewComposerPool", max) + defer func() { logger.Debug("Exiting NewComposerPool", result) }() + + return &ComposerPool{ + Pool: make(chan *Composer, max), + } +} + +// Get returns an existing Composer object from the pool, or creates a new one +// if no existing Composer objects are available. +func (cp *ComposerPool) Get() (result *Composer) { + logger.Debug("Entering ComposerPool.Get") + defer func() { logger.Debug("Exiting ComposerPool.Get", result) }() + + select { + case result = <-cp.Pool: + default: + result = NewComposer() + } + return result +} + +// Put stores an existing Composer object in the pool, or discards it if the pool +// is currently at capacity. +func (cp *ComposerPool) Put(composer *Composer) (result bool) { + logger.Debug("Entering ComposerPool.Put", composer) + defer func() { logger.Debug("Exiting ComposerPool.Put", result) }() + + select { + case cp.Pool <- composer: + return true + default: + return false + } +} diff --git a/packages/composer-runtime-hlf/concerto.go b/packages/composer-runtime-hlf/concerto.go deleted file mode 100644 index abfedf6675..0000000000 --- a/packages/composer-runtime-hlf/concerto.go +++ /dev/null @@ -1,300 +0,0 @@ -/* - * 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 main - -import ( - "errors" - "strings" - "time" - - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" -) - -// A JavaScript timer. -// From https://github.com/robertkrimen/natto -type _timer struct { - timer *time.Timer - duration time.Duration - interval bool - call otto.FunctionCall -} - -// Concerto is the chaincode class. It is an implementation of the -// Chaincode interface. -type Concerto struct { - VM *otto.Otto - TimerRegistry map[*_timer]*_timer - TimerReady chan *_timer - Container *Container - Engine *Engine -} - -// NewConcerto creates a new instance of the Concerto chaincode class. -func NewConcerto() (result *Concerto) { - logger.Debug("Entering NewConcerto") - defer func() { logger.Debug("Exiting NewConcerto", result) }() - - // Create the JavaScript engine. - result = &Concerto{} - result.createJavaScript() - - // Create the container and engine objects. - result.Container = NewContainer(result.VM, nil) - result.Engine = NewEngine(result.VM, result.Container) - - return result -} - -// createJavaScript ... -func (concerto *Concerto) createJavaScript() { - logger.Debug("Entering Concerto.createJavaScript") - defer func() { logger.Debug("Exiting Concerto.createJavaScript") }() - - // Create a new JavaScript virtual machine. - vm := otto.New() - if vm == nil { - panic("Failed to create JavaScript virtual machine") - } - concerto.VM = vm - - // Register event loop functions. - concerto.registerEventLoop() - - // Register the global, window and document objects (which Otto does not have ...) - _, err := vm.Run(` - var global = Function('return this')(); - var window = global; - var document = undefined; - var navigator = undefined; - `) - if err != nil { - panic(err) - } - - // Execute the Babel Polyfill JavaScript source inside the JavaScript virtual machine. - _, err = vm.Run(babelPolyfillJavaScript) - if err != nil { - panic(err) - } - - // Execute the Concerto JavaScript source inside the JavaScript virtual machine. - // We trim any trailing newlines as this is required for Otto to find the source maps. - _, err = vm.Run(strings.TrimRight(concertoJavaScript, "\n")) - if err != nil { - panic(err) - } - -} - -// registerEventLoop ... -// From https://github.com/robertkrimen/natto -func (concerto *Concerto) registerEventLoop() { - logger.Debug("Entering Concerto.registerEventLoop") - defer func() { logger.Debug("Exiting Concerto.registerEventLoop") }() - - concerto.TimerRegistry = map[*_timer]*_timer{} - concerto.TimerReady = make(chan *_timer) - - newTimer := func(call otto.FunctionCall, interval bool) (*_timer, otto.Value) { - delay, _ := call.Argument(1).ToInteger() - if 0 >= delay { - delay = 1 - } - - timer := &_timer{ - duration: time.Duration(delay) * time.Millisecond, - call: call, - interval: interval, - } - concerto.TimerRegistry[timer] = timer - - timer.timer = time.AfterFunc(timer.duration, func() { - concerto.TimerReady <- timer - }) - - value, err := call.Otto.ToValue(timer) - if err != nil { - panic(err) - } - - return timer, value - } - - setTimeout := func(call otto.FunctionCall) otto.Value { - _, value := newTimer(call, false) - return value - } - concerto.VM.Set("setTimeout", setTimeout) - - setInterval := func(call otto.FunctionCall) otto.Value { - _, value := newTimer(call, true) - return value - } - concerto.VM.Set("setInterval", setInterval) - - clearTimeout := func(call otto.FunctionCall) otto.Value { - timer, _ := call.Argument(0).Export() - if timer, ok := timer.(*_timer); ok { - timer.timer.Stop() - delete(concerto.TimerRegistry, timer) - } - return otto.UndefinedValue() - } - concerto.VM.Set("clearTimeout", clearTimeout) - concerto.VM.Set("clearInterval", clearTimeout) -} - -// pumpEventLoop ... -// From https://github.com/robertkrimen/natto -func (concerto *Concerto) pumpEventLoop() (err error) { - logger.Debug("Entering Concerto.pumpEventLoop") - defer func() { logger.Debug("Exiting Concerto.pumpEventLoop") }() - - for { - select { - case timer := <-concerto.TimerReady: - var arguments []interface{} - if len(timer.call.ArgumentList) > 2 { - tmp := timer.call.ArgumentList[2:] - arguments = make([]interface{}, 2+len(tmp)) - for i, value := range tmp { - arguments[i+2] = value - } - } else { - arguments = make([]interface{}, 1) - } - arguments[0] = timer.call.ArgumentList[0] - _, err := concerto.VM.Call(`Function.call.call`, nil, arguments...) - if err != nil { - for _, timer := range concerto.TimerRegistry { - timer.timer.Stop() - delete(concerto.TimerRegistry, timer) - return err - } - } - if timer.interval { - timer.timer.Reset(timer.duration) - } else { - delete(concerto.TimerRegistry, timer) - } - default: - // Escape valve! - // If this isn't here, we deadlock... - } - if len(concerto.TimerRegistry) == 0 { - break - } - } - return nil -} - -// handleError ... -func (concerto *Concerto) handleError(err error) (result error) { - logger.Debug("Entering Concerto.handleError", err) - defer func() { logger.Debug("Exiting Concerto.handleError", result) }() - - if jsError, ok := err.(*otto.Error); ok { - return errors.New(jsError.String()) - } - return err -} - -// Init is called by the Hyperledger Fabric when the chaincode is deployed. -// Init can read from and write to the world state. -func (concerto *Concerto) Init(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { - logger.Debug("Entering Concerto.Init", stub, function, arguments) - defer func() { logger.Debug("Exiting Concerto.Init", string(result), err) }() - - // Create all required objects. - context := NewContext(concerto.VM, concerto.Engine, stub) - - // Defer to the JavaScript function. - channel := concerto.Engine.Init(context, function, arguments) - - // Pump the event loop. - err = concerto.pumpEventLoop() - if err != nil { - return nil, err - } - - // Now read from the channel. - data, ok := <-channel - if !ok { - return nil, errors.New("Failed to receive callback from JavaScript function") - } - result = data.Result - err = data.Error - return result, concerto.handleError(err) - -} - -// Invoke is called by the Hyperledger Fabric when the chaincode is invoked. -// Invoke can read from and write to the world state. -func (concerto *Concerto) Invoke(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { - logger.Debug("Entering Concerto.Invoke", stub, function, arguments) - defer func() { logger.Debug("Exiting Concerto.Invoke", string(result), err) }() - - // Create all required objects. - context := NewContext(concerto.VM, concerto.Engine, stub) - - // Defer to the JavaScript function. - channel := concerto.Engine.Invoke(context, function, arguments) - - // Pump the event loop. - err = concerto.pumpEventLoop() - if err != nil { - return nil, concerto.handleError(err) - } - - // Now read from the channel. - data, ok := <-channel - if !ok { - return nil, errors.New("Failed to receive callback from JavaScript function") - } - result = data.Result - err = data.Error - return result, concerto.handleError(err) - -} - -// Query is called by the Hyperledger Fabric when the chaincode is queried. -// Query can read from, but not write to the world state. -func (concerto *Concerto) Query(stub shim.ChaincodeStubInterface, function string, arguments []string) (result []byte, err error) { - logger.Debug("Entering Concerto.Query", stub, function, arguments) - defer func() { logger.Debug("Exiting Concerto.Query", string(result), err) }() - - // Create all required objects. - context := NewContext(concerto.VM, concerto.Engine, stub) - - // Defer to the JavaScript function. - channel := concerto.Engine.Query(context, function, arguments) - - // Pump the event loop. - err = concerto.pumpEventLoop() - if err != nil { - return nil, concerto.handleError(err) - } - - // Now read from the channel. - data, ok := <-channel - if !ok { - return nil, errors.New("Failed to receive callback from JavaScript function") - } - result = data.Result - err = data.Error - return result, concerto.handleError(err) - -} diff --git a/packages/composer-runtime-hlf/concertopool.go b/packages/composer-runtime-hlf/concertopool.go deleted file mode 100644 index 2a4169b30a..0000000000 --- a/packages/composer-runtime-hlf/concertopool.go +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 main - -// ConcertoPool holds a pool of Concerto objects. -type ConcertoPool struct { - Pool chan *Concerto -} - -// NewConcertoPool creates a new pool of Concerto objects. -func NewConcertoPool(max int) (result *ConcertoPool) { - logger.Debug("Entering NewConcertoPool", max) - defer func() { logger.Debug("Exiting NewChaincode", result) }() - - return &ConcertoPool{ - Pool: make(chan *Concerto, max), - } -} - -// Get returns an existing Concerto object from the pool, or creates a new one -// if no existing Concerto objects are available. -func (cp *ConcertoPool) Get() (result *Concerto) { - logger.Debug("Entering ConcertoPool.Get") - defer func() { logger.Debug("Exiting ConcertoPool.Get", result) }() - - select { - case result = <-cp.Pool: - default: - result = NewConcerto() - } - return result -} - -// Put stores an existing Concerto object in the pool, or discards it if the pool -// is currently at capacity. -func (cp *ConcertoPool) Put(concerto *Concerto) (result bool) { - logger.Debug("Entering ConcertoPool.Put", concerto) - defer func() { logger.Debug("Exiting ConcertoPool.Put", result) }() - - select { - case cp.Pool <- concerto: - return true - default: - return false - } -} diff --git a/packages/composer-runtime-hlf/concertopool_test.go b/packages/composer-runtime-hlf/concertopool_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/concertopool_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/container.go b/packages/composer-runtime-hlf/container.go index ba99c6119d..fb0b51fc4d 100644 --- a/packages/composer-runtime-hlf/container.go +++ b/packages/composer-runtime-hlf/container.go @@ -15,65 +15,71 @@ package main import ( - "fmt" + duktape "gopkg.in/olebedev/go-duktape.v3" "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" ) // Container is a Go wrapper around an instance of the Container JavaScript class. type Container struct { - This *otto.Object + VM *duktape.Context LoggingService *LoggingService } // NewContainer creates a Go wrapper around a new instance of the Container JavaScript class. -func NewContainer(vm *otto.Otto, stub shim.ChaincodeStubInterface) (result *Container) { +func NewContainer(vm *duktape.Context, stub shim.ChaincodeStubInterface) (result *Container) { logger.Debug("Entering NewContainer", vm) defer func() { logger.Debug("Exiting NewContainer", result) }() - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.Container", nil) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of Container JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of Container JavaScript class is not an object") - } - object := temp.Object() + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) - // Add a pointer to the Go object into the JavaScript object. - result = &Container{This: object} - err = object.Set("$this", result) - if err != nil { - panic(fmt.Sprintf("Failed to store Go object in Container JavaScript object: %v", err)) - } + // Create the new container. + result = &Container{VM: vm} // Create the services. result.LoggingService = NewLoggingService(vm, result, stub) + // Create a new instance of the JavaScript Container class. + vm.PushGlobalObject() // [ global ] + vm.GetPropString(-1, "composer") // [ global composer ] + vm.GetPropString(-1, "Container") // [ global composer Container ] + err := vm.Pnew(0) // [ global composer theContainer ] + if err != nil { + panic(err) + } + vm.PushGlobalStash() // [ global composer theContainer stash ] + vm.Dup(-2) // [ global composer theContainer stash theContainer ] + vm.PutPropString(-2, "container") // [ global composer theContainer stash ] + vm.Pop() // [ global composer theContainer ] + // Bind the methods into the JavaScript object. - result.This.Set("getVersion", result.getVersion) - result.This.Set("getLoggingService", result.getLoggingService) - return result + vm.PushGoFunction(result.getVersion) // [ global composer theContainer getVersion ] + vm.PutPropString(-2, "getVersion") // [ global composer theContainer ] + vm.PushGoFunction(result.getLoggingService) // [ global composer theContainer getLoggingService ] + vm.PutPropString(-2, "getLoggingService") // [ global composer theContainer ] + // Return the new container. + return result } -// getVersion ... -func (container *Container) getVersion(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering Container.getVersion", call) +// getVersion returns the current version of the chaincode. +func (container *Container) getVersion(vm *duktape.Context) (result int) { + logger.Debug("Entering Container.getVersion", vm) defer func() { logger.Debug("Exiting Container.getVersion", result) }() - result, err := otto.ToValue(version) - if err != nil { - panic(err) - } - return result + // Return the chaincode version. + vm.PushString(version) + return 1 } -// getLoggingService ... -func (container *Container) getLoggingService(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering Container.getLoggingService", call) +// getLoggingService returns the logging service to use. +func (container *Container) getLoggingService(vm *duktape.Context) (result int) { + logger.Debug("Entering Container.getLoggingService", vm) defer func() { logger.Debug("Exiting Container.getLoggingService", result) }() - return container.LoggingService.This.Value() + // Return the JavaScript object from the global stash. + vm.PushGlobalStash() + vm.GetPropString(-1, "loggingService") + return 1 } diff --git a/packages/composer-runtime-hlf/container_test.go b/packages/composer-runtime-hlf/container_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/container_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/context.go b/packages/composer-runtime-hlf/context.go index 14e4178ec2..177160e0d8 100644 --- a/packages/composer-runtime-hlf/context.go +++ b/packages/composer-runtime-hlf/context.go @@ -15,15 +15,13 @@ package main import ( - "fmt" - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" + duktape "gopkg.in/olebedev/go-duktape.v3" ) // Context is a Go wrapper around an instance of the Context JavaScript class. type Context struct { - This *otto.Object + VM *duktape.Context DataService *DataService IdentityService *IdentityService EventService *EventService @@ -32,25 +30,15 @@ type Context struct { } // NewContext creates a Go wrapper around a new instance of the Context JavaScript class. -func NewContext(vm *otto.Otto, engine *Engine, stub shim.ChaincodeStubInterface) (result *Context) { - logger.Debug("Entering NewContext", vm, engine, stub) +func NewContext(vm *duktape.Context, engine *Engine, stub shim.ChaincodeStubInterface) (result *Context) { + logger.Debug("Entering NewContext", vm, engine, &stub) defer func() { logger.Debug("Exiting NewContext", result) }() - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.Context", nil, engine.This) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of Context JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of Context JavaScript class is not an object") - } - object := temp.Object() + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) - // Add a pointer to the Go object into the JavaScript object. - result = &Context{This: object} - err = object.Set("$this", result) - if err != nil { - panic(fmt.Sprintf("Failed to store Go object in Context JavaScript object: %v", err)) - } + // Create the new logging service. + result = &Context{VM: vm} // Create the services. result.DataService = NewDataService(vm, result, stub) @@ -59,52 +47,91 @@ func NewContext(vm *otto.Otto, engine *Engine, stub shim.ChaincodeStubInterface) result.HTTPService = NewHTTPService(vm, result, stub) result.QueryService = NewQueryService(vm, result, stub) - // Bind the methods into the JavaScript object. - result.This.Set("getDataService", result.getDataService) - result.This.Set("getIdentityService", result.getIdentityService) - result.This.Set("getEventService", result.getEventService) - result.This.Set("getHTTPService", result.getHTTPService) - result.This.Set("getQueryService", result.getQueryService) + // Find the JavaScript engine object. + vm.PushGlobalStash() // [ stash ] + vm.GetPropString(-1, "engine") // [ stash theEngine ] + // Create a new instance of the JavaScript Context class. + vm.PushGlobalObject() // [ stash theEngine global ] + vm.GetPropString(-1, "composer") // [ stash theEngine global composer ] + vm.GetPropString(-1, "Context") // [ stash theEngine global composer Context ] + vm.Dup(-4) // [ stash theEngine global composer Context theEngine ] + err := vm.Pnew(1) // [ stash theEngine global composer theContext ] + if err != nil { + panic(err) + } + + // Store the context into the global stash. + vm.DupTop() // [ stash theEngine global composer theContext theContext ] + vm.PutPropString(-6, "context") // [ stash theEngine global composer theContext ] + + // Bind the methods into the JavaScript object. + vm.PushGoFunction(result.getDataService) // [ stash theEngine global composer theContext getDataService ] + vm.PutPropString(-2, "getDataService") // [ stash theEngine global composer theContext ] + vm.PushGoFunction(result.getIdentityService) // [ stash theEngine global composer theContext getIdentityService ] + vm.PutPropString(-2, "getIdentityService") // [ stash theEngine global composer theContext ] + vm.PushGoFunction(result.getEventService) // [ stash theEngine global composer theContext getEventService ] + vm.PutPropString(-2, "getEventService") // [ stash theEngine global composer theContext ] + vm.PushGoFunction(result.getHTTPService) // [ stash theEngine global composer theContext getHTTPService ] + vm.PutPropString(-2, "getHTTPService") // [ stash theEngine global composer theContext ] + vm.PushGoFunction(result.getQueryService) // [ stash theEngine global composer theContext getQueryService ] + vm.PutPropString(-2, "getQueryService") // [ stash theEngine global composer theContext getQueryService ] + + // Return the new context. return result } -// getDataService ... -func (context *Context) getDataService(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering Context.getDataService", call) +// getDataService returns the data service to use. +func (context *Context) getDataService(vm *duktape.Context) (result int) { + logger.Debug("Entering Context.getDataService", vm) defer func() { logger.Debug("Exiting Context.getDataService", result) }() - return context.DataService.This.Value() + // Return the JavaScript object from the global stash. + vm.PushGlobalStash() + vm.GetPropString(-1, "dataService") + return 1 } -// getIdentityService ... -func (context *Context) getIdentityService(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering Context.getIdentityService", call) +// getIdentityService returns the identity service to use. +func (context *Context) getIdentityService(vm *duktape.Context) (result int) { + logger.Debug("Entering Context.getIdentityService", vm) defer func() { logger.Debug("Exiting Context.getIdentityService", result) }() - return context.IdentityService.This.Value() + // Return the JavaScript object from the global stash. + vm.PushGlobalStash() + vm.GetPropString(-1, "identityService") + return 1 } -// getEventService ... -func (context *Context) getEventService(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering Context.getEventService", call) - defer func() { logger.Debug("Exiting Context.getEventService", result) }() +// getHTTPService returns the http service to use. +func (context *Context) getHTTPService(vm *duktape.Context) (result int) { + logger.Debug("Entering Context.getHTTPService", vm) + defer func() { logger.Debug("Exiting Context.getHTTPService", result) }() - return context.EventService.This.Value() + // Return the JavaScript object from the global stash. + vm.PushGlobalStash() + vm.GetPropString(-1, "httpService") + return 1 } -// getHTTPService ... -func (context *Context) getHTTPService(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering Context.getHTTPService", call) - defer func() { logger.Debug("Exiting Context.getHTTPService", result) }() +// getEventService returns the event service to use. +func (context *Context) getEventService(vm *duktape.Context) (result int) { + logger.Debug("Entering Context.getEventService", vm) + defer func() { logger.Debug("Exiting Context.getEventService", result) }() - return context.HTTPService.This.Value() + // Return the JavaScript object from the global stash. + vm.PushGlobalStash() + vm.GetPropString(-1, "eventService") + return 1 } -// getQueryService ... -func (context *Context) getQueryService(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering Context.getQueryService", call) +// getQueryService returns the query service to use. +func (context *Context) getQueryService(vm *duktape.Context) (result int) { + logger.Debug("Entering Context.getQueryService", vm) defer func() { logger.Debug("Exiting Context.getQueryService", result) }() - return context.QueryService.This.Value() + // Return the JavaScript object from the global stash. + vm.PushGlobalStash() + vm.GetPropString(-1, "queryService") + return 1 } diff --git a/packages/composer-runtime-hlf/context_test.go b/packages/composer-runtime-hlf/context_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/context_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/datacollection.go b/packages/composer-runtime-hlf/datacollection.go index ec0f3bb297..967e452f94 100644 --- a/packages/composer-runtime-hlf/datacollection.go +++ b/packages/composer-runtime-hlf/datacollection.go @@ -15,290 +15,324 @@ package main import ( - "fmt" + duktape "gopkg.in/olebedev/go-duktape.v3" "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" ) // DataCollection is a Go wrapper around an instance of the DataCollection JavaScript class. type DataCollection struct { - This *otto.Object - Stub shim.ChaincodeStubInterface - TableName string + VM *duktape.Context + Stub shim.ChaincodeStubInterface + CollectionID string } // NewDataCollection creates a Go wrapper around a new instance of the DataCollection JavaScript class. -func NewDataCollection(vm *otto.Otto, dataService *DataService, stub shim.ChaincodeStubInterface, tableName string) (result *DataCollection) { - logger.Debug("Entering NewDataCollection", vm, dataService, stub) +func NewDataCollection(vm *duktape.Context, dataService *DataService, stub shim.ChaincodeStubInterface, collectionID string) (result *DataCollection) { + logger.Debug("Entering NewDataCollection", vm, dataService, &stub) defer func() { logger.Debug("Exiting NewDataCollection", result) }() - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.DataCollection", nil, dataService.This) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of DataCollection JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of DataCollection JavaScript class is not an object") - } - object := temp.Object() + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) + + // Create the new data service. + result = &DataCollection{VM: vm, Stub: stub, CollectionID: collectionID} - // Add a pointer to the Go object into the JavaScript object. - result = &DataCollection{This: temp.Object(), Stub: stub, TableName: tableName} - err = object.Set("$this", result) + // Get the data service. + vm.PushGlobalStash() // [ stash ] + vm.GetPropString(-1, "dataService") // [ stash theDataService ] + + // Create a new instance of the JavaScript DataCollection class. + vm.PushGlobalObject() // [ stash theDataService global ] + vm.GetPropString(-1, "composer") // [ stash theDataService global composer ] + vm.GetPropString(-1, "DataCollection") // [ stash theDataService global composer DataCollection ] + vm.Dup(-4) // [ stash theDataService global composer DataCollection theDataService ] + err := vm.Pnew(1) // [ stash theDataService global composer theDataCollection ] if err != nil { - panic(fmt.Sprintf("Failed to store Go object in DataCollection JavaScript object: %v", err)) + panic(err) } + // Store the data collection into the global stash. + vm.DupTop() // [ stash theDataService global composer theDataCollection theDataCollection ] + vm.PutPropString(-6, "dataCollection") // [ stash theDataService global composer theDataCollection ] + // Bind the methods into the JavaScript object. - result.This.Set("_getAll", result.getAll) - result.This.Set("_get", result.get) - result.This.Set("_exists", result.exists) - result.This.Set("_add", result.add) - result.This.Set("_update", result.update) - result.This.Set("_remove", result.remove) - return result + vm.PushGoFunction(result.getAll) // [ stash theDataService global composer theDataCollection getAll ] + vm.PutPropString(-2, "_getAll") // [ stash theDataService global composer theDataCollection ] + vm.PushGoFunction(result.get) // [ stash theDataService global composer theDataCollection get ] + vm.PutPropString(-2, "_get") // [ stash theDataService global composer theDataCollection ] + vm.PushGoFunction(result.exists) // [ stash theDataService global composer theDataCollection exists ] + vm.PutPropString(-2, "_exists") // [ stash theDataService global composer theDataCollection ] + vm.PushGoFunction(result.add) // [ stash theDataService global composer theDataCollection add ] + vm.PutPropString(-2, "_add") // [ stash theDataService global composer theDataCollection ] + vm.PushGoFunction(result.update) // [ stash theDataService global composer theDataCollection update ] + vm.PutPropString(-2, "_update") // [ stash theDataService global composer theDataCollection ] + vm.PushGoFunction(result.remove) // [ stash theDataService global composer theDataCollection remove ] + vm.PutPropString(-2, "_remove") // [ stash theDataService global composer theDataCollection ] + // Return the new data collection. + return result } -// getAll ... -func (dataCollection *DataCollection) getAll(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataCollection.getAll", call) +// getAll retreieves all of the objects in this collection from the world state. +func (dataCollection *DataCollection) getAll(vm *duktape.Context) (result int) { + logger.Debug("Entering DataCollection.getAll", vm) defer func() { logger.Debug("Exiting DataCollection.getAll", result) }() - callback := call.Argument(0) - if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } + // Validate the arguments from JavaScript. + vm.RequireFunction(0) + + // Create the composite key. + // The objects are stored with composite keys of collectionID + objectID. bigUglyMutex.Lock() // FAB-860 avoidance hack. defer bigUglyMutex.Unlock() // FAB-860 avoidance hack. - rows, err := dataCollection.Stub.GetRows(dataCollection.TableName, []shim.Column{}) + rows, err := dataCollection.Stub.GetRows(dataCollection.CollectionID, []shim.Column{}) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(0) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - objects := []interface{}{} + + // Iterate over all the keys returned by the partial query above. + arrIdx := vm.PushArray() + arrCount := uint(0) for row := range rows { - data := row.GetColumns()[1].GetString_() - object, err2 := call.Otto.Call("JSON.parse", nil, data) - if err2 != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err2.Error())) - if err2 != nil { - panic(err2) - } - return otto.UndefinedValue() - } - objects = append(objects, object) - } - _, err = callback.Call(callback, nil, objects) - if err != nil { - panic(err) + + // Read the current key and value. + value := row.GetColumns()[1].GetString_() + + // Parse the current value. + vm.PushString(string(value)) + vm.JsonDecode(-1) + vm.PutPropIndex(arrIdx, arrCount) + arrCount++ + } - return otto.UndefinedValue() + + // Call the callback. + vm.Dup(0) + vm.PushNull() + vm.Dup(arrIdx) + vm.Pcall(2) + return 0 } -// get ... -func (dataCollection *DataCollection) get(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataCollection.get", call) +// get retrieves a specific object in this collection from the world state. +func (dataCollection *DataCollection) get(vm *duktape.Context) (result int) { + logger.Debug("Entering DataCollection.get", vm) defer func() { logger.Debug("Exiting DataCollection.get", result) }() - id, callback := call.Argument(0), call.Argument(1) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } - row, err := dataCollection.Stub.GetRow(dataCollection.TableName, []shim.Column{{Value: &shim.Column_String_{String_: id.String()}}}) + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireFunction(1) + + // Create the composite key. + // The objects are stored with composite keys of collectionID + objectID. + row, err := dataCollection.Stub.GetRow(dataCollection.CollectionID, []shim.Column{{Value: &shim.Column_String_{String_: id}}}) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(1) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } else if len(row.GetColumns()) == 0 { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", fmt.Sprintf("Object with ID '%s' in collection with ID '%s' does not exist", id, dataCollection.TableName))) - if err != nil { + vm.Dup(1) + vm.PushErrorObjectVa(duktape.ErrError, "Object with ID '%s' in collection with ID '%s' does not exist", id, dataCollection.CollectionID) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - data := row.GetColumns()[1].GetString_() - object, err := call.Otto.Call("JSON.parse", nil, data) - if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { - panic(err) - } - return otto.UndefinedValue() - } - _, err = callback.Call(callback, nil, object) - if err != nil { - panic(err) + + // Get the collection. + value := row.GetColumns()[1].GetString_() + + // Parse the current value. + vm.PushString(string(value)) + vm.JsonDecode(-1) + + // Call the callback. + vm.Dup(1) + vm.PushNull() + vm.Dup(-3) + if vm.Pcall(2) == duktape.ExecError { + panic(vm.ToString(-1)) } - return otto.UndefinedValue() + return 0 } -// exists ... -func (dataCollection *DataCollection) exists(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataCollection.exists", call) +// exists checks to see if an object exists in this collection in the world state. +func (dataCollection *DataCollection) exists(vm *duktape.Context) (result int) { + logger.Debug("Entering DataCollection.exists", vm) defer func() { logger.Debug("Exiting DataCollection.exists", result) }() - id, callback := call.Argument(0), call.Argument(1) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } - row, err := dataCollection.Stub.GetRow(dataCollection.TableName, []shim.Column{{Value: &shim.Column_String_{String_: id.String()}}}) + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireFunction(1) + + // Create the composite key. + // The objects are stored with composite keys of collectionID + objectID. + row, err := dataCollection.Stub.GetRow(dataCollection.CollectionID, []shim.Column{{Value: &shim.Column_String_{String_: id}}}) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { - panic(err) - } - return otto.UndefinedValue() - } else if len(row.GetColumns()) == 0 { - _, err = callback.Call(callback, nil, false) - if err != nil { + vm.Dup(1) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - _, err = callback.Call(callback, nil, true) - if err != nil { - panic(err) + + // Call the callback. + vm.Dup(1) + vm.PushNull() + vm.PushBoolean(len(row.GetColumns()) != 0) + if vm.Pcall(2) == duktape.ExecError { + panic(vm.ToString(-1)) } - return otto.UndefinedValue() + return 0 } -// add ... -func (dataCollection *DataCollection) add(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataCollection.add", call) +// add adds an object to this collection in the world satte. +func (dataCollection *DataCollection) add(vm *duktape.Context) (result int) { + logger.Debug("Entering DataCollection.add", vm) defer func() { logger.Debug("Exiting DataCollection.add", result) }() - // force is ignored for this connector. Its provided by the runtime and is required for - // hyper V1 support. - id, object, force, callback := call.Argument(0), call.Argument(1), call.Argument(2), call.Argument(3) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !object.IsObject() { - panic(fmt.Errorf("object not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } else if !force.IsBoolean() { - panic(fmt.Errorf("force not specified or is not a boolean")) - } - - data, err := call.Otto.Call("JSON.stringify", nil, object) - if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { - panic(err) - } - return otto.UndefinedValue() - } + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireObjectCoercible(1) + force := vm.RequireBoolean(2) + vm.RequireFunction(3) + + // Serialize the object. + vm.Dup(1) + vm.JsonEncode(-1) + value := vm.RequireString(-1) + + // Create the composite key. + // The objects are stored with composite keys of collectionID + objectID. inserted, err := dataCollection.Stub.InsertRow( - dataCollection.TableName, + dataCollection.CollectionID, shim.Row{ Columns: []*shim.Column{ - {Value: &shim.Column_String_{String_: id.String()}}, - {Value: &shim.Column_String_{String_: data.String()}}, + {Value: &shim.Column_String_{String_: id}}, + {Value: &shim.Column_String_{String_: value}}, }, }, ) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(3) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() - } else if !inserted { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", fmt.Sprintf("Failed to insert row with id '%s' as row already exists", id))) - if err != nil { - panic(err) + return 0 + } + + if !force { + // Check to see if the object already exists. + if !inserted { + vm.Dup(3) + vm.PushErrorObjectVa(duktape.ErrError, "Failed to add object with ID '%s' as the object already exists", id) + if vm.Pcall(1) == duktape.ExecError { + panic(err) + } + return 0 } - return otto.UndefinedValue() } - _, err = callback.Call(callback, nil) - if err != nil { - panic(err) + + // Call the callback. + vm.Dup(3) + vm.PushNull() + if vm.Pcall(1) == duktape.ExecError { + panic(vm.ToString(-1)) } - return otto.UndefinedValue() + return 0 } -// update ... -func (dataCollection *DataCollection) update(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataCollection.update", call) +// update updates an existing object in this collection in the world state. +func (dataCollection *DataCollection) update(vm *duktape.Context) (result int) { + logger.Debug("Entering DataCollection.update", vm) defer func() { logger.Debug("Exiting DataCollection.update", result) }() - id, object, callback := call.Argument(0), call.Argument(1), call.Argument(2) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !object.IsObject() { - panic(fmt.Errorf("object not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } - data, err := call.Otto.Call("JSON.stringify", nil, object) - if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { - panic(err) - } - return otto.UndefinedValue() - } + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireObjectCoercible(1) + vm.RequireFunction(2) + + // Serialize the object. + vm.Dup(1) + vm.JsonEncode(-1) + value := vm.RequireString(-1) + + // Create the composite key. + // The objects are stored with composite keys of collectionID + objectID. updated, err := dataCollection.Stub.ReplaceRow( - dataCollection.TableName, + dataCollection.CollectionID, shim.Row{ Columns: []*shim.Column{ - {Value: &shim.Column_String_{String_: id.String()}}, - {Value: &shim.Column_String_{String_: data.String()}}, + {Value: &shim.Column_String_{String_: id}}, + {Value: &shim.Column_String_{String_: value}}, }, }, ) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(2) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() - } else if !updated { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", fmt.Sprintf("Failed to update row with id '%s' as row does not exist", id))) - if err != nil { + return 0 + } + + // Check to see if the object already exists. + if !updated { + vm.Dup(2) + vm.PushErrorObjectVa(duktape.ErrError, "Failed to update object with ID '%s' as the object does not exist", id) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - _, err = callback.Call(callback, nil) - if err != nil { - panic(err) + + // Call the callback. + vm.Dup(2) + vm.PushNull() + if vm.Pcall(1) == duktape.ExecError { + panic(vm.ToString(-1)) } - return otto.UndefinedValue() + return 0 } -// remove ... -func (dataCollection *DataCollection) remove(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataCollection.remove", call) +// remove removes an object from this collection in the world state. +func (dataCollection *DataCollection) remove(vm *duktape.Context) (result int) { + logger.Debug("Entering DataCollection.remove", vm) defer func() { logger.Debug("Exiting DataCollection.remove", result) }() - id, callback := call.Argument(0), call.Argument(1) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } - err := dataCollection.Stub.DeleteRow(dataCollection.TableName, []shim.Column{{Value: &shim.Column_String_{String_: id.String()}}}) + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireFunction(1) + + // Create the composite key. + // The objects are stored with composite keys of collectionID + objectID. + err := dataCollection.Stub.DeleteRow(dataCollection.CollectionID, []shim.Column{{Value: &shim.Column_String_{String_: id}}}) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(1) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - _, err = callback.Call(callback, nil) - if err != nil { - panic(err) + + // Call the callback. + vm.Dup(1) + vm.PushNull() + if vm.Pcall(1) == duktape.ExecError { + panic(vm.ToString(-1)) } - return otto.UndefinedValue() + return 0 } diff --git a/packages/composer-runtime-hlf/datacollection_test.go b/packages/composer-runtime-hlf/datacollection_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/datacollection_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/dataservice.go b/packages/composer-runtime-hlf/dataservice.go index 2e4725442f..ae16a666a9 100644 --- a/packages/composer-runtime-hlf/dataservice.go +++ b/packages/composer-runtime-hlf/dataservice.go @@ -15,178 +15,206 @@ package main import ( - "fmt" + duktape "gopkg.in/olebedev/go-duktape.v3" "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" ) +// This is the object type used to form composite keys for the collection of collections. +const collectionObjectType = "$syscollections" + // DataService is a Go wrapper around an instance of the DataService JavaScript class. type DataService struct { - This *otto.Object + VM *duktape.Context Stub shim.ChaincodeStubInterface } // NewDataService creates a Go wrapper around a new instance of the DataService JavaScript class. -func NewDataService(vm *otto.Otto, context *Context, stub shim.ChaincodeStubInterface) (result *DataService) { - logger.Debug("Entering NewDataService", vm, context, stub) +func NewDataService(vm *duktape.Context, context *Context, stub shim.ChaincodeStubInterface) (result *DataService) { + logger.Debug("Entering NewDataService", vm, context, &stub) defer func() { logger.Debug("Exiting NewDataService", result) }() - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.DataService", nil, context.This) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of DataService JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of DataService JavaScript class is not an object") - } - object := temp.Object() + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) - // Add a pointer to the Go object into the JavaScript object. - result = &DataService{This: temp.Object(), Stub: stub} - err = object.Set("$this", result) + // Create the new data service. + result = &DataService{VM: vm, Stub: stub} + + // Create a new instance of the JavaScript DataService class. + vm.PushGlobalObject() // [ global ] + vm.GetPropString(-1, "composer") // [ global composer ] + vm.GetPropString(-1, "DataService") // [ global composer DataService ] + err := vm.Pnew(0) // [ global composer theDataService ] if err != nil { - panic(fmt.Sprintf("Failed to store Go object in DataService JavaScript object: %v", err)) + panic(err) } + // Store the data service into the global stash. + vm.PushGlobalStash() // [ global composer theDataService stash ] + vm.Dup(-2) // [ global composer theDataService stash theDataService ] + vm.PutPropString(-2, "dataService") // [ global composer theDataService stash ] + vm.Pop() // [ global composer theDataService ] + // Bind the methods into the JavaScript object. - result.This.Set("_createCollection", result.createCollection) - result.This.Set("_deleteCollection", result.deleteCollection) - result.This.Set("_getCollection", result.getCollection) - result.This.Set("_existsCollection", result.existsCollection) + vm.PushGoFunction(result.createCollection) // [ global composer theDataService createCollection ] + vm.PutPropString(-2, "_createCollection") // [ global composer theDataService ] + vm.PushGoFunction(result.deleteCollection) // [ global composer theDataService deleteCollection ] + vm.PutPropString(-2, "_deleteCollection") // [ global composer theDataService ] + vm.PushGoFunction(result.getCollection) // [ global composer theDataService getCollection ] + vm.PutPropString(-2, "_getCollection") // [ global composer theDataService ] + vm.PushGoFunction(result.existsCollection) // [ global composer theDataService existsCollection ] + vm.PutPropString(-2, "existsCollection") // [ global composer theDataService ] + + // Return the new data service. return result - } -// createCollection ... -func (dataService *DataService) createCollection(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataService.createCollection", call) +// createCollection creates a collection of objects in the world state. +func (dataService *DataService) createCollection(vm *duktape.Context) (result int) { + logger.Debug("Entering DataService.createCollection", vm) defer func() { logger.Debug("Exiting DataService.createCollection", result) }() - // force is ignored for this connector. It is provided by the runtime but only - // required for hyper V1 connector. - id, force, callback := call.Argument(0), call.Argument(1), call.Argument(2) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } else if !force.IsBoolean() { - panic(fmt.Errorf("force not specified or is not a boolean")) - } + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireBoolean(1) + vm.RequireFunction(2) - - err := dataService.Stub.CreateTable(id.String(), []*shim.ColumnDefinition{ + // Create the composite key. + // The collection is stored with a composite key of collection ID. + err := dataService.Stub.CreateTable(id, []*shim.ColumnDefinition{ {Name: "id", Type: shim.ColumnDefinition_STRING, Key: true}, {Name: "data", Type: shim.ColumnDefinition_STRING, Key: false}, }) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(2) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - dataCollection := NewDataCollection(call.Otto, dataService, dataService.Stub, id.String()) - _, err = callback.Call(callback, nil, dataCollection.This) - if err != nil { - panic(err) + + // Create a new data collection. + NewDataCollection(vm, dataService, dataService.Stub, id) + + // Call the callback. + vm.PushGlobalStash() + vm.GetPropString(-1, "dataCollection") + vm.Dup(2) + vm.PushNull() + vm.Dup(-3) + if vm.Pcall(2) == duktape.ExecError { + panic(vm.ToString(-1)) } - return otto.UndefinedValue() + return 0 } -// deleteCollection ... -func (dataService *DataService) deleteCollection(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataService.deleteCollection", call) +// deleteCollection deletes a collection of objects in the world state. +func (dataService *DataService) deleteCollection(vm *duktape.Context) (result int) { + logger.Debug("Entering DataService.deleteCollection", vm) defer func() { logger.Debug("Exiting DataService.deleteCollection", result) }() - id, callback := call.Argument(0), call.Argument(1) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } - err := dataService.clearTable(id.String()) + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireFunction(1) + + // Remove all of the objects from the collection. + err := dataService.clearCollection(id) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(1) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - err = dataService.Stub.DeleteTable(id.String()) + + // Delete the collection. + err = dataService.Stub.DeleteTable(id) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(1) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - _, err = callback.Call(callback, nil) - if err != nil { - panic(err) + + // Call the callback. + vm.Dup(1) + vm.PushNull() + if vm.Pcall(1) == duktape.ExecError { + panic(vm.ToString(-1)) } - return otto.UndefinedValue() + return 0 } -// getCollection ... -func (dataService *DataService) getCollection(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataService.getCollection", call) +// getCollection retrieves an existing collection from the world state. +func (dataService *DataService) getCollection(vm *duktape.Context) (result int) { + logger.Debug("Entering DataService.getCollection", vm) defer func() { logger.Debug("Exiting DataService.getCollection", result) }() - id, callback := call.Argument(0), call.Argument(1) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } - _, err := dataService.Stub.GetTable(id.String()) + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireFunction(1) + + // Create the composite key. + // The collection is stored with a composite key of collection ID. + _, err := dataService.Stub.GetTable(id) if err != nil { - _, err = callback.Call(callback, call.Otto.MakeCustomError("Error", err.Error())) - if err != nil { + vm.Dup(1) + vm.PushErrorObjectVa(duktape.ErrError, "%s", err.Error()) + if vm.Pcall(1) == duktape.ExecError { panic(err) } - return otto.UndefinedValue() + return 0 } - dataCollection := NewDataCollection(call.Otto, dataService, dataService.Stub, id.String()) - _, err = callback.Call(callback, nil, dataCollection.This) - if err != nil { - panic(err) + + // Create the new data collection. + NewDataCollection(vm, dataService, dataService.Stub, id) + + // Call the callback. + vm.PushGlobalStash() + vm.GetPropString(-1, "dataCollection") + vm.Dup(1) + vm.PushNull() + vm.Dup(-3) + if vm.Pcall(2) == duktape.ExecError { + panic(vm.ToString(-1)) } - return otto.UndefinedValue() + return 0 } -// existsCollection ... -func (dataService *DataService) existsCollection(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataService.existsCollection", call) +// existsCollection checks to see if a collection exists in the world state. +func (dataService *DataService) existsCollection(vm *duktape.Context) (result int) { + logger.Debug("Entering DataService.existsCollection", vm) defer func() { logger.Debug("Exiting DataService.existsCollection", result) }() - id, callback := call.Argument(0), call.Argument(1) - if !id.IsString() { - panic(fmt.Errorf("id not specified or is not a string")) - } else if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } - _, err := dataService.Stub.GetTable(id.String()) + // Validate the arguments from JavaScript. + id := vm.RequireString(0) + vm.RequireFunction(1) - if err != nil { - _, err = callback.Call(callback, nil, false) - if err != nil { - panic(err) - } - return otto.UndefinedValue() - } - _, err = callback.Call(callback, nil, true) - if err != nil { - panic(err) - } - return otto.UndefinedValue() + // Create the composite key. + // The collection is stored with a composite key of collection ID. + _, err := dataService.Stub.GetTable(id) + // Call the callback. + vm.PushGlobalStash() + vm.GetPropString(-1, "dataCollection") + vm.Dup(1) + vm.PushNull() + vm.PushBoolean(err == nil) + if vm.Pcall(2) == duktape.ExecError { + panic(vm.ToString(-1)) + } + return 0 } -// clearTable is called to clear all rows from a table. -func (dataService *DataService) clearTable(tableName string) (err error) { - logger.Debug("Entering DataService.clearTable", tableName) - defer func() { logger.Debug("Exiting DataService.clearTable", err) }() - table, _ := dataService.Stub.GetTable(tableName) +// clearCollection is called to clear all objects from a collection. +func (dataService *DataService) clearCollection(collectionID string) (err error) { + logger.Debug("Entering DataService.clearCollection", collectionID) + defer func() { logger.Debug("Exiting DataService.clearCollection", err) }() + table, _ := dataService.Stub.GetTable(collectionID) if table != nil { keyIndexes := []int{} for index, column := range table.GetColumnDefinitions() { @@ -196,7 +224,7 @@ func (dataService *DataService) clearTable(tableName string) (err error) { } bigUglyMutex.Lock() // FAB-860 avoidance hack. defer bigUglyMutex.Unlock() // FAB-860 avoidance hack. - rows, err := dataService.Stub.GetRows(tableName, []shim.Column{}) + rows, err := dataService.Stub.GetRows(collectionID, []shim.Column{}) if err != nil { return err } @@ -207,7 +235,7 @@ func (dataService *DataService) clearTable(tableName string) (err error) { Value: row.GetColumns()[keyIndex].GetValue(), }) } - err = dataService.Stub.DeleteRow(tableName, keyColumns) + err = dataService.Stub.DeleteRow(collectionID, keyColumns) if err != nil { return err } diff --git a/packages/composer-runtime-hlf/dataservice_test.go b/packages/composer-runtime-hlf/dataservice_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/dataservice_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/engine.go b/packages/composer-runtime-hlf/engine.go index 5e5d712bd8..386eee0a3c 100644 --- a/packages/composer-runtime-hlf/engine.go +++ b/packages/composer-runtime-hlf/engine.go @@ -16,14 +16,13 @@ package main import ( "errors" - "fmt" - "github.com/robertkrimen/otto" + duktape "gopkg.in/olebedev/go-duktape.v3" ) // Engine is a Go wrapper around an instance of the Engine JavaScript class. type Engine struct { - This *otto.Object + VM *duktape.Context } // EngineCallback is a structure used for callbacks from the chaincode. @@ -33,69 +32,53 @@ type EngineCallback struct { } // NewEngine creates a Go wrapper around a new instance of the Engine JavaScript class. -func NewEngine(vm *otto.Otto, container *Container) (result *Engine) { +func NewEngine(vm *duktape.Context, container *Container) (result *Engine) { logger.Debug("Entering NewEngine", vm, container) defer func() { logger.Debug("Exiting NewEngine", result) }() + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) + + // Create the new engine. + result = &Engine{VM: vm} + + // Find the JavaScript container object. + vm.PushGlobalStash() + vm.GetPropString(-1, "container") + // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.Engine", nil, container.This) + vm.PushGlobalObject() // [ stash theContainer global ] + vm.GetPropString(-1, "composer") // [ stash theContainer global composer ] + vm.GetPropString(-1, "Engine") // [ stash theContainer global composer Engine ] + vm.Dup(-4) // [ stash theContainer global composer Engine theContainer ] + err := vm.Pnew(1) // [ stash theContainer global composer theEngine ] if err != nil { - panic(fmt.Sprintf("Failed to create new instance of Engine JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of Engine JavaScript class is not an object") + panic(err) } - object := temp.Object() - // Add a pointer to the Go object into the JavaScript object. - result = &Engine{This: object} - err = object.Set("$this", result) - if err != nil { - panic(fmt.Sprintf("Failed to store Go object in Engine JavaScript object: %v", err)) - } - return result + // Store the engine into the global stash. + vm.PutPropString(-5, "engine") // [ stash theContainer global composer ] + // Return the new engine. + return result } // HandleCallback handles the execution of a JavaScript callback by the chaincode. -func (engine *Engine) handleCallback(channel chan EngineCallback, call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering Engine.handleCallback", channel, call) +func (engine *Engine) handleCallback(channel chan EngineCallback, vm *duktape.Context) (result int) { + logger.Debug("Entering Engine.handleCallback", channel, vm) defer func() { logger.Debug("Exiting Engine.handleCallback", result) }() - // Extract the error and data arguments from the callback. - jsError := call.Argument(0) - jsData := call.Argument(1) - // If the error exists, pass it back to our channel. - if jsError.IsObject() { - jsString, err := jsError.ToString() - if err != nil { - channel <- EngineCallback{ - Result: nil, - Error: fmt.Errorf("Failed to convert JavaScript error into string: %v", err), - } - } else { - channel <- EngineCallback{ - Result: nil, - Error: errors.New(jsString), - } + if !vm.IsNullOrUndefined(0) { + channel <- EngineCallback{ + Result: nil, + Error: errors.New(vm.ToString(0)), } - } else if !jsData.IsUndefined() { - jsString, err := call.Otto.Call("JSON.stringify", nil, jsData) - if err != nil { - channel <- EngineCallback{ - Result: nil, - Error: fmt.Errorf("Failed to serialize JavaScript data as JSON string: %v", err), - } - } else if !jsString.IsString() { - channel <- EngineCallback{ - Result: nil, - Error: fmt.Errorf("Failed to serialize JavaScript data as JSON string"), - } - } else { - channel <- EngineCallback{ - Result: []byte(jsString.String()), - Error: nil, - } + } else if !vm.IsNullOrUndefined(1) { + vm.JsonEncode(1) + channel <- EngineCallback{ + Result: []byte(vm.RequireString(1)), + Error: nil, } } else { channel <- EngineCallback{ @@ -105,8 +88,7 @@ func (engine *Engine) handleCallback(channel chan EngineCallback, call otto.Func } // No return value from the callback. - return otto.UndefinedValue() - + return 0 } // Init executes the Engine.init(context, function, arguments, callback) JavaScript function. @@ -114,47 +96,75 @@ func (engine *Engine) Init(context *Context, function string, arguments []string logger.Debug("Entering Engine.Init", context, function, arguments) defer func() { logger.Debug("Exiting Engine.Init", channel) }() + // Ensure the JavaScript stack is reset. + vm := context.VM + defer vm.SetTop(vm.GetTop()) + // Create a channel to receieve the response from JavaScript. channel = make(chan EngineCallback, 1) // Call the JavaScript code and pass in a callback function. - _, err := engine.This.Call("_init", context.This, function, arguments, func(call otto.FunctionCall) otto.Value { - return engine.handleCallback(channel, call) + vm.PushGlobalStash() // [ stash ] + vm.GetPropString(-1, "engine") // [ stash engine ] + vm.PushString("_init") // [ stash engine _init ] + vm.GetPropString(-3, "context") // [ stash engine _init context ] + vm.PushString(function) // [ stash engine _init context function ] + arrIdx := vm.PushArray() // [ stash engine _init context function arguments ] + for i, argument := range arguments { + vm.PushString(argument) // [ stash engine _init context function arguments argument ] + vm.PutPropIndex(arrIdx, uint(i)) // [ stash engine _init context function arguments ] + } + vm.PushGoFunction(func(vm *duktape.Context) int { // [ stash engine _init context function arguments callback ] + return engine.handleCallback(channel, vm) }) - - // Check for an error being thrown from JavaScript. - if err != nil { + rc := vm.PcallProp(-6, 4) // [ stash engine result ] + if rc == duktape.ExecError { channel <- EngineCallback{ Result: nil, - Error: err, + Error: errors.New(vm.ToString(-1)), } } - return channel + // Return the channel. + return channel } -// Invoke executes the Engine.query(context, function, arguments, callback) JavaScript function. +// Invoke executes the Engine.invoke(context, function, arguments, callback) JavaScript function. func (engine *Engine) Invoke(context *Context, function string, arguments []string) (channel chan EngineCallback) { logger.Debug("Entering Engine.Invoke", context, function, arguments) defer func() { logger.Debug("Exiting Engine.Invoke", channel) }() + // Ensure the JavaScript stack is reset. + vm := context.VM + defer vm.SetTop(vm.GetTop()) + // Create a channel to receieve the response from JavaScript. channel = make(chan EngineCallback, 1) // Call the JavaScript code and pass in a callback function. - _, err := engine.This.Call("_invoke", context.This, function, arguments, func(call otto.FunctionCall) otto.Value { - return engine.handleCallback(channel, call) + vm.PushGlobalStash() // [ stash ] + vm.GetPropString(-1, "engine") // [ stash engine ] + vm.PushString("_invoke") // [ stash engine _invoke ] + vm.GetPropString(-3, "context") // [ stash engine _invoke context ] + vm.PushString(function) // [ stash engine _invoke context function ] + arrIdx := vm.PushArray() // [ stash engine _invoke context function arguments ] + for i, argument := range arguments { + vm.PushString(argument) // [ stash engine _invoke context function arguments argument ] + vm.PutPropIndex(arrIdx, uint(i)) // [ stash engine _invoke context function arguments ] + } + vm.PushGoFunction(func(vm *duktape.Context) int { // [ stash engine _invoke context function arguments callback ] + return engine.handleCallback(channel, vm) }) - - // Check for an error being thrown from JavaScript. - if err != nil { + rc := vm.PcallProp(-6, 4) // [ stash engine result ] + if rc == duktape.ExecError { channel <- EngineCallback{ Result: nil, - Error: err, + Error: errors.New(vm.ToString(-1)), } } - return channel + // Return the channel. + return channel } // Query executes the Engine.query(context, function, arguments, callback) JavaScript function. @@ -162,21 +172,35 @@ func (engine *Engine) Query(context *Context, function string, arguments []strin logger.Debug("Entering Engine.Query", context, function, arguments) defer func() { logger.Debug("Exiting Engine.Query", channel) }() + // Ensure the JavaScript stack is reset. + vm := context.VM + defer vm.SetTop(vm.GetTop()) + // Create a channel to receieve the response from JavaScript. channel = make(chan EngineCallback, 1) // Call the JavaScript code and pass in a callback function. - _, err := engine.This.Call("_query", context.This, function, arguments, func(call otto.FunctionCall) otto.Value { - return engine.handleCallback(channel, call) + vm.PushGlobalStash() // [ stash ] + vm.GetPropString(-1, "engine") // [ stash engine ] + vm.PushString("_query") // [ stash engine _query ] + vm.GetPropString(-3, "context") // [ stash engine _query context ] + vm.PushString(function) // [ stash engine _query context function ] + arrIdx := vm.PushArray() // [ stash engine _query context function arguments ] + for i, argument := range arguments { + vm.PushString(argument) // [ stash engine _query context function arguments argument ] + vm.PutPropIndex(arrIdx, uint(i)) // [ stash engine _query context function arguments ] + } + vm.PushGoFunction(func(vm *duktape.Context) int { // [ stash engine _query context function arguments callback ] + return engine.handleCallback(channel, vm) }) - - // Check for an error being thrown from JavaScript. - if err != nil { + rc := vm.PcallProp(-6, 4) // [ stash engine result ] + if rc == duktape.ExecError { channel <- EngineCallback{ Result: nil, - Error: err, + Error: errors.New(vm.ToString(-1)), } } - return channel + // Return the channel. + return channel } diff --git a/packages/composer-runtime-hlf/engine_test.go b/packages/composer-runtime-hlf/engine_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/engine_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/eventservice.go b/packages/composer-runtime-hlf/eventservice.go index aa23163d8b..1824d077a6 100644 --- a/packages/composer-runtime-hlf/eventservice.go +++ b/packages/composer-runtime-hlf/eventservice.go @@ -15,66 +15,81 @@ package main import ( - "fmt" + duktape "gopkg.in/olebedev/go-duktape.v3" "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" ) // EventService is a go wrapper around the EventService JavaScript class type EventService struct { - This *otto.Object + VM *duktape.Context Stub shim.ChaincodeStubInterface } // NewEventService creates a Go wrapper around a new instance of the EventService JavaScript class. -func NewEventService(vm *otto.Otto, context *Context, stub shim.ChaincodeStubInterface) (result *EventService) { +func NewEventService(vm *duktape.Context, context *Context, stub shim.ChaincodeStubInterface) (result *EventService) { logger.Debug("Entering NewEventService", vm, context, &stub) defer func() { logger.Debug("Exiting NewEventServce", result) }() - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.EventService", nil, context.This) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of EventService JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of EventService JavaScript class is not an object") - } - object := temp.Object() + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) - // Add a pointer to the Go object into the JavaScript object. - result = &EventService{This: temp.Object(), Stub: stub} - err = object.Set("$this", result) + // Create a new event service + result = &EventService{VM: vm, Stub: stub} + + //Create a new instance of the JavaScript EventService class. + vm.PushGlobalObject() // [ global ] + vm.GetPropString(-1, "composer") // [ global composer ] + vm.GetPropString(-1, "EventService") // [ global composer EventService ] + err := vm.Pnew(0) // [ global composer theEventService ] if err != nil { - panic(fmt.Sprintf("Failed to store Go object in EventService JavaScript object: %v", err)) + panic(err) } + // Store the event service into the global stash. + vm.PushGlobalStash() // [ global composer theEventService stash ] + vm.Dup(-2) // [ global composer theEventService stash theEventService ] + vm.PutPropString(-2, "eventService") // [ global composer theEventService stash ] + vm.Pop() // [ global composer theEventService ] + // Bind the methods into the JavaScript object. - result.This.Set("_transactionCommit", result.transactionCommit) + vm.PushGoFunction(result.transactionCommit) // [ global composer theEventService transactionCommit ] + vm.PushString("bind") // [ global composer theEventService transactionCommit "bind" ] + vm.Dup(-3) // [ global composer theEventService transactionCommit "bind" theEventService ] + vm.PcallProp(-3, 1) // [ global composer theEventService transactionCommit boundTransactionCommit ] + vm.PutPropString(-3, "_transactionCommit") // [ global composer theEventService transactionCommit ] + + // Return a new event service return result } // Serializes the buffered events and emits them -func (eventService *EventService) transactionCommit(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering EventService.transactionCommit", call) +func (eventService *EventService) transactionCommit(vm *duktape.Context) (result int) { + logger.Debug("Entering EventService.transactionCommit", vm) defer func() { logger.Debug("Exiting EventService.transactionCommit", result) }() - callback := call.Argument(0) + // Validate the arguments from JavaScript. + vm.RequireFunction(0) - value, err := call.This.Object().Call("serializeBuffer") + vm.PushThis() // [ theEventService ] + vm.GetPropString(-1, "getEvents") // [ theEventService, getEvents ] + vm.RequireFunction(-1) // [ theEventService, getEvents ] + vm.Dup(-2) // [ theEventService, getEvents, theEventService ] + vm.CallMethod(0) // [ theEventService, returnValue ] + vm.RequireObjectCoercible(-1) // [ theEventService, returnValue ] + vm.JsonEncode(-1) // [ theEventService, returnValue ] + value := vm.RequireString(-1) // [ theEventService, returnValue ] - if err != nil { - panic(err) - } - - if len(value.String()) > 0 { - logger.Debug("Emitting event from EventService.transactionCommit", value.String()) - eventService.Stub.SetEvent("composer", []byte(value.String())) + if len(value) > 0 { + logger.Debug("Emitting event from EventService.transactionCommit", value) + eventService.Stub.SetEvent("composer", []byte(value)) } - _, err = callback.Call(callback, nil) - if err != nil { - panic(err) + // Call the callback. + vm.Dup(0) + vm.PushNull() + if vm.Pcall(1) == duktape.ExecError { + panic(vm.ToString(-1)) } - - return otto.UndefinedValue() + return 0 } diff --git a/packages/composer-runtime-hlf/httpservice.go b/packages/composer-runtime-hlf/httpservice.go index 72ce983c4b..b11610140f 100644 --- a/packages/composer-runtime-hlf/httpservice.go +++ b/packages/composer-runtime-hlf/httpservice.go @@ -15,19 +15,19 @@ package main import ( + "bytes" "encoding/json" - "fmt" "io/ioutil" "net/http" - "strings" + + duktape "gopkg.in/olebedev/go-duktape.v3" "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" ) // HTTPService is a go wrapper around the HTTPService JavaScript class type HTTPService struct { - This *otto.Object + VM *duktape.Context Stub shim.ChaincodeStubInterface } @@ -38,76 +38,62 @@ type HTTPResponse struct { } // NewHTTPService creates a Go wrapper around a new instance of the HTTPService JavaScript class. -func NewHTTPService(vm *otto.Otto, context *Context, stub shim.ChaincodeStubInterface) (result *HTTPService) { - logger.Debug("Entering NewHTTPService", vm, context, &stub) - defer func() { logger.Debug("Exiting NewHTTPService", result) }() - - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.HTTPService", nil, context.This) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of HTTPService JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of HTTPService JavaScript class is not an object") - } - object := temp.Object() - - // Add a pointer to the Go object into the JavaScript object. - result = &HTTPService{This: temp.Object(), Stub: stub} - err = object.Set("$this", result) - if err != nil { - panic(fmt.Sprintf("Failed to store Go object in HTTPService JavaScript object: %v", err)) - } - - // Bind the methods into the JavaScript object. - result.This.Set("_post", result.post) - return result -} - -// HTTP POST to an external REST service and return a Promise to the results -func (httpService *HTTPService) post(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering HTTPService.post", call) - defer func() { logger.Debug("Exiting HTTPService.post", result) }() - - urlValue, err := call.This.Object().Get("url") - - if err != nil { - panic(err) - } - - url, err := urlValue.ToString() +func NewHTTPService(vm *duktape.Context, context *Context, stub shim.ChaincodeStubInterface) (result *HTTPService) { + logger.Debug("Entering HTTPService", vm, context, &stub) + defer func() { logger.Debug("Exiting HTTPService", result) }() - if err != nil { - panic(err) - } - - dataValue, err := call.This.Object().Get("data") - - // if err != nil { - // panic(err) - // } + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) - // data, err := dataValue.ToString() + // Create a new http service + result = &HTTPService{VM: vm, Stub: stub} + //Create a new instance of the JavaScript HTTPService class. + vm.PushGlobalObject() // [ global ] + vm.GetPropString(-1, "composer") // [ global composer ] + vm.GetPropString(-1, "HTTPService") // [ global composer HTTPService ] + err := vm.Pnew(0) // [ global composer theHTTPService ] if err != nil { panic(err) } - dataJSONValue, err := call.Otto.Call("JSON.stringify", nil, dataValue) + // Store the http service into the global stash. + vm.PushGlobalStash() // [ global composer theHTTPService stash ] + vm.Dup(-2) // [ global composer theHTTPService stash theHTTPService ] + vm.PutPropString(-2, "httpService") // [ global composer theHTTPService stash ] + vm.Pop() // [ global composer theHTTPService ] - if err != nil { - panic(err) - } + // Bind the methods into the JavaScript object. + vm.PushGoFunction(result.post) // [ global composer theHTTPService post ] + vm.PushString("bind") // [ global composer theHTTPService post "bind" ] + vm.Dup(-3) // [ global composer theHTTPService post "bind" theHTTPService ] + vm.PcallProp(-3, 1) // [ global composer theHTTPService post boundCommit ] + vm.PutPropString(-3, "_post") // [ global composer theHTTPService _post ] - dataJSON, err := dataJSONValue.ToString() + // Return a new http service + return result +} - if err != nil { - panic(err) - } +// HTTP POST to a URL and return a Promise to the reponse to the caller +func (httpService *HTTPService) post(vm *duktape.Context) (result int) { + logger.Debug("Entering HTTPService.post", vm) + defer func() { logger.Debug("Exiting HTTPService.post", result) }() - logger.Debug("HTTPService.post data", dataJSON) + vm.PushThis() // [ theHttpService ] + vm.PushString("data") // [ theHttpService data ] + vm.GetProp(-2) // [ theHttpService theData ] + vm.JsonEncode(-1) // [ theHttpService theDataJson ] + data := vm.RequireString(-1) + logger.Debug("HTTPService.post data", data) + vm.Pop() // [ theHttpService ] + vm.PushString("url") // [ theHttpService url ] + vm.GetProp(-2) // [ theHttpService theURL ] + url := vm.RequireString(-1) // [ theHttpService, theUrl ] logger.Debug("HTTPService.post url", url) + vm.Pop() // [ theHttpService ] - req, err := http.NewRequest("POST", url, strings.NewReader(dataJSON)) + var jsonStr = []byte(data) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) req.Header.Set("X-Composer-Version", version) req.Header.Set("Content-Type", "application/json") @@ -137,11 +123,10 @@ func (httpService *HTTPService) post(call otto.FunctionCall) (result otto.Value) logger.Info("JSON response " + string(jsonResponse)) - promise, err := call.Otto.Call("Promise.resolve", nil, string(jsonResponse)) - - if err != nil { - panic(err) - } + // push a Promise that resolves to the JSON response as a string + vm.PushString("Promise.resolve(" + string(jsonResponse) + ")") + vm.Eval() - return promise + // a return code of 1 signifies that the top of the stack should be returned to the caller + return 1 } diff --git a/packages/composer-runtime-hlf/identityservice.go b/packages/composer-runtime-hlf/identityservice.go index bebea10941..51ef5d35fa 100644 --- a/packages/composer-runtime-hlf/identityservice.go +++ b/packages/composer-runtime-hlf/identityservice.go @@ -15,60 +15,62 @@ package main import ( - "fmt" + duktape "gopkg.in/olebedev/go-duktape.v3" "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" ) // IdentityService is a Go wrapper around an instance of the IdentityService JavaScript class. type IdentityService struct { - This *otto.Object + VM *duktape.Context Stub shim.ChaincodeStubInterface } // NewIdentityService creates a Go wrapper around a new instance of the IdentityService JavaScript class. -func NewIdentityService(vm *otto.Otto, context *Context, stub shim.ChaincodeStubInterface) (result *IdentityService) { - logger.Debug("Entering NewIdentityService", vm, context, stub) +func NewIdentityService(vm *duktape.Context, context *Context, stub shim.ChaincodeStubInterface) (result *IdentityService) { + logger.Debug("Entering NewIdentityService", vm, context, &stub) defer func() { logger.Debug("Exiting NewIdentityService", result) }() - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.IdentityService", nil, context.This) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of IdentityService JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of IdentityService JavaScript class is not an object") - } - object := temp.Object() + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) + + // Create the new identity service. + result = &IdentityService{VM: vm, Stub: stub} - // Add a pointer to the Go object into the JavaScript object. - result = &IdentityService{This: temp.Object(), Stub: stub} - err = object.Set("$this", result) + // Create a new instance of the JavaScript IdentityService class. + vm.PushGlobalObject() // [ global ] + vm.GetPropString(-1, "composer") // [ global composer ] + vm.GetPropString(-1, "IdentityService") // [ global composer IdentityService ] + err := vm.Pnew(0) // [ global composer theIdentityService ] if err != nil { - panic(fmt.Sprintf("Failed to store Go object in IdentityService JavaScript object: %v", err)) + panic(err) } + // Store the identity service into the global stash. + vm.PushGlobalStash() // [ global composer theIdentityService stash ] + vm.Dup(-2) // [ global composer theIdentityService stash theIdentityService ] + vm.PutPropString(-2, "identityService") // [ global composer theIdentityService stash ] + vm.Pop() // [ global composer theIdentityService ] + // Bind the methods into the JavaScript object. - result.This.Set("getCurrentUserID", result.getCurrentUserID) - return result + vm.PushGoFunction(result.getCurrentUserID) // [ global composer theIdentityService getCurrentUserID ] + vm.PutPropString(-2, "getCurrentUserID") // [ global composer theIdentityService ] + // Return the new identity service. + return result } -// getCurrentUserID ... -func (identityService *IdentityService) getCurrentUserID(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering DataService.getCurrentUserID", call) - defer func() { logger.Debug("Exiting DataService.getCurrentUserID", result) }() +// getCurrentUserID retrieves the userID attribute from the users certificate. +func (identityService *IdentityService) getCurrentUserID(vm *duktape.Context) (result int) { + logger.Debug("Entering IdentityService.getCurrentUserID", vm) + defer func() { logger.Debug("Exiting IdentityService.getCurrentUserID", result) }() // Read the userID attribute value. bytes, err := identityService.Stub.ReadCertAttribute("userID") if err != nil { - return otto.NullValue() - } - value := string(bytes) - result, err = otto.ToValue(value) - if err != nil { - panic(call.Otto.MakeCustomError("Error", err.Error())) + vm.PushNull() + } else { + vm.PushString(string(bytes)) } - return result - + return 1 } diff --git a/packages/composer-runtime-hlf/identityservice_test.go b/packages/composer-runtime-hlf/identityservice_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/identityservice_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/loggingservice.go b/packages/composer-runtime-hlf/loggingservice.go index 6923b5810f..9184057626 100644 --- a/packages/composer-runtime-hlf/loggingservice.go +++ b/packages/composer-runtime-hlf/loggingservice.go @@ -15,100 +15,108 @@ package main import ( - "fmt" - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" + duktape "gopkg.in/olebedev/go-duktape.v3" ) // LoggingService is a Go wrapper around an instance of the LoggingService JavaScript class. type LoggingService struct { - This *otto.Object + VM *duktape.Context Stub shim.ChaincodeStubInterface } // NewLoggingService creates a Go wrapper around a new instance of the LoggingService JavaScript class. -func NewLoggingService(vm *otto.Otto, container *Container, stub shim.ChaincodeStubInterface) (result *LoggingService) { - logger.Debug("Entering NewLoggingService", vm, container, stub) +func NewLoggingService(vm *duktape.Context, container *Container, stub shim.ChaincodeStubInterface) (result *LoggingService) { + logger.Debug("Entering NewLoggingService", vm, container, &stub) defer func() { logger.Debug("Exiting NewLoggingService", result) }() - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.LoggingService", nil, container.This) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of LoggingService JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of LoggingService JavaScript class is not an object") - } - object := temp.Object() + // Ensure the JavaScript stack is reset. + defer vm.SetTop(vm.GetTop()) - // Add a pointer to the Go object into the JavaScript object. - result = &LoggingService{This: temp.Object(), Stub: stub} - err = object.Set("$this", result) + // Create the new logging service. + result = &LoggingService{VM: vm, Stub: stub} + + // Create a new instance of the JavaScript LoggingService class. + vm.PushGlobalObject() // [ global ] + vm.GetPropString(-1, "composer") // [ global composer ] + vm.GetPropString(-1, "LoggingService") // [ global composer LoggingService ] + err := vm.Pnew(0) // [ global composer theLoggingService ] if err != nil { - panic(fmt.Sprintf("Failed to store Go object in LoggingService JavaScript object: %v", err)) + panic(err) } + // Store the logging service into the global stash. + vm.PushGlobalStash() // [ global composer theLoggingService stash ] + vm.Dup(-2) // [ global composer theLoggingService stash theLoggingService ] + vm.PutPropString(-2, "loggingService") // [ global composer theLoggingService stash ] + vm.Pop() // [ global composer theLoggingService ] + // Bind the methods into the JavaScript object. - result.This.Set("logCritical", result.logCritical) - result.This.Set("logDebug", result.logDebug) - result.This.Set("logError", result.logError) - result.This.Set("logInfo", result.logInfo) - result.This.Set("logNotice", result.logNotice) - result.This.Set("logWarning", result.logWarning) + vm.PushGoFunction(result.logCritical) // [ global composer theLoggingService logCritical ] + vm.PutPropString(-2, "logCritical") // [ global composer theLoggingService ] + vm.PushGoFunction(result.logDebug) // [ global composer theLoggingService logDebug ] + vm.PutPropString(-2, "logDebug") // [ global composer theLoggingService ] + vm.PushGoFunction(result.logError) // [ global composer theLoggingService logError ] + vm.PutPropString(-2, "logError") // [ global composer theLoggingService ] + vm.PushGoFunction(result.logInfo) // [ global composer theLoggingService logInfo ] + vm.PutPropString(-2, "logInfo") // [ global composer theLoggingService ] + vm.PushGoFunction(result.logNotice) // [ global composer theLoggingService logNotice ] + vm.PutPropString(-2, "logNotice") // [ global composer theLoggingService ] + vm.PushGoFunction(result.logWarning) // [ global composer theLoggingService logWarning ] + vm.PutPropString(-2, "logWarning") // [ global composer theLoggingService ] + + // Return the new logging service. return result - } -func (loggingService *LoggingService) getLogInserts(call otto.FunctionCall) (result []interface{}) { +// getLogInserts extracts the list of JavaScript arguments and converts them into a Go array. +func (loggingService *LoggingService) getLogInserts(vm *duktape.Context) (result []interface{}) { result = []interface{}{} - for _, arg := range call.ArgumentList { - str, err := arg.ToString() - if err != nil { - str = err.Error() - } + for i := 0; i < vm.GetTop(); i++ { + str := vm.ToString(i) result = append(result, str) } return result } -// LogCritical ... -func (loggingService *LoggingService) logCritical(call otto.FunctionCall) otto.Value { - strings := loggingService.getLogInserts(call) +// logCritical writes a critical message to the log. +func (loggingService *LoggingService) logCritical(vm *duktape.Context) (result int) { + strings := loggingService.getLogInserts(vm) logger.Critical(strings...) - return otto.UndefinedValue() + return 0 } -// LogDebug ... -func (loggingService *LoggingService) logDebug(call otto.FunctionCall) otto.Value { - strings := loggingService.getLogInserts(call) +// logDebug writes a debug message to the log. +func (loggingService *LoggingService) logDebug(vm *duktape.Context) (result int) { + strings := loggingService.getLogInserts(vm) logger.Debug(strings...) - return otto.UndefinedValue() + return 0 } -// LogError ... -func (loggingService *LoggingService) logError(call otto.FunctionCall) otto.Value { - strings := loggingService.getLogInserts(call) +// logError writes a error message to the log. +func (loggingService *LoggingService) logError(vm *duktape.Context) (result int) { + strings := loggingService.getLogInserts(vm) logger.Error(strings...) - return otto.UndefinedValue() + return 0 } -// LogInfo ... -func (loggingService *LoggingService) logInfo(call otto.FunctionCall) otto.Value { - strings := loggingService.getLogInserts(call) +// logInfo writes a info message to the log. +func (loggingService *LoggingService) logInfo(vm *duktape.Context) (result int) { + strings := loggingService.getLogInserts(vm) logger.Info(strings...) - return otto.UndefinedValue() + return 0 } -// LogNotice ... -func (loggingService *LoggingService) logNotice(call otto.FunctionCall) otto.Value { - strings := loggingService.getLogInserts(call) +// logNotice writes a notice message to the log. +func (loggingService *LoggingService) logNotice(vm *duktape.Context) (result int) { + strings := loggingService.getLogInserts(vm) logger.Notice(strings...) - return otto.UndefinedValue() + return 0 } -// LogWarning ... -func (loggingService *LoggingService) logWarning(call otto.FunctionCall) otto.Value { - strings := loggingService.getLogInserts(call) +// logWarning writes a warning message to the log. +func (loggingService *LoggingService) logWarning(vm *duktape.Context) (result int) { + strings := loggingService.getLogInserts(vm) logger.Warning(strings...) - return otto.UndefinedValue() + return 0 } diff --git a/packages/composer-runtime-hlf/loggingservice_test.go b/packages/composer-runtime-hlf/loggingservice_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlf/loggingservice_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlf/main.go b/packages/composer-runtime-hlf/main.go index d82bb6ed9e..852f2de06e 100644 --- a/packages/composer-runtime-hlf/main.go +++ b/packages/composer-runtime-hlf/main.go @@ -25,7 +25,7 @@ import ( var bigUglyMutex = &sync.Mutex{} // The logger for all code in this chaincode. -var logger = shim.NewLogger("Concerto") +var logger = shim.NewLogger("Composer") // main starts the shim, which establishes the connection to the Hyperledger // Fabric and registers the chaincode for deploys, queries, and invokes. diff --git a/packages/composer-runtime-hlf/queryservice.go b/packages/composer-runtime-hlf/queryservice.go index 3270d5f856..4fd1ecf146 100644 --- a/packages/composer-runtime-hlf/queryservice.go +++ b/packages/composer-runtime-hlf/queryservice.go @@ -15,59 +15,68 @@ package main import ( - "fmt" - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/robertkrimen/otto" + duktape "gopkg.in/olebedev/go-duktape.v3" ) // QueryService is a go wrapper around the QueryService JavaScript class type QueryService struct { - This *otto.Object + VM *duktape.Context Stub shim.ChaincodeStubInterface } -// NewQueryService creates a Go wrapper around a new instance of the QueryService JavaScript class. -func NewQueryService(vm *otto.Otto, context *Context, stub shim.ChaincodeStubInterface) (result *QueryService) { - logger.Debug("Entering NewQueryService", vm, context, &stub) - defer func() { logger.Debug("Exiting NewQueryService", result) }() +// NewQueryService creates a Go Wrapper around a new instance of the QueryService JavaScript class +func NewQueryService(vm *duktape.Context, context *Context, stub shim.ChaincodeStubInterface) (result *QueryService) { + logger.Debug("Entering QueryService", vm, context, &stub) + defer func() { logger.Debug("Exiting QueryService", result) }() - // Create a new instance of the JavaScript chaincode class. - temp, err := vm.Call("new concerto.QueryService", nil, context.This) - if err != nil { - panic(fmt.Sprintf("Failed to create new instance of QueryService JavaScript class: %v", err)) - } else if !temp.IsObject() { - panic("New instance of QueryService JavaScript class is not an object") - } - object := temp.Object() + // Ensure the JavaScript stack is reset + defer vm.SetTop(vm.GetTop()) - // Add a pointer to the Go object into the JavaScript object. - result = &QueryService{This: temp.Object(), Stub: stub} - err = object.Set("$this", result) + // Create a new Query service + result = &QueryService{VM: vm, Stub: stub} + + // Create a new instance of the JavaScript QueryService class + vm.PushGlobalObject() // [ global ] + vm.GetPropString(-1, "composer") // [ global composer ] + vm.GetPropString(-1, "QueryService") //[ global composer QueryService ] + err := vm.Pnew(0) // [ global composer theQueryService ] if err != nil { - panic(fmt.Sprintf("Failed to store Go object in QueryService JavaScript object: %v", err)) + logger.Debug("Error received on vm.Pnew(0)", err) + panic(err) } - // Bind the methods into the JavaScript object. - result.This.Set("_queryNative", result.queryNative) + // Store the Query service into the global stash + vm.PushGlobalStash() // [ global composer theQueryService stash] + vm.Dup(-2) // [ global composer theQueryService stash theQueryService ] + vm.PutPropString(-2, "queryService") // [ global composer theQueryService stash ] + vm.Pop() // [ global composer theQueryService] + + //Bind the methods into the JavaScript object. + vm.PushGoFunction(result.queryNative) // [global composer theQueryService query] + vm.PushString("bind") // [global composer theQueryService query "bind"] + vm.Dup(-3) // [global composer theQueryService query "bind" theQueryService] + vm.PcallProp(-3, 1) // [global composer theQueryService query boundCommit ] + vm.PutPropString(-3, "_queryNative") // [global composer theQueryService ] + + // return a new query service + return result } -// queryNative ... -func (queryService *QueryService) queryNative(call otto.FunctionCall) (result otto.Value) { - logger.Debug("Entering QueryService.queryNative", call) +// Execute a CouchDB query and returns the result to the caller +func (queryService *QueryService) queryNative(vm *duktape.Context) (result int) { + logger.Debug("Entering QueryService.queryNative", vm) defer func() { logger.Debug("Exiting QueryService.queryNative", result) }() - callback := call.Argument(0) - if !callback.IsFunction() { - panic(fmt.Errorf("callback not specified or is not a string")) - } + // argument 0 is the CouchDB queryString + queryString := vm.RequireString(0) + logger.Debug("QueryService.queryNative CouchDB query: ", queryString) - object, _ := call.Otto.Object(`({data: "Not implemented"})`) - _, err := callback.Call(callback, nil, object) + // argument 1 is the callback function (err,response) + vm.RequireFunction(1) - if err != nil { - panic(err) - } - return otto.UndefinedValue() + vm.PushErrorObjectVa(duktape.ErrError, "%s", "The native query functionality is not available on this Blockchain platform") + vm.Throw() + return 0 } diff --git a/packages/composer-runtime-hlf/scripts/build.js b/packages/composer-runtime-hlf/scripts/build.js index 5a0bdd8e0d..0de681a9b0 100755 --- a/packages/composer-runtime-hlf/scripts/build.js +++ b/packages/composer-runtime-hlf/scripts/build.js @@ -22,10 +22,10 @@ const zlib = require('zlib'); const sourceFile = require.resolve('composer-runtime'); const sourcePolyfill = require.resolve('babel-polyfill/dist/polyfill.min.js'); -const targetFile = path.resolve(__dirname, '..', 'concerto.js.go'); -const targetFile2 = path.resolve(__dirname, '..', 'concerto.js.map'); -const targetFile3 = path.resolve(__dirname, '..', 'concerto.js'); -const targetFile4 = path.resolve(__dirname, '..', 'concerto.min.js'); +const targetFile = path.resolve(__dirname, '..', 'composer.js.go'); +const targetFile2 = path.resolve(__dirname, '..', 'composer.js.map'); +const targetFile3 = path.resolve(__dirname, '..', 'composer.js'); +const targetFile4 = path.resolve(__dirname, '..', 'composer.min.js'); fs.ensureFileSync(targetFile); fs.ensureFileSync(targetFile2); @@ -88,21 +88,7 @@ return Promise.resolve() }) .then(() => { return new Promise((resolve, reject) => { - const rstream = browserify(sourceFile, { standalone: 'concerto', debug: true }) - .transform('browserify-replace', { replace: [ - // These ugly hacks are due to Go and Otto only supporting Go regexes. - // Go regexes do not support PCRE features such as lookahead. - { - // This ugly hack changes a JavaScript only regex used by acorn into something safe for Go. - from: /\[\^\]/g, - to: '[^\\x{FFFF}]' - }, - { - // This ugly hack changes a JavaScript only regex used by thenify into something safe for Go. - from: /\/\\s\|bound\(\?!\$\)\/g/g, - to: '/(\s)|(bound)./g' - } - ], global: true }) + const rstream = browserify(sourceFile, { standalone: 'composer', debug: true }) // The ignore is to workaround these issues: // https://github.com/Starcounter-Jack/JSON-Patch/issues/140 .transform('babelify', { presets: [ 'latest' ], global: true, ignore: /fast-json-patch/ }) @@ -127,13 +113,13 @@ return Promise.resolve() }) .then(() => { return new Promise((resolve, reject) => { - wstream.write('\nconst concertoJavaScriptSource = "'); + wstream.write('\nconst composerJavaScriptSource = "'); const rstream = fs.createReadStream(targetFile4); const gzip = zlib.createGzip(); const cstream = rstream.pipe(gzip); cstream.setEncoding('base64'); cstream.on('end', () => { - wstream.write('"\n\nvar concertoJavaScript = parseEmbeddedData(concertoJavaScriptSource)\n'); + wstream.write('"\n\nvar composerJavaScript = parseEmbeddedData(composerJavaScriptSource)\n'); wstream.end(); resolve(); }); diff --git a/packages/composer-runtime-hlf/vendor/github.com/robertkrimen/otto b/packages/composer-runtime-hlf/vendor/github.com/robertkrimen/otto deleted file mode 160000 index 21ec96599b..0000000000 --- a/packages/composer-runtime-hlf/vendor/github.com/robertkrimen/otto +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 21ec96599b1279b5673e4df0097dd56bb8360068 diff --git a/packages/composer-runtime-hlf/vendor/gopkg.in/olebedev/go-duktape.v3 b/packages/composer-runtime-hlf/vendor/gopkg.in/olebedev/go-duktape.v3 new file mode 160000 index 0000000000..bb87dea2db --- /dev/null +++ b/packages/composer-runtime-hlf/vendor/gopkg.in/olebedev/go-duktape.v3 @@ -0,0 +1 @@ +Subproject commit bb87dea2db4a1f68f67b5c7a664ff5df9f279e49 diff --git a/packages/composer-runtime-hlf/vendor/gopkg.in/sourcemap.v1 b/packages/composer-runtime-hlf/vendor/gopkg.in/sourcemap.v1 deleted file mode 160000 index 6e83acea00..0000000000 --- a/packages/composer-runtime-hlf/vendor/gopkg.in/sourcemap.v1 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6e83acea0053641eff084973fee085f0c193c61a diff --git a/packages/composer-runtime-hlfv1/composer.go b/packages/composer-runtime-hlfv1/composer.go index 700038e56f..97492e2c20 100644 --- a/packages/composer-runtime-hlfv1/composer.go +++ b/packages/composer-runtime-hlfv1/composer.go @@ -118,11 +118,22 @@ func (composer *Composer) Init(stub shim.ChaincodeStubInterface, function string logger.Debug("Entering Composer.Init", &stub, function, arguments) defer func() { logger.Debug("Exiting Composer.Init", string(result), err) }() - // Create all required objects. - context := NewContext(composer.VM, composer.Engine, stub) + // Start a scope for locking the JavaScript virtual machine. + var channel chan EngineCallback + func() { - // Defer to the JavaScript function. - channel := composer.Engine.Init(context, function, arguments) + // Lock the JavaScript virtual machine. + vm := composer.VM + vm.Lock() + defer vm.Unlock() + + // Create all required objects. + context := NewContext(composer.VM, composer.Engine, stub) + + // Defer to the JavaScript function. + channel = composer.Engine.Init(context, function, arguments) + + }() // Now read from the channel. This will be triggered when the JavaScript // code calls the callback function. @@ -139,11 +150,22 @@ func (composer *Composer) Invoke(stub shim.ChaincodeStubInterface, function stri logger.Debug("Entering Composer.Invoke", &stub, function, arguments) defer func() { logger.Debug("Exiting Composer.Invoke", string(result), err) }() - // Create all required objects. - context := NewContext(composer.VM, composer.Engine, stub) + // Start a scope for locking the JavaScript virtual machine. + var channel chan EngineCallback + func() { + + // Lock the JavaScript virtual machine. + vm := composer.VM + vm.Lock() + defer vm.Unlock() + + // Create all required objects. + context := NewContext(composer.VM, composer.Engine, stub) + + // Defer to the JavaScript function. + channel = composer.Engine.Invoke(context, function, arguments) - // Defer to the JavaScript function. - channel := composer.Engine.Invoke(context, function, arguments) + }() // Now read from the channel. This will be triggered when the JavaScript // code calls the callback function. diff --git a/packages/composer-runtime-hlfv1/composerpool_test.go b/packages/composer-runtime-hlfv1/composerpool_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlfv1/composerpool_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlfv1/container_test.go b/packages/composer-runtime-hlfv1/container_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlfv1/container_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlfv1/context.go b/packages/composer-runtime-hlfv1/context.go index 1e81db8376..4ebe0ce720 100644 --- a/packages/composer-runtime-hlfv1/context.go +++ b/packages/composer-runtime-hlfv1/context.go @@ -75,7 +75,7 @@ func NewContext(vm *duktape.Context, engine *Engine, stub shim.ChaincodeStubInte vm.PushGoFunction(result.getHTTPService) // [ stash theEngine global composer theContext getHTTPService ] vm.PutPropString(-2, "getHTTPService") // [ stash theEngine global composer theContext ] vm.PushGoFunction(result.getQueryService) // [ stash theEngine global composer theContext getQueryService ] - vm.PutPropString(-2, "getQueryService") // [ stash theEngine global composer theContext getQueryService] + vm.PutPropString(-2, "getQueryService") // [ stash theEngine global composer theContext getQueryService ] // Return the new context. return result diff --git a/packages/composer-runtime-hlfv1/context_test.go b/packages/composer-runtime-hlfv1/context_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlfv1/context_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlfv1/datacollection_test.go b/packages/composer-runtime-hlfv1/datacollection_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlfv1/datacollection_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlfv1/dataservice_test.go b/packages/composer-runtime-hlfv1/dataservice_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlfv1/dataservice_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlfv1/engine.go b/packages/composer-runtime-hlfv1/engine.go index 18959fc3fa..e1715466c3 100644 --- a/packages/composer-runtime-hlfv1/engine.go +++ b/packages/composer-runtime-hlfv1/engine.go @@ -96,12 +96,8 @@ func (engine *Engine) Init(context *Context, function string, arguments []string logger.Debug("Entering Engine.Init", context, function, arguments) defer func() { logger.Debug("Exiting Engine.Init", channel) }() - // Lock the JavaScript virtual machine. - vm := context.VM - vm.Lock() - defer vm.Unlock() - // Ensure the JavaScript stack is reset. + vm := context.VM defer vm.SetTop(vm.GetTop()) // Create a channel to receieve the response from JavaScript. @@ -133,17 +129,13 @@ func (engine *Engine) Init(context *Context, function string, arguments []string return channel } -// Invoke executes the Engine.query(context, function, arguments, callback) JavaScript function. +// Invoke executes the Engine.invoke(context, function, arguments, callback) JavaScript function. func (engine *Engine) Invoke(context *Context, function string, arguments []string) (channel chan EngineCallback) { logger.Debug("Entering Engine.Invoke", context, function, arguments) defer func() { logger.Debug("Exiting Engine.Invoke", channel) }() - // Lock the JavaScript virtual machine. - vm := context.VM - vm.Lock() - defer vm.Unlock() - // Ensure the JavaScript stack is reset. + vm := context.VM defer vm.SetTop(vm.GetTop()) // Create a channel to receieve the response from JavaScript. diff --git a/packages/composer-runtime-hlfv1/engine_test.go b/packages/composer-runtime-hlfv1/engine_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlfv1/engine_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlfv1/eventservice.go b/packages/composer-runtime-hlfv1/eventservice.go index 6d50d9526b..1824d077a6 100644 --- a/packages/composer-runtime-hlfv1/eventservice.go +++ b/packages/composer-runtime-hlfv1/eventservice.go @@ -71,14 +71,14 @@ func (eventService *EventService) transactionCommit(vm *duktape.Context) (result // Validate the arguments from JavaScript. vm.RequireFunction(0) - vm.PushThis() // [ theEventService ] - vm.GetPropString(-1, "serializeBuffer") // [ theEventService, serializeBuffer ] - vm.RequireFunction(-1) // [ theEventService, serializeBuffer ] - vm.Dup(-2) // [ theEventService, serializeBuffer, theEventService ] - vm.CallMethod(0) // [ theEventService, returnValue ] - vm.RequireObjectCoercible(-1) // [ theEventService, returnValue ] - vm.JsonEncode(-1) // [ theEventService, returnValue ] - value := vm.RequireString(-1) // [ theEventService, returnValue ] + vm.PushThis() // [ theEventService ] + vm.GetPropString(-1, "getEvents") // [ theEventService, getEvents ] + vm.RequireFunction(-1) // [ theEventService, getEvents ] + vm.Dup(-2) // [ theEventService, getEvents, theEventService ] + vm.CallMethod(0) // [ theEventService, returnValue ] + vm.RequireObjectCoercible(-1) // [ theEventService, returnValue ] + vm.JsonEncode(-1) // [ theEventService, returnValue ] + value := vm.RequireString(-1) // [ theEventService, returnValue ] if len(value) > 0 { logger.Debug("Emitting event from EventService.transactionCommit", value) diff --git a/packages/composer-runtime-hlfv1/identityservice_test.go b/packages/composer-runtime-hlfv1/identityservice_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlfv1/identityservice_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-hlfv1/loggingservice_test.go b/packages/composer-runtime-hlfv1/loggingservice_test.go deleted file mode 100644 index 534d0658c9..0000000000 --- a/packages/composer-runtime-hlfv1/loggingservice_test.go +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 main diff --git a/packages/composer-runtime-web/lib/webeventservice.js b/packages/composer-runtime-web/lib/webeventservice.js index 7ee034819e..a06b25db03 100644 --- a/packages/composer-runtime-web/lib/webeventservice.js +++ b/packages/composer-runtime-web/lib/webeventservice.js @@ -44,7 +44,7 @@ class WebEventService extends EventService { transactionCommit() { return super.transactionCommit() .then(() => { - const jsonEvent = JSON.parse(this.serializeBuffer()); + const jsonEvent = this.getEvents(); this.eventSink.emit('events', jsonEvent); }); } diff --git a/packages/composer-runtime-web/lib/webqueryservice.js b/packages/composer-runtime-web/lib/webqueryservice.js index 577e8f3784..10e4aeeab8 100644 --- a/packages/composer-runtime-web/lib/webqueryservice.js +++ b/packages/composer-runtime-web/lib/webqueryservice.js @@ -37,16 +37,12 @@ class WebQueryService extends QueryService { /** * Query the underlying world-state store using a store native query string. + * @abstract * @param {string} queryString - the native query string * @return {Promise} A promise that will be resolved with a JS object containing the results of the query */ queryNative(queryString) { - const method = 'queryNative'; - LOG.entry(method, queryString); - this.queryString = queryString; - LOG.debug(method, queryString); - // TODO (DCS) - we need an implementation! - return Promise.resolve({data: 'not implemented'}); + throw new Error('The native query functionality is not available on this Blockchain platform'); } } diff --git a/packages/composer-runtime-web/test/webeventservice.js b/packages/composer-runtime-web/test/webeventservice.js index 55efe89dd0..342e563e37 100644 --- a/packages/composer-runtime-web/test/webeventservice.js +++ b/packages/composer-runtime-web/test/webeventservice.js @@ -50,10 +50,10 @@ describe('WebEventService', () => { describe('#transactionCommit', () => { it ('should emit a list of events', () => { - sinon.stub(eventService, 'serializeBuffer').returns('[{"event":"event"}]'); + sinon.stub(eventService, 'getEvents').returns([{'event':'event'}]); return eventService.transactionCommit() .then(() => { - sinon.assert.calledOnce(eventService.serializeBuffer); + sinon.assert.calledOnce(eventService.getEvents); sinon.assert.calledOnce(mockEventEmitter.emit); sinon.assert.calledWith(mockEventEmitter.emit, 'events', [{'event':'event'}]); }); diff --git a/packages/composer-runtime-web/test/webqueryservice.js b/packages/composer-runtime-web/test/webqueryservice.js index da33ce33da..ad62fcddc4 100644 --- a/packages/composer-runtime-web/test/webqueryservice.js +++ b/packages/composer-runtime-web/test/webqueryservice.js @@ -38,11 +38,10 @@ describe('WebQueryService', () => { describe('#queryNative', () => { - it ('should return the query string', () => { - return queryService.queryNative('dummyString') - .then((result) => { - result.should.deep.equal({data: 'not implemented'}); - }); + it ('should throw as not supported on this runtime', () => { + (() => { + queryService.queryNative('dummyString'); + }).should.throw(/not available on this Blockchain platform/); }); }); }); diff --git a/packages/composer-runtime/lib/eventservice.js b/packages/composer-runtime/lib/eventservice.js index 557350a446..faff91963c 100644 --- a/packages/composer-runtime/lib/eventservice.js +++ b/packages/composer-runtime/lib/eventservice.js @@ -49,14 +49,14 @@ class EventService extends Service { } /** - * Get an array of events as a string - * @return {String} - An array of serialized events + * Get an array of emitted events + * @return {Resource[]} - An array of emitted events */ - serializeBuffer() { - const method = 'serializeBuffer'; + getEvents() { + const method = 'getEvents'; LOG.entry(method); LOG.exit(method, this.eventBuffer); - return JSON.stringify(this.eventBuffer); + return this.eventBuffer; } /** diff --git a/packages/composer-runtime/test/eventservice.js b/packages/composer-runtime/test/eventservice.js index a68325ab83..85c3db8a84 100644 --- a/packages/composer-runtime/test/eventservice.js +++ b/packages/composer-runtime/test/eventservice.js @@ -43,12 +43,12 @@ describe('EventService', () => { }); }); - describe('#serializeBuffer', () => { + describe('#getEvents', () => { it('should return the list of events that are to be comitted', () => { let event = {'$class': 'much.wow'}; eventService.eventBuffer = [ event ]; - eventService.serializeBuffer().should.equal('[{"$class":"much.wow"}]'); + eventService.getEvents().should.deep.equal([{'$class':'much.wow'}]); }); }); diff --git a/packages/composer-systests/systest/data/accesscontrols.acl b/packages/composer-systests/systest/data/accesscontrols.acl index 44865ae6f9..817d5324aa 100644 --- a/packages/composer-systests/systest/data/accesscontrols.acl +++ b/packages/composer-systests/systest/data/accesscontrols.acl @@ -23,4 +23,4 @@ rule R3 { resource(p): "systest.accesscontrols.SampleParticipant" condition: (p.getIdentifier() === po.getIdentifier()) action: ALLOW -} \ No newline at end of file +} diff --git a/packages/composer-systests/systest/features/support/steps.js b/packages/composer-systests/systest/features/support/steps.js deleted file mode 100644 index 239f65ee83..0000000000 --- a/packages/composer-systests/systest/features/support/steps.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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. - */ - -'use strict'; - -module.exports = function () { - - this.Given(/I have an empty asset registry/, function () { - - }); - - this.When(/I add the following asset(|s) to the asset registry/, function (uri, data) { - - }); - - this.Then(/the asset registry contains the following asset(|s)/, function (uri, data) { - - }); - -}; diff --git a/packages/composer-systests/systest/features/test.feature b/packages/composer-systests/systest/features/test.feature deleted file mode 100644 index 759458c0b9..0000000000 --- a/packages/composer-systests/systest/features/test.feature +++ /dev/null @@ -1,10 +0,0 @@ -Feature: Example feature - - Scenario: Trying it out - Given I have an empty asset registry - When I add the following asset to the asset registry: - | uri | data | - | http://some/asset | Some asset | - Then the asset registry contains the following assets: - | uri | data | - | http://some/asset | Some asset |