diff --git a/core/scripts/common/vrf/setup-envs/main.go b/core/scripts/common/vrf/setup-envs/main.go index 598238bebef..2bff50db6d3 100644 --- a/core/scripts/common/vrf/setup-envs/main.go +++ b/core/scripts/common/vrf/setup-envs/main.go @@ -5,7 +5,6 @@ import ( "encoding/json" "flag" "fmt" - "io" "math/big" "os" "strings" @@ -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" @@ -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 = "❌" @@ -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) @@ -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) @@ -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) @@ -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 diff --git a/core/scripts/common/vrf/util/util.go b/core/scripts/common/vrf/util/util.go index 751aa013201..b6b793714db 100644 --- a/core/scripts/common/vrf/util/util.go +++ b/core/scripts/common/vrf/util/util.go @@ -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 { @@ -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 +} diff --git a/core/scripts/vrfv2/testnet/main.go b/core/scripts/vrfv2/testnet/main.go index 151549cc587..be6a1345e2b 100644 --- a/core/scripts/vrfv2/testnet/main.go +++ b/core/scripts/vrfv2/testnet/main.go @@ -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" @@ -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" @@ -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" @@ -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