Skip to content

Commit d654ac5

Browse files
authored
feat(capabilities): Merge byoc capabilities with existing capabilities and pricing (#3871)
* feat(capabilities): Add BYOC external capability and integrate pricing logic - Introduced Capability_BYOCExternal constant to represent the new BYOC external capability. - Updated CapabilityNameLookup to include a name for BYOC external capability. - Enhanced GetCapabilitiesPrices method in orchestrator to append pricing for BYOC external capabilities, ensuring seamless integration with existing pricing logic. * Add tests for BYOC external capabilities pricing - Introduced multiple test cases for BYOC external capabilities, including validation of pricing logic and edge cases. - Implemented tests to ensure correct pricing retrieval for both default and sender-specific scenarios. - Added checks for handling zero and negative prices, as well as cases with nil external capabilities. * Refactor BYOC external capability to BYOC - Renamed Capability_BYOCExternal to Capability_BYOC for consistency. - Updated CapabilityNameLookup and related tests to reflect the new naming. - Adjusted orchestrator pricing logic to use the updated capability constant.
1 parent f117b44 commit d654ac5

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

core/capabilities.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const (
8888
Capability_ImageToText Capability = 34
8989
Capability_LiveVideoToVideo Capability = 35
9090
Capability_TextToSpeech Capability = 36
91+
Capability_BYOC Capability = 37
9192
)
9293

9394
var CapabilityNameLookup = map[Capability]string{
@@ -129,6 +130,7 @@ var CapabilityNameLookup = map[Capability]string{
129130
Capability_ImageToText: "Image to text",
130131
Capability_LiveVideoToVideo: "Live video to video",
131132
Capability_TextToSpeech: "Text to speech",
133+
Capability_BYOC: "byoc",
132134
}
133135

134136
var CapabilityTestLookup = map[Capability]CapabilityTest{

core/orch_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,6 +1667,140 @@ func TestAllCapsPriceInfo(t *testing.T) {
16671667
}
16681668
}
16691669

1670+
func TestBYOCExternalCapabilityConstant(t *testing.T) {
1671+
assert := assert.New(t)
1672+
assert.Equal(Capability(37), Capability_BYOC)
1673+
1674+
name, ok := CapabilityNameLookup[Capability_BYOC]
1675+
assert.True(ok, "Capability_BYOC should exist in CapabilityNameLookup")
1676+
assert.Equal("byoc", name)
1677+
}
1678+
1679+
func TestBYOCExternalCapsPriceInfo(t *testing.T) {
1680+
n, _ := NewLivepeerNode(nil, "", nil)
1681+
n.SetBasePrice("default", NewFixedPrice(big.NewRat(1, 1)))
1682+
n.Recipient = new(pm.MockRecipient)
1683+
orch := NewOrchestrator(n, nil)
1684+
1685+
addr1 := "0x1000000000000000000000000000000000000000"
1686+
1687+
n.ExternalCapabilities.Capabilities["my-service"] = &ExternalCapability{Name: "my-service"}
1688+
n.ExternalCapabilities.Capabilities["another-service"] = &ExternalCapability{Name: "another-service"}
1689+
n.SetPriceForExternalCapability("default", "my-service", big.NewRat(10, 1))
1690+
n.SetPriceForExternalCapability("default", "another-service", big.NewRat(20, 1))
1691+
1692+
// Also set a built-in cap price so we verify both coexist
1693+
n.SetBasePriceForCap("default", Capability_TextToImage, "default", NewFixedPrice(big.NewRat(5, 1)))
1694+
1695+
prices, err := orch.GetCapabilitiesPrices(ethcommon.HexToAddress(addr1))
1696+
assert.Nil(t, err)
1697+
assert.Len(t, prices, 3) // 1 built-in + 2 BYOC external
1698+
1699+
byocPrices := map[string]*net.PriceInfo{}
1700+
builtInCount := 0
1701+
for _, p := range prices {
1702+
if p.Capability == uint32(Capability_BYOC) {
1703+
byocPrices[p.Constraint] = p
1704+
} else {
1705+
builtInCount++
1706+
}
1707+
}
1708+
assert.Equal(t, 1, builtInCount)
1709+
assert.Len(t, byocPrices, 2)
1710+
1711+
myService := byocPrices["my-service"]
1712+
assert.NotNil(t, myService)
1713+
assert.Equal(t, int64(10), myService.PricePerUnit)
1714+
assert.Equal(t, int64(1), myService.PixelsPerUnit)
1715+
assert.Equal(t, uint32(Capability_BYOC), myService.Capability)
1716+
assert.Equal(t, "my-service", myService.Constraint)
1717+
1718+
anotherService := byocPrices["another-service"]
1719+
assert.NotNil(t, anotherService)
1720+
assert.Equal(t, int64(20), anotherService.PricePerUnit)
1721+
}
1722+
1723+
func TestBYOCExternalCapsSenderPricing(t *testing.T) {
1724+
n, _ := NewLivepeerNode(nil, "", nil)
1725+
n.SetBasePrice("default", NewFixedPrice(big.NewRat(1, 1)))
1726+
n.Recipient = new(pm.MockRecipient)
1727+
orch := NewOrchestrator(n, nil)
1728+
1729+
addr1 := "0x1000000000000000000000000000000000000000"
1730+
addr2 := "0x2000000000000000000000000000000000000000"
1731+
addr3 := "0x3000000000000000000000000000000000000000"
1732+
1733+
n.ExternalCapabilities.Capabilities["my-service"] = &ExternalCapability{Name: "my-service"}
1734+
n.SetPriceForExternalCapability("default", "my-service", big.NewRat(10, 1))
1735+
n.SetPriceForExternalCapability(addr1, "my-service", big.NewRat(100, 1))
1736+
n.SetPriceForExternalCapability(addr2, "my-service", big.NewRat(200, 1))
1737+
1738+
getBYOCPrice := func(addr string) int64 {
1739+
prices, err := orch.GetCapabilitiesPrices(ethcommon.HexToAddress(addr))
1740+
assert.Nil(t, err)
1741+
for _, p := range prices {
1742+
if p.Capability == uint32(Capability_BYOC) {
1743+
return p.PricePerUnit
1744+
}
1745+
}
1746+
t.Fatalf("no BYOC price found for %s", addr)
1747+
return 0
1748+
}
1749+
1750+
assert.Equal(t, int64(100), getBYOCPrice(addr1), "sender-specific price")
1751+
assert.Equal(t, int64(200), getBYOCPrice(addr2), "sender-specific price")
1752+
assert.Equal(t, int64(10), getBYOCPrice(addr3), "falls back to default")
1753+
}
1754+
1755+
func TestBYOCExternalCapsPriceEdgeCases(t *testing.T) {
1756+
addr := "0x1000000000000000000000000000000000000000"
1757+
1758+
tests := []struct {
1759+
name string
1760+
price *big.Rat
1761+
nilExtCaps bool
1762+
expectPresent bool
1763+
expectPrice int64
1764+
}{
1765+
{"zero price included", big.NewRat(0, 1), false, true, 0},
1766+
{"negative price skipped", big.NewRat(-5, 1), false, false, 0},
1767+
{"nil ExternalCapabilities", nil, true, false, 0},
1768+
}
1769+
1770+
for _, tt := range tests {
1771+
t.Run(tt.name, func(t *testing.T) {
1772+
n, _ := NewLivepeerNode(nil, "", nil)
1773+
n.SetBasePrice("default", NewFixedPrice(big.NewRat(1, 1)))
1774+
n.Recipient = new(pm.MockRecipient)
1775+
1776+
if tt.nilExtCaps {
1777+
n.ExternalCapabilities = nil
1778+
} else {
1779+
n.ExternalCapabilities.Capabilities["svc"] = &ExternalCapability{Name: "svc"}
1780+
n.SetPriceForExternalCapability("default", "svc", tt.price)
1781+
}
1782+
1783+
orch := NewOrchestrator(n, nil)
1784+
prices, err := orch.GetCapabilitiesPrices(ethcommon.HexToAddress(addr))
1785+
assert.Nil(t, err)
1786+
1787+
var byocPrice *net.PriceInfo
1788+
for _, p := range prices {
1789+
if p.Capability == uint32(Capability_BYOC) {
1790+
byocPrice = p
1791+
}
1792+
}
1793+
1794+
if tt.expectPresent {
1795+
assert.NotNil(t, byocPrice, "BYOC price should be present")
1796+
assert.Equal(t, tt.expectPrice, byocPrice.PricePerUnit)
1797+
} else {
1798+
assert.Nil(t, byocPrice, "BYOC price should not be present")
1799+
}
1800+
})
1801+
}
1802+
}
1803+
16701804
func TestDebitFees(t *testing.T) {
16711805
n, _ := NewLivepeerNode(nil, "", nil)
16721806
n.Balances = NewAddressBalances(5 * time.Second)

core/orchestrator.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,32 @@ func (orch *orchestrator) GetCapabilitiesPrices(sender ethcommon.Address) ([]*ne
306306
capPrices = append(capPrices, price)
307307
}
308308

309+
// Append BYOC external capability prices using Capability_BYOC.
310+
// The registered capability name is set as the Constraint, making BYOC
311+
// pricing seamless alongside built-in capabilities like LiveVideoToVideo.
312+
if orch.node != nil && orch.node.ExternalCapabilities != nil {
313+
for name := range orch.node.ExternalCapabilities.Capabilities {
314+
price := orch.node.GetPriceForJob(ethAddr, name)
315+
if price == nil {
316+
price = orch.node.GetPriceForJob("default", name)
317+
}
318+
if price == nil || price.Num().Sign() < 0 {
319+
continue
320+
}
321+
priceInt64, err := common.PriceToInt64(price)
322+
if err != nil {
323+
glog.Errorf("error converting external capability %q price to int64: %v", name, err)
324+
continue
325+
}
326+
capPrices = append(capPrices, &net.PriceInfo{
327+
PricePerUnit: priceInt64.Num().Int64(),
328+
PixelsPerUnit: priceInt64.Denom().Int64(),
329+
Capability: uint32(Capability_BYOC),
330+
Constraint: name,
331+
})
332+
}
333+
}
334+
309335
return capPrices, nil
310336
}
311337

0 commit comments

Comments
 (0)