aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-08-19 14:43:39 +0200
committerChristian Grothoff <christian@grothoff.org>2021-08-19 14:43:39 +0200
commit677c7e87a5da584e68194c9cca19a91191c3140c (patch)
tree33425fd28b6580c29d2f5fd80ab7241a5a643f13
parent4067891ed9f66eb5e47a709d3ea21c2ed36a1e86 (diff)
downloadanastasis-677c7e87a5da584e68194c9cca19a91191c3140c.tar.gz
anastasis-677c7e87a5da584e68194c9cca19a91191c3140c.zip
-implement 'poll' transition in state machine
-rw-r--r--doc/sphinx/reducer.rst228
-rw-r--r--src/include/anastasis.h8
-rw-r--r--src/lib/anastasis_recovery.c8
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c165
4 files changed, 307 insertions, 102 deletions
diff --git a/doc/sphinx/reducer.rst b/doc/sphinx/reducer.rst
index e5f1699..68df5b1 100644
--- a/doc/sphinx/reducer.rst
+++ b/doc/sphinx/reducer.rst
@@ -1459,121 +1459,139 @@ that applications must all handle. States other than ``solved`` are:
1459 } 1459 }
1460 } 1460 }
1461 1461
1462 - **body**: Here, the server provided an HTTP reply for 1462 - **body**: Here, the server provided an HTTP reply for
1463 how to solve the challenge, but the reducer could not parse 1463 how to solve the challenge, but the reducer could not parse
1464 them into a known format. A mime-type may be provided and may 1464 them into a known format. A mime-type may be provided and may
1465 help parse the details. 1465 help parse the details.
1466 1466
1467 .. code-block:: json 1467 .. code-block:: json
1468 1468
1469 { 1469 {
1470 "recovery_state": "CHALLENGE_SOLVING", 1470 "recovery_state": "CHALLENGE_SOLVING",
1471 "recovery_information": { 1471 "recovery_information": {
1472 "...": "..." 1472 "...": "..."
1473 } 1473 }
1474 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", 1474 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1475 "challenge_feedback": { 1475 "challenge_feedback": {
1476 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { 1476 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1477 "state": "body", 1477 "state": "body",
1478 "body": "CROCKFORDBASE32ENCODEDBODY", 1478 "body": "CROCKFORDBASE32ENCODEDBODY",
1479 "http_status": 403, 1479 "http_status": 403,
1480 "mime_type" : "anything/possible" 1480 "mime_type" : "anything/possible"
1481 } 1481 }
1482 } 1482 }
1483 } 1483 }
1484 1484
1485 - **hint**: Here, the server provided human-readable hint for 1485 - **hint**: Here, the server provided human-readable hint for
1486 how to solve the challenge. Note that the ``hint`` provided this 1486 how to solve the challenge. Note that the ``hint`` provided this
1487 time is from the Anastasis provider and may differ from the ``instructions`` 1487 time is from the Anastasis provider and may differ from the ``instructions``
1488 for the challenge under ``recovery_information``: 1488 for the challenge under ``recovery_information``:
1489 1489
1490 .. code-block:: json 1490 .. code-block:: json
1491 1491
1492 { 1492 {
1493 "recovery_state": "CHALLENGE_SOLVING", 1493 "recovery_state": "CHALLENGE_SOLVING",
1494 "recovery_information": { 1494 "recovery_information": {
1495 "...": "..." 1495 "...": "..."
1496 } 1496 }
1497 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", 1497 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1498 "challenge_feedback": { 1498 "challenge_feedback": {
1499 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { 1499 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1500 "state": "hint", 1500 "state": "hint",
1501 "hint": "Recovery TAN send to email mail@DOMAIN", 1501 "hint": "Recovery TAN send to email mail@DOMAIN",
1502 "http_status": 403 1502 "http_status": 403
1503 } 1503 }
1504 }
1505 }
1506
1507 - **details**: Here, the server provided a detailed JSON status response
1508 related to solving the challenge:
1509
1510 .. code-block:: json
1511
1512 {
1513 "recovery_state": "CHALLENGE_SOLVING",
1514 "recovery_information": {
1515 "...": "..."
1516 }
1517 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1518 "challenge_feedback": {
1519 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1520 "state": "details",
1521 "details": {
1522 "code": 8111,
1523 "hint": "The client's response to the challenge was invalid.",
1524 "detail" : null
1525 },
1526 "http_status": 403
1504 } 1527 }
1505 } 1528 }
1529 }
1506 1530
1507 - **details**: Here, the server provided a detailed JSON status response 1531 - **redirect**: To solve the challenge, the user must visit the indicated
1508 related to solving the challenge: 1532 Web site at ``redirect_url``, for example to perform video authentication:
1509 1533
1510 .. code-block:: json 1534 .. code-block:: json
1511 1535
1512 { 1536 {
1513 "recovery_state": "CHALLENGE_SOLVING", 1537 "recovery_state": "CHALLENGE_SOLVING",
1514 "recovery_information": { 1538 "recovery_information": {
1515 "...": "..." 1539 "...": "..."
1516 } 1540 }
1517 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", 1541 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1518 "challenge_feedback": { 1542 "challenge_feedback": {
1519 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { 1543 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1520 "state": "details", 1544 "state": "redirect",
1521 "details": { 1545 "redirect_url": "https://videoconf.example.com/",
1522 "code": 8111, 1546 "http_status": 303
1523 "hint": "The client's response to the challenge was invalid.",
1524 "detail" : null
1525 },
1526 "http_status": 403
1527 }
1528 } 1547 }
1529 } 1548 }
1549 }
1530 1550
1531 - **redirect**: To solve the challenge, the user must visit the indicated 1551 - **server-failure**: This indicates that the Anastasis provider encountered
1532 Web site at ``redirect_url``, for example to perform video authentication: 1552 a failure and recovery using this challenge cannot proceed at this time.
1553 Examples for failures might be that the provider is unable to send SMS
1554 messages at this time due to an outage. The body includes details about
1555 the failure. The user may try again later or continue with other challenges.
1533 1556
1534 .. code-block:: json 1557 .. code-block:: json
1535 1558
1536 { 1559 {
1537 "recovery_state": "CHALLENGE_SOLVING", 1560 "recovery_state": "CHALLENGE_SELECTING",
1538 "recovery_information": { 1561 "recovery_information": {
1539 "...": "..." 1562 "...": "..."
1540 }
1541 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1542 "challenge_feedback": {
1543 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1544 "state": "redirect",
1545 "redirect_url": "https://videoconf.example.com/",
1546 "http_status": 303
1547 }
1548 }
1549 } 1563 }
1564 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1565 "challenge_feedback": {
1566 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1567 "state": "server-failure",
1568 "http_status": "500",
1569 "error_code": 52
1570 }
1571 }
1572 }
1550 1573
1551 - **server-failure**: This indicates that the Anastasis provider encountered 1574 - **truth-unknown**: This indicates that the Anastasis provider is unaware of
1552 a failure and recovery using this challenge cannot proceed at this time. 1575 the specified challenge. This is typically a permanent failure, and user
1553 Examples for failures might be that the provider is unable to send SMS 1576 interfaces should not allow users to re-try this challenge.
1554 messages at this time due to an outage. The body includes details about
1555 the failure. The user may try again later or continue with other challenges.
1556 1577
1557 .. code-block:: json 1578 .. code-block:: json
1558 1579
1559 { 1580 {
1560 "recovery_state": "CHALLENGE_SELECTING", 1581 "recovery_state": "CHALLENGE_SELECTING",
1561 "recovery_information": { 1582 "recovery_information": {
1562 "...": "..." 1583 "...": "..."
1563 } 1584 }
1564 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", 1585 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1565 "challenge_feedback": { 1586 "challenge_feedback": {
1566 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { 1587 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1567 "state": "server-failure", 1588 "state": "truth-unknown",
1568 "http_status": "500", 1589 "error_code": 8108
1569 "error_code": 52 1590 }
1570 } 1591 }
1571 } 1592 }
1572 }
1573 1593
1574 - **truth-unknown**: This indicates that the Anastasis provider is unaware of 1594 - **rate-limit-exceeded**:
1575 the specified challenge. This is typically a permanent failure, and user
1576 interfaces should not allow users to re-try this challenge.
1577 1595
1578 .. code-block:: json 1596 .. code-block:: json
1579 1597
@@ -1585,13 +1603,13 @@ that applications must all handle. States other than ``solved`` are:
1585 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", 1603 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1586 "challenge_feedback": { 1604 "challenge_feedback": {
1587 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { 1605 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1588 "state": "truth-unknown", 1606 "state": "rate-limit-exceeded",
1589 "error_code": 8108 1607 "error_code": 8121
1590 } 1608 }
1591 } 1609 }
1592 } 1610 }
1593 1611
1594 - **rate-limit-exceeded**: 1612 - **authentication-timeout**:
1595 1613
1596 .. code-block:: json 1614 .. code-block:: json
1597 1615
@@ -1603,12 +1621,24 @@ that applications must all handle. States other than ``solved`` are:
1603 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0", 1621 "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
1604 "challenge_feedback": { 1622 "challenge_feedback": {
1605 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": { 1623 "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
1606 "state": "rate-limit-exceeded", 1624 "state": "authentication-timeout",
1607 "error_code": 8121 1625 "error_code": 8122
1608 } 1626 }
1609 } 1627 }
1610 } 1628 }
1611 1629
1630
1631**poll:**
1632
1633With a ``poll`` transition, the application indicates that it wants to wait longer for one or more of the challenges that are in state ``authentication-timeout`` to possibly complete. While technically optional, the ``timeout`` argument should really be provided to enable long-polling, for example:
1634
1635.. code-block:: json
1636
1637 {
1638 "timeout" : { "d_ms" : 5000 },
1639 }
1640
1641
1612**pay:** 1642**pay:**
1613 1643
1614With a ``pay`` transition, the application indicates to the reducer that 1644With a ``pay`` transition, the application indicates to the reducer that
diff --git a/src/include/anastasis.h b/src/include/anastasis.h
index 8443eb6..3027e2a 100644
--- a/src/include/anastasis.h
+++ b/src/include/anastasis.h
@@ -71,10 +71,16 @@ struct ANASTASIS_ChallengeDetails
71 const char *instructions; 71 const char *instructions;
72 72
73 /** 73 /**
74 * true if challenged was already solved, else false. 74 * true if challenge was already solved, else false.
75 */ 75 */
76 bool solved; 76 bool solved;
77 77
78 /**
79 * true if challenge is awaiting asynchronous
80 * resolution by the user.
81 */
82 bool async;
83
78}; 84};
79 85
80 86
diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c
index 4e23db0..623e882 100644
--- a/src/lib/anastasis_recovery.c
+++ b/src/lib/anastasis_recovery.c
@@ -348,6 +348,7 @@ keyshare_lookup_cb (void *cls,
348 = dd->details.server_failure.http_status 348 = dd->details.server_failure.http_status
349 }; 349 };
350 350
351 c->ci.async = true;
351 c->af (c->af_cls, 352 c->af (c->af_cls,
352 &csr); 353 &csr);
353 return; 354 return;
@@ -1043,7 +1044,9 @@ ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r)
1043 GNUNET_JSON_pack_string ("instructions", 1044 GNUNET_JSON_pack_string ("instructions",
1044 c->instructions), 1045 c->instructions),
1045 GNUNET_JSON_pack_bool ("solved", 1046 GNUNET_JSON_pack_bool ("solved",
1046 c->ci.solved)); 1047 c->ci.solved),
1048 GNUNET_JSON_pack_bool ("async",
1049 c->ci.async));
1047 GNUNET_assert (0 == 1050 GNUNET_assert (0 ==
1048 json_array_append_new (cs_arr, 1051 json_array_append_new (cs_arr,
1049 cs)); 1052 cs));
@@ -1119,6 +1122,9 @@ parse_cs_array (struct ANASTASIS_Recovery *r,
1119 GNUNET_JSON_spec_string ("type", 1122 GNUNET_JSON_spec_string ("type",
1120 &escrow_type), 1123 &escrow_type),
1121 GNUNET_JSON_spec_mark_optional ( 1124 GNUNET_JSON_spec_mark_optional (
1125 GNUNET_JSON_spec_bool ("async",
1126 &c->ci.async)),
1127 GNUNET_JSON_spec_mark_optional (
1122 GNUNET_JSON_spec_fixed_auto ("key_share", 1128 GNUNET_JSON_spec_fixed_auto ("key_share",
1123 &c->key_share)), 1129 &c->key_share)),
1124 GNUNET_JSON_spec_end () 1130 GNUNET_JSON_spec_end ()
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
index fab3c24..95632cc 100644
--- a/src/reducer/anastasis_api_recovery_redux.c
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -151,6 +151,13 @@ struct SelectChallengeContext
151 * Payment secret, if we are in the "pay" state. 151 * Payment secret, if we are in the "pay" state.
152 */ 152 */
153 struct ANASTASIS_PaymentSecretP ps; 153 struct ANASTASIS_PaymentSecretP ps;
154
155 /**
156 * Application asked us to only poll for existing
157 * asynchronous challenges, and not to being a
158 * new one.
159 */
160 bool poll_only;
154}; 161};
155 162
156 163
@@ -741,7 +748,6 @@ solve_challenge_cb (void *cls,
741 &ps), 748 &ps),
742 GNUNET_JSON_spec_end () 749 GNUNET_JSON_spec_end ()
743 }; 750 };
744
745 json_t *challenge; 751 json_t *challenge;
746 752
747 if (NULL == ri) 753 if (NULL == ri)
@@ -769,6 +775,80 @@ solve_challenge_cb (void *cls,
769 return; 775 return;
770 } 776 }
771 777
778 /* resume all async, unsolved challenges */
779 {
780 bool poll_started = false;
781
782 for (unsigned int i = 0; i<ri->cs_len; i++)
783 {
784 struct ANASTASIS_Challenge *ci = ri->cs[i];
785 const struct ANASTASIS_ChallengeDetails *cd;
786 json_t *challenge;
787 json_t *pin;
788
789 cd = ANASTASIS_challenge_get_details (ci);
790 if (cd->solved ||
791 (! cd->async) )
792 continue;
793
794 challenge = find_challenge_in_ri (sctx->state,
795 &cd->uuid);
796 if (NULL == challenge)
797 {
798 GNUNET_break_op (0);
799 ANASTASIS_redux_fail_ (sctx->cb,
800 sctx->cb_cls,
801 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
802 "challenge not found");
803 sctx_free (sctx);
804 return;
805 }
806 pin = json_object_get (challenge,
807 "answer-pin");
808 if (! json_is_integer (pin))
809 {
810 GNUNET_break_op (0);
811 ANASTASIS_redux_fail_ (sctx->cb,
812 sctx->cb_cls,
813 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
814 "async challenge 'answer-pin' not found");
815 sctx_free (sctx);
816 return;
817 }
818 if (GNUNET_OK !=
819 ANASTASIS_challenge_answer2 (ci,
820 psp,
821 timeout,
822 json_integer_value (pin),
823 &answer_feedback_cb,
824 sctx))
825 {
826 ANASTASIS_redux_fail_ (sctx->cb,
827 sctx->cb_cls,
828 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
829 "Failed to begin answering asynchronous challenge");
830 sctx_free (sctx);
831 return;
832 }
833 poll_started = true;
834 }
835
836 if (sctx->poll_only)
837 {
838 if (! poll_started)
839 {
840 GNUNET_break_op (0);
841 ANASTASIS_redux_fail_ (sctx->cb,
842 sctx->cb_cls,
843 TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
844 "no challenge available for polling");
845 return;
846 }
847 /* only polling, do not start new challenges */
848 return;
849 }
850 } /* end resuming async challenges */
851
772 /* Check if we got a payment_secret */ 852 /* Check if we got a payment_secret */
773 challenge = find_challenge_in_ri (sctx->state, 853 challenge = find_challenge_in_ri (sctx->state,
774 &sctx->uuid); 854 &sctx->uuid);
@@ -823,6 +903,7 @@ solve_challenge_cb (void *cls,
823 psp = &ps; 903 psp = &ps;
824 } 904 }
825 905
906 /* start or solve selected challenge */
826 for (unsigned int i = 0; i<ri->cs_len; i++) 907 for (unsigned int i = 0; i<ri->cs_len; i++)
827 { 908 {
828 struct ANASTASIS_Challenge *ci = ri->cs[i]; 909 struct ANASTASIS_Challenge *ci = ri->cs[i];
@@ -830,6 +911,8 @@ solve_challenge_cb (void *cls,
830 int ret; 911 int ret;
831 912
832 cd = ANASTASIS_challenge_get_details (ci); 913 cd = ANASTASIS_challenge_get_details (ci);
914 if (cd->async)
915 continue; /* handled above */
833 if (0 != 916 if (0 !=
834 GNUNET_memcmp (&sctx->uuid, 917 GNUNET_memcmp (&sctx->uuid,
835 &cd->uuid)) 918 &cd->uuid))
@@ -883,6 +966,12 @@ solve_challenge_cb (void *cls,
883 { 966 {
884 uint64_t ianswer = json_integer_value (pin); 967 uint64_t ianswer = json_integer_value (pin);
885 968
969 /* persist answer, in case async processing
970 happens via poll */
971 GNUNET_assert (0 ==
972 json_object_set (challenge,
973 "answer-pin",
974 pin));
886 ret = ANASTASIS_challenge_answer2 (ci, 975 ret = ANASTASIS_challenge_answer2 (ci,
887 psp, 976 psp,
888 timeout, 977 timeout,
@@ -1153,6 +1242,75 @@ solve_challenge (json_t *state,
1153 1242
1154 1243
1155/** 1244/**
1245 * The user asked for us to poll on pending
1246 * asynchronous challenges to see if they have
1247 * now completed / been satisfied.
1248 *
1249 * @param[in] state we are in
1250 * @param arguments our arguments with the solution
1251 * @param cb functiont o call with the new state
1252 * @param cb_cls closure for @a cb
1253 * @return handle to cancel challenge selection step
1254 */
1255static struct ANASTASIS_ReduxAction *
1256poll_challenges (json_t *state,
1257 const json_t *arguments,
1258 ANASTASIS_ActionCallback cb,
1259 void *cb_cls)
1260{
1261 struct SelectChallengeContext *sctx
1262 = GNUNET_new (struct SelectChallengeContext);
1263 json_t *rd;
1264
1265 if (NULL == arguments)
1266 {
1267 ANASTASIS_redux_fail_ (cb,
1268 cb_cls,
1269 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
1270 "arguments missing");
1271 return NULL;
1272 }
1273 rd = json_object_get (state,
1274 "recovery_document");
1275 if (NULL == rd)
1276 {
1277 GNUNET_break_op (0);
1278 ANASTASIS_redux_fail_ (cb,
1279 cb_cls,
1280 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
1281 "poll_challenges");
1282 return NULL;
1283 }
1284 sctx->poll_only = true;
1285 sctx->cb = cb;
1286 sctx->cb_cls = cb_cls;
1287 sctx->state = json_incref (state);
1288 sctx->args = json_incref ((json_t*) arguments);
1289 sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
1290 rd,
1291 &solve_challenge_cb,
1292 sctx,
1293 &core_secret_cb,
1294 sctx);
1295 if (NULL == sctx->r)
1296 {
1297 json_decref (sctx->state);
1298 json_decref (sctx->args);
1299 GNUNET_free (sctx);
1300 GNUNET_break_op (0);
1301 ANASTASIS_redux_fail_ (cb,
1302 cb_cls,
1303 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
1304 "'recovery_document' invalid");
1305 return NULL;
1306 }
1307 sctx->ra.cleanup = &sctx_free;
1308 sctx->ra.cleanup_cls = sctx;
1309 return &sctx->ra;
1310}
1311
1312
1313/**
1156 * The user selected a challenge to be solved. Handle the payment 1314 * The user selected a challenge to be solved. Handle the payment
1157 * process. 1315 * process.
1158 * 1316 *
@@ -1712,6 +1870,11 @@ ANASTASIS_recovery_action_ (json_t *state,
1712 }, 1870 },
1713 { 1871 {
1714 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING, 1872 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
1873 "poll",
1874 &poll_challenges
1875 },
1876 {
1877 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
1715 "back", 1878 "back",
1716 &ANASTASIS_back_generic_decrement_ 1879 &ANASTASIS_back_generic_decrement_
1717 }, 1880 },