diff --git a/deploy/compile/perform_compilation.go b/deploy/compile/compilers.go similarity index 82% rename from deploy/compile/perform_compilation.go rename to deploy/compile/compilers.go index c072513bf058f1822bd11ba93b8c27723cd7a7ef..15976c926c8f0cfa4123235b2d002e4870d7862f 100644 --- a/deploy/compile/perform_compilation.go +++ b/deploy/compile/compilers.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" + "github.com/hyperledger/burrow/crypto" log "github.com/sirupsen/logrus" ) @@ -78,12 +79,7 @@ type ResponseItem struct { Binary SolidityOutputContract `json:"binary"` } -const ( - AddressLength = 40 - RelocationLength = 20 -) - -func RequestBinaryLinkage(file string, libraries map[string]string) (*BinaryResponse, error) { +func LinkFile(file string, libraries map[string]string) (*BinaryResponse, error) { //Create Binary Request, send it off codeB, err := ioutil.ReadFile(file) if err != nil { @@ -94,6 +90,10 @@ func RequestBinaryLinkage(file string, libraries map[string]string) (*BinaryResp if err != nil { return &BinaryResponse{}, err } + return LinkContract(contract, libraries) +} + +func LinkContract(contract SolidityOutputContract, libraries map[string]string) (*BinaryResponse, error) { bin := contract.Evm.Bytecode.Object if !strings.Contains(bin, "_") { return &BinaryResponse{ @@ -102,29 +102,31 @@ func RequestBinaryLinkage(file string, libraries map[string]string) (*BinaryResp Error: "", }, nil } - var links map[string]map[string]struct{ Start, Length int } - err = json.Unmarshal(contract.Evm.Bytecode.LinkReferences, &links) + var links map[string]map[string][]struct{ Start, Length int } + err := json.Unmarshal(contract.Evm.Bytecode.LinkReferences, &links) if err != nil { return &BinaryResponse{}, err } for _, f := range links { - for name, relo := range f { + for name, relos := range f { addr, ok := libraries[name] if !ok { return &BinaryResponse{}, fmt.Errorf("library %s is not defined", name) } - if relo.Length != RelocationLength { - return &BinaryResponse{}, fmt.Errorf("linkReference should be %d bytes long, not %d", RelocationLength, relo.Length) - } - if len(addr) != AddressLength { - return &BinaryResponse{}, fmt.Errorf("address %s should be %d character long, not %d", addr, AddressLength, len(addr)) - } - start := relo.Start * 2 - end := relo.Start*2 + AddressLength - if bin[start+1] != '_' || bin[end-1] != '_' { - return &BinaryResponse{}, fmt.Errorf("relocation dummy not found at %d in %s ", relo.Start, bin) + for _, relo := range relos { + if relo.Length != crypto.AddressLength { + return &BinaryResponse{}, fmt.Errorf("linkReference should be %d bytes long, not %d", crypto.AddressLength, relo.Length) + } + if len(addr) != crypto.AddressHexLength { + return &BinaryResponse{}, fmt.Errorf("address %s should be %d character long, not %d", addr, crypto.AddressHexLength, len(addr)) + } + start := relo.Start * 2 + end := relo.Start*2 + crypto.AddressHexLength + if bin[start+1] != '_' || bin[end-1] != '_' { + return &BinaryResponse{}, fmt.Errorf("relocation dummy not found at %d in %s ", relo.Start, bin) + } + bin = bin[:start] + addr + bin[end:] } - bin = bin[:start] + addr + bin[end:] } } @@ -135,7 +137,7 @@ func RequestBinaryLinkage(file string, libraries map[string]string) (*BinaryResp }, nil } -func RequestCompile(file string, optimize bool, libraries map[string]string) (*Response, error) { +func Compile(file string, optimize bool, libraries map[string]string) (*Response, error) { input := SolidityInput{Language: "Solidity", Sources: make(map[string]SolidityInputSource)} input.Sources[file] = SolidityInputSource{Urls: []string{file}} diff --git a/deploy/compile/compilers_test.go b/deploy/compile/compilers_test.go index a6f112b2d8e69b698a22d1f5a659517934d12dda..b2277880e567befb874305a3c676026433e4d9d5 100644 --- a/deploy/compile/compilers_test.go +++ b/deploy/compile/compilers_test.go @@ -61,7 +61,7 @@ func TestLocalMulti(t *testing.T) { Version: "", Error: "", } - resp, err := RequestCompile("contractImport1.sol", false, make(map[string]string)) + resp, err := Compile("contractImport1.sol", false, make(map[string]string)) if err != nil { t.Fatal(err) } @@ -108,7 +108,7 @@ func TestLocalSingle(t *testing.T) { Version: "", Error: "", } - resp, err := RequestCompile("simpleContract.sol", false, make(map[string]string)) + resp, err := Compile("simpleContract.sol", false, make(map[string]string)) if err != nil { t.Fatal(err) } @@ -126,7 +126,7 @@ func TestFaultyContract(t *testing.T) { actualOutput, err := exec.Command("solc", "--combined-json", "bin,abi", "faultyContract.sol").CombinedOutput() err = json.Unmarshal(actualOutput, expectedSolcResponse) t.Log(expectedSolcResponse.Error) - resp, err := RequestCompile("faultyContract.sol", false, make(map[string]string)) + resp, err := Compile("faultyContract.sol", false, make(map[string]string)) t.Log(resp.Error) if err != nil { if expectedSolcResponse.Error != resp.Error { diff --git a/deploy/jobs/job_manager.go b/deploy/jobs/job_manager.go index f9b2fe927e8a9c64e1927e66d4d494fde770c666..fce69361818cdf831f139d44d8aa3a9fca1a7364 100644 --- a/deploy/jobs/job_manager.go +++ b/deploy/jobs/job_manager.go @@ -2,13 +2,28 @@ package jobs import ( "fmt" + "path/filepath" "strings" + compilers "github.com/hyperledger/burrow/deploy/compile" "github.com/hyperledger/burrow/deploy/def" "github.com/hyperledger/burrow/deploy/util" log "github.com/sirupsen/logrus" ) +type trackJob struct { + payload def.Payload + job *def.Job + compilerResp *compilers.Response + err error + done chan struct{} +} + +func compile(contract string, track *trackJob) { + (*track).compilerResp, (*track).err = compilers.Compile(contract, false, nil) + close(track.done) +} + func RunJobs(do *def.Packages) error { // Dial the chain if needed @@ -40,21 +55,51 @@ func RunJobs(do *def.Packages) error { return fmt.Errorf("error validating Burrow deploy file at %s: %v", do.YAMLPath, err) } + intermediateJobs := make([]*trackJob, 0, len(do.Package.Jobs)) + for _, job := range do.Package.Jobs { payload, err := job.Payload() if err != nil { return fmt.Errorf("could not get Job payload: %v", payload) } - err = util.PreProcessFields(payload, do) + + track := trackJob{job: job, payload: payload} + intermediateJobs = append(intermediateJobs, &track) + + // Do compilation first + switch payload.(type) { + case *def.Build: + track.done = make(chan struct{}) + go compile(job.Build.Contract, &track) + case *def.Deploy: + if filepath.Ext(job.Deploy.Contract) == ".sol" { + track.done = make(chan struct{}) + go compile(job.Deploy.Contract, &track) + } + } + } + + for _, m := range intermediateJobs { + job := m.job + + err = util.PreProcessFields(m.payload, do) if err != nil { return err } // Revalidate with possible replacements - err = payload.Validate() + err = m.payload.Validate() if err != nil { return fmt.Errorf("error validating job %s after pre-processing variables: %v", job.Name, err) } - switch payload.(type) { + + if m.done != nil { + <-m.done + if m.err != nil { + return m.err + } + } + + switch m.payload.(type) { // Meta Job case *def.Meta: announce(job.Name, "Meta") @@ -88,13 +133,13 @@ func RunJobs(do *def.Packages) error { // Contracts jobs case *def.Deploy: announce(job.Name, "Deploy") - job.Result, err = DeployJob(job.Deploy, do) + job.Result, err = DeployJob(job.Deploy, do, m.compilerResp) case *def.Call: announce(job.Name, "Call") job.Result, job.Variables, err = CallJob(job.Call, do) case *def.Build: announce(job.Name, "Build") - job.Result, err = BuildJob(job.Build, do) + job.Result, err = BuildJob(job.Build, do, m.compilerResp) // State jobs case *def.RestoreState: @@ -222,6 +267,9 @@ func burrowConnectionNeeded(do *def.Packages) (error, bool) { return fmt.Errorf("could not get Job payload: %v", payload), false } switch payload.(type) { + case *def.Meta: + // A meta jobs will call runJobs again, so it does not need a connection for itself + continue case *def.Build: continue case *def.Set: diff --git a/deploy/jobs/jobs_contracts.go b/deploy/jobs/jobs_contracts.go index ce6427418cf1519e83556318d67843862b8131af..62c6c57c367d7cd64b2705e0d7c03926b166ef6d 100644 --- a/deploy/jobs/jobs_contracts.go +++ b/deploy/jobs/jobs_contracts.go @@ -18,7 +18,7 @@ import ( log "github.com/sirupsen/logrus" ) -func BuildJob(build *def.Build, do *def.Packages) (result string, err error) { +func BuildJob(build *def.Build, do *def.Packages, resp *compilers.Response) (result string, err error) { // assemble contract contractPath, err := findContractFile(build.Contract, do.BinPath) if err != nil { @@ -28,10 +28,9 @@ func BuildJob(build *def.Build, do *def.Packages) (result string, err error) { log.WithField("=>", contractPath).Info("Contract path") // normal compilation/deploy sequence - resp, err := compilers.RequestCompile(contractPath, false, make(map[string]string)) - if err != nil { - log.Errorln("Error compiling contracts: Compilers error:") - return "", err + if resp == nil { + log.Errorln("Error compiling contracts: Missing compiler result") + return "", fmt.Errorf("internal error") } else if resp.Error != "" { log.Errorln("Error compiling contracts: Language error:") return "", fmt.Errorf("%v", resp.Error) @@ -78,7 +77,7 @@ func BuildJob(build *def.Build, do *def.Packages) (result string, err error) { return "", nil } -func DeployJob(deploy *def.Deploy, do *def.Packages) (result string, err error) { +func DeployJob(deploy *def.Deploy, do *def.Packages, resp *compilers.Response) (result string, err error) { deploy.Libraries, _ = util.PreProcessLibs(deploy.Libraries, do) // trim the extension contractName := strings.TrimSuffix(deploy.Contract, filepath.Ext(deploy.Contract)) @@ -118,7 +117,7 @@ func DeployJob(deploy *def.Deploy, do *def.Packages) (result string, err error) log.Info("Binary file detected. Using binary deploy sequence.") log.WithField("=>", contractPath).Info("Binary path") - binaryResponse, err := compilers.RequestBinaryLinkage(contractPath, libs) + binaryResponse, err := compilers.LinkFile(contractPath, libs) if err != nil { return "", fmt.Errorf("Something went wrong with your binary deployment: %v", err) } @@ -153,11 +152,10 @@ func DeployJob(deploy *def.Deploy, do *def.Packages) (result string, err error) contractPath = deploy.Contract log.WithField("=>", contractPath).Info("Contract path") // normal compilation/deploy sequence - resp, err := compilers.RequestCompile(contractPath, false, libs) - if err != nil { - log.Errorln("Error compiling contracts: Compilers error:") - return "", err + if resp == nil { + log.Errorln("Error compiling contracts: Missing compiler result") + return "", fmt.Errorf("internal error") } else if resp.Error != "" { log.Errorln("Error compiling contracts: Language error:") return "", fmt.Errorf("%v", resp.Error) @@ -172,7 +170,7 @@ func DeployJob(deploy *def.Deploy, do *def.Packages) (result string, err error) log.WithField("=>", string(response.Binary.Abi)).Info("Abi") log.WithField("=>", response.Binary.Evm.Bytecode.Object).Info("Bin") if response.Binary.Evm.Bytecode.Object != "" { - result, err = deployContract(deploy, do, response) + result, err = deployContract(deploy, do, response, libs) if err != nil { return "", err } @@ -184,7 +182,7 @@ func DeployJob(deploy *def.Deploy, do *def.Packages) (result string, err error) if response.Binary.Evm.Bytecode.Object == "" { continue } - result, err = deployContract(deploy, do, response) + result, err = deployContract(deploy, do, response, libs) if err != nil { return "", err } @@ -205,7 +203,7 @@ func DeployJob(deploy *def.Deploy, do *def.Packages) (result string, err error) if matchInstanceName(response.Objectname, deploy.Instance) { log.WithField("=>", string(response.Binary.Abi)).Info("Abi") log.WithField("=>", response.Binary.Evm.Bytecode.Object).Info("Bin") - result, err = deployContract(deploy, do, response) + result, err = deployContract(deploy, do, response, libs) if err != nil { return "", err } @@ -241,9 +239,14 @@ func findContractFile(contract, binPath string) (string, error) { } // TODO [rj] refactor to remove [contractPath] from functions signature => only used in a single error throw. -func deployContract(deploy *def.Deploy, do *def.Packages, compilersResponse compilers.ResponseItem) (string, error) { +func deployContract(deploy *def.Deploy, do *def.Packages, compilersResponse compilers.ResponseItem, libs map[string]string) (string, error) { log.WithField("=>", string(compilersResponse.Binary.Abi)).Debug("Specification (From Compilers)") - contractCode := compilersResponse.Binary.Evm.Bytecode.Object + + linked, err := compilers.LinkContract(compilersResponse.Binary, libs) + if err != nil { + return "", err + } + contractCode := linked.Binary // Save if _, err := os.Stat(do.BinPath); os.IsNotExist(err) {