mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 04:34:19 +01:00
Support onboarding & Drop faucet (#119)
* Renaming * Add server-side support for onboarding * add onboard --amount command * support client side onboarding * Drop dummy tx builder * Drop faucet * Fixes * fix public key encoding * fix schnorr pub key check in validation * fix server/README to accomodate onboarding --------- Co-authored-by: Louis <louis@vulpem.com> Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a95a829b20
commit
1650ea5935
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/ark-network/ark/internal/core/domain"
|
||||
"github.com/ark-network/ark/internal/core/ports"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
@@ -20,10 +21,6 @@ import (
|
||||
var (
|
||||
paymentsThreshold = int64(128)
|
||||
dustAmount = uint64(450)
|
||||
faucetVtxo = domain.VtxoKey{
|
||||
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
VOut: 0,
|
||||
}
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
@@ -32,12 +29,12 @@ type Service interface {
|
||||
SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error)
|
||||
ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error
|
||||
SignVtxos(ctx context.Context, forfeitTxs []string) error
|
||||
FaucetVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) error
|
||||
GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.Round, error)
|
||||
GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent
|
||||
UpdatePaymentStatus(ctx context.Context, id string) error
|
||||
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
|
||||
}
|
||||
|
||||
type service struct {
|
||||
@@ -88,8 +85,9 @@ func NewService(
|
||||
}
|
||||
repoManager.RegisterEventsHandler(
|
||||
func(round *domain.Round) {
|
||||
svc.updateProjectionStore(round)
|
||||
svc.propagateEvents(round)
|
||||
go svc.updateVtxoSet(round)
|
||||
go svc.propagateEvents(round)
|
||||
go svc.scheduleSweepVtxosForRound(round)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -165,43 +163,6 @@ func (s *service) UpdatePaymentStatus(_ context.Context, id string) error {
|
||||
return s.paymentRequests.updatePingTimestamp(id)
|
||||
}
|
||||
|
||||
func (s *service) FaucetVtxos(ctx context.Context, userPubkey *secp256k1.PublicKey) error {
|
||||
pubkey := hex.EncodeToString(userPubkey.SerializeCompressed())
|
||||
|
||||
payment, err := domain.NewPayment([]domain.Vtxo{
|
||||
{
|
||||
VtxoKey: faucetVtxo,
|
||||
Receiver: domain.Receiver{
|
||||
Pubkey: pubkey,
|
||||
Amount: 10000,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := payment.AddReceivers([]domain.Receiver{
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
{Pubkey: pubkey, Amount: 1000},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.paymentRequests.push(*payment); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.paymentRequests.updatePingTimestamp(payment.Id)
|
||||
}
|
||||
|
||||
func (s *service) SignVtxos(ctx context.Context, forfeitTxs []string) error {
|
||||
return s.forfeitTxs.sign(forfeitTxs)
|
||||
}
|
||||
@@ -223,6 +184,40 @@ func (s *service) GetInfo(ctx context.Context) (string, int64, int64, error) {
|
||||
return hex.EncodeToString(s.pubkey.SerializeCompressed()), s.roundLifetime, s.exitDelay, nil
|
||||
}
|
||||
|
||||
func (s *service) Onboard(
|
||||
ctx context.Context, boardingTx string,
|
||||
congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey,
|
||||
) error {
|
||||
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)
|
||||
}
|
||||
|
||||
utx, _ := ptx.UnsignedTx()
|
||||
txid := utx.TxHash().String()
|
||||
|
||||
isConfirmed, _, err := s.wallet.IsTransactionConfirmed(ctx, txid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch confirmation info for boaridng tx: %s", err)
|
||||
}
|
||||
if !isConfirmed {
|
||||
return fmt.Errorf("boarding tx not confirmed yet, please retry later")
|
||||
}
|
||||
|
||||
pubkey := hex.EncodeToString(userPubkey.SerializeCompressed())
|
||||
payments := getPaymentsFromOnboarding(congestionTree, pubkey)
|
||||
round := domain.NewFinalizedRound(
|
||||
dustAmount, pubkey, txid, boardingTx, congestionTree, payments,
|
||||
)
|
||||
|
||||
return s.saveEvents(ctx, round.Id, round.Events())
|
||||
}
|
||||
|
||||
func (s *service) start() {
|
||||
s.startRound()
|
||||
}
|
||||
@@ -365,15 +360,6 @@ func (s *service) finalizeRound() {
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
expirationTimestamp := now + s.roundLifetime + 30 // add 30 secs to be sure that the tx is confirmed
|
||||
|
||||
if err := s.sweeper.schedule(expirationTimestamp, txid, round.CongestionTree); err != nil {
|
||||
changes = round.Fail(fmt.Errorf("failed to schedule sweep tx: %s", err))
|
||||
log.WithError(err).Warn("failed to schedule sweep tx")
|
||||
return
|
||||
}
|
||||
|
||||
changes, _ = round.EndFinalization(forfeitTxs, txid)
|
||||
|
||||
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
||||
@@ -403,49 +389,50 @@ func (s *service) listenToRedemptions() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) updateProjectionStore(round *domain.Round) {
|
||||
ctx := context.Background()
|
||||
lastChange := round.Events()[len(round.Events())-1]
|
||||
func (s *service) updateVtxoSet(round *domain.Round) {
|
||||
// Update the vtxo set only after a round is finalized.
|
||||
if _, ok := lastChange.(domain.RoundFinalized); ok {
|
||||
repo := s.repoManager.Vtxos()
|
||||
spentVtxos := getSpentVtxos(round.Payments)
|
||||
if len(spentVtxos) > 0 {
|
||||
for {
|
||||
if err := repo.SpendVtxos(ctx, spentVtxos); err != nil {
|
||||
log.WithError(err).Warn("failed to add new vtxos, retrying soon")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
log.Debugf("spent %d vtxos", len(spentVtxos))
|
||||
break
|
||||
}
|
||||
}
|
||||
if !round.IsEnded() {
|
||||
return
|
||||
}
|
||||
|
||||
newVtxos := s.getNewVtxos(round)
|
||||
ctx := context.Background()
|
||||
repo := s.repoManager.Vtxos()
|
||||
spentVtxos := getSpentVtxos(round.Payments)
|
||||
if len(spentVtxos) > 0 {
|
||||
for {
|
||||
if err := repo.AddVtxos(ctx, newVtxos); err != nil {
|
||||
if err := repo.SpendVtxos(ctx, spentVtxos); err != nil {
|
||||
log.WithError(err).Warn("failed to add new vtxos, retrying soon")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
log.Debugf("added %d new vtxos", len(newVtxos))
|
||||
log.Debugf("spent %d vtxos", len(spentVtxos))
|
||||
break
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
if err := s.startWatchingVtxos(newVtxos); err != nil {
|
||||
log.WithError(err).Warn(
|
||||
"failed to start watching vtxos, retrying in a moment...",
|
||||
)
|
||||
continue
|
||||
}
|
||||
log.Debugf("started watching %d vtxos", len(newVtxos))
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
newVtxos := s.getNewVtxos(round)
|
||||
for {
|
||||
if err := repo.AddVtxos(ctx, newVtxos); err != nil {
|
||||
log.WithError(err).Warn("failed to add new vtxos, retrying soon")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
log.Debugf("added %d new vtxos", len(newVtxos))
|
||||
break
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
if err := s.startWatchingVtxos(newVtxos); err != nil {
|
||||
log.WithError(err).Warn(
|
||||
"failed to start watching vtxos, retrying in a moment...",
|
||||
)
|
||||
continue
|
||||
}
|
||||
log.Debugf("started watching %d vtxos", len(newVtxos))
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *service) propagateEvents(round *domain.Round) {
|
||||
@@ -465,6 +452,23 @@ func (s *service) propagateEvents(round *domain.Round) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) scheduleSweepVtxosForRound(round *domain.Round) {
|
||||
// Schedule the sweeping procedure only for completed round.
|
||||
if !round.IsEnded() {
|
||||
return
|
||||
}
|
||||
|
||||
expirationTimestamp := time.Now().Add(
|
||||
time.Duration(s.roundLifetime+30) * time.Second,
|
||||
)
|
||||
|
||||
if err := s.sweeper.schedule(
|
||||
expirationTimestamp.Unix(), round.Txid, round.CongestionTree,
|
||||
); err != nil {
|
||||
log.WithError(err).Warn("failed to schedule sweep tx")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) getNewVtxos(round *domain.Round) []domain.Vtxo {
|
||||
leaves := round.CongestionTree.Leaves()
|
||||
vtxos := make([]domain.Vtxo, 0)
|
||||
@@ -590,11 +594,25 @@ func getSpentVtxos(payments map[string]domain.Payment) []domain.VtxoKey {
|
||||
vtxos := make([]domain.VtxoKey, 0)
|
||||
for _, p := range payments {
|
||||
for _, vtxo := range p.Inputs {
|
||||
if vtxo.VtxoKey == faucetVtxo {
|
||||
continue
|
||||
}
|
||||
vtxos = append(vtxos, vtxo.VtxoKey)
|
||||
}
|
||||
}
|
||||
return vtxos
|
||||
}
|
||||
|
||||
func getPaymentsFromOnboarding(
|
||||
congestionTree tree.CongestionTree, userKey string,
|
||||
) []domain.Payment {
|
||||
leaves := congestionTree.Leaves()
|
||||
receivers := make([]domain.Receiver, 0, len(leaves))
|
||||
for _, node := range leaves {
|
||||
ptx, _ := psetv2.NewPsetFromBase64(node.Tx)
|
||||
receiver := domain.Receiver{
|
||||
Pubkey: userKey,
|
||||
Amount: ptx.Outputs[0].Value,
|
||||
}
|
||||
receivers = append(receivers, receiver)
|
||||
}
|
||||
payment := domain.NewPaymentUnsafe(nil, receivers)
|
||||
return []domain.Payment{*payment}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user