mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-12-28 01:24:42 +01:00
572942c783
Per BIP-0171, the signature map is of pubkey to "The signature as would be pushed to the stack from a scriptSig or witness". Fixes 5298 Changelog-Fixed: PSBT: Fix signature encoding to comply with BIP-0171. Signed-off-by: Jon Griffiths <jon_p_griffiths@yahoo.com>
557 lines
15 KiB
C
557 lines
15 KiB
C
#include "config.h"
|
|
#include "../funder_policy.c"
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <common/setup.h>
|
|
#include <stdio.h>
|
|
|
|
/* AUTOGENERATED MOCKS START */
|
|
/* Generated stub for fromwire_bigsize */
|
|
bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
|
|
{ fprintf(stderr, "fromwire_bigsize called!\n"); abort(); }
|
|
/* Generated stub for fromwire_channel_id */
|
|
bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
|
|
struct channel_id *channel_id UNNEEDED)
|
|
{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); }
|
|
/* Generated stub for fromwire_node_id */
|
|
void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED)
|
|
{ fprintf(stderr, "fromwire_node_id called!\n"); abort(); }
|
|
/* Generated stub for towire_bigsize */
|
|
void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED)
|
|
{ fprintf(stderr, "towire_bigsize called!\n"); abort(); }
|
|
/* Generated stub for towire_channel_id */
|
|
void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED)
|
|
{ fprintf(stderr, "towire_channel_id called!\n"); abort(); }
|
|
/* Generated stub for towire_node_id */
|
|
void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED)
|
|
{ fprintf(stderr, "towire_node_id called!\n"); abort(); }
|
|
/* AUTOGENERATED MOCKS END */
|
|
|
|
struct test_case {
|
|
struct amount_sat their_funds;
|
|
struct amount_sat available_funds;
|
|
struct amount_sat channel_max;
|
|
struct amount_sat lease_request;
|
|
|
|
struct funder_policy policy;
|
|
|
|
struct amount_sat exp_our_funds;
|
|
bool expect_err;
|
|
};
|
|
|
|
struct test_case cases[] = {
|
|
/* Straight fixed */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(100000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 1111,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(1111),
|
|
.expect_err = false,
|
|
},
|
|
/* Match 0 */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(500),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = MATCH,
|
|
.mod = 0,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(1000),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(0),
|
|
.expect_err = false,
|
|
},
|
|
/* Match 100 */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(6000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = MATCH,
|
|
.mod = 100,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(1000),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(5000),
|
|
.expect_err = false,
|
|
},
|
|
/* Match 200 */
|
|
{
|
|
.their_funds = AMOUNT_SAT(2500),
|
|
.available_funds = AMOUNT_SAT(6000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = MATCH,
|
|
.mod = 200,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(1000),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(5000),
|
|
.expect_err = false,
|
|
},
|
|
/* Available 0 */
|
|
{
|
|
.their_funds = AMOUNT_SAT(2500),
|
|
.available_funds = AMOUNT_SAT(5000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = AVAILABLE,
|
|
.mod = 0,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(1000),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(0),
|
|
.expect_err = false,
|
|
},
|
|
/* Available 50 */
|
|
{
|
|
.their_funds = AMOUNT_SAT(2500),
|
|
.available_funds = AMOUNT_SAT(3000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = AVAILABLE,
|
|
.mod = 50,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(1000),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(1500),
|
|
.expect_err = false,
|
|
},
|
|
/* Available 100+ */
|
|
{
|
|
.their_funds = AMOUNT_SAT(2500),
|
|
.available_funds = AMOUNT_SAT(5000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = AVAILABLE,
|
|
.mod = 100,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(1000),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(5000),
|
|
.expect_err = false,
|
|
},
|
|
/* Fixed above per-channel max*/
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(5000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
/* We give these weird numbering so
|
|
* they're easy to identify when they break */
|
|
.mod = 1011,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(900),
|
|
.per_channel_min = AMOUNT_SAT(100),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(900),
|
|
.expect_err = false,
|
|
},
|
|
/* Fixed less than available space */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(5000),
|
|
.channel_max = AMOUNT_SAT(5500),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 1002,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(500),
|
|
.expect_err = false,
|
|
},
|
|
/* Fixed less than available funds */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(500),
|
|
.channel_max = AMOUNT_SAT(10000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 1001,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(500),
|
|
.expect_err = false,
|
|
},
|
|
/* Peer is under 'min_their_funding' */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(1000),
|
|
.channel_max = AMOUNT_SAT(10000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 999,
|
|
.min_their_funding = AMOUNT_SAT(5001),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(0),
|
|
.expect_err = true,
|
|
},
|
|
/* Peer exceeds 'max_their_funding' */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5001),
|
|
.available_funds = AMOUNT_SAT(5000),
|
|
.channel_max = AMOUNT_SAT(10000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 998,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(500),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(0),
|
|
.expect_err = true,
|
|
},
|
|
/* Fixed less than available funds less reserve tank */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(1000),
|
|
.channel_max = AMOUNT_SAT(10000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 997,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(100),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(900),
|
|
.expect_err = false,
|
|
},
|
|
/* Fixed no funds available after reserve */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(999),
|
|
.channel_max = AMOUNT_SAT(10000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 996,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(1000),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(0),
|
|
.expect_err = true,
|
|
},
|
|
/* Fixed no funds in channel */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(5000),
|
|
.channel_max = AMOUNT_SAT(5000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 995,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(1000),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(0),
|
|
.expect_err = true,
|
|
},
|
|
/* Fixed below per-channel min */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(5000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 988,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(10000),
|
|
.per_channel_min = AMOUNT_SAT(989),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(0),
|
|
.expect_err = true,
|
|
},
|
|
/* By default, use lease request as ceiling */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(100000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(980),
|
|
.policy = {
|
|
.opt = MATCH,
|
|
.mod = 100,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(100000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = false,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(980),
|
|
.expect_err = false,
|
|
},
|
|
/* Only fund lease requests */
|
|
{
|
|
.their_funds = AMOUNT_SAT(5000),
|
|
.available_funds = AMOUNT_SAT(100000),
|
|
.channel_max = AMOUNT_SAT(11000),
|
|
.lease_request = AMOUNT_SAT(0),
|
|
.policy = {
|
|
.opt = FIXED,
|
|
.mod = 985,
|
|
.min_their_funding = AMOUNT_SAT(0),
|
|
.max_their_funding = AMOUNT_SAT(10000),
|
|
.per_channel_max = AMOUNT_SAT(100000),
|
|
.per_channel_min = AMOUNT_SAT(0),
|
|
.fuzz_factor = 0,
|
|
.reserve_tank = AMOUNT_SAT(0),
|
|
.fund_probability = 100,
|
|
.leases_only = true,
|
|
},
|
|
|
|
.exp_our_funds = AMOUNT_SAT(0),
|
|
.expect_err = true,
|
|
},
|
|
};
|
|
|
|
static void check_fuzzing(struct test_case fuzzcase)
|
|
{
|
|
struct node_id id;
|
|
struct amount_sat our_funds;
|
|
struct amount_sat fuzz_max = AMOUNT_SAT(0),
|
|
fuzz_min = AMOUNT_SAT(UINT_MAX);
|
|
u64 fuzz_amt = fuzzcase.policy.mod * fuzzcase.policy.fuzz_factor / 100;
|
|
|
|
memset(&id, 2, sizeof(struct node_id));
|
|
|
|
for (size_t i = 0; i < 100; i++) {
|
|
calculate_our_funding(&fuzzcase.policy, id,
|
|
fuzzcase.their_funds,
|
|
fuzzcase.available_funds,
|
|
fuzzcase.channel_max,
|
|
fuzzcase.lease_request,
|
|
&our_funds);
|
|
if (amount_sat_greater(our_funds, fuzz_max))
|
|
fuzz_max = our_funds;
|
|
if (amount_sat_less(our_funds, fuzz_min))
|
|
fuzz_min = our_funds;
|
|
}
|
|
|
|
assert(fuzz_max.satoshis <= fuzzcase.policy.mod + fuzz_amt);
|
|
assert(fuzz_min.satoshis >= fuzzcase.policy.mod - fuzz_amt);
|
|
}
|
|
|
|
int main(int argc, const char *argv[])
|
|
{
|
|
struct funder_policy *policy;
|
|
struct node_id id;
|
|
struct amount_sat empty = AMOUNT_SAT(0), our_funds;
|
|
bool ok = true;
|
|
size_t i = 0, flips = 0;
|
|
struct test_case flipcase, fuzzcase;
|
|
size_t flipcount = 0;
|
|
const char *err;
|
|
|
|
common_setup(argv[0]);
|
|
memset(&id, 2, sizeof(struct node_id));
|
|
|
|
/* Check the default funder policy, at fixed (0msat) */
|
|
policy = default_funder_policy(tmpctx, FIXED, 0);
|
|
|
|
err = calculate_our_funding(policy, id,
|
|
AMOUNT_SAT(50000),
|
|
AMOUNT_SAT(50000),
|
|
AMOUNT_SAT(100000),
|
|
AMOUNT_SAT(100000),
|
|
&our_funds);
|
|
assert(amount_sat_eq(empty, our_funds));
|
|
assert(!err);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cases); i++) {
|
|
err = calculate_our_funding(&cases[i].policy, id,
|
|
cases[i].their_funds,
|
|
cases[i].available_funds,
|
|
cases[i].channel_max,
|
|
cases[i].lease_request,
|
|
&our_funds);
|
|
if (!amount_sat_eq(cases[i].exp_our_funds, our_funds)) {
|
|
fprintf(stderr, "FAIL policy: %s. expected %s, got %s\n",
|
|
funder_policy_desc(NULL, &cases[i].policy),
|
|
type_to_string(NULL, struct amount_sat,
|
|
&cases[i].exp_our_funds),
|
|
type_to_string(NULL, struct amount_sat,
|
|
&our_funds));
|
|
ok = false;
|
|
}
|
|
if (cases[i].expect_err != (err != NULL)) {
|
|
fprintf(stderr, "FAIL policy: %s. expected %serr,"
|
|
" got %s\n",
|
|
funder_policy_desc(NULL, &cases[i].policy),
|
|
cases[i].expect_err ? "" : "no ",
|
|
err ? err : "no err");
|
|
ok = false;
|
|
}
|
|
}
|
|
if (!ok)
|
|
exit(1);
|
|
|
|
/* Try a few fund_probabilitys, we should only fund
|
|
* 1/10th of the time */
|
|
flips = 10;
|
|
flipcase = cases[0];
|
|
flipcase.policy.fund_probability = flips;
|
|
|
|
for (i = 0; i < 100 * flips; i++) {
|
|
calculate_our_funding(&flipcase.policy, id,
|
|
flipcase.their_funds,
|
|
flipcase.available_funds,
|
|
flipcase.channel_max,
|
|
flipcase.lease_request,
|
|
&our_funds);
|
|
if (!amount_sat_eq(our_funds, AMOUNT_SAT(0)))
|
|
flipcount++;
|
|
}
|
|
/* We should be close to 100, give or take 100 on each side */
|
|
assert(flipcount > 0);
|
|
assert(flipcount < 200);
|
|
|
|
/* Try some value fuzzing with a high fuzz (for roll overs) */
|
|
fuzzcase = cases[0];
|
|
fuzzcase.policy.mod = 1000;
|
|
/* This is higher than our allowed fuzz factor, it'll
|
|
* get shifted down to 100 */
|
|
fuzzcase.policy.fuzz_factor = 100;
|
|
check_fuzzing(fuzzcase);
|
|
|
|
/* Try some fuzzing with a low fuzz */
|
|
fuzzcase.policy.fuzz_factor = 1;
|
|
check_fuzzing(fuzzcase);
|
|
common_shutdown();
|
|
|
|
return 0;
|
|
}
|