Skip to content

Commit

Permalink
script for manually fulfilling old VRF V2 requests
Browse files Browse the repository at this point in the history
  • Loading branch information
jinhoonbang committed Jan 10, 2024
1 parent 768edec commit 9926a73
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 41 deletions.
45 changes: 4 additions & 41 deletions core/scripts/common/vrf/setup-envs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"math/big"
"os"
"strings"
Expand All @@ -17,6 +16,7 @@ import (
helpers "github.com/smartcontractkit/chainlink/core/scripts/common"
"github.com/smartcontractkit/chainlink/core/scripts/common/vrf/constants"
"github.com/smartcontractkit/chainlink/core/scripts/common/vrf/model"
"github.com/smartcontractkit/chainlink/core/scripts/common/vrf/util"
"github.com/smartcontractkit/chainlink/core/scripts/vrfv2/testnet/v2scripts"
"github.com/smartcontractkit/chainlink/core/scripts/vrfv2plus/testnet/v2plusscripts"
clcmd "github.com/smartcontractkit/chainlink/v2/core/cmd"
Expand All @@ -25,28 +25,6 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/web/presenters"
)

func newApp(remoteNodeURL string, writer io.Writer) (*clcmd.Shell, *cli.App) {
prompter := clcmd.NewTerminalPrompter()
client := &clcmd.Shell{
Renderer: clcmd.RendererJSON{Writer: writer},
AppFactory: clcmd.ChainlinkAppFactory{},
KeyStoreAuthenticator: clcmd.TerminalKeyStoreAuthenticator{Prompter: prompter},
FallbackAPIInitializer: clcmd.NewPromptingAPIInitializer(prompter),
Runner: clcmd.ChainlinkRunner{},
PromptingSessionRequestBuilder: clcmd.NewPromptingSessionRequestBuilder(prompter),
ChangePasswordPrompter: clcmd.NewChangePasswordPrompter(),
PasswordPrompter: clcmd.NewPasswordPrompter(),
}
app := clcmd.NewApp(client)
fs := flag.NewFlagSet("blah", flag.ContinueOnError)
fs.Bool("json", true, "")
fs.String("remote-node-url", remoteNodeURL, "")
helpers.PanicErr(app.Before(cli.NewContext(nil, fs, nil)))
// overwrite renderer since it's set to stdout after Before() is called
client.Renderer = clcmd.RendererJSON{Writer: writer}
return client, app
}

var (
checkMarkEmoji = "✅"
xEmoji = "❌"
Expand Down Expand Up @@ -142,7 +120,7 @@ func main() {
output := &bytes.Buffer{}
for key, node := range nodesMap {
node := node
client, app := connectToNode(&node.URL, output, node.CredsFile)
client, app := util.ConnectToNode(&node.URL, output, &node.CredsFile)
ethKeys := createETHKeysIfNeeded(client, app, output, numEthKeys, &node.URL, maxGasPriceGwei)
if key == model.VRFPrimaryNodeName {
vrfKeys := createVRFKeyIfNeeded(client, app, output, numVRFKeys, &node.URL)
Expand Down Expand Up @@ -242,7 +220,7 @@ func main() {

for key, node := range nodesMap {
node := node
client, app := connectToNode(&node.URL, output, node.CredsFile)
client, app := util.ConnectToNode(&node.URL, output, &node.CredsFile)

//GET ALL JOBS
jobIDs := getAllJobIDs(client, app, output)
Expand Down Expand Up @@ -291,7 +269,7 @@ func importVRFKeyToNodeIfSet(vrfBackupNodeURL *string, nodes map[string]model.No
vrfPrimaryNode := nodes[model.VRFBackupNodeName]

if len(vrfBackupNode.VrfKeys) == 0 || vrfPrimaryNode.VrfKeys[0] != vrfBackupNode.VrfKeys[0] {
client, app := connectToNode(&vrfBackupNode.URL, output, file)
client, app := util.ConnectToNode(&vrfBackupNode.URL, output, &file)
importVRFKey(client, app, output)

vrfKeys := getVRFKeys(client, app, output)
Expand Down Expand Up @@ -421,21 +399,6 @@ func printVRFKeyData(vrfKeys []presenters.VRFKeyResource) {
}
}

func connectToNode(nodeURL *string, output *bytes.Buffer, credFile string) (*clcmd.Shell, *cli.App) {
client, app := newApp(*nodeURL, output)
// login first to establish the session
fmt.Println("logging in to:", *nodeURL)
loginFs := flag.NewFlagSet("test", flag.ContinueOnError)
loginFs.String("file", credFile, "")
loginFs.Bool("bypass-version-check", true, "")
loginCtx := cli.NewContext(app, loginFs, nil)
err := client.RemoteLogin(loginCtx)
helpers.PanicErr(err)
output.Reset()
fmt.Println()
return client, app
}

func createVRFKeyIfNeeded(client *clcmd.Shell, app *cli.App, output *bytes.Buffer, numVRFKeys *int, nodeURL *string) []presenters.VRFKeyResource {
var allVRFKeys []presenters.VRFKeyResource
var newKeys []presenters.VRFKeyResource
Expand Down
47 changes: 47 additions & 0 deletions core/scripts/common/vrf/util/util.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package util

import (
"bytes"
"flag"
"fmt"
"io"

helpers "github.com/smartcontractkit/chainlink/core/scripts/common"
"github.com/smartcontractkit/chainlink/core/scripts/common/vrf/model"
clcmd "github.com/smartcontractkit/chainlink/v2/core/cmd"
"github.com/urfave/cli"
)

func MapToSendingKeyArr(nodeSendingKeys []string) []model.SendingKey {
Expand All @@ -20,3 +28,42 @@ func MapToAddressArr(sendingKeys []model.SendingKey) []string {
}
return sendingKeysString
}

func ConnectToNode(nodeURL *string, output *bytes.Buffer, credFile *string) (*clcmd.Shell, *cli.App) {
client, app := newApp(*nodeURL, output)
// login first to establish the session
fmt.Println("logging in to:", *nodeURL)
loginFs := flag.NewFlagSet("test", flag.ContinueOnError)
if credFile != nil {
loginFs.String("file", *credFile, "")
}
loginFs.Bool("bypass-version-check", true, "")
loginCtx := cli.NewContext(app, loginFs, nil)
err := client.RemoteLogin(loginCtx)
helpers.PanicErr(err)
output.Reset()
fmt.Println()
return client, app
}

func newApp(remoteNodeURL string, writer io.Writer) (*clcmd.Shell, *cli.App) {
prompter := clcmd.NewTerminalPrompter()
client := &clcmd.Shell{
Renderer: clcmd.RendererJSON{Writer: writer},
AppFactory: clcmd.ChainlinkAppFactory{},
KeyStoreAuthenticator: clcmd.TerminalKeyStoreAuthenticator{Prompter: prompter},
FallbackAPIInitializer: clcmd.NewPromptingAPIInitializer(prompter),
Runner: clcmd.ChainlinkRunner{},
PromptingSessionRequestBuilder: clcmd.NewPromptingSessionRequestBuilder(prompter),
ChangePasswordPrompter: clcmd.NewChangePasswordPrompter(),
PasswordPrompter: clcmd.NewPasswordPrompter(),
}
app := clcmd.NewApp(client)
fs := flag.NewFlagSet("blah", flag.ContinueOnError)
fs.Bool("json", true, "")
fs.String("remote-node-url", remoteNodeURL, "")
helpers.PanicErr(app.Before(cli.NewContext(nil, fs, nil)))
// overwrite renderer since it's set to stdout after Before() is called
client.Renderer = clcmd.RendererJSON{Writer: writer}
return client, app
}
116 changes: 116 additions & 0 deletions core/scripts/vrfv2/testnet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package main
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"math/big"
"net/http"
"os"
"strings"

Expand All @@ -23,6 +27,8 @@ import (

"github.com/jmoiron/sqlx"

vrfutil "github.com/smartcontractkit/chainlink/core/scripts/common/vrf/util"

helpers "github.com/smartcontractkit/chainlink/core/scripts/common"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
Expand All @@ -43,6 +49,7 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/services/blockhashstore"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey"
"github.com/smartcontractkit/chainlink/v2/core/services/pg"
"github.com/smartcontractkit/chainlink/v2/core/services/vrf/proof"
"github.com/smartcontractkit/chainlink/v2/core/utils"
Expand All @@ -56,6 +63,115 @@ func main() {
e := helpers.SetupEnv(false)

switch os.Args[1] {
case "generate-proof":
cmd := flag.NewFlagSet("generate-proof", flag.ExitOnError)
vrfNodeURL := cmd.String("vrf-node-url", "", "remote node URL")
pubKey := cmd.String("compressed-pub-key", "", "compressed public key")
requestBlock := cmd.Uint64("request-block", 0, "request block")
requestID := cmd.String("request-id", "", "request ID")
coordinatorAddress := cmd.String("coordinator-address", "", "request ID")
dryrun := cmd.Bool("dryrun", true, "dryrun generates proof but doesn't execute tx on-chain")
helpers.ParseArgs(cmd, os.Args[2:], "vrf-node-url", "compressed-pub-key", "request-block", "request-id", "coordinator-address")

output := &bytes.Buffer{}
client, _ := vrfutil.ConnectToNode(vrfNodeURL, output, nil)

// generate password for encrypting the key
randomBytes := make([]byte, 32)
_, err := rand.Read(randomBytes)
helpers.PanicErr(err)
pw := base64.URLEncoding.EncodeToString(randomBytes)

fmt.Println("Exporting vrf key...")
resp, err := client.HTTP.Post("/v2/keys/vrf/export/"+*pubKey+"?newpassword="+pw, nil)
helpers.PanicErr(err)
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("invalid status code: ", resp.StatusCode))
}

keyJson, err := io.ReadAll(resp.Body)
helpers.PanicErr(err)

fmt.Println("Received vrf key")

key, err := vrfkey.FromEncryptedJSON(keyJson, pw)
helpers.PanicErr(err)

logs, err := e.Ec.FilterLogs(context.Background(), ethereum.FilterQuery{
FromBlock: big.NewInt(0).SetUint64(*requestBlock),
ToBlock: big.NewInt(0).SetUint64(*requestBlock),
Addresses: []common.Address{
common.HexToAddress(*coordinatorAddress),
},
Topics: [][]common.Hash{
{
vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested{}.Topic(),
},
},
})
helpers.PanicErr(err)

fmt.Println("Searching for request log...")

coordinator, err := vrf_coordinator_v2.NewVRFCoordinatorV2(
common.HexToAddress(*coordinatorAddress),
e.Ec)
helpers.PanicErr(err)

var request *vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested
for _, log := range logs {
unpacked, err := coordinator.ParseLog(log)
helpers.PanicErr(err)
rwr, ok := unpacked.(*vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested)
if !ok {
// should never happen
panic("cast to *vrf_coordinator_v2.VRFCoordinatorV2RandomWordsRequested")
}
if rwr.RequestId.String() == *requestID {
fmt.Printf("found request ID: %s\n", *requestID)
request = rwr
break
}
}

if request == nil {
panic(fmt.Sprintf("couldn't find request ID: %s in block: %d", *requestID, *requestBlock))
}

ps, err := proof.BigToSeed(request.PreSeed)
helpers.PanicErr(err)
preSeedData := proof.PreSeedDataV2{
PreSeed: ps,
BlockHash: request.Raw.BlockHash,
BlockNum: *requestBlock,
SubId: request.SubId,
CallbackGasLimit: request.CallbackGasLimit,
NumWords: request.NumWords,
Sender: request.Sender,
}

fmt.Printf("preseed data : %+v\n", preSeedData)
finalSeed := proof.FinalSeedV2(preSeedData)

p, err := key.GenerateProof(finalSeed)
helpers.PanicErr(err)

proof, rc, err := proof.GenerateProofResponseFromProofV2(p, preSeedData)
helpers.PanicErr(err)

fmt.Printf("Proof: %+v, commitment: %+v\n", proof, rc)

if *dryrun {
fmt.Println("Sending fulfillment!")
tx, err := coordinator.FulfillRandomWords(e.Owner, proof, rc)
helpers.PanicErr(err)

helpers.ConfirmTXMined(context.Background(), e.Ec, tx, e.ChainID, fmt.Sprintf("manual fulfillment of request ID: %s", *requestID))
}
fmt.Println("done!")

case "manual-fulfill":
cmd := flag.NewFlagSet("manual-fulfill", flag.ExitOnError)
// In order to get the tx data for a fulfillment transaction, you can grep the
Expand Down

0 comments on commit 9926a73

Please sign in to comment.