diff --git a/src/controller/queue.lua b/src/controller/queue.lua index 34744c1..2ab9fe5 100644 --- a/src/controller/queue.lua +++ b/src/controller/queue.lua @@ -1,5 +1,3 @@ -local utils = require ".utils.utils" - local mod = {} -- Add or remove a user from the queue in the controller @@ -46,20 +44,33 @@ function mod.setQueued(address, queued) } end --- Make a handle function use the global queue of the controller ----@param handle HandlerFunction Handle function to wrap ----@param errorHandler fun(msg: Message, env: Message, err: unknown)? Optional error handler ----@return HandlerFunction -function mod.useQueue(handle, errorHandler) - return function (msg, env) - -- default sender of the interaction is the message sender - local sender = msg.From - local isCreditNotice = msg.Tags.Action == "Credit-Notice" - - -- if the message is a credit notice, update the sender - if isCreditNotice then - sender = msg.Tags.Sender - end +-- Get the user to be queued, depending on the type of the message +---@param msg Message Message to process +---@return string +function mod.getUserToQueue(msg) + -- return sender if it is a credit notice + if msg.Tags.Action == "Credit-Notice" then + return msg.Tags.Sender + end + + -- the msg sender should be queued if it is not a credit-notice + return msg.From +end + +-- Make a handler use the global queue of the controller. This is +-- usually needed for handlers that impelement complex behavior by +-- waiting for several message responses before completion to prevent +-- double spending +---@param config table Configuration for the handler +---@return table +function mod.useQueue(config) + -- original handle function and error handler + local handle = config.handle + local errorHandler = config.errorHandler + + -- override handle function to queue and unqueue + config.handle = function (msg, env) + local sender = mod.getUserToQueue(msg) -- update and set queue local res = mod.setQueued(sender, true).receive() @@ -76,7 +87,10 @@ function mod.useQueue(handle, errorHandler) errorHandler(msg, env, err) else -- no error handler, throw the error - error(err) + -- do not error() here - that would trigger + -- the handler's error handler, which would + -- try to unqueue the user + Handlers.defaultErrorHandler(msg, env, err) end return @@ -99,6 +113,26 @@ function mod.useQueue(handle, errorHandler) end end end + + -- override error handler to unqueue the user + config.errorHandler = function (msg, env, err) + -- call wrapped error handler if provided + if errorHandler ~= nil then + errorHandler(msg, env, err or "Unknown error") + else + Handlers.defaultErrorHandler(msg, env, err) + end + + -- get user to unqueue + local sender = mod.getUserToQueue(msg) + + -- unqueue and notify if it failed + mod + .setQueued(sender, false) + .notifyOnFailedQueue() + end + + return config end return mod diff --git a/src/process.lua b/src/process.lua index 5b6b540..b147767 100644 --- a/src/process.lua +++ b/src/process.lua @@ -239,22 +239,21 @@ local function setup_handlers() msg.reply({ ["Borrow-Balance"] = tostring(borrowBalance) }) end ) - Handlers.add( - "borrow-loan-borrow", - Handlers.utils.hasMatchingTag("Action", "Borrow"), - -- needs unqueueing because of coroutines - queue.useQueue(oracle.withOracle(borrow)) - ) - Handlers.advanced({ + Handlers.advanced(queue.useQueue({ + name = "borrow-loan-borrow", + pattern = { Action = "Borrow" }, + handle = oracle.withOracle(borrow) + })) + Handlers.advanced(queue.useQueue({ name = "borrow-repay", pattern = { From = CollateralID, Action = "Credit-Notice", ["X-Action"] = "Repay" }, - handle = queue.useQueue(repay.handler), + handle = repay.handler, errorHandler = repay.error - }) + })) Handlers.add( "borrow-position-collateralization", Handlers.utils.hasMatchingTag("Action", "Position"), @@ -271,16 +270,16 @@ local function setup_handlers() position.handlers.allPositions ) - Handlers.advanced({ + Handlers.advanced(queue.useQueue({ name = "supply-mint", pattern = { From = CollateralID, Action = "Credit-Notice", ["X-Action"] = "Mint" }, - handle = queue.useQueue(mint.handler), + handle = mint.handler, errorHandler = mint.error - }) + })) Handlers.add( "supply-price", Handlers.utils.hasMatchingTag("Action", "Exchange-Rate-Current"), @@ -302,12 +301,11 @@ local function setup_handlers() }) end ) - -- needs unqueueing because of coroutines - Handlers.add( - "supply-redeem", - Handlers.utils.hasMatchingTag("Action", "Redeem"), - queue.useQueue(oracle.withOracle(redeem)) - ) + Handlers.advanced(queue.useQueue({ + name = "supply-redeem", + pattern = { Action = "Redeem" }, + handle = oracle.withOracle(redeem) + })) Handlers.add( "token-info", @@ -329,12 +327,11 @@ local function setup_handlers() Handlers.utils.hasMatchingTag("Action", "Balances"), balance.balances ) - -- needs unqueueing because of coroutines - Handlers.add( - "token-transfer", - Handlers.utils.hasMatchingTag("Action", "Transfer"), - queue.useQueue(oracle.withOracle(transfer)) - ) + Handlers.advanced(queue.useQueue({ + name = "token-transfer", + pattern = { Action = "Transfer" }, + handle = oracle.withOracle(transfer) + })) HandlersAdded = true end diff --git a/src/utils/handlers.lua b/src/utils/handlers.lua index af15377..d75c73a 100644 --- a/src/utils/handlers.lua +++ b/src/utils/handlers.lua @@ -464,6 +464,7 @@ end -- @treturn The response from the handler(s). Returns a default message if no handler matches. function handlers.evaluate(msg, env) local handled = false + local toRemove = {} -- handlers to remove after evaluation assert(type(msg) == 'table', 'msg is not valid') assert(type(env) == 'table', 'env is not valid') @@ -482,8 +483,8 @@ function handlers.evaluate(msg, env) o.onRemove("timeout") o.onRemove = nil end - handlers.remove(o.name) - match = 0 + o.handle = function () end + table.insert(toRemove, o.name) end end @@ -550,7 +551,7 @@ function handlers.evaluate(msg, env) o.onRemove("expired") o.onRemove = nil end - handlers.remove(o.name) + table.insert(toRemove, o.name) end end end @@ -563,8 +564,16 @@ function handlers.evaluate(msg, env) -- reset current error handler handlers.currentErrorHandler = handlers.defaultErrorHandler + -- remove marked handlers + for _, name in ipairs(toRemove) do + handlers.remove(name) + end + -- make sure the request was handled - assert(handled or msg.Id == ao.id, "The request could not be handled") + assert( + handled or msg.Id == ao.id or string.match(msg.Action or "", "Error$"), + "The request could not be handled" + ) end return handlers