Skip to content
This repository has been archived by the owner on Oct 18, 2019. It is now read-only.

Feature: Move order into a placed state #66

Merged
merged 7 commits into from
Jun 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions broker-daemon/block-order-worker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const EventEmitter = require('events')
const { promisify } = require('util')
const safeid = require('generate-safe-id')
const { BlockOrder } = require('../models')
const OrderStateMachine = require('./order-state-machine')
const { OrderStateMachine } = require('../state-machines')
const { BlockOrderNotFoundError } = require('./errors')

/**
Expand Down Expand Up @@ -145,7 +145,7 @@ class BlockOrderWorker extends EventEmitter {
{ side, baseSymbol, counterSymbol, baseAmount, counterAmount }
)

this.logger.info('Created single order for BlockOrder', { blockOrderId: blockOrder.id, orderId: order.id })
this.logger.info('Created single order for BlockOrder', { blockOrderId: blockOrder.id, orderId: order.orderId })
}
}

Expand Down
7 changes: 5 additions & 2 deletions broker-daemon/models/block-order.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { expect, sinon } = require('test/test-helper')
const bigInt = require('big-integer')

const BlockOrder = require('./block-order')
const OrderStateMachine = require('../block-order-worker/order-state-machine')
const { OrderStateMachine } = require('../state-machines')

describe('BlockOrder', () => {
describe('::fromStorage', () => {
Expand Down Expand Up @@ -205,7 +205,10 @@ describe('BlockOrder', () => {
payTo: 'ln:1231243fasdf',
feePaymentRequest: 'lnbcasodifjoija',
depositPaymentRequest: 'lnbcaosdifjaosdfj',
__state: 'CREATED'
__stateMachine: {
state: 'created',
history: []
}
})})
})

Expand Down
1 change: 1 addition & 0 deletions broker-daemon/proto/src/order.proto
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum OrderStatus {
FILLED = 2;
EXECUTED = 3;
COMPLETED = 4;
REJECTED = 5;
}

message Order {
Expand Down
5 changes: 5 additions & 0 deletions broker-daemon/state-machines/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const OrderStateMachine = require('./order-state-machine')

module.exports = {
OrderStateMachine
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
const { promisify } = require('util')
const { getRecords } = require('../utils')
const StateMachine = require('javascript-state-machine')
const StateMachineHistory = require('javascript-state-machine/lib/history')
const { Order } = require('../models')

/**
* @class Finite State Machine for managing order lifecycle
*/
const OrderStateMachine = StateMachine.factory({
plugins: [
new StateMachineHistory()
],
/**
* Definition of the transitions and states for the OrderStateMachine
* @type {Array}
Expand All @@ -17,11 +21,21 @@ const OrderStateMachine = StateMachine.factory({
* @type {Object}
*/
{ name: 'create', from: 'none', to: 'created' },
/**
* place transition: second transition in the order lifecycle
* @type {Object}
*/
{ name: 'place', from: 'created', to: 'placed' },
/**
* goto transition: go to the named state from any state, used for re-hydrating from disk
* @type {Object}
*/
{ name: 'goto', from: '*', to: (s) => s }
{ name: 'goto', from: '*', to: (s) => s },
/**
* reject transition: a created order was rejected during placement
* @type {Object}
*/
{ name: 'reject', from: 'created', to: 'rejected' }
],
/**
* Instantiate the data on the state machine
Expand All @@ -38,6 +52,67 @@ const OrderStateMachine = StateMachine.factory({
return { store, logger, relayer, engine, order: {} }
},
methods: {
/**
* Wrapper for running the next transition with error handling
* @param {string} transitionName Name of the transition to run
* @param {...Array} arguments Arguments to the apply to the transition
* @return {void}
*/
nextTransition: function (transitionName, ...args) {
this.logger.debug(`Queuing transition: ${transitionName}`)
process.nextTick(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what was the reasoning for using nextTick here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't move to another transition while you're still in one. nextTick gets you out of the current transition. See this issue: jakesgordon/javascript-state-machine#143

this.logger.debug(`Running transition: ${transitionName}`)
try {
if (!this.transitions().includes(transitionName)) {
throw new Error(`${transitionName} is invalid transition from ${this.state}`)
}

await this[transitionName](...args)
} catch (e) {
// TODO: bubble/handle error
// TODO: rejected state to clean up paid invoices, etc
this.logger.error(`Error encountered while running ${transitionName} transition`, e)
this.reject(e)
}
})
},

/**
* Save the current state of the state machine to the store using the `host` as a carrier
* @param {Object} host Host object to store in the data store with state machine metadata attached
* @return {Promise<void>} Promise that resolves when the state is persisted
*/
persist: async function ({ key, valueObject }) {
if (!key) {
throw new Error(`An order key is required to save state`)
}

if (!valueObject) {
// console.log('this.order', this.order)
throw new Error(`An Order object is required to save state`)
}

const { state, history } = this
let error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] would change this to errorMessage to be more specific


if (this.error) {
error = this.error.message
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when would an error not have a message in our code? Do our custom errors not implement this correctly?


if (!error) {
this.logger.error('Saving state machine error state with no error message', { key })
}
}

const stateMachine = { state, history, error }

const value = JSON.stringify(Object.assign(valueObject, { __stateMachine: stateMachine }))
Copy link
Contributor

@dannypaz dannypaz Jun 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we need to for this PR, but would be worth it to throw this into its own serialize/deserialize method, in the same way we have fromStore

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was part of the desire for #67


// somehow spit an error if this fails?
await promisify(this.store.put)(key, value)

this.logger.debug('Saved state machine in store', { orderId: this.order.orderId })
},

onBeforeTransition: function (lifecycle) {
this.logger.info(`BEFORE: ${lifecycle.transition}`)
},
Expand All @@ -55,18 +130,11 @@ const OrderStateMachine = StateMachine.factory({

if (lifecycle.transition === 'goto') {
this.logger.debug('Skipping database save since we are using a goto')
return
}

if (lifecycle.to === 'none') {
} else if (lifecycle.to === 'none') {
this.logger.debug('Skipping database save for the \'none\' state')
return
} else {
this.persist(this.order)
}

// somehow spit an error if this fails?
await promisify(this.store.put)(this.order.key, JSON.stringify(Object.assign(this.order.valueObject, { __state: this.state })))

this.logger.debug('Saved state machine in store', { orderId: this.order.orderId })
},
onAfterTransition: function (lifecycle) {
this.logger.info(`AFTER: ${lifecycle.transition}`)
Expand Down Expand Up @@ -100,6 +168,40 @@ const OrderStateMachine = StateMachine.factory({
this.order.addCreatedParams(await this.relayer.createOrder(this.order.createParams))

this.logger.info(`Created order ${this.order.orderId} on the relayer`)
},

/**
* Attempt to place the order as soon as its created
* @param {Object} lifecycle Lifecycle object passed by javascript-state-machine
* @return {void}
*/
onAfterCreate: function (lifecycle) {
this.logger.info(`Create transition completed, triggering place`)

this.nextTransition('place')
},

/**
* Place the order on the relayer during transition.
* This function gets called before the `place` transition (triggered by a call to `place`)
* Actual placement on the relayer is done in `onBeforePlace` so that the transition can be cancelled
* if placement on the Relayer fails.
*
* @param {Object} lifecycle Lifecycle object passed by javascript-state-machine
* @return {Promise} Promise that rejects if placement on the relayer fails
*/
onBeforePlace: async function (lifecycle) {
throw new Error('Placing orders is currently un-implemented')
},

/**
* Handle rejection by assigning the error to the state machine
* @param {Object} lifecycle Lifecycle object passed by javascript-state-machine
* @param {Object} error Error that triggered rejection
* @return {void}
*/
onBeforeReject: function (lifecycle, error) {
this.error = error
}
}
})
Expand Down Expand Up @@ -136,12 +238,27 @@ OrderStateMachine.getAll = async function ({ store, ...initParams }) {
*/
OrderStateMachine.fromStore = function (initParams, { key, value }) {
const parsedValue = JSON.parse(value)
const stateMachine = parsedValue.__stateMachine

if (!stateMachine) {
throw new Error('Values must have a `__stateMachine` property to be created as state machines')
}

const orderStateMachine = new OrderStateMachine(initParams)

orderStateMachine.order = Order.fromObject(key, parsedValue)

orderStateMachine.goto(parsedValue.__state)
// re-inflate state machine properties

// state machine current state
orderStateMachine.goto(stateMachine.state)

// state machine history
orderStateMachine.clearHistory()
orderStateMachine.history = stateMachine.history

// state machine errors
orderStateMachine.error = new Error(stateMachine.error)

return orderStateMachine
}
Expand Down
Loading