Unverified Commit 16cceaab authored by Tankred Hase's avatar Tankred Hase Committed by GitHub

Merge pull request #1098 from lightninglabs/dev/autopilot-grpc-mobile

Dev/autopilot grpc mobile
parents daaf6b9e 27ea446a
...@@ -2365,3 +2365,76 @@ message ChannelBackupSubscription {} ...@@ -2365,3 +2365,76 @@ message ChannelBackupSubscription {}
message VerifyChanBackupResponse { message VerifyChanBackupResponse {
} }
// Autopilot is a service that can be used to get information about the current
// state of the daemon's autopilot agent, and also supply it with information
// that can be used when deciding where to open channels.
service Autopilot {
/**
Status returns whether the daemon's autopilot agent is active.
*/
rpc Status(StatusRequest) returns (StatusResponse);
/**
ModifyStatus is used to modify the status of the autopilot agent, like
enabling or disabling it.
*/
rpc ModifyStatus(ModifyStatusRequest) returns (ModifyStatusResponse);
/**
QueryScores queries all available autopilot heuristics, in addition to any
active combination of these heruristics, for the scores they would give to
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{
}
message StatusResponse{
/// Indicates whether the autopilot is active or not.
bool active = 1 [json_name = "active"];
}
message ModifyStatusRequest{
/// Whether the autopilot agent should be enabled or not.
bool enable = 1 [json_name = "enable"];
}
message ModifyStatusResponse {}
message QueryScoresRequest{
repeated string pubkeys = 1 [json_name = "pubkeys"];
/// If set, we will ignore the local channel state when calculating scores.
bool ignore_local_state = 2 [json_name = "no_state"];
}
message QueryScoresResponse {
message HeuristicResult {
string heuristic = 1 [json_name = "heuristic"];
map<string, double> scores= 2 [json_name = "scores"];
}
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 {}
...@@ -12,9 +12,12 @@ bitcoin.node=neutrino ...@@ -12,9 +12,12 @@ bitcoin.node=neutrino
[Neutrino] [Neutrino]
neutrino.connect=btcd-testnet.lightning.computer neutrino.connect=btcd-testnet.lightning.computer
neutrino.feeurl=https://nodes.lightning.computer/fees/v1/btctestnet-fee-estimates.json
[autopilot] [autopilot]
autopilot.active=1 autopilot.active=0
autopilot.private=1 autopilot.private=1
autopilot.minconfs=0 autopilot.minconfs=0
autopilot.allocation=0.95 autopilot.allocation=0.95
\ No newline at end of file autopilot.heuristic=externalscore:0.95
autopilot.heuristic=preferential:0.05
...@@ -135,6 +135,7 @@ RCT_EXPORT_METHOD(start: (RCTPromiseResolveBlock)resolve ...@@ -135,6 +135,7 @@ RCT_EXPORT_METHOD(start: (RCTPromiseResolveBlock)resolve
@"WalletBalance" : ^(NSData* bytes, NativeCallback* cb) { LndmobileWalletBalance(bytes, cb); }, @"WalletBalance" : ^(NSData* bytes, NativeCallback* cb) { LndmobileWalletBalance(bytes, cb); },
@"ChannelBalance" : ^(NSData* bytes, NativeCallback* cb) { LndmobileChannelBalance(bytes, cb); }, @"ChannelBalance" : ^(NSData* bytes, NativeCallback* cb) { LndmobileChannelBalance(bytes, cb); },
@"NewAddress" : ^(NSData* bytes, NativeCallback* cb) { LndmobileNewAddress(bytes, cb); }, @"NewAddress" : ^(NSData* bytes, NativeCallback* cb) { LndmobileNewAddress(bytes, cb); },
@"EstimateFee" : ^(NSData* bytes, NativeCallback* cb) { LndmobileEstimateFee(bytes, cb); },
@"Status" : ^(NSData* bytes, NativeCallback* cb) { LndmobileAutopilotStatus(bytes, cb); }, @"Status" : ^(NSData* bytes, NativeCallback* cb) { LndmobileAutopilotStatus(bytes, cb); },
@"SetScores" : ^(NSData* bytes, NativeCallback* cb) { LndmobileAutopilotSetScores(bytes, cb); }, @"SetScores" : ^(NSData* bytes, NativeCallback* cb) { LndmobileAutopilotSetScores(bytes, cb); },
@"QueryScores" : ^(NSData* bytes, NativeCallback* cb) { LndmobileAutopilotQueryScores(bytes, cb); }, @"QueryScores" : ^(NSData* bytes, NativeCallback* cb) { LndmobileAutopilotQueryScores(bytes, cb); },
......
...@@ -12,9 +12,12 @@ bitcoin.node=neutrino ...@@ -12,9 +12,12 @@ bitcoin.node=neutrino
[Neutrino] [Neutrino]
neutrino.connect=btcd-testnet.lightning.computer neutrino.connect=btcd-testnet.lightning.computer
neutrino.feeurl=https://nodes.lightning.computer/fees/v1/btctestnet-fee-estimates.json
[autopilot] [autopilot]
autopilot.active=1 autopilot.active=0
autopilot.private=1 autopilot.private=1
autopilot.minconfs=0 autopilot.minconfs=0
autopilot.allocation=0.95 autopilot.allocation=0.95
autopilot.heuristic=externalscore:0.95
autopilot.heuristic=preferential:0.05
...@@ -56,6 +56,30 @@ class GrpcAction { ...@@ -56,6 +56,30 @@ class GrpcAction {
return this._lnrpcRequest(method, body); return this._lnrpcRequest(method, body);
} }
//
// Autopilot grpc client
//
/**
* This is called to initialize the GRPC client to autopilot. Once `autopilotReady`
* is set to true on the store GRPC calls can be made to the client.
* @return {Promise<undefined>}
*/
async initAutopilot() {
this._store.autopilotReady = true;
log.info('GRPC autopilotReady');
}
/**
* Wrapper function to execute calls to the autopilot grpc client.
* @param {string} method The autopilot GRPC api to call
* @param {Object} body The payload passed to the api
* @return {Promise<Object>}
*/
async sendAutopilotCommand(method, body) {
return this._lnrpcRequest(method, body);
}
// //
// Lightning (lnd) grpc client // Lightning (lnd) grpc client
// //
......
...@@ -88,7 +88,13 @@ when(() => store.unlockerReady, () => wallet.init()); ...@@ -88,7 +88,13 @@ when(() => store.unlockerReady, () => wallet.init());
* Triggered after the user's password has unlocked the wallet * Triggered after the user's password has unlocked the wallet
* or a user's password has been successfully reset. * or a user's password has been successfully reset.
*/ */
when(() => store.walletUnlocked, () => grpc.initLnd()); when(
() => store.walletUnlocked,
async () => {
await grpc.initLnd();
await grpc.initAutopilot();
}
);
/** /**
* Triggered once the main lnd grpc client is initialized. This is when * Triggered once the main lnd grpc client is initialized. This is when
...@@ -107,6 +113,17 @@ when( ...@@ -107,6 +113,17 @@ when(
} }
); );
/**
* Initialize autopilot after syncing is finished and the grpc client
* is ready
*/
when(
() => store.syncedToChain && store.network && store.autopilotReady,
() => {
autopilot.init();
}
);
// STUB DURING DEVELOPMENT // STUB DURING DEVELOPMENT
sinon.stub(wallet, 'update'); sinon.stub(wallet, 'update');
sinon.stub(wallet, 'getExchangeRate'); sinon.stub(wallet, 'getExchangeRate');
......
...@@ -106,7 +106,7 @@ when( ...@@ -106,7 +106,7 @@ when(
* is ready * is ready
*/ */
when( when(
() => store.syncedToChain && store.autopilotReady, () => store.syncedToChain && store.network && store.autopilotReady,
async () => { async () => {
await nap(); await nap();
autopilot.init(); autopilot.init();
......
...@@ -6,7 +6,12 @@ import Background from '../component/background'; ...@@ -6,7 +6,12 @@ import Background from '../component/background';
import MainContent from '../component/main-content'; import MainContent from '../component/main-content';
import { InputField, AmountInputField } from '../component/field'; import { InputField, AmountInputField } from '../component/field';
import { Header, Title } from '../component/header'; import { Header, Title } from '../component/header';
import { CancelButton, BackButton, SmallGlasButton } from '../component/button'; import {
MaxButton,
CancelButton,
BackButton,
SmallGlasButton,
} from '../component/button';
import Card from '../component/card'; import Card from '../component/card';
import BitcoinIcon from '../asset/icon/bitcoin'; import BitcoinIcon from '../asset/icon/bitcoin';
import { FormStretcher, FormText } from '../component/form'; import { FormStretcher, FormText } from '../component/form';
...@@ -17,12 +22,13 @@ const styles = StyleSheet.create({ ...@@ -17,12 +22,13 @@ const styles = StyleSheet.create({
description: { description: {
maxWidth: 290, maxWidth: 290,
}, },
balance: {
marginBottom: 10,
},
unit: { unit: {
color: color.blackText, color: color.blackText,
}, },
maxBtn: {
marginTop: 10,
marginBottom: 20,
},
nextBtn: { nextBtn: {
marginTop: 20, marginTop: 20,
backgroundColor: color.orange, backgroundColor: color.orange,
...@@ -45,7 +51,7 @@ const PayBitcoinView = ({ store, nav, payment }) => ( ...@@ -45,7 +51,7 @@ const PayBitcoinView = ({ store, nav, payment }) => (
minutes or more to confirm. minutes or more to confirm.
</FormText> </FormText>
<FormStretcher> <FormStretcher>
<BalanceLabel style={styles.balance}> <BalanceLabel>
<AmountInputField <AmountInputField
autoFocus={true} autoFocus={true}
value={store.payment.amount} value={store.payment.amount}
...@@ -56,6 +62,11 @@ const PayBitcoinView = ({ store, nav, payment }) => ( ...@@ -56,6 +62,11 @@ const PayBitcoinView = ({ store, nav, payment }) => (
{store.unitFiatLabel} {store.unitFiatLabel}
</BalanceLabelUnit> </BalanceLabelUnit>
</BalanceLabel> </BalanceLabel>
<MaxButton
style={styles.maxBtn}
active={store.payment.sendAll}
onPress={() => payment.toggleMax()}
/>
<InputField <InputField
placeholder="Bitcoin Address" placeholder="Bitcoin Address"
value={store.payment.address} value={store.payment.address}
......
...@@ -86,6 +86,60 @@ describe('Action GRPC Mobile Unit Tests', () => { ...@@ -86,6 +86,60 @@ describe('Action GRPC Mobile Unit Tests', () => {
}); });
}); });
describe('initAutopilot()', () => {
it('should set autopilotReady', async () => {
await grpc.initAutopilot();
expect(store.autopilotReady, 'to be', true);
});
});
describe('sendAutopilotCommand()', () => {
it('should work for Status', async () => {
LndReactModuleStub.sendCommand.resolves({
data: grpc._serializeResponse('Status'),
});
await grpc.sendAutopilotCommand('Status');
expect(LndReactModuleStub.sendCommand, 'was called with', 'Status', '');
});
it('should work for SetScores', async () => {
LndReactModuleStub.sendCommand.resolves({
data: grpc._serializeResponse('SetScores'),
});
await grpc.sendAutopilotCommand('SetScores', {
heuristic: 'externalscore',
scores: { 'some-pubkey': 0.14035087 },
});
expect(
LndReactModuleStub.sendCommand,
'was called with',
'SetScores',
'Cg1leHRlcm5hbHNjb3JlEhYKC3NvbWUtcHVia2V5ESe9Tm4E98E/'
);
});
it('should work for QueryScores', async () => {
LndReactModuleStub.sendCommand.resolves({
data: grpc._serializeResponse('QueryScores'),
});
await grpc.sendAutopilotCommand('QueryScores');
expect(LndReactModuleStub.sendCommand, 'was called once');
});
it('should work for ModifyStatus', async () => {
LndReactModuleStub.sendCommand.resolves({
data: grpc._serializeResponse('ModifyStatus'),
});
await grpc.sendAutopilotCommand('ModifyStatus', { enable: true });
expect(
LndReactModuleStub.sendCommand,
'was called with',
'ModifyStatus',
'CAE='
);
});
});
describe('initLnd()', () => { describe('initLnd()', () => {
it('should set lndReady', async () => { it('should set lndReady', async () => {
LndReactModuleStub.start.resolves(); LndReactModuleStub.start.resolves();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment