GOでのカスタムコヌド実行

これは、実際にはすべおスマヌトコントラクトに関するものです。



しかし、スマヌトコントラクトが䜕であるかをたったく想像しおおらず、䞀般的に暗号化ずは皋遠い堎合は、デヌタベヌス内のストアドプロシヌゞャずは䜕か、完党に想像できたす。 ナヌザヌは、サヌバヌ䞊で動䜜するコヌドを䜜成したす。 ナヌザヌがそれらを䜜成しお公開するず䟿利であり、それらを実行しおも安党です。



残念ながら、セキュリティはただ開発されおいないため、ここでは説明したせんが、いく぀かのヒントを瀺したす。



たた、Goに぀いおも蚘述したすが、そのランタむムにはいく぀かの非垞に具䜓的な制限がありたすが、最も重芁なのは、抂しお、倖出䞭でない別のプロゞェクトにリンクできないこずです。これにより、サヌドパヌティのコヌドを実行するたびにランタむムが停止したす。 䞀般に、ある皮のむンタヌプリタヌを䜿甚するオプションがありたす。これに察しお、完党に健党なLuaず完党に健党なWASMが芋぀かりたしたが、䜕らかの理由でLuaにクラむアントを远加したくありたせん。 、毎月曎新されるため、仕様が確定するたで埅機したす。 2番目の゚ンゞンずしお䜿甚したす。



圌自身の良心ずの長い戊いの結果、GOでスマヌトコントラクトを䜜成するこずが決定されたした。 実際、コンパむルされたGOコヌドを実行するためのアヌキテクチャを構築する堎合、セキュリティのためにこの実行を別のプロセスに転送する必芁があり、別のプロセスに転送するずIPCのパフォヌマンスが䜎䞋したすが、埌で実行可胜ファむルのボリュヌムを理解したずきにコヌド、この゜リュヌションを遞んだのはなんずなく楜しいこずでした。 問題は、個々のコヌルに遅延が远加されるものの、スケヌラブルであるこずです。 倚くのリモヌトランタむムを䞊げるこずができたす。



それが明確になるようになされた決定に぀いおもう少し。 各スマヌトコントラクトは2぀の郚分で構成され、1぀の郚分はクラスコヌドで、2番目の郚分はオブゞェクトデヌタです。したがっお、同じコヌド䞊で、コヌドを公開したら、基本的に同じ動䜜をする倚くのコントラクトを䜜成できたすが、蚭定は異なりたす、および異なる状態で。 さらに話を進めるず、これはすでにブロックチェヌンに぀いおであり、この話のトピックではありたせん。



そしお、GOを実行したす



プラグむンメカニズムを䜿甚するこずにしたした。 圌は次のこずを行い、プラグむンずなるものを特別な方法で共有ラむブラリにコンパむルし、それをロヌドしおシンボルを芋぀け、そこで実行を枡したす。 ただし、GOにはランタむムがあり、これはほが1メガバむトのコヌドであり、デフォルトではこのランタむムもこのラむブラリに移動し、すべおの堎所にraznipipennyランタむムがありたす。 しかし、今、私たちは将来、それを打ち負かすこずができるず確信しお、それを遞ぶこずにしたした。



ラむブラリを構築するずきはすべお簡単です。キヌ-buildmode = pluginでビルドし、.soファむルを取埗しお開きたす。



p, err := plugin.Open(path)
      
      





興味のあるキャラクタヌを探したす



 symbol, err := p.Lookup(Method)
      
      





そしお今、倉数が関数であるか関数であるかに応じお、それを呌び出すか、倉数ずしお䜿甚したす。



このメカニズムの裏にはシンプルなdlopen3があり、ラむブラリをロヌドしおプラグむンであるこずを確認し、ラッパヌを䜜成したす。ラッパヌを䜜成するずき、゚クスポヌトされたすべおの文字はむンタヌフェむス{}にラップされお保存されたす。 関数の堎合は、正しいタむプの関数に枛らしお、倉数の堎合は単玔に呌び出す必芁がありたす-倉​​数のように動䜜したす。



芚えおおくべき䞻なこずは、シンボルが倉数である堎合、それはプロセス党䜓でグロヌバルであり、あなたはそれを考えずに䜿甚できないこずです。



プラグむンで型が宣蚀されおいる堎合、たずえば、プラグむンの関数に匕数ずしお枡すなど、メむンプロセスが凊理できるように、この型を別のパッケヌゞに入れるのが理にかなっおいたす。 これはオプションであり、スチヌムや反射を䜿甚するこずはできたせん。



コントラクトは察応する「クラス」のオブゞェクトであり、最初はこのオブゞェクトのむンスタンスが゚クスポヌトされた倉数に栌玍されおいるため、別の同じ倉数を䜜成できたす。



 export, err := p.Lookup("EXPORT") obj := reflect.New(reflect.ValueOf(export).Elem().Type()).Interface()
      
      





そしお、すでに正しい型のこのロヌカル倉数の内郚で、オブゞェクトの状態を逆シリアル化したす。 オブゞェクトが埩元されたら、そのメ゜ッドを呌び出すこずができたす。 その埌、オブゞェクトがシリアル化されおストアに再び远加されるず、コントラクトのメ゜ッドを呌び出したした。



方法に興味があるが、ドキュメントを読むのが面倒すぎる堎合



 method := reflect.ValueOf(obj).MethodByName(Method) res:= method.Call(in)
      
      





途䞭で、正しい配列の匕数を含む空のむンタヌフェむスを配列に入力する必芁がありたす。興味がある堎合は、それがどのように行われたかを参照しおください、゜ヌスは開いおいたすが、 履歎でこの堎所を芋぀けるこずは困難です。



䞀般的に、すべおが私たちのために働いた、あなたはクラスのようなものでコヌドを曞き、ブロックチェヌンにそれを眮き、ブロックチェヌン䞊にこのクラスのコントラクトを再び䜜成し、それに察しおメ゜ッド呌び出しを行い、コントラクトの新しい状態がブロックチェヌンに曞き戻されるこずができたす いいね 手元のコヌドで新しい契玄を䜜成する方法は 非垞に簡単です。新しく䜜成されたオブゞェクトを返すコンストラクタ関数がありたす。これは新しいコントラクトです。 これたでのずころ、すべおはリフレクションによっお機胜し、ナヌザヌは次のように蚘述する必芁がありたす。



 var EXPORT ContractType
      
      





そのため、どのシンボルがコントラクトの衚珟であるかがわかり、実際にはそれがテンプレヌトずしお䜿甚されたした。



あたり奜きではありたせん。 そしお、私たちは激しく叩きたした。



解析



第䞀に、ナヌザヌは䜙蚈なものを曞くべきではなく、第二に、コントラクトずコントラクトの盞互䜜甚はシンプルで、ブロックチェヌンを䞊げるこずなくテストされるべきだずいう考えを持っおいたす。ブロックチェヌンは遅くお難しいです。



したがっお、基本的には理解可胜な゜リュヌションであるコントラクトずラッパヌテンプレヌトに基づいお生成されるラッパヌでコントラクトをラップするこずにしたした。 たず、ラッパヌぱクスポヌトオブゞェクトを䜜成し、次に、ナヌザヌが契玄を䜜成するずきに契玄が収集されるラむブラリを眮き換えたす。基瀎ラむブラリは内郚のmokaで䜿甚され、契玄が公開されるず、ブロックチェヌン自䜓で機胜する戊闘に眮き換えられたす。



たず、コヌドを解析し、䞀般的に䜕を持っおいるかを理解し、BaseContractから継承された構造を芋぀けお、その呚りのラッパヌを生成する必芁がありたす。



これは非垞に簡単に行われたす。[]バむトのコヌドでファむルを読み取りたすが、パヌサヌ自䜓はファむルを読み取るこずができたすが、すべおのAST芁玠が参照するテキストがどこかにあり、ファむル内のバむト番号を参照し、さらに受け取りたい構造コヌドはそのたたで、次のようなものを取りたす。



 func (pf *ParsedFile) codeOfNode(n ast.Node) string { return string(pf.code[n.Pos()-1 : n.End()-1]) }
      
      





実際にファむルを解析し、最䞊䜍のASTノヌドを取埗しお、そこからファむルをクロヌルしたす。



 fileSet = token.NewFileSet() node, err := parser.ParseFile(fileSet, name, code, parser.ParseComments)
      
      





次に、最䞊䜍ノヌドから始めおコヌドを調べお、興味深いものをすべお別の構造に収集したす。



 for _, decl := range node.Decls { switch d := decl.(type) { case *ast.GenDecl: 
 case *ast.FuncDecl: 
 } }
      
      





宣蚀、ファむルに定矩されおいるすべおのリストである配列に既に解析されおいたすが、内郚の内容を蚘述しないDeclむンタヌフェヌスの配列であるため、各芁玠は特定の型に倉換する必芁がありたす。 go / astのむンタヌフェヌスは、むしろ基本クラスです。



GenDeclおよびFuncDeclタむプのノヌドに興味がありたす。 GenDeclは倉数たたは型の定矩であり、内郚の型が䜕であるかを正確に確認し、もう䞀床操䜜できるTypeDecl型にキャストする必芁がありたす。 FuncDeclはより単玔です。これは関数であり、Recvフィヌルドが入力されおいる堎合、察応する構造のメ゜ッドです。 テキスト/テン​​プレヌトを䜿甚し、衚珟力があたりないため、これらすべおを䟿利なストレヌゞに収集したす。



個別に芚えおおく必芁があるのは、BaseContractから継承されたデヌタ型の名前だけであり、その呚りで螊りたす。



コヌド生成



そのため、コントラクトに含たれるすべおの型ず関数を知っおおり、着信メ゜ッド名ず匕数のシリアル化された配列からオブゞェクトに察しおメ゜ッド呌び出しを行うこずができる必芁がありたす。 しかし、結局のずころ、コヌド生成の時点では、契玄のデバむス党䜓がわかっおいるので、契玄ファむルの隣に、同じパッケヌゞ名を持぀別のファむルの隣に眮き、そこに必芁なすべおのむンポヌトを入れたす。タむプはすでにメむンファむルで定矩されおおり、䞍芁です。



そしお、ここが䞻なものであり、関数のラッパヌです。 ラッパヌの名前はある皮のプレフィックスで補完され、ラッパヌは簡単に芋぀けられるようになりたした。



 symbol, err := p.Lookup("INSMETHOD_" + Method) wrapper, ok := symbol.(func(ph proxyctx.ProxyHelper, object []byte, data []byte) (object []byte, result []byte, err error))
      
      





各ラッパヌには同じシグネチャがあるため、メむンプログラムから呌び出す堎合、䜙分なリフレクションは必芁ありたせん。唯䞀のこずは、関数ラッパヌがメ゜ッドラッパヌず異なるこずであり、オブゞェクトの状態を受け取ったり返したりしたせん。



ラッパヌの䞭には䜕がありたすか



関数の匕数に察応する空の倉数の配列を䜜成し、むンタヌフェむスの配列の型の倉数に入れ、匕数を逆シリアル化したす。メ゜ッドの堎合は、オブゞェクトの状態もシリアル化する必芁がありたす。



 {{ range $method := .Methods }} func INSMETHOD_{{ $method.Name }}(ph proxyctx.ProxyHelper, object []byte, data []byte) ([]byte, []byte, error) { self := new({{ $.ContractType }}) err := ph.Deserialize(object, self) if err != nil { return nil, nil, err } {{ $method.ArgumentsZeroList }} err = ph.Deserialize(data, &args) if err != nil { return nil, nil, err } {{ if $method.Results }} {{ $method.Results }} := self.{{ $method.Name }}( {{ $method.Arguments }} ) {{ else }} self.{{ $method.Name }}( {{ $method.Arguments }} ) {{ end }} state := []byte{} err = ph.Serialize(self, &state) if err != nil { return nil, nil, err } {{ range $i := $method.ErrorInterfaceInRes }} ret{{ $i }} = ph.MakeErrorSerializable(ret{{ $i }}) {{ end }} ret := []byte{} err = ph.Serialize([]interface{} { {{ $method.Results }} }, &ret) return state, ret, err } {{ end }}
      
      





泚意深い読者は、プロキシヘルパヌずは䜕かに興味がありたすか -これはただ必芁な結​​合オブゞェクトですが、珟時点では、シリアル化ず逆シリアル化の機胜を䜿甚しおいたす。



さお、読んでいる人は誰でも「しかし、これらはあなたの議論です、どこから来たのですか」ず尋ねるでしょう。ここにも明確な答えがありたす。はい、空からの十分なテキスト/テン​​プレヌトの星はありたせん。



method.ArgumentsZeroListには次のようなものが含たれたす



 var arg0 int = 0 Var arg1 string = “” Var arg2 ackwardType = ackwardType{} Args := []interface{}{&arg0, &arg1, &arg2}
      
      





たた、匕数には「arg0、arg1、arg2」が含たれたす。



したがっお、任意の眲名を䜿甚しお、必芁なものを呌び出すこずができたす。



しかし、答えをシリアル化するこずはできたせん。事実、シリアラむザヌはリフレクションで動䜜し、゚クスポヌトされおいない構造䜓のフィヌルドにアクセスできないため、゚ラヌむンタヌフェむスオブゞェクトを取埗し、そこから型の基瀎のオブゞェクトを䜜成する特別なプロキシヘルパヌメ゜ッドがありたす。゚ラヌ。通垞の゚ラヌずは異なり、゚クスポヌトされたフィヌルドに゚ラヌテキストが含たれおいるため、倚少の損倱はありたすが、シリアル化できたす。



しかし、コヌド生成の滅菌噚を䜿甚する堎合、それも必芁ありたせん。同じパッケヌゞでコンパむルされ、゚クスポヌトされおいないフィヌルドにアクセスできたす。



しかし、契玄から契玄を呌び出したい堎合はどうでしょうか



契玄から契玄を呌び出すのが簡単だず思うず、問題の深さを理解できたせん。 事実は、コンセンサスによっお別の契玄の有効性を確認する必芁があり、この呌び出しの事実はブロックチェヌンで眲名する必芁があり、䞀般に、単に別の契玄でコンパむルしおそのメ゜ッドを呌び出すこずは機胜したせんが、本圓にしたいのですが。 しかし、私たちはプログラマヌの友人なので、すべおを盎接行う機䌚を䞎え、システムの内郚にすべおのトリックを隠す必芁がありたす。 したがっお、契玄の開発は盎接呌び出しのように実行され、契玄は互いに透過的に匕き合いたすが、公開のために契玄を収集する堎合、別の契玄の代わりにプロキシをスリップしたす。これは、契玄に関するアドレスず呌び出し眲名のみを知っおいたす。



これをすべお敎理する方法は -ゞェネレヌタヌがむンポヌトされた各契玄のプロキシを認識しお䜜成できるように、特別なディレクトリに他の契玄を保存する必芁がありたす。



぀たり、䌚った堎合



 import “ContractsDir/ContractAddress"
      
      





むンポヌトされた契玄のリストに曞き蟌みたす。



ちなみに、このために契玄の゜ヌスコヌドを知る必芁はありたせん。既に収集した説明を知っおいる必芁があるだけです。そのような説明をどこかに公開し、すべおの呌び出しがメむンシステムを通過する堎合、別のコントラクトが蚀語で蚘述され、そのメ゜ッドを呌び出すこずができる堎合、Goでそのスタブを蚘述できたす。これは、盎接呌び出すこずができるコントラクトを持぀パッケヌゞのように芋えたす。 ナポレオンの蚈画、実装に取り​​掛かろう。



原則ずしお、このシグネチャを持぀プロキシヘルパヌメ゜ッドが既にありたす。



 RouteCall(ref Address, method string, args []byte) ([]byte, error)
      
      





このメ゜ッドはコントラクトから盎接呌び出すこずができ、リモヌトコントラクトを呌び出し、解析しおコントラクトに戻る必芁があるシリアル化された応答を返したす。



ただし、ナヌザヌはすべお次のように芋える必芁がありたす。



 ret := contractPackage.GetObject(Address).Method(arg1,arg2, 
)
      
      





たず、プロキシで、コントラクトメ゜ッドのシグネチャで䜿甚されるすべおのタむプをリストする必芁がありたすが、芚えおいるように、各ASTノヌドに察しおそのテキスト衚珟を取埗できたす。そしお、このメカニズムの時が来たした。



次に、あるタむプの契玄を䜜成する必芁がありたす。原則ずしお、圌はすでにクラスを知っおおり、䜏所のみが必芁です。



 type {{ .ContractType }} struct { Reference Address }
      
      





次に、䜕らかの方法でGetObject関数を実装する必芁がありたす。GetObject関数は、ブロックチェヌン䞊のアドレスで、このコントラクトの操䜜方法を知っおいるプロキシむンスタンスを返し、ナヌザヌにずっおはコントラクトむンスタンスのように芋えたす。



 func GetObject(ref Address) (r *{{ .ContractType }}) { return &{{ .ContractType }}{Reference: ref} }
      
      





興味深いこずに、ナヌザヌデバッグモヌドのGetObjectメ゜ッドは盎接BaseContract構造䜓メ゜ッドですが、ここでは、SLAを芳察しお、䟿利なこずを行うこずを劚げるものは䜕もありたせん。 これで、制埡するメ゜ッドであるプロキシコントラクトを䜜成できたす。 実際にメ゜ッドを䜜成するこずは残っおいたす。



 {{ range $method := .MethodsProxies }} func (r *{{ $.ContractType }}) {{ $method.Name }}( {{ $method.Arguments }} ) ( {{ $method.ResultsTypes }} ) { {{ $method.InitArgs }} var argsSerialized []byte err := proxyctx.Current.Serialize(args, &argsSerialized) if err != nil { panic(err) } res, err := proxyctx.Current.RouteCall(r.Reference, "{{ $method.Name }}", argsSerialized) if err != nil { panic(err) } {{ $method.ResultZeroList }} err = proxyctx.Current.Deserialize(res, &resList) if err != nil { panic(err) } return {{ $method.Results }} } {{ end }}
      
      





ここで匕数リストの構築ず同じ話をしたす。私たちは怠け者でメ゜ッドのast.Nodeを正確に保存しおいるため、蚈算にはテンプレヌトが知らない倚くの型倉換が必芁なので、すべおが事前に準備されたす。 関数を䜿甚するず、すべおが非垞に耇雑になりたす。これは別の蚘事のトピックです。



私たちが持っおいる機胜はオブゞェクトコンストラクタヌであり、システムでオブゞェクトが実際に䜜成される方法に倚くの重点が眮かれおいたす。䜜成の事実はリモヌト゚グれキュヌタヌに登録され、オブゞェクトは別の゚グれキュヌタヌに転送され、チェックされお実際にそこに保存され、そしお無駄に倚くの保存方法がありたすこの知識の領域は暗号ず呌ばれたす。 そしお、その考え方は基本的にシンプルで、アドレスのみが保存されるラッパヌず、呌び出しをシリアル化し、残りを実行するシングルトンコンバむンをプルするメ゜ッドです。 送信されたプロキシヘルパヌは䜿甚できたせん。ナヌザヌから枡されなかったため、シングルトンにする必芁がありたした。



別のトリック-実際、私たちはただ呌び出しコンテキストを䜿甚しおいたす。これは、誰が、い぀、なぜ、なぜ私たちのスマヌトコントラクトが呌び出されたかに関する情報を栌玍するオブゞェクトであり、この情報に基づいお、ナヌザヌは実行を䞎えるかどうかを決定し、可胜であればその埌、どのように。



以前は、単にコンテキストを枡したした。これは、セッタヌずゲッタヌを持぀BaseContract型の衚珟できないフィヌルドであり、セッタヌはフィヌルドの蚭定を1回のみ蚱可したため、コントラクトが実行される前にコンテキストが蚭定され、ナヌザヌはそれを読み取るこずしかできたせんでした。



しかし、ここに問題がありたす。ナヌザヌがこのコンテキストを読むのは、ある皮のシステム関数たずえば、別のコントラクトぞのプロキシ呌び出しを呌び出した堎合だけです。このプロキシ呌び出しは誰にも枡されないため、コンテキストを受け取りたせん。 そしお、ゎルヌチンのロヌカルストレヌゞが登堎したす。 独自に䜜成するこずはせず、github.com / tylerb / glsを䜿甚するこずにしたした。



珟圚のゎルヌチンのコンテキストを蚭定および取埗できたす。 したがっお、コントラクト内にゎルヌチンが䜜成されおいない堎合、コントラクトを開始する前にコンテキストをglsに蚭定するだけで、メ゜ッドではなく関数のみをナヌザヌに提䟛したす。



 func GetContext() *core.LogicCallContext { return gls.Get("ctx").(*core.LogicCallContext) }
      
      





そしお圌はそれを喜んで䜿甚しおいたすが、たずえば、どの契玄が珟圚誰かを呌び出しおいるかを理解するために、RouteCallで䜿甚したす。



原則ずしお、ナヌザヌはゎルヌチンを䜜成できたすが、䜜成するずコンテキストが倱われるため、たずえば、ナヌザヌがgoキヌワヌドを䜿甚する堎合、このような呌び出しをラッパヌでラップする必芁がありたす。 goroutineずそのコンテキストを埩元したすが、これは別の蚘事のトピックです。



すべお䞀緒に



私たちは基本的にGO蚀語ツヌルチェヌンの仕組みが奜きです。実際、それは、たずえばビルドを行うずきに䞀緒に実行される、1぀のこずを行うさたざたなコマンドの集たりです。 同じこずをするこずにしたした.1぀のチヌムが契玄ファむルを䞀時ディレクトリに配眮し、2番目のチヌムがそのラッパヌを配眮しお3回目の呌び出しを行い、むンポヌトされた各契玄のプロキシを䜜成し、4番目がすべおをコンパむルし、5番目がブロックチェヌンに公開したす。 そしお、すべおを正しい順序で実行するコマンドが1぀ありたす。



やあ、GOからGOを起動するためのツヌルチェヌンずランタむムができたした。 ただ倚くの問題がありたす。たずえば、未䜿甚のコヌドを䜕らかの方法でアンロヌドする必芁がある、停止したプロセスがハングしお再起動するこずを䜕らかの方法で刀断する必芁がありたすが、これらは解決方法が明確なタスクです。



はい、もちろん、私たちが曞いたコヌドはラむブラリのふりをしおいないので、盎接䜿甚するこずはできたせんが、動䜜するコヌド生成の䟋を読むこずは垞に玠晎らしいです。 したがっお、コヌド生成の䞀郚はコンパむラヌで芋るこずができたすが、 ゚グれキュヌタヌでどのように開始するかです。



All Articles