Merge pull request #2212 from halseth/autopilot-bos-scores

Bos score enabled Autopilot
This commit is contained in:
Olaoluwa Osuntokun 2019-02-14 15:22:13 -08:00 committed by GitHub
commit 81783a60dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 511 additions and 37 deletions

@ -25,7 +25,7 @@ type WeightedCombAttachment struct {
// NewWeightedCombAttachment creates a new instance of a WeightedCombAttachment.
func NewWeightedCombAttachment(h ...*WeightedHeuristic) (
AttachmentHeuristic, error) {
*WeightedCombAttachment, error) {
// The sum of weights given to the sub-heuristics must sum to exactly
// 1.0.
@ -44,8 +44,9 @@ func NewWeightedCombAttachment(h ...*WeightedHeuristic) (
}
// A compile time assertion to ensure WeightedCombAttachment meets the
// AttachmentHeuristic interface.
// AttachmentHeuristic and ScoreSettable interfaces.
var _ AttachmentHeuristic = (*WeightedCombAttachment)(nil)
var _ ScoreSettable = (*WeightedCombAttachment)(nil)
// Name returns the name of this heuristic.
//
@ -126,3 +127,36 @@ func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []Channel,
return scores, nil
}
// SetNodeScores is used to set the internal map from NodeIDs to scores. The
// passed scores must be in the range [0, 1.0]. The fist parameter is the name
// of the targeted heuristic, to allow recursively target specific
// sub-heuristics. The returned boolean indicates whether the targeted
// heuristic was found.
//
// Since this heuristic doesn't keep any internal scores, it will recursively
// apply the scores to its sub-heuristics.
//
// NOTE: This is a part of the ScoreSettable interface.
func (c *WeightedCombAttachment) SetNodeScores(targetHeuristic string,
newScores map[NodeID]float64) (bool, error) {
found := false
for _, h := range c.heuristics {
// It must be ScoreSettable to be available for external
// scores.
s, ok := h.AttachmentHeuristic.(ScoreSettable)
if !ok {
continue
}
// Heuristic supports scoring, attempt to set them.
applied, err := s.SetNodeScores(targetHeuristic, newScores)
if err != nil {
return false, err
}
found = found || applied
}
return found, nil
}

@ -0,0 +1,122 @@
package autopilot
import (
"fmt"
"sync"
"github.com/btcsuite/btcutil"
)
// ExternalScoreAttachment is an implementation of the AttachmentHeuristic
// interface that allows an external source to provide it with node scores.
type ExternalScoreAttachment struct {
// TODO(halseth): persist across restarts.
nodeScores map[NodeID]float64
sync.Mutex
}
// NewExternalScoreAttachment creates a new instance of an
// ExternalScoreAttachment.
func NewExternalScoreAttachment() *ExternalScoreAttachment {
return &ExternalScoreAttachment{}
}
// A compile time assertion to ensure ExternalScoreAttachment meets the
// AttachmentHeuristic and ScoreSettable interfaces.
var _ AttachmentHeuristic = (*ExternalScoreAttachment)(nil)
var _ ScoreSettable = (*ExternalScoreAttachment)(nil)
// Name returns the name of this heuristic.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (s *ExternalScoreAttachment) Name() string {
return "externalscore"
}
// SetNodeScores is used to set the internal map from NodeIDs to scores. The
// passed scores must be in the range [0, 1.0]. The fist parameter is the name
// of the targeted heuristic, to allow recursively target specific
// sub-heuristics. The returned boolean indicates whether the targeted
// heuristic was found.
//
// NOTE: This is a part of the ScoreSettable interface.
func (s *ExternalScoreAttachment) SetNodeScores(targetHeuristic string,
newScores map[NodeID]float64) (bool, error) {
// Return if this heuristic wasn't targeted.
if targetHeuristic != s.Name() {
return false, nil
}
// Since there's a requirement that all score are in the range [0,
// 1.0], we validate them before setting the internal list.
for nID, s := range newScores {
if s < 0 || s > 1.0 {
return false, fmt.Errorf("invalid score %v for "+
"nodeID %v", s, nID)
}
}
s.Lock()
defer s.Unlock()
s.nodeScores = newScores
return true, nil
}
// NodeScores is a method that given the current channel graph and current set
// of local channels, scores the given nodes according to the preference of
// opening a channel of the given size with them. The returned channel
// candidates maps the NodeID to a NodeScore for the node.
//
// The returned scores will be in the range [0, 1.0], where 0 indicates no
// improvement in connectivity if a channel is opened to this node, while 1.0
// is the maximum possible improvement in connectivity.
//
// The scores are determined by checking the internal node scores list. Nodes
// not known will get a score of 0.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (s *ExternalScoreAttachment) NodeScores(g ChannelGraph, chans []Channel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) {
existingPeers := make(map[NodeID]struct{})
for _, c := range chans {
existingPeers[c.Node] = struct{}{}
}
s.Lock()
defer s.Unlock()
// Fill the map of candidates to return.
candidates := make(map[NodeID]*NodeScore)
for nID := range nodes {
var score float64
if nodeScore, ok := s.nodeScores[nID]; ok {
score = float64(nodeScore)
}
_, ok := existingPeers[nID]
switch {
// If the node is among or existing channel peers, we don't
// need another channel.
case ok:
continue
// Instead of adding a node with score 0 to the returned set,
// we just skip it.
case score == 0:
continue
}
candidates[nID] = &NodeScore{
NodeID: nID,
Score: score,
}
}
return candidates, nil
}

@ -0,0 +1,100 @@
package autopilot_test
import (
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/autopilot"
)
// randKey returns a random public key.
func randKey() (*btcec.PublicKey, error) {
priv, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return nil, err
}
return priv.PubKey(), nil
}
// TestSetNodeScores tests that the scores returned by the
// ExternalScoreAttachment correctly reflects the scores we set last.
func TestSetNodeScores(t *testing.T) {
t.Parallel()
const name = "externalscore"
h := autopilot.NewExternalScoreAttachment()
// Create a list of random node IDs.
const numKeys = 20
var pubkeys []autopilot.NodeID
for i := 0; i < numKeys; i++ {
k, err := randKey()
if err != nil {
t.Fatal(err)
}
nID := autopilot.NewNodeID(k)
pubkeys = append(pubkeys, nID)
}
// Set the score of half of the nodes.
scores := make(map[autopilot.NodeID]float64)
for i := 0; i < numKeys/2; i++ {
nID := pubkeys[i]
scores[nID] = 0.05 * float64(i)
}
applied, err := h.SetNodeScores(name, scores)
if err != nil {
t.Fatal(err)
}
if !applied {
t.Fatalf("scores were not applied")
}
// Query all scores, half should be set, half should be zero.
q := make(map[autopilot.NodeID]struct{})
for _, nID := range pubkeys {
q[nID] = struct{}{}
}
resp, err := h.NodeScores(
nil, nil, btcutil.Amount(btcutil.SatoshiPerBitcoin), q,
)
if err != nil {
t.Fatal(err)
}
for i := 0; i < numKeys; i++ {
var expected float64
if i < numKeys/2 {
expected = 0.05 * float64(i)
}
nID := pubkeys[i]
var score float64
if s, ok := resp[nID]; ok {
score = s.Score
}
if score != expected {
t.Fatalf("expected score %v, got %v",
expected, score)
}
}
// Try to apply scores with bogus name, should not be applied.
applied, err = h.SetNodeScores("dummy", scores)
if err != nil {
t.Fatal(err)
}
if applied {
t.Fatalf("scores were applied")
}
}

@ -142,11 +142,27 @@ type AttachmentHeuristic interface {
map[NodeID]*NodeScore, error)
}
// ScoreSettable is an interface that indicates that the scores returned by the
// heuristic can be mutated by an external caller. The ExternalScoreAttachment
// currently implements this interface, and so should any heuristic that is
// using the ExternalScoreAttachment as a sub-heuristic, or keeps their own
// internal list of mutable scores, to allow access to setting the internal
// scores.
type ScoreSettable interface {
// SetNodeScores is used to set the internal map from NodeIDs to
// scores. The passed scores must be in the range [0, 1.0]. The fist
// parameter is the name of the targeted heuristic, to allow
// recursively target specific sub-heuristics. The returned boolean
// indicates whether the targeted heuristic was found.
SetNodeScores(string, map[NodeID]float64) (bool, error)
}
var (
// availableHeuristics holds all heuristics possible to combine for use
// with the autopilot agent.
availableHeuristics = []AttachmentHeuristic{
NewPrefAttachment(),
NewExternalScoreAttachment(),
}
// AvailableHeuristics is a map that holds the name of available

@ -287,3 +287,27 @@ func (m *Manager) QueryHeuristics(nodes []NodeID) (HeuristicScores, error) {
log.Debugf("Querying heuristics for %d nodes", len(n))
return m.pilot.queryHeuristics(n)
}
// SetNodeScores is used to set the scores of the given heuristic, if it is
// active, and ScoreSettable.
func (m *Manager) SetNodeScores(name string, scores map[NodeID]float64) error {
// It must be ScoreSettable to be available for external
// scores.
s, ok := m.cfg.PilotCfg.Heuristic.(ScoreSettable)
if !ok {
return fmt.Errorf("current heuristic doesn't support " +
"external scoring")
}
// Heuristic was found, set its node scores.
applied, err := s.SetNodeScores(name, scores)
if err != nil {
return err
}
if !applied {
return fmt.Errorf("heuristic with name %v not found", name)
}
return nil
}

@ -33,7 +33,7 @@ func (m *StatusRequest) Reset() { *m = StatusRequest{} }
func (m *StatusRequest) String() string { return proto.CompactTextString(m) }
func (*StatusRequest) ProtoMessage() {}
func (*StatusRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_7db7978f022d4696, []int{0}
return fileDescriptor_autopilot_569ccc5858bc499b, []int{0}
}
func (m *StatusRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StatusRequest.Unmarshal(m, b)
@ -65,7 +65,7 @@ func (m *StatusResponse) Reset() { *m = StatusResponse{} }
func (m *StatusResponse) String() string { return proto.CompactTextString(m) }
func (*StatusResponse) ProtoMessage() {}
func (*StatusResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_7db7978f022d4696, []int{1}
return fileDescriptor_autopilot_569ccc5858bc499b, []int{1}
}
func (m *StatusResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StatusResponse.Unmarshal(m, b)
@ -104,7 +104,7 @@ func (m *ModifyStatusRequest) Reset() { *m = ModifyStatusRequest{} }
func (m *ModifyStatusRequest) String() string { return proto.CompactTextString(m) }
func (*ModifyStatusRequest) ProtoMessage() {}
func (*ModifyStatusRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_7db7978f022d4696, []int{2}
return fileDescriptor_autopilot_569ccc5858bc499b, []int{2}
}
func (m *ModifyStatusRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ModifyStatusRequest.Unmarshal(m, b)
@ -141,7 +141,7 @@ func (m *ModifyStatusResponse) Reset() { *m = ModifyStatusResponse{} }
func (m *ModifyStatusResponse) String() string { return proto.CompactTextString(m) }
func (*ModifyStatusResponse) ProtoMessage() {}
func (*ModifyStatusResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_7db7978f022d4696, []int{3}
return fileDescriptor_autopilot_569ccc5858bc499b, []int{3}
}
func (m *ModifyStatusResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ModifyStatusResponse.Unmarshal(m, b)
@ -172,7 +172,7 @@ func (m *QueryScoresRequest) Reset() { *m = QueryScoresRequest{} }
func (m *QueryScoresRequest) String() string { return proto.CompactTextString(m) }
func (*QueryScoresRequest) ProtoMessage() {}
func (*QueryScoresRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_7db7978f022d4696, []int{4}
return fileDescriptor_autopilot_569ccc5858bc499b, []int{4}
}
func (m *QueryScoresRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QueryScoresRequest.Unmarshal(m, b)
@ -210,7 +210,7 @@ func (m *QueryScoresResponse) Reset() { *m = QueryScoresResponse{} }
func (m *QueryScoresResponse) String() string { return proto.CompactTextString(m) }
func (*QueryScoresResponse) ProtoMessage() {}
func (*QueryScoresResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_7db7978f022d4696, []int{5}
return fileDescriptor_autopilot_569ccc5858bc499b, []int{5}
}
func (m *QueryScoresResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QueryScoresResponse.Unmarshal(m, b)
@ -249,7 +249,7 @@ func (m *QueryScoresResponse_HeuristicResult) Reset() { *m = QueryScores
func (m *QueryScoresResponse_HeuristicResult) String() string { return proto.CompactTextString(m) }
func (*QueryScoresResponse_HeuristicResult) ProtoMessage() {}
func (*QueryScoresResponse_HeuristicResult) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_7db7978f022d4696, []int{5, 0}
return fileDescriptor_autopilot_569ccc5858bc499b, []int{5, 0}
}
func (m *QueryScoresResponse_HeuristicResult) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_QueryScoresResponse_HeuristicResult.Unmarshal(m, b)
@ -283,6 +283,86 @@ func (m *QueryScoresResponse_HeuristicResult) GetScores() map[string]float64 {
return nil
}
type SetScoresRequest struct {
// / The name of the heuristic to provide scores to.
Heuristic string `protobuf:"bytes,1,opt,name=heuristic,proto3" json:"heuristic,omitempty"`
// *
// A map from hex-encoded public keys to scores. Scores must be in the range
// [0.0, 1.0].
Scores map[string]float64 `protobuf:"bytes,2,rep,name=scores,proto3" json:"scores,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"fixed64,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SetScoresRequest) Reset() { *m = SetScoresRequest{} }
func (m *SetScoresRequest) String() string { return proto.CompactTextString(m) }
func (*SetScoresRequest) ProtoMessage() {}
func (*SetScoresRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_569ccc5858bc499b, []int{6}
}
func (m *SetScoresRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SetScoresRequest.Unmarshal(m, b)
}
func (m *SetScoresRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SetScoresRequest.Marshal(b, m, deterministic)
}
func (dst *SetScoresRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_SetScoresRequest.Merge(dst, src)
}
func (m *SetScoresRequest) XXX_Size() int {
return xxx_messageInfo_SetScoresRequest.Size(m)
}
func (m *SetScoresRequest) XXX_DiscardUnknown() {
xxx_messageInfo_SetScoresRequest.DiscardUnknown(m)
}
var xxx_messageInfo_SetScoresRequest proto.InternalMessageInfo
func (m *SetScoresRequest) GetHeuristic() string {
if m != nil {
return m.Heuristic
}
return ""
}
func (m *SetScoresRequest) GetScores() map[string]float64 {
if m != nil {
return m.Scores
}
return nil
}
type SetScoresResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SetScoresResponse) Reset() { *m = SetScoresResponse{} }
func (m *SetScoresResponse) String() string { return proto.CompactTextString(m) }
func (*SetScoresResponse) ProtoMessage() {}
func (*SetScoresResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_autopilot_569ccc5858bc499b, []int{7}
}
func (m *SetScoresResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SetScoresResponse.Unmarshal(m, b)
}
func (m *SetScoresResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SetScoresResponse.Marshal(b, m, deterministic)
}
func (dst *SetScoresResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_SetScoresResponse.Merge(dst, src)
}
func (m *SetScoresResponse) XXX_Size() int {
return xxx_messageInfo_SetScoresResponse.Size(m)
}
func (m *SetScoresResponse) XXX_DiscardUnknown() {
xxx_messageInfo_SetScoresResponse.DiscardUnknown(m)
}
var xxx_messageInfo_SetScoresResponse proto.InternalMessageInfo
func init() {
proto.RegisterType((*StatusRequest)(nil), "autopilotrpc.StatusRequest")
proto.RegisterType((*StatusResponse)(nil), "autopilotrpc.StatusResponse")
@ -292,6 +372,9 @@ func init() {
proto.RegisterType((*QueryScoresResponse)(nil), "autopilotrpc.QueryScoresResponse")
proto.RegisterType((*QueryScoresResponse_HeuristicResult)(nil), "autopilotrpc.QueryScoresResponse.HeuristicResult")
proto.RegisterMapType((map[string]float64)(nil), "autopilotrpc.QueryScoresResponse.HeuristicResult.ScoresEntry")
proto.RegisterType((*SetScoresRequest)(nil), "autopilotrpc.SetScoresRequest")
proto.RegisterMapType((map[string]float64)(nil), "autopilotrpc.SetScoresRequest.ScoresEntry")
proto.RegisterType((*SetScoresResponse)(nil), "autopilotrpc.SetScoresResponse")
}
// Reference imports to suppress errors if they are not otherwise used.
@ -318,6 +401,10 @@ type AutopilotClient interface {
// active combination of these heruristics, for the scores they would give to
// the given nodes.
QueryScores(ctx context.Context, in *QueryScoresRequest, opts ...grpc.CallOption) (*QueryScoresResponse, error)
// *
// SetScores attempts to set the scores used by the running autopilot agent,
// if the external scoring heuristic is enabled.
SetScores(ctx context.Context, in *SetScoresRequest, opts ...grpc.CallOption) (*SetScoresResponse, error)
}
type autopilotClient struct {
@ -355,6 +442,15 @@ func (c *autopilotClient) QueryScores(ctx context.Context, in *QueryScoresReques
return out, nil
}
func (c *autopilotClient) SetScores(ctx context.Context, in *SetScoresRequest, opts ...grpc.CallOption) (*SetScoresResponse, error) {
out := new(SetScoresResponse)
err := c.cc.Invoke(ctx, "/autopilotrpc.Autopilot/SetScores", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// AutopilotServer is the server API for Autopilot service.
type AutopilotServer interface {
// *
@ -369,6 +465,10 @@ type AutopilotServer interface {
// active combination of these heruristics, for the scores they would give to
// the given nodes.
QueryScores(context.Context, *QueryScoresRequest) (*QueryScoresResponse, error)
// *
// SetScores attempts to set the scores used by the running autopilot agent,
// if the external scoring heuristic is enabled.
SetScores(context.Context, *SetScoresRequest) (*SetScoresResponse, error)
}
func RegisterAutopilotServer(s *grpc.Server, srv AutopilotServer) {
@ -429,6 +529,24 @@ func _Autopilot_QueryScores_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler)
}
func _Autopilot_SetScores_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetScoresRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AutopilotServer).SetScores(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/autopilotrpc.Autopilot/SetScores",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AutopilotServer).SetScores(ctx, req.(*SetScoresRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Autopilot_serviceDesc = grpc.ServiceDesc{
ServiceName: "autopilotrpc.Autopilot",
HandlerType: (*AutopilotServer)(nil),
@ -445,40 +563,47 @@ var _Autopilot_serviceDesc = grpc.ServiceDesc{
MethodName: "QueryScores",
Handler: _Autopilot_QueryScores_Handler,
},
{
MethodName: "SetScores",
Handler: _Autopilot_SetScores_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "autopilotrpc/autopilot.proto",
}
func init() {
proto.RegisterFile("autopilotrpc/autopilot.proto", fileDescriptor_autopilot_7db7978f022d4696)
proto.RegisterFile("autopilotrpc/autopilot.proto", fileDescriptor_autopilot_569ccc5858bc499b)
}
var fileDescriptor_autopilot_7db7978f022d4696 = []byte{
// 391 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x4d, 0xaf, 0xd2, 0x40,
0x14, 0xcd, 0x94, 0x58, 0xec, 0x05, 0xc5, 0x0c, 0x84, 0x34, 0x95, 0x45, 0xe9, 0xaa, 0x1b, 0xdb,
0x88, 0x2e, 0xd4, 0xc4, 0x85, 0x1a, 0x13, 0x13, 0xe3, 0xc2, 0x21, 0x6c, 0xdc, 0xb5, 0x65, 0x84,
0x09, 0x75, 0xa6, 0xce, 0x07, 0xa6, 0x7f, 0xc8, 0xff, 0xe1, 0xef, 0x72, 0xf3, 0x42, 0x3f, 0x78,
0xed, 0x0b, 0xe1, 0xe5, 0xed, 0x7a, 0xee, 0x3d, 0xe7, 0xdc, 0x3b, 0x67, 0x3a, 0xb0, 0x48, 0x8c,
0x16, 0x05, 0xcb, 0x85, 0x96, 0x45, 0x16, 0x9f, 0x41, 0x54, 0x48, 0xa1, 0x05, 0x1e, 0x77, 0xbb,
0xc1, 0x04, 0x9e, 0xac, 0x75, 0xa2, 0x8d, 0x22, 0xf4, 0xb7, 0xa1, 0x4a, 0x07, 0x21, 0x3c, 0x6d,
0x0b, 0xaa, 0x10, 0x5c, 0x51, 0x3c, 0x07, 0x3b, 0xc9, 0x34, 0x3b, 0x52, 0x17, 0xf9, 0x28, 0x7c,
0x4c, 0x1a, 0x14, 0xbc, 0x80, 0xe9, 0x37, 0xb1, 0x65, 0x3f, 0xcb, 0x9e, 0xc1, 0x89, 0x4e, 0x79,
0x92, 0xe6, 0x67, 0x7a, 0x8d, 0x82, 0x39, 0xcc, 0xfa, 0xf4, 0xda, 0x3e, 0x88, 0x00, 0x7f, 0x37,
0x54, 0x96, 0xeb, 0x4c, 0x48, 0x7a, 0x76, 0x71, 0x61, 0x58, 0x98, 0xf4, 0x40, 0x4b, 0xe5, 0x22,
0x7f, 0x10, 0x3a, 0xa4, 0x85, 0xc1, 0x5f, 0x0b, 0xa6, 0x3d, 0x41, 0xb3, 0xe6, 0x57, 0x18, 0x4a,
0xaa, 0x4c, 0xae, 0x6b, 0xc5, 0x68, 0xf5, 0x32, 0xea, 0x9e, 0x34, 0xba, 0xa0, 0x89, 0xbe, 0x50,
0x23, 0x99, 0xd2, 0x2c, 0x23, 0x95, 0x92, 0xb4, 0x0e, 0xde, 0x3f, 0x04, 0x93, 0x3b, 0x4d, 0xbc,
0x00, 0x67, 0xdf, 0x96, 0xaa, 0xb3, 0x39, 0xe4, 0xb6, 0x80, 0x37, 0x60, 0xab, 0xca, 0xdc, 0xb5,
0xaa, 0xe9, 0xef, 0x1f, 0x3c, 0x3d, 0xaa, 0xdb, 0x9f, 0xb9, 0x96, 0x25, 0x69, 0xcc, 0xbc, 0xb7,
0x30, 0xea, 0x94, 0xf1, 0x33, 0x18, 0x1c, 0x68, 0xd9, 0x4c, 0x3f, 0x7d, 0xe2, 0x19, 0x3c, 0x3a,
0x26, 0xb9, 0xa1, 0xae, 0xe5, 0xa3, 0x10, 0x91, 0x1a, 0xbc, 0xb3, 0xde, 0xa0, 0xd5, 0x7f, 0x04,
0xce, 0x87, 0x76, 0x07, 0xfc, 0x09, 0xec, 0x3a, 0x78, 0xfc, 0xbc, 0xbf, 0x59, 0xef, 0xf6, 0xbc,
0xc5, 0xe5, 0x66, 0x93, 0xf1, 0x06, 0xc6, 0xdd, 0x3b, 0xc4, 0xcb, 0x3e, 0xfb, 0xc2, 0xef, 0xe0,
0x05, 0xd7, 0x28, 0x8d, 0x2d, 0x81, 0x51, 0x27, 0x1f, 0xec, 0x5f, 0x89, 0xae, 0x36, 0x5d, 0xde,
0x1b, 0xee, 0xc7, 0xd7, 0x3f, 0x56, 0x3b, 0xa6, 0xf7, 0x26, 0x8d, 0x32, 0xf1, 0x2b, 0xce, 0xd9,
0x6e, 0xaf, 0x39, 0xe3, 0x3b, 0x4e, 0xf5, 0x1f, 0x21, 0x0f, 0x71, 0xce, 0xb7, 0x71, 0xce, 0x7b,
0xef, 0x43, 0x16, 0x59, 0x6a, 0x57, 0x6f, 0xe4, 0xd5, 0x4d, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa1,
0xe9, 0x30, 0xf3, 0x43, 0x03, 0x00, 0x00,
var fileDescriptor_autopilot_569ccc5858bc499b = []byte{
// 439 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0x4d, 0x8b, 0xd3, 0x40,
0x18, 0xc7, 0x99, 0x2c, 0x76, 0xcd, 0xd3, 0xd5, 0x5d, 0xa7, 0xcb, 0x12, 0x62, 0xd1, 0xee, 0x9c,
0x8a, 0x60, 0x8a, 0xd5, 0x83, 0x0a, 0x1e, 0x5c, 0x11, 0x04, 0xf5, 0xe0, 0x94, 0xbd, 0x78, 0x4b,
0xb2, 0x63, 0x3b, 0x34, 0xce, 0xc4, 0x79, 0x59, 0xc9, 0x17, 0xf2, 0xea, 0x67, 0xf0, 0xe8, 0xb7,
0x92, 0x66, 0x92, 0x98, 0x84, 0x1a, 0x11, 0xbc, 0xe5, 0x79, 0xfb, 0x3d, 0x2f, 0xf9, 0x33, 0x30,
0x8d, 0xad, 0x91, 0x39, 0xcf, 0xa4, 0x51, 0x79, 0xba, 0x68, 0x8c, 0x28, 0x57, 0xd2, 0x48, 0x7c,
0xd4, 0x8e, 0x92, 0x63, 0xb8, 0xb5, 0x32, 0xb1, 0xb1, 0x9a, 0xb2, 0x2f, 0x96, 0x69, 0x43, 0xe6,
0x70, 0xbb, 0x76, 0xe8, 0x5c, 0x0a, 0xcd, 0xf0, 0x19, 0x8c, 0xe2, 0xd4, 0xf0, 0x6b, 0x16, 0xa0,
0x19, 0x9a, 0xdf, 0xa4, 0x95, 0x45, 0x1e, 0xc2, 0xe4, 0xbd, 0xbc, 0xe2, 0x9f, 0x8a, 0x0e, 0x60,
0x97, 0xce, 0x44, 0x9c, 0x64, 0x4d, 0xba, 0xb3, 0xc8, 0x19, 0x9c, 0x76, 0xd3, 0x1d, 0x9e, 0x44,
0x80, 0x3f, 0x58, 0xa6, 0x8a, 0x55, 0x2a, 0x15, 0x6b, 0x28, 0x01, 0x1c, 0xe6, 0x36, 0xd9, 0xb2,
0x42, 0x07, 0x68, 0x76, 0x30, 0xf7, 0x69, 0x6d, 0x92, 0x6f, 0x1e, 0x4c, 0x3a, 0x05, 0xd5, 0x98,
0x6f, 0xe1, 0x50, 0x31, 0x6d, 0x33, 0xe3, 0x2a, 0xc6, 0xcb, 0x47, 0x51, 0x7b, 0xd3, 0x68, 0x4f,
0x4d, 0xf4, 0x86, 0x59, 0xc5, 0xb5, 0xe1, 0x29, 0x2d, 0x2b, 0x69, 0x4d, 0x08, 0x7f, 0x20, 0x38,
0xee, 0x05, 0xf1, 0x14, 0xfc, 0x4d, 0xed, 0x2a, 0x77, 0xf3, 0xe9, 0x6f, 0x07, 0xbe, 0x84, 0x91,
0x2e, 0xe1, 0x81, 0x57, 0x76, 0x7f, 0xf1, 0xcf, 0xdd, 0x23, 0x17, 0x7e, 0x2d, 0x8c, 0x2a, 0x68,
0x05, 0x0b, 0x9f, 0xc1, 0xb8, 0xe5, 0xc6, 0x27, 0x70, 0xb0, 0x65, 0x45, 0xd5, 0x7d, 0xf7, 0x89,
0x4f, 0xe1, 0xc6, 0x75, 0x9c, 0x59, 0x16, 0x78, 0x33, 0x34, 0x47, 0xd4, 0x19, 0xcf, 0xbd, 0xa7,
0x88, 0x7c, 0x47, 0x70, 0xb2, 0x62, 0xa6, 0x7b, 0xd7, 0xe1, 0x25, 0x2e, 0x7a, 0x4b, 0x3c, 0xe8,
0x2e, 0xd1, 0xa7, 0xfd, 0xef, 0x89, 0x27, 0x70, 0xa7, 0xd5, 0xc2, 0x5d, 0x69, 0xf9, 0xd3, 0x03,
0xff, 0x65, 0x3d, 0x05, 0x7e, 0x05, 0x23, 0xa7, 0x1f, 0x7c, 0xb7, 0x37, 0x5b, 0x5b, 0x84, 0xe1,
0x74, 0x7f, 0xb0, 0x92, 0xca, 0x25, 0x1c, 0xb5, 0xa5, 0x88, 0xcf, 0xbb, 0xd9, 0x7b, 0x54, 0x1d,
0x92, 0xa1, 0x94, 0x0a, 0x4b, 0x61, 0xdc, 0xfa, 0xcd, 0x78, 0x36, 0xa0, 0x00, 0x07, 0x3d, 0xff,
0xab, 0x46, 0xf0, 0x3b, 0xf0, 0x9b, 0x93, 0xe0, 0x7b, 0xc3, 0xbf, 0x23, 0xbc, 0xff, 0xc7, 0xb8,
0xa3, 0x5d, 0x3c, 0xf9, 0xb8, 0x5c, 0x73, 0xb3, 0xb1, 0x49, 0x94, 0xca, 0xcf, 0x8b, 0x8c, 0xaf,
0x37, 0x46, 0x70, 0xb1, 0x16, 0xcc, 0x7c, 0x95, 0x6a, 0xbb, 0xc8, 0xc4, 0xd5, 0x22, 0x13, 0x9d,
0x47, 0x43, 0xe5, 0x69, 0x32, 0x2a, 0x1f, 0x8e, 0xc7, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xcf,
0x06, 0x7e, 0xe7, 0x58, 0x04, 0x00, 0x00,
}

@ -25,6 +25,12 @@ service Autopilot {
the given nodes.
*/
rpc QueryScores(QueryScoresRequest) returns (QueryScoresResponse);
/**
SetScores attempts to set the scores used by the running autopilot agent,
if the external scoring heuristic is enabled.
*/
rpc SetScores(SetScoresRequest) returns (SetScoresResponse);
}
message StatusRequest{
@ -54,3 +60,16 @@ message QueryScoresResponse {
repeated HeuristicResult results = 1 [json_name = "results"];
}
message SetScoresRequest{
/// The name of the heuristic to provide scores to.
string heuristic = 1 [json_name = "heuristic"];
/**
A map from hex-encoded public keys to scores. Scores must be in the range
[0.0, 1.0].
*/
map<string, double> scores = 2 [json_name = "scores"];
}
message SetScoresResponse {}

@ -40,6 +40,13 @@ var (
Entity: "info",
Action: "read",
}},
"/autopilotrpc.Autopilot/SetScores": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "offchain",
Action: "write",
}},
}
)
@ -206,3 +213,30 @@ func (s *Server) QueryScores(ctx context.Context, in *QueryScoresRequest) (
return resp, nil
}
// SetScores sets the scores of the external score heuristic, if active.
//
// NOTE: Part of the AutopilotServer interface.
func (s *Server) SetScores(ctx context.Context,
in *SetScoresRequest) (*SetScoresResponse, error) {
scores := make(map[autopilot.NodeID]float64)
for pubStr, score := range in.Scores {
pubHex, err := hex.DecodeString(pubStr)
if err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(pubHex, btcec.S256())
if err != nil {
return nil, err
}
nID := autopilot.NewNodeID(pubKey)
scores[nID] = score
}
if err := s.manager.SetNodeScores(in.Heuristic, scores); err != nil {
return nil, err
}
return &SetScoresResponse{}, nil
}