Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
35 changes: 5 additions & 30 deletions internal/core/application/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"math"
"runtime"
"slices"
"sort"
"strings"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -330,15 +329,11 @@ func (s *service) registerEventHandlers() {
return
}

// Calculate depth for new vtxos: max(parent depths) + 1
var maxDepth uint32
for _, v := range spentVtxos {
if v.Depth > maxDepth {
maxDepth = v.Depth
}
}
// Depth for new vtxos: max(parent depths) + 1, shared with the
// accept path via the same domain helper.
depth, _ := domain.ChainDepthAndParentMarkers(spentVtxos)
for i := range newVtxos {
newVtxos[i].Depth = maxDepth + 1
newVtxos[i].Depth = depth
}

// Make sure to mark new vtxos as swept if any of the spent inputs is swept as well or
Expand Down Expand Up @@ -1131,27 +1126,7 @@ func (s *service) SubmitOffchainTx(
}

// Compute depth and parent markers from spent VTXOs for the accepted event.
var maxDepth uint32
parentMarkerSet := make(map[string]struct{})
for _, v := range spentVtxos {
if v.Depth > maxDepth {
maxDepth = v.Depth
}
for _, markerID := range v.MarkerIDs {
if markerID != "" {
parentMarkerSet[markerID] = struct{}{}
}
}
}
var newDepth uint32
if len(spentVtxos) > 0 {
newDepth = maxDepth + 1
}
parentMarkerIDs := make([]string, 0, len(parentMarkerSet))
for id := range parentMarkerSet {
parentMarkerIDs = append(parentMarkerIDs, id)
}
sort.Strings(parentMarkerIDs)
newDepth, parentMarkerIDs := domain.ChainDepthAndParentMarkers(spentVtxos)

change, err := offchainTx.Accept(
fullySignedArkTx, signedCheckpointTxsMap,
Expand Down
38 changes: 37 additions & 1 deletion internal/core/domain/marker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package domain

import "fmt"
import (
"fmt"
"sort"
)

// MarkerInterval is the depth interval at which markers are created.
// VTXOs at depth 0, 100, 200, etc. create new markers.
Expand All @@ -27,6 +30,39 @@ type Marker struct {
ParentMarkerIDs []string
}

// ChainDepthAndParentMarkers computes the chain depth and inherited parent
// marker IDs for a new tx from the vtxos it spends. Depth is
// max(parent depths) + 1, or 0 when nothing is spent. ParentMarkerIDs is the
// deduplicated union of the spent vtxos' non-empty marker IDs, sorted so the
// result is deterministic regardless of input order.
//
// This is the single source of truth for the computation recorded on the
// OffchainTxAccepted event, so the call sites that need it cannot drift.
func ChainDepthAndParentMarkers(spent []Vtxo) (uint32, []string) {
var maxDepth uint32
parentSet := make(map[string]struct{})
for _, v := range spent {
if v.Depth > maxDepth {
maxDepth = v.Depth
}
for _, id := range v.MarkerIDs {
if id != "" {
parentSet[id] = struct{}{}
}
}
}
var depth uint32
if len(spent) > 0 {
depth = maxDepth + 1
}
parents := make([]string, 0, len(parentSet))
for id := range parentSet {
parents = append(parents, id)
}
sort.Strings(parents)
return depth, parents
}

// NewMarker computes marker information for a new offchain transaction.
// If the depth is at a marker boundary, it returns a new Marker and the marker IDs
// to assign to the child VTXOs (just the new marker ID).
Expand Down
35 changes: 35 additions & 0 deletions internal/core/domain/marker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,3 +659,38 @@ func TestDepth20k_MarkerBoundaryAndInheritance(t *testing.T) {
require.False(t, isAtMarkerBoundary(20099))
})
}

func TestChainDepthAndParentMarkers(t *testing.T) {
t.Run("empty spent input yields depth 0 and no markers", func(t *testing.T) {
depth, markers := ChainDepthAndParentMarkers(nil)
require.Equal(t, uint32(0), depth)
require.Empty(t, markers)
})

t.Run("single parent increments depth and inherits its marker", func(t *testing.T) {
depth, markers := ChainDepthAndParentMarkers([]Vtxo{
{Depth: 5, MarkerIDs: []string{"a"}},
})
require.Equal(t, uint32(6), depth)
require.Equal(t, []string{"a"}, markers)
})

t.Run("depth is max parent plus one, markers deduped sorted empties dropped", func(t *testing.T) {
depth, markers := ChainDepthAndParentMarkers([]Vtxo{
{Depth: 3, MarkerIDs: []string{"b"}},
{Depth: 7, MarkerIDs: []string{"a"}},
{Depth: 7, MarkerIDs: []string{"a", ""}},
})
require.Equal(t, uint32(8), depth)
require.Equal(t, []string{"a", "b"}, markers)
})

t.Run("parents without markers still increment depth", func(t *testing.T) {
depth, markers := ChainDepthAndParentMarkers([]Vtxo{
{Depth: 2, MarkerIDs: nil},
{Depth: 4, MarkerIDs: nil},
})
require.Equal(t, uint32(5), depth)
require.Empty(t, markers)
})
}
Loading