add create hub and join hub

master
cyhhao 2 years ago
parent 547746302e
commit 46c16d759a

@ -25,7 +25,8 @@
"rxjs": "^7.8.0",
"rxjs-async-map": "^0.2.0",
"rxjs-stream": "^5.0.0",
"superpathjoin": "^2.0.1"
"superpathjoin": "^2.0.1",
"url-parse": "^1.5.10"
},
"scripts": {
"clean": "rm -rf ./dist ./bin",
@ -45,6 +46,7 @@
"@types/inquirer": "^9.0.3",
"@types/node": "^18.11.18",
"@types/parse-git-config": "^3.0.1",
"@types/url-parse": "^1.4.8",
"esbuild": "^0.17.0",
"pkg": "^5.8.0",
"ts-node": "^10.9.1",

@ -3,6 +3,7 @@ import nameServices from "../config/name-services.js"
import { ETHStorage } from "../storage/ETHStorage.js"
import { SLIStorage } from "../storage/SLIStorage.js"
import { getWallet, randomRPC, setupContract } from "./wallet.js"
import Url from "url-parse"
import network from "../config/evm-network.js"
import abis from "../config/abis.js"
@ -14,7 +15,7 @@ export type Git3Protocol = {
chainId: number
netConfig: Record<string, any>
wallet: ethers.Wallet
contract: ethers.Contract
hub: ethers.Contract
storageClass: any
ns?: Record<string, any>
nsName?: string
@ -22,31 +23,38 @@ export type Git3Protocol = {
}
type Option = {
skipRepoName: boolean
skipRepoName?: boolean
ignoreProtocolHeader?: boolean
}
export async function parseGit3URI(
uri: string,
option: Option = { skipRepoName: false }
option: Option = { skipRepoName: false, ignoreProtocolHeader: false }
): Promise<Git3Protocol> {
const url = new URL(uri)
if (option.ignoreProtocolHeader) {
if (!uri.startsWith("git3://")) {
uri = "git3://" + uri
}
}
console.error("uri", uri)
const url = new Url(uri)
let sender = url.username || "default"
let chainId = url.port ? parseInt(url.port) : null
let hub = url.hostname
let hostname = url.hostname
let hubAddress
let nsName, nsDomain, ns
if (!hub) throw new Error("invalid git3 uri, no hub address")
if (!hostname) throw new Error("invalid git3 uri, no hub address")
let repoName = url.pathname.slice(1)
if (!option.skipRepoName && !repoName) throw new Error("invalid git3 uri, no repo name")
if (hub.indexOf(".") < 0) {
if (url.hostname.startsWith("0x")) {
hubAddress = url.hostname
if (hostname.indexOf(".") < 0) {
if (hostname.startsWith("0x")) {
hubAddress = hostname
} else {
throw new Error("invalid git3 uri, hub must be NS or address")
}
} else {
;[nsName, nsDomain] = url.hostname.split(".")
;[nsName, nsDomain] = hostname.split(".")
ns = nameServices[nsDomain]
if (!ns) throw new Error(`invalid name service ${nsDomain}`)
chainId = chainId || ns.chainId
@ -78,16 +86,16 @@ export async function parseGit3URI(
let storageClass, abi
if (chainId == 3334) {
storageClass = ETHStorage
abi = abis.ETHStorage
abi = abis.Hub
} else {
storageClass = SLIStorage
abi = abis.SLIStorage
abi = abis.Hub
}
let rpc = randomRPC(netConfig.rpc)
const provider = new ethers.providers.JsonRpcProvider(rpc)
let contract = setupContract(provider, hubAddress, abi, wallet)
wallet = wallet.connect(contract.provider)
let hub = setupContract(provider, hubAddress, abi, wallet)
wallet = wallet.connect(hub.provider)
return {
sender,
@ -97,7 +105,7 @@ export async function parseGit3URI(
chainId,
netConfig,
wallet,
contract,
hub: hub,
storageClass,
ns,
nsName,

@ -34,3 +34,11 @@ export function setupContract(
export function randomRPC(rpcs: string[]): string {
return rpcs[Math.floor(Math.random() * rpcs.length)]
}
export function explorerTxUrl(txHash: string, explorers: any[]): string {
if (explorers && explorers.length > 0) {
return explorers[0].url.replace(/\/+$/, "") + "/tx/" + txHash
} else {
return txHash
}
}

File diff suppressed because one or more lines are too long

@ -89,7 +89,7 @@ const evmNetworks: Record<number, any> = {
txConst: {
blockTimeSec: 7,
},
contracts: { git3: "0x59ef6b2dbfE86CcAaD84E2d8e78177f528521Da9" },
contracts: { factory: "0xF2CAd0c8997584D8DDe1c0726De0Fa9ECC3dDa04" },
},
421613: {
name: "Arbitrum - Goerli",
@ -114,7 +114,7 @@ const evmNetworks: Record<number, any> = {
rbfTimes: 5,
boardcastTimes: 10,
},
contracts: { git3: "0x7Bb1038106fC6490195ec9906b29C81217ab090d" },
contracts: { factory: "0x51bb7F23193b88696D25EAec7E3293a2C96e55Ee" },
},
}

@ -1,15 +1,15 @@
const ns: Record<string, any> = {
w3q: {
chainId: 3334,
resolver: "0x3144d2EF1ac2fD651a250c9B5EfC095330ED9C05",
resolver: "0x372343Dd5274bb7B571F81d4FBA3bC0E28FeF3D9",
},
arb: {
chainId: 42170,
resolver: "0x3144d2EF1ac2fD651a250c9B5EfC095330ED9C05",
resolver: "0x372343Dd5274bb7B571F81d4FBA3bC0E28FeF3D9",
},
arbg: {
chainId: 421613,
resolver: "0x3144d2EF1ac2fD651a250c9B5EfC095330ED9C05",
resolver: "0x372343Dd5274bb7B571F81d4FBA3bC0E28FeF3D9",
},
}

@ -120,7 +120,7 @@ class Git {
async download(sha: string): Promise<Error | null> {
log("fetching...", sha)
let [status, data] = await this.storage.download(this.objectPath(sha))
let [status, data] = await this.storage.download(sha) //this.objectPath(sha)
if (status == Status.SUCCEED) {
let computedSha = GitUtils.decodeObject(data)
if (computedSha != sha) {
@ -146,7 +146,7 @@ class Git {
pendings.push(this.putObject(obj))
}
let resault = await this.storage.uploadCommit()
if(resault!= Status.SUCCEED){
if (resault != Status.SUCCEED) {
return `error ${dst} upload commit fail`
}
let resaults = await Promise.all(pendings)
@ -166,11 +166,7 @@ class Git {
}
}
async wirteRef(
newSha: string,
dst: string,
force: boolean
): Promise<string | null> {
async wirteRef(newSha: string, dst: string, force: boolean): Promise<string | null> {
let sha = this.refs.get(dst)
if (sha) {
if (!GitUtils.objectExists(sha)) {
@ -200,16 +196,16 @@ class Git {
async putObject(sha: string): Promise<string> {
let data = GitUtils.encodeObject(sha)
let path = this.objectPath(sha)
let path = sha //this.objectPath(sha)
let status = await this.storage.upload(path, data)
return status
}
objectPath(name: string): string {
const prefix = name.slice(0, 2)
const suffix = name.slice(2)
return join("objects", prefix, suffix)
}
// objectPath(name: string): string {
// const prefix = name.slice(0, 2)
// const suffix = name.slice(2)
// return join("objects", prefix, suffix)
// }
async getRefs(forPush: boolean): Promise<Ref[]> {
let refs = await this.storage.listRefs()

@ -5,9 +5,12 @@ import bip39 from "bip39"
import inquirer from "inquirer"
import { importActions, generateActions } from "./actions.js"
import network from "../config/evm-network.js"
import { getWallet, randomRPC } from "../common/wallet.js"
import { explorerTxUrl, getWallet, randomRPC, setupContract } from "../common/wallet.js"
import { parseGit3URI } from "../common/git3-protocol.js"
import { TxManager } from "../common/tx-manager.js"
import nameServices from "../config/name-services.js"
import abis from "../config/abis.js"
const program = new Command()
program.name("git3").description("git3 mangement tool").version("0.1.0")
@ -20,8 +23,7 @@ program
.action(() => {
inquirer.prompt(generateActions).then((answers) => {
const { keyType, name } = answers
const walletType =
keyType === "private key" ? "privateKey" : "mnemonic"
const walletType = keyType === "private key" ? "privateKey" : "mnemonic"
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
@ -64,9 +66,7 @@ program
if (params.raw) {
console.log(`[${file}]`)
console.log(
` ${content.split("\n")[0]} - ${content.split("\n")[1]}`
)
console.log(` ${content.split("\n")[0]} - ${content.split("\n")[1]}`)
console.log("\t")
return
}
@ -89,8 +89,7 @@ program
.action(() => {
inquirer.prompt(importActions).then((answers) => {
const { keyType, key, name } = answers
const walletType =
keyType === "private key" ? "privateKey" : "mnemonic"
const walletType = keyType === "private key" ? "privateKey" : "mnemonic"
const keyPath = process.env.HOME + "/.git3/keys"
mkdirSync(keyPath, { recursive: true })
@ -114,9 +113,7 @@ program
const wallets = readdirSync(keyPath)
if (wallets.length === 0) {
console.error(
"No wallet found, you can generate one with `git3 generate`"
)
console.error("No wallet found, you can generate one with `git3 generate`")
return
}
@ -135,49 +132,108 @@ program
})
})
program
let create = program
.command("create")
.description("create hub [is_permissionless] OR create repo <uri>")
create
.command("hub")
.argument("<chain>", "chain name or chain id")
.argument("[is_permissionless]", "true or false", false)
.description("create a new hub")
.action(async (chain, isPermissionless) => {
let netConfig, chainId
chainId = parseInt(chain)
if (chainId) {
netConfig = network[chainId]
} else {
let ns = nameServices[chain]
if (!ns) throw new Error(`invalid name service ${chain}`)
chainId = ns.chainId
netConfig = network[chainId]
}
const wallet = await getWallet()
let rpc = randomRPC(netConfig.rpc)
const provider = new ethers.providers.JsonRpcProvider(rpc)
let factory = setupContract(provider, netConfig.contracts.factory, abis.Factory, wallet)
let txManager = new TxManager(factory, chainId, netConfig.txConst)
let receipt = await txManager.SendCall("createHub", [isPermissionless])
// let CreateHubEvent = factory.interface.getEvent("CreateHub");
console.log(explorerTxUrl(receipt.transactionHash, netConfig.explorers))
let events = receipt.logs
.map((log: any) => {
try {
return factory.interface.parseLog(log)
} catch (e) {
return null
}
})
.filter((item: any) => item !== null && item.name === "CreateHub")
console.log("hub address:", events[0].args.hub)
console.log("hub owner:", events[0].args.creator)
})
create
.command("repo")
.argument("<uri>", "ex: git3.w3q/repo_name")
.description("create a new repo")
.action(async (uri) => {
if (!uri.startsWith("git3://")) {
uri = "git3://" + uri
const protocol = await parseGit3URI(uri, { ignoreProtocolHeader: true })
let isMember = await protocol.hub.membership(protocol.wallet.address)
if (!isMember) {
let hubName = protocol.ns
? `${protocol.nsName}.${protocol.nsDomain}`
: protocol.hubAddress
console.error(`you are not a member of this hub: ${hubName}`)
let isPermissionless = await protocol.hub.permissionless()
if (isPermissionless) {
console.error(
`this hub is permissionless, you can join it with: git3 join ${hubName}`
)
} else {
console.error(
`this hub is not permissionless, you can ask the hub owner to add you as a member`
)
}
return
}
const protocol = await parseGit3URI(uri)
let owner = await protocol.contract.repoNameToOwner(
Buffer.from(protocol.repoName)
)
let owner = await protocol.hub.repoOwner(Buffer.from(protocol.repoName))
if (owner != "0x0000000000000000000000000000000000000000") {
console.error(`repo ${protocol.repoName} already exists`)
return
}
console.log(
`creating repo ${protocol.repoName} on ${protocol.netConfig.name}...`
)
const txManager = new TxManager(
protocol.contract,
protocol.chainId,
protocol.netConfig.txConst
)
let receipt = await txManager.SendCall("createRepo", [
Buffer.from(protocol.repoName),
])
if (
protocol.netConfig.explorers &&
protocol.netConfig.explorers.length > 0
) {
console.log(
protocol.netConfig.explorers[0].url.replace(/\/+$/, "") +
"/tx/" +
receipt.transactionHash
)
} else {
console.log(receipt.transactionHash)
}
console.log(`creating repo ${protocol.repoName} on ${protocol.netConfig.name}...`)
const txManager = new TxManager(protocol.hub, protocol.chainId, protocol.netConfig.txConst)
let receipt = await txManager.SendCall("createRepo", [Buffer.from(protocol.repoName)])
console.log(explorerTxUrl(receipt.transactionHash, protocol.netConfig.explorers))
console.log(`repo ${protocol.repoName} created.`)
})
program
.command("join")
.argument("<hub>", "hub_name.NS or hub_address:chain_id")
.description("join a permissionless hub")
.action(async (hub) => {
let protocol = await parseGit3URI(hub, { ignoreProtocolHeader: true, skipRepoName: true })
let isPermissionless = await protocol.hub.permissionless()
if (!isPermissionless) {
console.error(`hub ${protocol.hubAddress} is not permissionless`)
return
}
const txManager = new TxManager(protocol.hub, protocol.chainId, protocol.netConfig.txConst)
let receipt = await txManager.SendCall("permissionlessJoin", [])
console.log(explorerTxUrl(receipt.transactionHash, protocol.netConfig.explorers))
})
program
.command("info")
.argument("[wallet]", "wallet you want to get info", "default")
@ -191,9 +247,7 @@ program
console.log(`address: ${address}`)
for (let [_, net] of Object.entries(network)) {
const provider = new ethers.providers.JsonRpcProvider(
randomRPC(net.rpc)
)
const provider = new ethers.providers.JsonRpcProvider(randomRPC(net.rpc))
const balance = provider.getBalance(address)
balance.then((res) => {
console.log(
@ -216,11 +270,7 @@ program
uri = "git3://" + uri
}
const protocol = await parseGit3URI(uri, { skipRepoName: true })
const txManager = new TxManager(
protocol.contract,
protocol.chainId,
protocol.netConfig.txConst
)
const txManager = new TxManager(protocol.hub, protocol.chainId, protocol.netConfig.txConst)
let nonce = await protocol.wallet.getTransactionCount()
console.log(`current nonce: ${nonce}`)
await txManager.clearPendingNonce(num)

@ -11,7 +11,7 @@ export class ETHStorage implements Storage {
constructor(protocol: Git3Protocol) {
this.repoName = protocol.repoName
this.contract = protocol.contract
this.contract = protocol.hub
this.wallet = protocol.wallet
this.txManager = new TxManager(this.contract, protocol.chainId, protocol.netConfig.txConst)
}
@ -20,7 +20,7 @@ export class ETHStorage implements Storage {
}
async repoRoles(): Promise<string[]> {
let owner = await this.contract.repoNameToOwner(Buffer.from(this.repoName))
let owner = await this.contract.repoOwner(Buffer.from(this.repoName))
if (owner === ethers.constants.AddressZero) return []
return [owner]
}
@ -60,7 +60,7 @@ export class ETHStorage implements Storage {
}
async listRefs(): Promise<Ref[]> {
const res: string[][] = await this.contract.listRefs(Buffer.from(this.repoName))
const res: string[][] = await this.contract.listRepoRefs(Buffer.from(this.repoName))
let refs = res.map((i) => ({
ref: Buffer.from(i[1].slice(2), "hex")
.toString("utf8")
@ -73,7 +73,7 @@ export class ETHStorage implements Storage {
async setRef(path: string, sha: string): Promise<Status> {
try {
console.error(`=== setting ref ${path} ===`)
await this.txManager.SendCall("setRef", [
await this.txManager.SendCall("setRepoRef", [
Buffer.from(this.repoName),
Buffer.from(path),
"0x" + sha,

@ -25,7 +25,7 @@ export class SLIStorage implements Storage {
constructor(protocol: Git3Protocol) {
this.repoName = protocol.repoName
this.contract = protocol.contract
this.contract = protocol.hub
this.wallet = protocol.wallet
this.txManager = new TxManager(this.contract, protocol.chainId, protocol.netConfig.txConst)
this.auth = [
@ -52,7 +52,7 @@ export class SLIStorage implements Storage {
}
async repoRoles(): Promise<string[]> {
let owner = await this.contract.repoNameToOwner(Buffer.from(this.repoName))
let owner = await this.contract.repoOwner(Buffer.from(this.repoName))
if (owner === ethers.constants.AddressZero) return []
return [owner]
}
@ -102,7 +102,7 @@ export class SLIStorage implements Storage {
try {
console.error(`=== uploading file ${path} ===`)
const cid = await this.storeIPFS(file)
console.error(`ipfs cid: ${cid}`)
// console.error(`ipfs cid: ${cid}`)
this.batchQueue.push({ path, cid })
if (this.commitTimer) clearTimeout(this.commitTimer)
@ -177,7 +177,7 @@ export class SLIStorage implements Storage {
}
async listRefs(): Promise<Ref[]> {
const res: string[][] = await this.contract.listRefs(Buffer.from(this.repoName))
const res: string[][] = await this.contract.listRepoRefs(Buffer.from(this.repoName))
let refs = res.map((i) => ({
ref: Buffer.from(i[1].slice(2), "hex")
.toString("utf8")
@ -190,7 +190,7 @@ export class SLIStorage implements Storage {
async setRef(path: string, sha: string): Promise<Status> {
try {
console.error(`=== setting ref ${path} ===`)
await this.txManager.SendCall("setRef", [
await this.txManager.SendCall("setRepoRef", [
Buffer.from(this.repoName),
Buffer.from(path),
"0x" + sha,

@ -648,6 +648,11 @@
dependencies:
"@types/node" "*"
"@types/url-parse@^1.4.8":
version "1.4.8"
resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.8.tgz#c3825047efbca1295b7f1646f38203d9145130d6"
integrity sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
@ -1776,6 +1781,11 @@ punycode@2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d"
integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@ -1825,6 +1835,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
resolve@^1.22.0:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
@ -2156,6 +2171,14 @@ universalify@^2.0.0:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
url-parse@^1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"

Loading…
Cancel
Save