Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ticket redemption script #2014

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
run: |
export PKG_CONFIG_PATH=~/compiled/lib/pkgconfig
./ci_env.sh make
rm -rf ~/build && mkdir ~/build && mv livepeer* ~/build/
rm -rf ~/build && mkdir ~/build && mv livepeer* ~/build/ && mv ticket_redeem ~/build/
env:
GHA_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
test:
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SHELL=/bin/bash

all: net/lp_rpc.pb.go net/redeemer.pb.go net/redeemer_mock.pb.go core/test_segment.go livepeer livepeer_cli livepeer_router livepeer_bench
all: net/lp_rpc.pb.go net/redeemer.pb.go net/redeemer_mock.pb.go core/test_segment.go livepeer livepeer_cli livepeer_router livepeer_bench ticket_redeem

net/lp_rpc.pb.go: net/lp_rpc.proto
protoc -I=. --go_out=plugins=grpc:. $^
Expand Down Expand Up @@ -40,6 +40,10 @@ livepeer_bench:
livepeer_router:
GO111MODULE=on CGO_LDFLAGS="$(cgo_ldflags)" go build -ldflags="$(ldflags)" cmd/livepeer_router/*.go

.PHONY: ticket_redeem
ticket_redeem:
GO111MODULE=on CGO_LDFLAGS="$(cgo_ldflags)" go build -ldflags="$(ldflags)" cmd/ticket_redeem/*.go

.PHONY: localdocker
localdocker:
./print_version.sh > .git.describe
Expand Down
16 changes: 3 additions & 13 deletions cmd/devtool/devtool.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/livepeer/go-livepeer/eth"
Expand Down Expand Up @@ -177,9 +176,7 @@ func ethSetup(ethAcctAddr, keystoreDir string, isBroadcaster bool) {
return
}

signer := types.NewEIP155Signer(chainID)

am, err := eth.NewAccountManager(ethcommon.HexToAddress(ethAcctAddr), keystoreDir, signer)
am, err := eth.NewAccountManager(ethcommon.HexToAddress(ethAcctAddr), keystoreDir, chainID)
if err != nil {
glog.Errorf("Error creating Ethereum account manager: %v", err)
return
Expand All @@ -200,7 +197,6 @@ func ethSetup(ethAcctAddr, keystoreDir string, isBroadcaster bool) {
EthClient: backend,
GasPriceMonitor: gpm,
TransactionManager: tm,
Signer: signer,
}

client, err := eth.NewClient(ethCfg)
Expand Down Expand Up @@ -436,10 +432,7 @@ func remoteConsole(destAccountAddr string) error {
console.log(logs[0][0].address);''
`
glog.Infof("Running eth script: %s", getEthControllerScript)
err = console.Evaluate(getEthControllerScript)
if err != nil {
glog.Error(err)
}
console.Evaluate(getEthControllerScript)
if printer.Len() == 0 {
glog.Fatal("Can't find deployed controller")
}
Expand All @@ -452,10 +445,7 @@ func remoteConsole(destAccountAddr string) error {
gethMiningAccount, broadcasterGeth)
glog.Infof("Running eth script: %s", script)

err = console.Evaluate(script)
if err != nil {
glog.Error(err)
}
console.Evaluate(script)

time.Sleep(3 * time.Second)

Expand Down
6 changes: 1 addition & 5 deletions cmd/livepeer/livepeer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"strings"
"time"

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/livepeer/go-livepeer/build"
Expand Down Expand Up @@ -400,9 +399,7 @@ func main() {
}
defer gpm.Stop()

signer := types.NewEIP155Signer(chainID)

am, err := eth.NewAccountManager(ethcommon.HexToAddress(*ethAcctAddr), keystoreDir, signer)
am, err := eth.NewAccountManager(ethcommon.HexToAddress(*ethAcctAddr), keystoreDir, chainID)
if err != nil {
glog.Errorf("Error creating Ethereum account manager: %v", err)
return
Expand All @@ -423,7 +420,6 @@ func main() {
EthClient: backend,
GasPriceMonitor: gpm,
TransactionManager: tm,
Signer: signer,
}

client, err := eth.NewClient(ethCfg)
Expand Down
179 changes: 179 additions & 0 deletions cmd/ticket_redeem/ticket_redeem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package main

import (
"context"
"flag"
"fmt"
"math/big"
"os"
"os/user"
"path/filepath"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/golang/glog"
"github.com/livepeer/go-livepeer/common"
"github.com/livepeer/go-livepeer/eth"
"github.com/livepeer/go-livepeer/eth/contracts"
"github.com/livepeer/go-livepeer/pm"
)

const ticketValidityPeriod = 2
const txTimeout = 10 * time.Minute

func main() {
flag.Set("logtostderr", "true")
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)

datadir := flag.String("datadir", "", "Data directory for the node")

ethUrl := flag.String("ethUrl", "", "Ethereum node JSON-RPC URL")
ethAcctAddr := flag.String("ethAcctAddr", "", "Existing Eth account address")
ethPassword := flag.String("ethPassword", "", "Password for existing Eth account address")

brokerAddr := flag.String("brokerAddr", "", "ETH address of TicketBroker contract")
broadcaster := flag.String("broadcaster", "", "ETH address of broadcaster")
currentRound := flag.Int64("currentRound", 0, "Current round of the Livepeer protocol")

flag.Parse()

keystoreDir := filepath.Join(*datadir, "keystore")

usr, err := user.Current()
if err != nil {
glog.Fatalf("Cannot find current user: %v", err)
}

if *datadir == "" {
homedir := os.Getenv("HOME")
if homedir == "" {
homedir = usr.HomeDir
}
*datadir = filepath.Join(homedir, ".lpData", "mainnet")
}

if *ethUrl == "" {
glog.Fatal("Need to specify an Ethereum node JSON-RPC URL using -ethUrl")
}

backend, err := ethclient.Dial(*ethUrl)
if err != nil {
glog.Fatalf("Failed to connect to Ethereum client: %v", err)
}

chainID, err := backend.ChainID(context.Background())
if err != nil {
glog.Fatalf("Failed to get chain ID: %v", err)
}

am, err := eth.NewAccountManager(ethcommon.HexToAddress(*ethAcctAddr), keystoreDir, chainID)
if err != nil {
glog.Fatalf("Error creating Ethereum account manager: %v", err)
}

if err := am.Unlock(*ethPassword); err != nil {
glog.Fatalf("Error unlocking Ethereum account: %v", err)
}

broker, err := contracts.NewTicketBroker(ethcommon.HexToAddress(*brokerAddr), backend)
if err != nil {
glog.Fatalf("Error creating TicketBroker binding: %v", err)
}

opts, err := am.CreateTransactOpts(0)
if err != nil {
glog.Fatalf("Error creating transact opts: %v", err)
}

ticketBrokerSession := &contracts.TicketBrokerSession{
Contract: broker,
TransactOpts: *opts,
}

db, err := common.InitDB(*datadir + "/lpdb.sqlite3")
if err != nil {
glog.Fatalf("Error opening DB: %v", err)
}
defer db.Close()

minCreationRound := *currentRound - ticketValidityPeriod
ticket, err := db.SelectEarliestWinningTicket(ethcommon.HexToAddress(*broadcaster), minCreationRound)
if err != nil {
glog.Fatalf("Error selecting earliest winning ticket: %v", err)
}

if ticket == nil {
glog.Fatalf("No tickets to redeem")
}

// 1. Create tx without sending
ticketBrokerSession.TransactOpts.NoSend = true
tx, err := redeemWinningTicket(ticketBrokerSession, ticket.Ticket, ticket.Sig, ticket.RecipientRand)
if err != nil {
glog.Fatalf("Error creating ticket redemption tx: %v", err)
}

// 2. Log tx data
fmt.Printf("maxPriorityFeePerGas = %v\n", tx.GasTipCap())
fmt.Printf("maxFeePerGas = %v\n", tx.GasFeeCap())
fmt.Printf("This tx will cost at most %v\n", tx.Cost())

fmt.Println("Enter 'y' if you would like to send this tx")

var resp string
fmt.Scanln(&resp)

if resp != "y" {
glog.Infof("Tx not sent")
return
}

// 3. Send tx
if err := backend.SendTransaction(context.Background(), tx); err != nil {
glog.Fatalf("Error sending tx: %v", err)
}

glog.Infof("Submitted ticket redemption tx %v", tx.Hash())
glog.Infof("Waiting for tx to be mined...")

ctx, cancel := context.WithTimeout(context.Background(), txTimeout)
defer cancel()
receipt, err := bind.WaitMined(ctx, backend, tx)
if err != nil {
glog.Fatalf("Error waiting for tx to be mined %v", err)
}

defer func() {
if err := db.MarkWinningTicketRedeemed(ticket, tx.Hash()); err != nil {
glog.Fatalf("Error marking ticket as redeemed for tx %v", tx.Hash())
}
}()

if receipt.Status == uint64(0) {
glog.Fatalf("Tx %v failed", tx.Hash())
}

glog.Infof("Tx %v succeeded", tx.Hash())
}

func redeemWinningTicket(ticketBrokerSession *contracts.TicketBrokerSession, ticket *pm.Ticket, sig []byte, recipientRand *big.Int) (*types.Transaction, error) {
var recipientRandHash [32]byte
copy(recipientRandHash[:], ticket.RecipientRandHash.Bytes()[:32])

return ticketBrokerSession.RedeemWinningTicket(
contracts.MTicketBrokerCoreTicket{
Recipient: ticket.Recipient,
Sender: ticket.Sender,
FaceValue: ticket.FaceValue,
WinProb: ticket.WinProb,
SenderNonce: new(big.Int).SetUint64(uint64(ticket.SenderNonce)),
RecipientRandHash: recipientRandHash,
AuxData: ticket.AuxData(),
},
sig,
recipientRand,
)
}
7 changes: 3 additions & 4 deletions eth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ If the smart contracts are updated you can generate new Go bindings by doing the

```
cd $GOPATH/src/github.com/livepeer/go-livepeer/eth
git clone https://github.com/livepeer/protocol.git $GOPATH/src/github.com/livepeer/go-livepeer/eth/protocol
git clone https://github.com/livepeer/protocol.git
cd $GOPATH/src/github.com/livepeer/go-livepeer/eth/protocol
npm install
npm run compile
node scripts/parseArtifacts.js
yarn
yarn compile
cd $GOPATH/src/github.com/livepeer/go-livepeer/eth
go generate client.go
```
35 changes: 16 additions & 19 deletions eth/accountmanager.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package eth

import (
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/console/prompt"
"github.com/ethereum/go-ethereum/core/types"
"github.com/golang/glog"
"github.com/livepeer/go-livepeer/common"
Expand All @@ -31,12 +31,12 @@ type AccountManager interface {

type accountManager struct {
account accounts.Account
signer types.Signer
chainID *big.Int
unlocked bool
keyStore *keystore.KeyStore
}

func NewAccountManager(accountAddr ethcommon.Address, keystoreDir string, signer types.Signer) (AccountManager, error) {
func NewAccountManager(accountAddr ethcommon.Address, keystoreDir string, chainID *big.Int) (AccountManager, error) {
keyStore := keystore.NewKeyStore(keystoreDir, keystore.StandardScryptN, keystore.StandardScryptP)

acctExists := keyStore.HasAddress(accountAddr)
Expand Down Expand Up @@ -70,7 +70,7 @@ func NewAccountManager(accountAddr ethcommon.Address, keystoreDir string, signer

return &accountManager{
account: acct,
signer: signer,
chainID: chainID,
unlocked: false,
keyStore: keyStore,
}, nil
Expand Down Expand Up @@ -124,27 +124,24 @@ func (am *accountManager) CreateTransactOpts(gasLimit uint64) (*bind.TransactOpt
return nil, ErrLocked
}

return &bind.TransactOpts{
From: am.account.Address,
GasLimit: gasLimit,
Signer: func(signer types.Signer, address ethcommon.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != am.account.Address {
return nil, errors.New("not authorized to sign this account")
}
opts, err := bind.NewKeyStoreTransactorWithChainID(am.keyStore, am.account, am.chainID)
if err != nil {
return nil, err
}
opts.GasLimit = gasLimit

return am.SignTx(tx)
},
}, nil
return opts, nil
}

// Sign a transaction. Account must be unlocked
func (am *accountManager) SignTx(tx *types.Transaction) (*types.Transaction, error) {
signature, err := am.keyStore.SignHash(am.account, am.signer.Hash(tx).Bytes())
signer := types.LatestSignerForChainID(am.chainID)
signature, err := am.keyStore.SignHash(am.account, signer.Hash(tx).Bytes())
if err != nil {
return nil, err
}

return tx.WithSignature(am.signer, signature)
return tx.WithSignature(signer, signature)
}

// Sign byte array message. Account must be unlocked
Expand Down Expand Up @@ -202,13 +199,13 @@ func createAccount(keyStore *keystore.KeyStore) (accounts.Account, error) {

// Prompt for passphrase
func getPassphrase(shouldConfirm bool) (string, error) {
passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
passphrase, err := prompt.Stdin.PromptPassword("Passphrase: ")
if err != nil {
return "", err
}

if shouldConfirm {
confirmation, err := console.Stdin.PromptPassword("Repeat passphrase: ")
confirmation, err := prompt.Stdin.PromptPassword("Repeat passphrase: ")
if err != nil {
return "", err
}
Expand Down
Loading