Add TrustedOnboarding RPC (#138)

* add CreateOnboardingAddress rpc

* add TrustedOnboarding rpc

* remove log.Info in notifications.go
This commit is contained in:
Louis Singer
2024-04-23 14:54:27 +02:00
committed by GitHub
parent 7a58d97a34
commit 740d4fb7b1
11 changed files with 760 additions and 249 deletions

View File

@@ -16,6 +16,7 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
log "github.com/sirupsen/logrus"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/payment"
"github.com/vulpemventures/go-elements/psetv2"
)
@@ -36,6 +37,7 @@ type Service interface {
ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, error)
GetInfo(ctx context.Context) (string, int64, int64, error)
Onboard(ctx context.Context, boardingTx string, congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey) error
TrustedOnboarding(ctx context.Context, userPubKey *secp256k1.PublicKey, onboardingAmount uint64) (string, uint64, error)
}
type onboarding struct {
@@ -64,6 +66,9 @@ type service struct {
eventsCh chan domain.RoundEvent
onboardingCh chan onboarding
trustedOnboardingScriptLock *sync.Mutex
trustedOnboardingScripts map[string]*secp256k1.PublicKey
}
func NewService(
@@ -91,6 +96,7 @@ func NewService(
roundLifetime, roundInterval, unilateralExitDelay, minRelayFee,
walletSvc, repoManager, builder, scanner, sweeper,
paymentRequests, forfeitTxs, eventsCh, onboardingCh,
&sync.Mutex{}, make(map[string]*secp256k1.PublicKey),
}
repoManager.RegisterEventsHandler(
func(round *domain.Round) {
@@ -103,7 +109,7 @@ func NewService(
if err := svc.restoreWatchingVtxos(); err != nil {
return nil, fmt.Errorf("failed to restore watching vtxos: %s", err)
}
go svc.listenToRedemptions()
go svc.listenToScannerNotifications()
go svc.listenToOnboarding()
return svc, nil
}
@@ -201,15 +207,16 @@ func (s *service) Onboard(
ctx context.Context, boardingTx string,
congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey,
) error {
ptx, err := psetv2.NewPsetFromBase64(boardingTx)
if err != nil {
return fmt.Errorf("failed to parse boarding tx: %s", err)
}
if err := tree.ValidateCongestionTree(
congestionTree, boardingTx, s.pubkey, s.roundLifetime,
); err != nil {
return err
}
ptx, err := psetv2.NewPsetFromBase64(boardingTx)
if err != nil {
return fmt.Errorf("failed to parse boarding tx: %s", err)
}
extracted, err := psetv2.Extract(ptx)
if err != nil {
@@ -237,6 +244,46 @@ func (s *service) Onboard(
return nil
}
func (s *service) TrustedOnboarding(
ctx context.Context, userPubKey *secp256k1.PublicKey, onboardingAmount uint64,
) (string, uint64, error) {
congestionTreeLeaf := tree.Receiver{
Pubkey: hex.EncodeToString(userPubKey.SerializeCompressed()),
Amount: onboardingAmount,
}
_, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
s.onchainNework.AssetID, s.pubkey, []tree.Receiver{congestionTreeLeaf},
s.minRelayFee, s.roundLifetime, s.unilateralExitDelay,
)
if err != nil {
return "", 0, err
}
pay, err := payment.FromScript(sharedOutputScript, &s.onchainNework, nil)
if err != nil {
return "", 0, err
}
address, err := pay.TaprootAddress()
if err != nil {
return "", 0, err
}
s.trustedOnboardingScriptLock.Lock()
script := hex.EncodeToString(sharedOutputScript)
s.trustedOnboardingScripts[script] = userPubKey
s.trustedOnboardingScriptLock.Unlock()
if err := s.scanner.WatchScripts(ctx, []string{script}); err != nil {
return "", 0, err
}
return address, sharedOutputAmount, nil
}
func (s *service) start() {
s.startRound()
}
@@ -392,6 +439,7 @@ func (s *service) listenToOnboarding() {
func (s *service) handleOnboarding(onboarding onboarding) {
ctx := context.Background()
ptx, _ := psetv2.NewPsetFromBase64(onboarding.tx)
utx, _ := psetv2.Extract(ptx)
txid := utx.TxHash().String()
@@ -431,18 +479,77 @@ func (s *service) handleOnboarding(onboarding onboarding) {
}
}
func (s *service) listenToRedemptions() {
func (s *service) listenToScannerNotifications() {
ctx := context.Background()
chVtxos := s.scanner.GetNotificationChannel(ctx)
mutx := &sync.Mutex{}
for vtxoKeys := range chVtxos {
go func(vtxoKeys []domain.VtxoKey) {
go func(vtxoKeys map[string]ports.VtxoWithValue) {
vtxosRepo := s.repoManager.Vtxos()
roundRepo := s.repoManager.Rounds()
for _, v := range vtxoKeys {
vtxos, err := vtxosRepo.GetVtxos(ctx, []domain.VtxoKey{v})
for script, v := range vtxoKeys {
if userPubkey, ok := s.trustedOnboardingScripts[script]; ok {
// onboarding
defer func() {
s.trustedOnboardingScriptLock.Lock()
delete(s.trustedOnboardingScripts, script)
s.trustedOnboardingScriptLock.Unlock()
}()
congestionTreeLeaf := tree.Receiver{
Pubkey: hex.EncodeToString(userPubkey.SerializeCompressed()),
Amount: v.Value - s.minRelayFee,
}
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
s.onchainNework.AssetID, s.pubkey, []tree.Receiver{congestionTreeLeaf},
s.minRelayFee, s.roundLifetime, s.unilateralExitDelay,
)
if err != nil {
log.WithError(err).Warn("failed to craft onboarding congestion tree")
return
}
congestionTree, err := treeFactoryFn(
psetv2.InputArgs{
Txid: v.Txid,
TxIndex: v.VOut,
},
)
if err != nil {
log.WithError(err).Warn("failed to build onboarding congestion tree")
return
}
if sharedOutputAmount != v.Value {
log.Errorf("shared output amount mismatch, expected %d, got %d", sharedOutputAmount, v.Value)
return
}
precomputedScript, _ := hex.DecodeString(script)
if !bytes.Equal(sharedOutputScript, precomputedScript) {
log.Errorf("shared output script mismatch, expected %x, got %x", sharedOutputScript, precomputedScript)
return
}
pubkey := hex.EncodeToString(userPubkey.SerializeCompressed())
payments := getPaymentsFromOnboarding(congestionTree, pubkey)
round := domain.NewFinalizedRound(
dustAmount, pubkey, v.Txid, "", congestionTree, payments,
)
if err := s.saveEvents(ctx, round.Id, round.Events()); err != nil {
log.WithError(err).Warn("failed to store new round events")
return
}
return
}
// redeem
vtxos, err := vtxosRepo.GetVtxos(ctx, []domain.VtxoKey{v.VtxoKey})
if err != nil {
log.WithError(err).Warn("failed to retrieve vtxos, skipping...")
continue