diff --git a/objects/account.go b/objects/account.go index c7423f0..d08d388 100644 --- a/objects/account.go +++ b/objects/account.go @@ -706,6 +706,7 @@ func (obj Account) GetChannels(requester *requester.Requester, bitcoinNetwork Bi id } channel_short_channel_id: short_channel_id + channel_channel_point: channel_point } } } diff --git a/objects/channel.go b/objects/channel.go index 950f9e0..84e80ea 100644 --- a/objects/channel.go +++ b/objects/channel.go @@ -66,6 +66,9 @@ type Channel struct { // ShortChannelId The unique identifier of the channel on Lightning Network, which is the location in the chain that the channel was confirmed. The format is ::. ShortChannelId *string `json:"channel_short_channel_id"` + // Typename The typename of the object + ChannelPoint *string `json:"channel_channel_point"` + // Typename The typename of the object Typename string `json:"__typename"` } @@ -165,6 +168,7 @@ fragment ChannelFragment on Channel { id } channel_short_channel_id: short_channel_id + channel_channel_point: channel_point } ` ) diff --git a/objects/lightspark_node_to_ripcord_updates_connection.go b/objects/lightspark_node_to_ripcord_updates_connection.go new file mode 100644 index 0000000..e7daa66 --- /dev/null +++ b/objects/lightspark_node_to_ripcord_updates_connection.go @@ -0,0 +1,119 @@ +// Copyright ©, 2024-present, Lightspark Group, Inc. - All Rights Reserved +package objects + +import "github.com/lightsparkdev/go-sdk/types" + +type LightsparkNodeToRipcordUpdatesConnection struct { + FromDate types.Date `json:"lightspark_node_to_ripcord_updates_connection_from_date"` + + ToDate types.Date `json:"lightspark_node_to_ripcord_updates_connection_to_date"` + + // Entities The daily liquidity forecasts for the current page of this connection. + Entities []RipcordUpdate `json:"lightspark_node_to_ripcord_updates_connection_entities"` +} + +const ( + LightsparkNodeToRipcordUpdatesConnectionFragment = ` +fragment LightsparkNodeToRipcordUpdatesConnectionFragment on LightsparkNodeToRipcordUpdatesConnection { + __typename + ripcord_update_commitment_number: commitment_number + ripcord_update_ripcord_update_status: ripcord_update_status + ripcord_update_data: data + ripcord_update_channel: channel { + __typename + channel_id: id + channel_created_at: created_at + channel_updated_at: updated_at + channel_funding_transaction: funding_transaction { + id + } + channel_capacity: capacity { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_local_balance: local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_local_unsettled_balance: local_unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_remote_balance: remote_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_remote_unsettled_balance: remote_unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_unsettled_balance: unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_status: status + channel_estimated_force_closure_wait_minutes: estimated_force_closure_wait_minutes + channel_commit_fee: commit_fee { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_fees: fees { + __typename + channel_fees_base_fee: base_fee { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_fees_fee_rate_per_mil: fee_rate_per_mil + } + channel_remote_node: remote_node { + id + } + channel_local_node: local_node { + id + } + channel_short_channel_id: short_channel_id + channel_channel_point: channel_point + } +} +` +) diff --git a/objects/lightspark_node_with_o_s_k.go b/objects/lightspark_node_with_o_s_k.go index c4cb608..b98e459 100644 --- a/objects/lightspark_node_with_o_s_k.go +++ b/objects/lightspark_node_with_o_s_k.go @@ -455,6 +455,7 @@ func (obj LightsparkNodeWithOSK) GetChannels(requester *requester.Requester, fir id } channel_short_channel_id: short_channel_id + channel_channel_point: channel_point } } } @@ -523,3 +524,130 @@ func (obj LightsparkNodeWithOSK) GetDailyLiquidityForecasts(requester *requester json.Unmarshal(jsonString, &result) return result, nil } + +// GetRipcordUpdates The ripcord updates that correspond to state updates of channels that this node is part of. +func (obj LightsparkNodeWithOSK) GetRipcordUpdates(requester *requester.Requester, first *int, after *string) (*LightsparkNodeToRipcordUpdatesConnection, error) { + query := `query FetchLightsparkNodeToRipcordUpdatesConnection($entity_id: ID!, $first: Int, $after: String) { + entity(id: $entity_id) { + ... on LightsparkNodeWithOSK { + ripcord_updates(, first: $first, after: $after) { + __typename + ripcord_update_commitment_number: commitment_number + ripcord_update_ripcord_update_status: ripcord_update_status + ripcord_update_data: data + ripcord_update_channel: channel { + __typename + channel_id: id + channel_created_at: created_at + channel_updated_at: updated_at + channel_funding_transaction: funding_transaction { + id + } + channel_capacity: capacity { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_local_balance: local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_local_unsettled_balance: local_unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_remote_balance: remote_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_remote_unsettled_balance: remote_unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_unsettled_balance: unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_status: status + channel_estimated_force_closure_wait_minutes: estimated_force_closure_wait_minutes + channel_commit_fee: commit_fee { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_fees: fees { + __typename + channel_fees_base_fee: base_fee { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_fees_fee_rate_per_mil: fee_rate_per_mil + } + channel_remote_node: remote_node { + id + } + channel_local_node: local_node { + id + } + channel_short_channel_id: short_channel_id + channel_channel_point: channel_point + } + } + } + } +}` + variables := map[string]interface{}{ + "entity_id": obj.Id, + "first": first, + "after": after, + } + + response, err := requester.ExecuteGraphql(query, variables, nil) + if err != nil { + return nil, err + } + + output := response["entity"].(map[string]interface{})["ripcord_updates"].(map[string]interface{}) + var result *LightsparkNodeToRipcordUpdatesConnection + jsonString, err := json.Marshal(output) + json.Unmarshal(jsonString, &result) + return result, nil +} diff --git a/objects/lightspark_node_with_remote_signing.go b/objects/lightspark_node_with_remote_signing.go index d6f27a0..310b831 100644 --- a/objects/lightspark_node_with_remote_signing.go +++ b/objects/lightspark_node_with_remote_signing.go @@ -447,6 +447,7 @@ func (obj LightsparkNodeWithRemoteSigning) GetChannels(requester *requester.Requ id } channel_short_channel_id: short_channel_id + channel_channel_point: channel_point } } } @@ -515,3 +516,132 @@ func (obj LightsparkNodeWithRemoteSigning) GetDailyLiquidityForecasts(requester json.Unmarshal(jsonString, &result) return result, nil } + +// GetRipcordUpdates The ripcord updates that correspond to state updates of channels that this node is part of. +func (obj LightsparkNodeWithRemoteSigning) GetRipcordUpdates(requester *requester.Requester, first *int, after *string) (*LightsparkNodeToRipcordUpdatesConnection, error) { + query := `query FetchLightsparkNodeToRipcordUpdatesConnection($entity_id: ID!, $first: Int, $after: String) { + entity(id: $entity_id) { + ... on LightsparkNodeWithRemoteSigning { + ripcord_updates(, first: $first, after: $after) { + __typename + lightspark_node_to_ripcord_updates_connection_entities: entities { + ripcord_update_commitment_number: commitment_number + ripcord_update_ripcord_update_status: ripcord_update_status + ripcord_update_data: data + ripcord_update_channel: channel { + __typename + channel_id: id + channel_created_at: created_at + channel_updated_at: updated_at + channel_funding_transaction: funding_transaction { + id + } + channel_capacity: capacity { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_local_balance: local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_local_unsettled_balance: local_unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_remote_balance: remote_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_remote_unsettled_balance: remote_unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_unsettled_balance: unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_status: status + channel_estimated_force_closure_wait_minutes: estimated_force_closure_wait_minutes + channel_commit_fee: commit_fee { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_fees: fees { + __typename + channel_fees_base_fee: base_fee { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_fees_fee_rate_per_mil: fee_rate_per_mil + } + channel_remote_node: remote_node { + id + } + channel_local_node: local_node { + id + } + channel_short_channel_id: short_channel_id + channel_channel_point: channel_point + } + } + } + } + } +}` + variables := map[string]interface{}{ + "entity_id": obj.Id, + "first": first, + "after": after, + } + + response, err := requester.ExecuteGraphql(query, variables, nil) + if err != nil { + return nil, err + } + + output := response["entity"].(map[string]interface{})["ripcord_updates"].(map[string]interface{}) + var result *LightsparkNodeToRipcordUpdatesConnection + jsonString, err := json.Marshal(output) + json.Unmarshal(jsonString, &result) + return result, nil +} diff --git a/objects/ripcord_update.go b/objects/ripcord_update.go new file mode 100644 index 0000000..d12493f --- /dev/null +++ b/objects/ripcord_update.go @@ -0,0 +1,124 @@ +// Copyright ©, 2024-present, Lightspark Group, Inc. - All Rights Reserved +package objects + +// RipcordUpdate This is an object representing the state of a channel which can be combined into broadcastable transactions for customers to claim their funds. +type RipcordUpdate struct { + + // CommitmentNumber The decreasing number that represents the state of a channel. + CommitmentNumber *int `json:"ripcord_update_commitment_number"` + + // RipcordUpdateStatus The status of the ripcord update that signifies db creation and s3 sending. + RipcordUpdateStatus *string `json:"ripcord_update_ripcord_update_status"` + + // Data The json blob that has all of the relevant state information. + Data *string `json:"ripcord_update_data"` + + // Channel The ent channel that this ripcord update is regarding. + Channel *Channel `json:"ripcord_update_channel"` +} + +const ( + RipcordUpdateFragment = ` +fragment RipcordUpdateFragment on RipcordUpdate { + __typename + ripcord_update_commitment_number: commitment_number + ripcord_update_ripcord_update_status: ripcord_update_status + ripcord_update_data: data + ripcord_update_channel: channel { + __typename + channel_id: id + channel_created_at: created_at + channel_updated_at: updated_at + channel_funding_transaction: funding_transaction { + id + } + channel_capacity: capacity { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_local_balance: local_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_local_unsettled_balance: local_unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_remote_balance: remote_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_remote_unsettled_balance: remote_unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_unsettled_balance: unsettled_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_total_balance: total_balance { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_status: status + channel_estimated_force_closure_wait_minutes: estimated_force_closure_wait_minutes + channel_commit_fee: commit_fee { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_fees: fees { + __typename + channel_fees_base_fee: base_fee { + __typename + currency_amount_original_value: original_value + currency_amount_original_unit: original_unit + currency_amount_preferred_currency_unit: preferred_currency_unit + currency_amount_preferred_currency_value_rounded: preferred_currency_value_rounded + currency_amount_preferred_currency_value_approx: preferred_currency_value_approx + } + channel_fees_fee_rate_per_mil: fee_rate_per_mil + } + channel_remote_node: remote_node { + id + } + channel_local_node: local_node { + id + } + channel_short_channel_id: short_channel_id + channel_channel_point: channel_point + } +} +` +) diff --git a/scripts/set_ripcord_address_sparknode.go b/scripts/set_ripcord_address_sparknode.go new file mode 100644 index 0000000..b73beaa --- /dev/null +++ b/scripts/set_ripcord_address_sparknode.go @@ -0,0 +1,14 @@ +// Copyright ©, 2024-present, Lightspark Group, Inc. - All Rights Reserved +package scripts + +const SET_RIPCORD_ADDRESS_SPARKNODE_MUTATION = ` +mutation SetRipcordAddressSparknode( + $node_id: ID! + $ripcord_address: String! +) { + set_ripcord_address_sparknode(input: { + node_id: $node_id + ripcord_address: $ripcord_address + }) +} +` \ No newline at end of file diff --git a/services/test/regtest/regtest_client_test.go b/services/test/regtest/regtest_client_test.go index b86e1d5..1062004 100644 --- a/services/test/regtest/regtest_client_test.go +++ b/services/test/regtest/regtest_client_test.go @@ -1,6 +1,3 @@ -//go:build integration -// +build integration - package regtest_test import ( @@ -10,6 +7,7 @@ import ( "time" "github.com/lightsparkdev/go-sdk/objects" + "github.com/lightsparkdev/go-sdk/scripts" "github.com/lightsparkdev/go-sdk/services" servicestest "github.com/lightsparkdev/go-sdk/services/test" "github.com/lightsparkdev/go-sdk/utils" @@ -156,6 +154,133 @@ func TestGetFundingAddress(t *testing.T) { t.Log(address) } +// TestRipcordMultipleUpdates makes multiple payments, and confirms the ripcord state updates makes sense for each payment +func TestRipcordMultipleUpdates(t *testing.T) { + env := servicestest.NewConfig() + client := services.NewLightsparkClient(env.ApiClientID, env.ApiClientSecret, &env.ApiClientEndpoint) + + variables := map[string]interface{}{ + "node_id": env.NodeID, + "ripcord_address": "bcrt1qdvqqntu9kxq996ur477un3z4y8z4yky0ex3yye", // random test regtest bitcoin wallet address + } + _, err := client.Requester.ExecuteGraphql(scripts.SET_RIPCORD_ADDRESS_SPARKNODE_MUTATION, variables, nil) + if err != nil { + t.Fatalf("Failed to set ripcord address on the sparknode. %v\n", err) + } + + // We initially get a snapshot of the state of the updates table because this can vary depending on how and when this test runs. + // The main things we care about are the channel point and commitment number which can uniquely identify succesful state updates + nodeEntity, err := client.GetEntity(env.NodeID) + require.NoError(t, err) + castNode, didCast := (*nodeEntity).(objects.LightsparkNodeWithRemoteSigning) + require.True(t, didCast) + limit := 1 + updates, err := castNode.GetRipcordUpdates(client.Requester, &limit, nil) + require.NoError(t, err) + initialUpdateLength := len(updates.Entities) + var initialChannelPoint string + var initialCommitmentNumber int + if initialUpdateLength > 0 { + initialChannelPoint = *updates.Entities[0].Channel.ChannelPoint + initialCommitmentNumber = *updates.Entities[0].CommitmentNumber + } + + // Payment 1 + invoice, err := createInvoiceForNode(client, env.NodeID) + require.NoError(t, err) + t.Logf("Created invoice %v", invoice) + payment, err := client.CreateTestModePayment(env.NodeID, invoice.Data.EncodedPaymentRequest, nil) + require.NoError(t, err) + t.Logf("Created payment %v", payment) + tx := *waitForPaymentCompletion(t, client, payment.Id) + if tx != nil { + require.Equal(t, objects.TransactionStatusSuccess, tx.GetStatus()) + t.Log(tx) + } + + var lastUpdate objects.RipcordUpdate + compareRipcordUpdates := func(initialChannelPoint string, initialCommitmentNumber int) { + // If the initial snapshot of the ripcord state is not empty, which is likely to be the case, we continuously poll + // for the latest updates until either the channel point is different or the commitment number is different. Right + // now we cannot force the payment through a specific channel easily. As such, if the channel points are not the + // same, we know that we do not need to compare the two updates, we can simply do a status check and return indicating + // that the ripcord update was successful. If the channel point stays the same, we break out of the below loop when + // the commitment number changes, indicating that a new commitment was received. We then compare the commitment numbers + // of the previous two states and check if they are related in the correct way. If no new update happens, this section + // will timeout the test, causing it to fail. This polling section seems sturdier than sleeping a default amount. + updates, err = castNode.GetRipcordUpdates(client.Requester, &limit, nil) + require.NoError(t, err) + lastUpdate = updates.Entities[0] + if lastUpdate.Channel.ChannelPoint == nil { + t.Fatalf("Channel point should be set.") + } + if lastUpdate.CommitmentNumber == nil { + t.Fatalf("Commitment number should be set.") + } + for initialChannelPoint == *lastUpdate.Channel.ChannelPoint && initialCommitmentNumber == *lastUpdate.CommitmentNumber { + updates, err = castNode.GetRipcordUpdates(client.Requester, &limit, nil) + require.NoError(t, err) + lastUpdate = updates.Entities[0] + } + if initialChannelPoint == *lastUpdate.Channel.ChannelPoint { + if *lastUpdate.CommitmentNumber >= initialCommitmentNumber { + t.Fatalf("Commitment Numbers for newer states should be strictly decreasing.") + } + if initialCommitmentNumber - *lastUpdate.CommitmentNumber > 2 { + t.Fatalf("These should not differ by more than 2. There will be two new updates every payment, one for htlc and one for the main payment.") + } + } + if *lastUpdate.RipcordUpdateStatus != "SUCCESS" { + t.Fatalf("Ripcord Update status should be successful to indicate proper storing in the db.") + } + } + if initialUpdateLength == 0 { + // If the initial snapshot of the ripcord state for this node is nonexistent, we just wait and poll until there + // exists updates. When there is an update that exists, we wait to see if it is the status we expect. + updates, err = castNode.GetRipcordUpdates(client.Requester, &limit, nil) + require.NoError(t, err) + + for len(updates.Entities) == 0 { + nodeEntity, err := client.GetEntity(env.NodeID) + require.NoError(t, err) + castNode, didCast = (*nodeEntity).(objects.LightsparkNodeWithRemoteSigning) + require.True(t, didCast) + updates, err = castNode.GetRipcordUpdates(client.Requester, &limit, nil) + require.NoError(t, err) + time.Sleep(100 * time.Millisecond) + } + lastUpdate = updates.Entities[0] + if lastUpdate.RipcordUpdateStatus == nil { + t.Fatalf("Ripcord Update Status should be set.") + } + if *lastUpdate.RipcordUpdateStatus != "SUCCESS" { + t.Fatalf("Ripcord Update status should be successful to indicate proper storing in the db.") + } + } else { + compareRipcordUpdates(initialChannelPoint, initialCommitmentNumber) + } + + // Once again, after the first round of payments we get the intermediate state information to compare with the second round + // of payments. + initialCommitmentNumber = *lastUpdate.CommitmentNumber + initialChannelPoint = *lastUpdate.Channel.ChannelPoint + + // Payment 2 + invoice, err = createInvoiceForNode(client, env.NodeID) + require.NoError(t, err) + t.Logf("Created invoice %v", invoice) + payment, err = client.CreateTestModePayment(env.NodeID, invoice.Data.EncodedPaymentRequest, nil) + require.NoError(t, err) + t.Logf("Created payment %v", payment) + tx = *waitForPaymentCompletion(t, client, payment.Id) + if tx != nil { + require.Equal(t, objects.TransactionStatusSuccess, tx.GetStatus()) + t.Log(tx) + } + + compareRipcordUpdates(initialChannelPoint, initialCommitmentNumber) +} + func createInvoiceForNode(client *services.LightsparkClient, nodeID string) (*objects.Invoice, error) { invoice, err := client.CreateInvoice(nodeID, InvoiceAmount, nil, nil, nil) if err != nil {