diff --git a/TODO.md b/TODO.md index b323e0bcb8..1b4d3cfc53 100644 --- a/TODO.md +++ b/TODO.md @@ -20,7 +20,7 @@ Done - [x] wasp node dashboard: show structure of committee, which SCs are running, etc Pending -- [ ] wwallet: Allow more than one instance of same SC +- [ ] wwallet: separate binaries for admin/client operations - [ ] dwf: allow withdrawing colored tokens - [ ] BufferedKVStore: Cache DB reads (which should not change in the DB during the BufferedKVStore lifetime) @@ -29,11 +29,17 @@ Pending - [ ] Add authentication to web api calls To discuss/RFC +- [ ] optimize SC ledger database. Currently key/value is stored twice: in the virtual state and in the batch which +last updated the value. For small virtual states it is OK. For big ones (data Oracle) it would be better +to for virtual state keep reference to the last updating mutatation in the batch/state update +- [ ] identity system for nodes +- [ ] secure access to API, to SC admin functions - [ ] refactor 'request code' from uint16 value to string - [ ] smart contract state access from outside. The current approach is to provide universal node API to query state. The alternatives would be to expose access functions (like view in Solidity) from the smart contract code itself. Another approach can be expose data schema + generic access -- [ ] Merkle proofs of smart contract state elements +- [ ] Merkle proofs of smart contract state elements The idea is to have relatively short (logoarithmically) proof +of some data element is in the virtual state. Currently proof is the whole batch chain, i.e. linear. - [ ] Standard subscription mechanisms for events: (a) VM events (NanoMsg, ZMQ, MQTT) and (b) smart contract events (signalled by request to subscriber smart contract) - [ ] balance sheet metaphor in the smart contract state. Ownership concept of BS "liability+equity" items @@ -44,14 +50,14 @@ for each committee member with its public key. Option 2: move request data off-t Functional testing - [ ] test fault-tolerance - [ ] test access node function -- [ ] test several concurrent/interacting contracts -- [ ] test random confirmation delays (probably not needed if running on Pollen) +- [X] test several concurrent/interacting contracts +- [X] test random confirmation delays (probably not needed if running on Pollen) - [ ] test big committees (~100 nodes) -- [ ] test overlapping committees +- [X] test overlapping committees Future - [ ] rewrite DKG -- [ ] `Oracle Data Bulletin Board` description. Postponed +- [ ] `Oracle Data Bulletin Board` specs. Postponed - [ ] enable and test 1 node committees - [ ] test quorum == 1 - [ ] optimize logging diff --git a/articles/intro/dwf.md b/articles/intro/dwf.md index d867869148..bad8ac4bd5 100644 --- a/articles/intro/dwf.md +++ b/articles/intro/dwf.md @@ -20,10 +20,10 @@ In the future all smart contract programs will be run on wasm VM)_ The common practice is to display the donation address on a web page for anyone wanting to send us a donation, for example with their Trinity wallet. -What if we also want each donor to attach some feedback text to the donation? For example, +What if we also want each donor to attach some feedback/comment text to the donation? For example, “Here I send you 2 MIOTA because I like your site”? -To support that possibility, we would need some kind of application or an extension of the existing IOTA wallet -with a database for feedback messages behind and so on (of course, there is more than one way to do that, including +To support that possibility, we would need some kind of application, or an extension of the existing IOTA wallet +with a database for comment messages behind and so on (of course, there is more than one way to do that, including embedding the text right into the transaction). _DonateWithFeedback_ smart contract takes responsibility to handle both the donation @@ -53,7 +53,7 @@ The state of any smart contract consists of two parts: - The collection of _key-value pairs_ which can be interpreted as variables and their values. It is an **off-tangle** part of the state. In general, it can contain any data of arbitrary size. In the _DonateWithFeedback_ smart contract the dashboard displays the generic data of the state in -a SC-specific and user-friendly format: +an SC-specific and user-friendly format: the statistics of donations and list of feedback messages stored in the log. (note that we use [Base58 encoding](https://en.bitcoinwiki.org/wiki/Base58) for binary data of fixed size, like diff --git a/main.go b/main.go index 5cfb1809a4..b8690255da 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "github.com/iotaledger/wasp/plugins/dashboard" "github.com/iotaledger/wasp/plugins/database" "github.com/iotaledger/wasp/plugins/dispatcher" + "github.com/iotaledger/wasp/plugins/examples" "github.com/iotaledger/wasp/plugins/gracefulshutdown" "github.com/iotaledger/wasp/plugins/logger" "github.com/iotaledger/wasp/plugins/nodeconn" @@ -41,6 +42,7 @@ func main() { runvm.Init(), publisher.Init(), dashboard.Init(), + examples.Init(), ) testPlugins := node.Plugins( diff --git a/packages/apilib/deploysc.go b/packages/apilib/deploysc.go index a6aa06916a..ce0cca9990 100644 --- a/packages/apilib/deploysc.go +++ b/packages/apilib/deploysc.go @@ -171,8 +171,8 @@ func CreateSC(par CreateSCParams) (*address.Address, *balance.Color, error) { fmt.Fprintf(textout, "checking program metadata: FAILED: %v\n", err) return nil, nil, err } - fmt.Fprintf(textout, "checking program metadata: OK. location: '%s', VMType: '%s', description: '%s'\n", - md.Location, md.VMType, md.Description) + fmt.Fprintf(textout, "checking program metadata: OK. VMType: '%s', description: '%s'\n", + md.VMType, md.Description) // generate distributed key set on committee nodes scAddr, err := GenerateNewDistributedKeySet(par.CommitteeApiHosts, par.N, par.T) diff --git a/packages/apilib/progmdata.go b/packages/apilib/program.go similarity index 61% rename from packages/apilib/progmdata.go rename to packages/apilib/program.go index b7ff51d4a0..06e9bdacd4 100644 --- a/packages/apilib/progmdata.go +++ b/packages/apilib/program.go @@ -5,58 +5,57 @@ import ( "encoding/json" "errors" "fmt" + "net/http" + "time" + "github.com/iotaledger/wasp/packages/hashing" - "github.com/iotaledger/wasp/packages/progmeta" "github.com/iotaledger/wasp/packages/registry" "github.com/iotaledger/wasp/packages/util/multicall" "github.com/iotaledger/wasp/plugins/webapi/admapi" - "github.com/iotaledger/wasp/plugins/webapi/misc" - "net/http" - "time" ) -// PutProgramMetadata calls node to write ProgramMetadata record -func PutProgramMetadata(host string, md registry.ProgramMetadata) error { - data, err := json.Marshal(&admapi.ProgramMetadataJsonable{ - ProgramHash: md.ProgramHash.String(), - Location: md.Location, - Description: md.Description, +// PutProgramMetadata calls node to write program code and ProgramMetadata record +func PutProgram(host string, vmType string, description string, code []byte) (*hashing.HashValue, error) { + data, err := json.Marshal(&admapi.PutProgramRequest{ + ProgramMetadata: admapi.ProgramMetadata{ + VMType: vmType, + Description: description, + }, + Code: code, }) if err != nil { - return err + return nil, err } - url := fmt.Sprintf("http://%s/adm/putprogrammetadata", host) + url := fmt.Sprintf("http://%s/adm/program", host) resp, err := http.Post(url, "application/json", bytes.NewBuffer(data)) if err != nil { - return err + return nil, err } - var result misc.SimpleResponse + var result admapi.PutProgramResponse err = json.NewDecoder(resp.Body).Decode(&result) if err != nil { - return err + return nil, err } if result.Error != "" { - err = errors.New(result.Error) + return nil, errors.New(result.Error) } - return err -} - -// GetProgramMetadata calls node to get ProgramMetadata by program hash -func GetProgramMetadata(host string, progHash *hashing.HashValue) (*progmeta.ProgramMetadata, error) { - data, err := json.Marshal(&admapi.GetProgramMetadataRequest{ - ProgramHash: progHash.String(), - }) + hash, err := hashing.HashValueFromBase58(result.ProgramHash) if err != nil { return nil, err } - url := fmt.Sprintf("http://%s/adm/getprogrammetadata", host) - resp, err := http.Post(url, "application/json", bytes.NewBuffer(data)) + return &hash, nil +} + +// GetProgramMetadata calls node to get ProgramMetadata by program hash +func GetProgramMetadata(host string, progHash *hashing.HashValue) (*registry.ProgramMetadata, error) { + url := fmt.Sprintf("http://%s/adm/program/%s", host, progHash.String()) + resp, err := http.Get(url) if err != nil { return nil, err } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("response status %d", resp.StatusCode) + if resp.StatusCode == http.StatusNotFound { + return nil, nil } var dresp admapi.GetProgramMetadataResponse err = json.NewDecoder(resp.Body).Decode(&dresp) @@ -66,27 +65,21 @@ func GetProgramMetadata(host string, progHash *hashing.HashValue) (*progmeta.Pro if dresp.Error != "" { return nil, errors.New(dresp.Error) } - if !dresp.ExistsMetadata { - return nil, nil - } - ph, err := hashing.HashValueFromBase58(dresp.ProgramHash) - if err != nil { - return nil, err + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("response status %d", resp.StatusCode) } - return &progmeta.ProgramMetadata{ - ProgramHash: ph, - Location: dresp.Location, - VMType: dresp.VMType, - Description: dresp.Description, - CodeAvailable: dresp.ExistsCode, + return ®istry.ProgramMetadata{ + ProgramHash: *progHash, + VMType: dresp.VMType, + Description: dresp.Description, }, nil } // CheckProgramMetadata checks if metadata exists in hosts and is consistent // return program meta data from the first host if all consistent, otherwise nil -func CheckProgramMetadata(hosts []string, progHash *hashing.HashValue) (*progmeta.ProgramMetadata, error) { +func CheckProgramMetadata(hosts []string, progHash *hashing.HashValue) (*registry.ProgramMetadata, error) { funs := make([]func() error, len(hosts)) - mdata := make([]*progmeta.ProgramMetadata, len(hosts)) + mdata := make([]*registry.ProgramMetadata, len(hosts)) for i, host := range hosts { var err error h := host @@ -110,7 +103,7 @@ func CheckProgramMetadata(hosts []string, progHash *hashing.HashValue) (*progmet } // consistentProgramMetadata does not check if code exists -func consistentProgramMetadata(md1, md2 *progmeta.ProgramMetadata) bool { +func consistentProgramMetadata(md1, md2 *registry.ProgramMetadata) bool { if md1 == nil || md2 == nil { return false } @@ -123,8 +116,5 @@ func consistentProgramMetadata(md1, md2 *progmeta.ProgramMetadata) bool { if md1.Description != md2.Description { return false } - if md1.Location != md2.Location { - return false - } return true } diff --git a/packages/committee/consensus/eventproc.go b/packages/committee/consensus/eventproc.go index 3fe319c2b1..dce06b7dda 100644 --- a/packages/committee/consensus/eventproc.go +++ b/packages/committee/consensus/eventproc.go @@ -57,7 +57,7 @@ func (op *operator) EventStateTransitionMsg(msg *committee.StateTransitionMsg) { progHashStr := progHash.String() op.processorReady = processor.CheckProcessor(progHashStr) if !op.processorReady { - processor.LoadProcessorAsync(progHashStr, func(err error) { + processor.LoadProcessorAsync(progHash, func(err error) { if err == nil { op.committee.ReceiveMessage(committee.ProcessorIsReady{ ProgramHash: progHashStr, diff --git a/packages/dashboard/util.go b/packages/dashboard/util.go index e0ee78e52e..2f7378a4f4 100644 --- a/packages/dashboard/util.go +++ b/packages/dashboard/util.go @@ -6,6 +6,15 @@ import ( "time" ) +const maxLength = 150 + +func Trim(s string) string { + if len(s) > maxLength { + return s[0:maxLength] + "..." + } + return s +} + func FormatTimestamp(ts interface{}) string { t, ok := ts.(time.Time) if !ok { diff --git a/packages/progmeta/medatada.go b/packages/progmeta/medatada.go deleted file mode 100644 index 556d996267..0000000000 --- a/packages/progmeta/medatada.go +++ /dev/null @@ -1,51 +0,0 @@ -package progmeta - -import ( - "github.com/iotaledger/wasp/packages/hashing" - "github.com/iotaledger/wasp/packages/registry" - "github.com/iotaledger/wasp/packages/vm/examples" -) - -type ProgramMetadata struct { - ProgramHash hashing.HashValue - Location string - VMType string - Description string - CodeAvailable bool -} - -// GetProgramMetadata return nil, nil if metadata does not exist -func GetProgramMetadata(progHashStr string) (*ProgramMetadata, error) { - ph, err := hashing.HashValueFromBase58(progHashStr) - if err != nil { - return nil, err - } - proc, ok := examples.GetProcessor(progHashStr) - if ok { - return &ProgramMetadata{ - ProgramHash: ph, - Location: "builtin", - VMType: "builtin", - Description: proc.GetDescription(), - CodeAvailable: true, - }, nil - } - md, exists, err := registry.GetProgramMetadata(&ph) - if err != nil { - return nil, err - } - if !exists { - return nil, nil - } - _, exists, err = registry.GetProgramCode(&ph) - if err != nil { - return nil, err - } - return &ProgramMetadata{ - ProgramHash: ph, - Location: md.Location, - VMType: md.VMType, - Description: md.Description, - CodeAvailable: exists, - }, nil -} diff --git a/packages/registry/programcode.go b/packages/registry/programcode.go new file mode 100644 index 0000000000..82b1536b3c --- /dev/null +++ b/packages/registry/programcode.go @@ -0,0 +1,39 @@ +package registry + +import ( + "fmt" + + "github.com/iotaledger/wasp/packages/hashing" + "github.com/iotaledger/wasp/plugins/database" + "github.com/iotaledger/wasp/plugins/publisher" +) + +func dbkeyProgramCode(progHash *hashing.HashValue) []byte { + return database.MakeKey(database.ObjectTypeProgramCode, progHash[:]) +} + +// TODO save program code in the smart contract state +func GetProgramCode(progHash *hashing.HashValue) ([]byte, error) { + db := database.GetRegistryPartition() + data, err := db.Get(dbkeyProgramCode(progHash)) + if err != nil { + return nil, err + } + hash := hashing.HashData(data) + if *hash != *progHash { + return nil, fmt.Errorf("program code is corrupted. Expected: %s. Got: %s", progHash.String(), hash.String()) + } + return data, nil +} + +func SaveProgramCode(programCode []byte) (ret hashing.HashValue, err error) { + progHash := hashing.HashData(programCode) + db := database.GetRegistryPartition() + if err = db.Set(dbkeyProgramCode(progHash), programCode); err != nil { + return + } + ret = *progHash + + defer publisher.Publish("programcode", progHash.String()) + return +} diff --git a/packages/registry/program.go b/packages/registry/programmetadata.go similarity index 54% rename from packages/registry/program.go rename to packages/registry/programmetadata.go index d2a005981b..44d5b5735f 100644 --- a/packages/registry/program.go +++ b/packages/registry/programmetadata.go @@ -3,65 +3,46 @@ package registry import ( "bytes" "fmt" + "io" + "github.com/iotaledger/hive.go/kvstore" "github.com/iotaledger/wasp/packages/hashing" "github.com/iotaledger/wasp/packages/util" "github.com/iotaledger/wasp/plugins/database" "github.com/iotaledger/wasp/plugins/publisher" - "io" ) -// each program is uniquely identified by the hash of it's (binary) code +// each program is uniquely identified by the hash of its binary code type ProgramMetadata struct { // program hash. Persist in key ProgramHash hashing.HashValue - // it is interpreted by the loader to locate and cache program's code - Location string // VM type. It is used to distinguish between several types of VMs VMType string // description any text Description string } -func dbkeyProgramMetadata(progHash *hashing.HashValue) []byte { - return database.MakeKey(database.ObjectTypeProgramMetadata, progHash[:]) -} +var builtinPrograms = make(map[hashing.HashValue]*ProgramMetadata) -func dbkeyProgramCode(progHash *hashing.HashValue) []byte { - return database.MakeKey(database.ObjectTypeProgramCode, progHash[:]) +func RegisterBuiltinProgramMetadata(progHash *hashing.HashValue, description string) { + builtinPrograms[*progHash] = &ProgramMetadata{ + ProgramHash: *progHash, + VMType: "builtin", + Description: description, + } } -// TODO save program code in the smart contract state -func GetProgramCode(progHash *hashing.HashValue) ([]byte, bool, error) { - db := database.GetRegistryPartition() - data, err := db.Get(dbkeyProgramCode(progHash)) - if err == kvstore.ErrKeyNotFound { - return nil, false, nil - } - if err != nil { - return nil, false, err - } - hashData := hashing.HashData(data) - if *progHash != *hashData { - return nil, false, fmt.Errorf("program code is corrupted. Program hash: %s", progHash.String()) - } - return data, true, nil +func dbkeyProgramMetadata(progHash *hashing.HashValue) []byte { + return database.MakeKey(database.ObjectTypeProgramMetadata, progHash[:]) } -func SaveProgramCode(programCode []byte) (ret hashing.HashValue, err error) { - progHash := hashing.HashData(programCode) - db := database.GetRegistryPartition() - if err = db.Set(dbkeyProgramCode(progHash), programCode); err != nil { - return +func (md *ProgramMetadata) Save() error { + _, ok := builtinPrograms[md.ProgramHash] + if ok { + return fmt.Errorf("Cannot save builtin program %s", md.ProgramHash.String()) } - ret = *progHash - - defer publisher.Publish("programcode", progHash.String()) - return -} -func SaveProgramMetadata(md *ProgramMetadata) error { data, err := util.Bytes(md) if err != nil { return err @@ -76,28 +57,30 @@ func SaveProgramMetadata(md *ProgramMetadata) error { return nil } -func GetProgramMetadata(progHash *hashing.HashValue) (*ProgramMetadata, bool, error) { +func GetProgramMetadata(progHash *hashing.HashValue) (*ProgramMetadata, error) { + md, ok := builtinPrograms[*progHash] + if ok { + return md, nil + } + db := database.GetRegistryPartition() data, err := db.Get(dbkeyProgramMetadata(progHash)) if err == kvstore.ErrKeyNotFound { - return nil, false, nil + return nil, nil } if err != nil { - return nil, false, err + return nil, err } ret := &ProgramMetadata{} err = ret.Read(bytes.NewReader(data)) if err != nil { - return nil, false, err + return nil, err } ret.ProgramHash = *progHash - return ret, true, nil + return ret, nil } func (md *ProgramMetadata) Write(w io.Writer) error { - if err := util.WriteString16(w, md.Location); err != nil { - return err - } if err := util.WriteString16(w, md.VMType); err != nil { return err } @@ -109,9 +92,6 @@ func (md *ProgramMetadata) Write(w io.Writer) error { func (md *ProgramMetadata) Read(r io.Reader) error { var err error - if md.Location, err = util.ReadString16(r); err != nil { - return err - } if md.VMType, err = util.ReadString16(r); err != nil { return err } diff --git a/packages/util/strutil.go b/packages/util/strutil.go new file mode 100644 index 0000000000..8aa99be137 --- /dev/null +++ b/packages/util/strutil.go @@ -0,0 +1,15 @@ +package util + +const ( + ending = "[..]" +) + +func GentleCut(s string, length int) string { + if len(s) <= length { + return s + } + if length <= len(ending) { + return ending + } + return s[:length-len(ending)] + ending +} diff --git a/packages/util/strutil_test.go b/packages/util/strutil_test.go new file mode 100644 index 0000000000..24b23f6532 --- /dev/null +++ b/packages/util/strutil_test.go @@ -0,0 +1,21 @@ +package util + +import "testing" + +func TestCutGently(t *testing.T) { + t.Log(GentleCut("kukukuku", 10)) + t.Log(GentleCut("kukukuku", 8)) + t.Log(GentleCut("kukukuku", 5)) + t.Log(GentleCut("kukukukukuku", 5)) + t.Log(GentleCut("kukukukukuku", 6)) + t.Log(GentleCut("kukukukukuku", 7)) + t.Log(GentleCut("kukukukukuku", 8)) + t.Log(GentleCut("kukukukukuku", 9)) + t.Log(GentleCut("kukukukukuku", 10)) + t.Log(GentleCut("kukukukukuku", 11)) + t.Log(GentleCut("kukukukukuku", 12)) + t.Log(GentleCut("ku", 1)) + t.Log(GentleCut("ku", 5)) + t.Log(GentleCut("kuku", 1)) + t.Log(GentleCut("kuku", 4)) +} diff --git a/packages/vm/examples/donatewithfeedback/dwfimpl/impl.go b/packages/vm/examples/donatewithfeedback/dwfimpl/impl.go index 830fbf53c7..faa8c22ad1 100644 --- a/packages/vm/examples/donatewithfeedback/dwfimpl/impl.go +++ b/packages/vm/examples/donatewithfeedback/dwfimpl/impl.go @@ -4,6 +4,7 @@ package dwfimpl import ( "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance" "github.com/iotaledger/wasp/packages/sctransaction" + "github.com/iotaledger/wasp/packages/util" "github.com/iotaledger/wasp/packages/vm/examples/donatewithfeedback" "github.com/iotaledger/wasp/packages/vm/vmtypes" "strings" @@ -51,6 +52,8 @@ func (ep dwfEntryPoint) WithGasLimit(_ int) vmtypes.EntryPoint { return ep } +const maxComment = 150 + // donate implements request 'donate'. It takes feedback text from the request // and adds it into the log of feedback messages func donate(ctx vmtypes.Sandbox) { @@ -61,6 +64,7 @@ func donate(ctx vmtypes.Sandbox) { donated := ctx.AccessSCAccount().AvailableBalanceFromRequest(&balance.ColorIOTA) // take feedback text contained in the request feedback, ok, err := ctx.AccessRequest().Args().GetString(donatewithfeedback.VarReqFeedback) + feedback = util.GentleCut(feedback, maxComment) stateAccess := ctx.AccessState() tlog := stateAccess.GetTimestampedLog(donatewithfeedback.VarStateTheLog) diff --git a/packages/vm/examples/fairauction/impl.go b/packages/vm/examples/fairauction/impl.go index cdac556820..4a2f9f6b36 100644 --- a/packages/vm/examples/fairauction/impl.go +++ b/packages/vm/examples/fairauction/impl.go @@ -65,6 +65,7 @@ const ( OwnerMarginDefault = 50 // 5% OwnerMarginMin = 5 // minimum 0.5% OwnerMarginMax = 100 // max 10% + MaxDescription = 150 ) // validating constants at node boot @@ -289,6 +290,7 @@ func startAuction(ctx vmtypes.Sandbox) { if !ok { description = "N/A" } + description = util.GentleCut(description, MaxDescription) // find out if auction for this color already exist in the dictionary auctions := ctx.AccessState().GetDictionary(VarStateAuctions) diff --git a/packages/vm/examples/load.go b/packages/vm/examples/load.go deleted file mode 100644 index a5a2558ada..0000000000 --- a/packages/vm/examples/load.go +++ /dev/null @@ -1,34 +0,0 @@ -package examples - -import ( - "github.com/iotaledger/wasp/packages/vm/examples/donatewithfeedback/dwfimpl" - "github.com/iotaledger/wasp/packages/vm/examples/fairauction" - "github.com/iotaledger/wasp/packages/vm/examples/fairroulette" - "github.com/iotaledger/wasp/packages/vm/examples/inccounter" - "github.com/iotaledger/wasp/packages/vm/examples/logsc" - "github.com/iotaledger/wasp/packages/vm/examples/sc7" - "github.com/iotaledger/wasp/packages/vm/examples/sc8" - "github.com/iotaledger/wasp/packages/vm/examples/sc9" - "github.com/iotaledger/wasp/packages/vm/examples/tokenregistry" - "github.com/iotaledger/wasp/packages/vm/examples/vmnil" - "github.com/iotaledger/wasp/packages/vm/vmtypes" -) - -var allProcessors = map[string]func() vmtypes.Processor{ - vmnil.ProgramHash: vmnil.GetProcessor, - logsc.ProgramHash: logsc.GetProcessor, - inccounter.ProgramHash: inccounter.GetProcessor, - fairroulette.ProgramHash: fairroulette.GetProcessor, - //wasmpoc.ProgramHash: wasmpoc.GetProcessor, - fairauction.ProgramHash: fairauction.GetProcessor, - tokenregistry.ProgramHash: tokenregistry.GetProcessor, - sc7.ProgramHash: sc7.GetProcessor, - sc8.ProgramHash: sc8.GetProcessor, - sc9.ProgramHash: sc9.GetProcessor, - dwfimpl.ProgramHash: dwfimpl.GetProcessor, -} - -func GetProcessor(progHashStr string) (vmtypes.Processor, bool) { - getProcessor, ok := allProcessors[progHashStr] - return getProcessor(), ok -} diff --git a/packages/vm/examples/tokenregistry/impl.go b/packages/vm/examples/tokenregistry/impl.go index 2250e5aff0..d2885cbe55 100644 --- a/packages/vm/examples/tokenregistry/impl.go +++ b/packages/vm/examples/tokenregistry/impl.go @@ -84,6 +84,8 @@ func initSC(ctx vmtypes.Sandbox) { ctx.Publishf("TokenRegistry: initSC") } +const maxDescription = 150 + // mintSupply implements 'mint supply' request func mintSupply(ctx vmtypes.Sandbox) { ctx.Publish("TokenRegistry: mintSupply") @@ -116,6 +118,8 @@ func mintSupply(ctx vmtypes.Sandbox) { if !ok { description = "no dscr" } + description = util.GentleCut(description, maxDescription) + // get the additional arbitrary deta attached to the supply record uddata, err := reqAccess.Args().Get(VarReqUserDefinedMetadata) if err != nil { diff --git a/packages/vm/processor/load.go b/packages/vm/processor/load.go index 71d0b79b24..c98ac8f29c 100644 --- a/packages/vm/processor/load.go +++ b/packages/vm/processor/load.go @@ -2,16 +2,12 @@ package processor import ( "fmt" + "sync" + "github.com/iotaledger/wasp/packages/hashing" - "github.com/iotaledger/wasp/packages/parameters" "github.com/iotaledger/wasp/packages/registry" "github.com/iotaledger/wasp/packages/util/sema" - "github.com/iotaledger/wasp/packages/vm/examples" "github.com/iotaledger/wasp/packages/vm/vmtypes" - "io/ioutil" - "net/url" - "path" - "sync" ) type processorInstance struct { @@ -22,14 +18,17 @@ type processorInstance struct { // TODO implement multiple workers/instances per program hash. Currently only one var ( - processors = make(map[string]processorInstance) - processorsMutex sync.RWMutex + processors = make(map[string]processorInstance) + processorsMutex sync.RWMutex + builtinProcessors = make(map[hashing.HashValue]func() vmtypes.Processor) ) -// LoadProcessorAsync creates and registers processor for program hash -// asynchronously -// possibly, locates Wasm program code in the file system, in IPFS etc -func LoadProcessorAsync(programHash string, onFinish func(err error)) { +func RegisterBuiltinProcessor(programHash *hashing.HashValue, proc func() vmtypes.Processor) { + builtinProcessors[*programHash] = proc +} + +// LoadProcessorAsync creates and registers processor for program hash asynchronously +func LoadProcessorAsync(programHash *hashing.HashValue, onFinish func(err error)) { go func() { proc, err := loadProcessor(programHash) if err != nil { @@ -38,7 +37,7 @@ func LoadProcessorAsync(programHash string, onFinish func(err error)) { } processorsMutex.Lock() - processors[programHash] = processorInstance{ + processors[programHash.String()] = processorInstance{ Processor: proc, timedLock: sema.New(), } @@ -49,58 +48,28 @@ func LoadProcessorAsync(programHash string, onFinish func(err error)) { } // loadProcessor creates processor instance -// first tries to resolve known program hashes used for testing -// then tries to create from the binary in the registry cache -// finally tries to load binary code from the location specified in the metadata -func loadProcessor(progHashStr string) (vmtypes.Processor, error) { - proc, ok := examples.GetProcessor(progHashStr) +func loadProcessor(progHash *hashing.HashValue) (vmtypes.Processor, error) { + proc, ok := builtinProcessors[*progHash] if ok { - return proc, nil + return proc(), nil } - progHash, err := hashing.HashValueFromBase58(progHashStr) - binaryCode, exist, err := registry.GetProgramCode(&progHash) - md, exist, err := registry.GetProgramMetadata(&progHash) + md, err := registry.GetProgramMetadata(progHash) if err != nil { return nil, err } - if exist { - return vmtypes.FromBinaryCode(md.VMType, binaryCode) + if md == nil { + return nil, fmt.Errorf("Program metadata for hash %s not found", progHash.String()) } - binaryCode, err = loadBinaryCode(md.Location, &progHash) - if err != nil { - return nil, fmt.Errorf("failed to load program's binary data from location %s, program hash = %s", - md.Location, progHashStr) - } - return vmtypes.FromBinaryCode(md.VMType, binaryCode) -} -// loads binary code of the VM, possibly from remote location -// caches it into the the registry -func loadBinaryCode(location string, progHash *hashing.HashValue) ([]byte, error) { - urlStruct, err := url.Parse(location) - if err != nil { - return nil, err - } - var data []byte - switch urlStruct.Scheme { - case "file": - file := path.Join(parameters.GetString(parameters.VMBinaryDir), urlStruct.Host) - if data, err = ioutil.ReadFile(file); err != nil { - return nil, err - } - - default: - return nil, fmt.Errorf("unknown Wasm binary location scheme '%s'", urlStruct.Scheme) + if md.VMType == "builtin" { + return nil, fmt.Errorf("Processor for builtin program %s not registered", progHash.String()) } - h := hashing.HashData(data) - if *h != *progHash { - return nil, fmt.Errorf("binary data or hash is not valid") - } - _, err = registry.SaveProgramCode(data) + binaryCode, err := registry.GetProgramCode(progHash) if err != nil { return nil, err } - return data, nil + + return vmtypes.FromBinaryCode(md.VMType, binaryCode) } diff --git a/plugins/examples/plugin.go b/plugins/examples/plugin.go new file mode 100644 index 0000000000..10674a231d --- /dev/null +++ b/plugins/examples/plugin.go @@ -0,0 +1,56 @@ +package examples + +import ( + "github.com/iotaledger/hive.go/node" + "github.com/iotaledger/wasp/packages/hashing" + "github.com/iotaledger/wasp/packages/registry" + "github.com/iotaledger/wasp/packages/vm/examples/donatewithfeedback/dwfimpl" + "github.com/iotaledger/wasp/packages/vm/examples/fairauction" + "github.com/iotaledger/wasp/packages/vm/examples/fairroulette" + "github.com/iotaledger/wasp/packages/vm/examples/inccounter" + "github.com/iotaledger/wasp/packages/vm/examples/logsc" + "github.com/iotaledger/wasp/packages/vm/examples/sc7" + "github.com/iotaledger/wasp/packages/vm/examples/sc8" + "github.com/iotaledger/wasp/packages/vm/examples/sc9" + "github.com/iotaledger/wasp/packages/vm/examples/tokenregistry" + "github.com/iotaledger/wasp/packages/vm/examples/vmnil" + "github.com/iotaledger/wasp/packages/vm/processor" + "github.com/iotaledger/wasp/packages/vm/vmtypes" +) + +const PluginName = "Examples" + +type example struct { + programHash string + getProcessor func() vmtypes.Processor + name string +} + +func Init() *node.Plugin { + return node.NewPlugin(PluginName, node.Enabled, configure, run) +} + +func configure(ctx *node.Plugin) { + allExamples := []example{ + {vmnil.ProgramHash, vmnil.GetProcessor, "vmnil"}, + {logsc.ProgramHash, logsc.GetProcessor, "logsc"}, + {inccounter.ProgramHash, inccounter.GetProcessor, "inccounter"}, + {fairroulette.ProgramHash, fairroulette.GetProcessor, "FairRoulette"}, + //{wasmpoc.ProgramHash, wasmpoc.GetProcessor, "wasmpoc"}, + {fairauction.ProgramHash, fairauction.GetProcessor, "FairAuction"}, + {tokenregistry.ProgramHash, tokenregistry.GetProcessor, "TokenRegistry"}, + {sc7.ProgramHash, sc7.GetProcessor, "sc7"}, + {sc8.ProgramHash, sc8.GetProcessor, "sc8"}, + {sc9.ProgramHash, sc9.GetProcessor, "sc9"}, + {dwfimpl.ProgramHash, dwfimpl.GetProcessor, "DonateWithFeedback"}, + } + + for _, ex := range allExamples { + hash, _ := hashing.HashValueFromBase58(ex.programHash) + registry.RegisterBuiltinProgramMetadata(&hash, ex.name+" (Built-in Smart Contract example)") + processor.RegisterBuiltinProcessor(&hash, ex.getProcessor) + } +} + +func run(ctx *node.Plugin) { +} diff --git a/plugins/webapi/admapi/programdata.go b/plugins/webapi/admapi/programdata.go index 95c43fd4eb..d28b464044 100644 --- a/plugins/webapi/admapi/programdata.go +++ b/plugins/webapi/admapi/programdata.go @@ -1,84 +1,92 @@ package admapi import ( + "net/http" + "github.com/iotaledger/wasp/packages/hashing" - "github.com/iotaledger/wasp/packages/progmeta" "github.com/iotaledger/wasp/packages/registry" "github.com/iotaledger/wasp/plugins/webapi/misc" "github.com/labstack/echo" ) -type ProgramMetadataJsonable struct { - ProgramHash string `json:"program_hash"` - Location string `json:"location"` +type ProgramMetadata struct { VMType string `json:"vm_type"` Description string `json:"description"` } +type PutProgramRequest struct { + ProgramMetadata + Code []byte `json:"code"` +} + +type PutProgramResponse struct { + ProgramHash string `json:"program_hash"` + Error string `json:"err"` +} + //---------------------------------------------------------- -func HandlerPutProgramMetaData(c echo.Context) error { - var req ProgramMetadataJsonable +func HandlerPutProgram(c echo.Context) error { + var req PutProgramRequest var err error if err := c.Bind(&req); err != nil { - return misc.OkJsonErr(c, err) + return c.JSONPretty(http.StatusBadRequest, &PutProgramResponse{Error: err.Error()}, " ") } - rec := registry.ProgramMetadata{} - - if rec.ProgramHash, err = hashing.HashValueFromBase58(req.ProgramHash); err != nil { - return misc.OkJsonErr(c, err) + if req.VMType == "" { + return c.JSONPretty(http.StatusBadRequest, &PutProgramResponse{Error: "vm_type is required"}, " ") + } + if req.Description == "" { + return c.JSONPretty(http.StatusBadRequest, &PutProgramResponse{Error: "description is required"}, " ") + } + if req.Code == nil || len(req.Code) == 0 { + return c.JSONPretty(http.StatusBadRequest, &PutProgramResponse{Error: "code is required (base64-encoded binary data)"}, " ") } - rec.Location = req.Location - rec.VMType = req.VMType - rec.Description = req.Description - // TODO it is always overwritten! + progHash, err := registry.SaveProgramCode(req.Code) + if err != nil { + return c.JSONPretty(http.StatusInternalServerError, &PutProgramResponse{Error: err.Error()}, " ") + } - if err = registry.SaveProgramMetadata(&rec); err != nil { - return misc.OkJsonErr(c, err) + md := ®istry.ProgramMetadata{ + ProgramHash: progHash, + VMType: req.VMType, + Description: req.Description, } - log.Infof("Program metadata record has been saved. Program hash: %s, description: %s, location: %s", - rec.ProgramHash.String(), rec.Description, rec.Location) - return misc.OkJsonErr(c, nil) -} + // TODO it is always overwritten! + if err = md.Save(); err != nil { + return c.JSONPretty(http.StatusInternalServerError, &PutProgramResponse{Error: err.Error()}, " ") + } -type GetProgramMetadataRequest struct { - ProgramHash string `json:"program_hash"` + log.Infof("Program metadata record has been saved. Program hash: %s, description: %s", + md.ProgramHash.String(), md.Description) + return misc.OkJson(c, &PutProgramResponse{ProgramHash: progHash.String()}) } type GetProgramMetadataResponse struct { - ProgramMetadataJsonable - ExistsMetadata bool `json:"exists_metadata"` - ExistsCode bool `json:"exists_code"` - Error string `json:"err"` + ProgramMetadata + Error string `json:"err"` } func HandlerGetProgramMetadata(c echo.Context) error { - var req GetProgramMetadataRequest - - if err := c.Bind(&req); err != nil { - return misc.OkJson(c, &GetProgramMetadataResponse{ - Error: err.Error(), - }) + progHash, err := hashing.HashValueFromBase58(c.Param("hash")) + if err != nil { + return c.JSONPretty(http.StatusBadRequest, &GetProgramMetadataResponse{Error: err.Error()}, " ") } - md, err := progmeta.GetProgramMetadata(req.ProgramHash) + + md, err := registry.GetProgramMetadata(&progHash) if err != nil { - return misc.OkJson(c, &GetProgramMetadataResponse{Error: err.Error()}) + return c.JSONPretty(http.StatusBadRequest, &GetProgramMetadataResponse{Error: err.Error()}, " ") } if md == nil { - return misc.OkJson(c, &GetProgramMetadataResponse{}) + return c.JSONPretty(http.StatusNotFound, &GetProgramMetadataResponse{Error: "Not found"}, " ") } return misc.OkJson(c, &GetProgramMetadataResponse{ - ProgramMetadataJsonable: ProgramMetadataJsonable{ - ProgramHash: md.ProgramHash.String(), - Location: md.Location, + ProgramMetadata: ProgramMetadata{ VMType: md.VMType, Description: md.Description, }, - ExistsMetadata: true, - ExistsCode: md.CodeAvailable, }) } diff --git a/plugins/webapi/endpoints.go b/plugins/webapi/endpoints.go index 8e132d991d..571ae6dce2 100644 --- a/plugins/webapi/endpoints.go +++ b/plugins/webapi/endpoints.go @@ -40,8 +40,8 @@ func addEndpoints(adminWhitelist []net.IP) { adm.POST("/sc/:scaddress/deactivate", admapi.HandlerDeactivateSC) adm.GET("/sc/:scaddress/dumpstate", admapi.HandlerDumpSCState) - adm.POST("/putprogrammetadata", admapi.HandlerPutProgramMetaData) - adm.POST("/getprogrammetadata", admapi.HandlerGetProgramMetadata) + adm.POST("/program", admapi.HandlerPutProgram) + adm.GET("/program/:hash", admapi.HandlerGetProgramMetadata) } log.Infof("added web api endpoints") diff --git a/plugins/webapi/misc/utils.go b/plugins/webapi/misc/utils.go index 12605d141a..35a4696be9 100644 --- a/plugins/webapi/misc/utils.go +++ b/plugins/webapi/misc/utils.go @@ -13,6 +13,10 @@ func OkJson(c echo.Context, data interface{}) error { return c.JSONPretty(http.StatusOK, data, " ") } +func ErrJson(c echo.Context, code int, err error) error { + return c.JSONPretty(code, &SimpleResponse{Error: err.Error()}, " ") +} + func OkJsonErr(c echo.Context, err error) error { serr := "" if err != nil { diff --git a/tools/cluster/demo/tests/deploy-test.sh b/tools/cluster/demo/tests/deploy-test.sh index 0644e689b1..2d4764cf8d 100644 --- a/tools/cluster/demo/tests/deploy-test.sh +++ b/tools/cluster/demo/tests/deploy-test.sh @@ -6,10 +6,8 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi wwallet -c owner.json init wwallet -c owner.json request-funds -r=$(wwallet -c owner.json sc deploy '0,1,2,3' 3 '8h2RGcbsUgKckh9rZ4VUF75NUfxP4bj1FC66oSF9us6p' 'TokenRegistry') -echo "$r" -[[ "$r" =~ SC[[:space:]]Address:[[:space:]]([[:alnum:]]+)$ ]] -scaddress=${BASH_REMATCH[1]} +wwallet --sc=tr -c owner.json sc deploy '0,1,2,3' 3 '8h2RGcbsUgKckh9rZ4VUF75NUfxP4bj1FC66oSF9us6p' 'TokenRegistry' +scaddress=$(cat owner.json | jq .sc.tr.address -r) wwallet -c owner.json send-funds $scaddress IOTA 100 # operating capital @@ -19,7 +17,7 @@ wwallet tr set address $scaddress r=$(wwallet tr mint "My first coin" 10) echo "$r" -[[ "$r" =~ of[[:space:]]color[[:space:]]([[:alnum:]]+)$ ]] +[[ "$r" =~ of[[:space:]]color[[:space:]]([[:alnum:]]+) ]] color=${BASH_REMATCH[1]} # verify diff --git a/tools/cluster/demo/tests/dwf-test.sh b/tools/cluster/demo/tests/dwf-test.sh index e35c78d4d8..9749539d37 100644 --- a/tools/cluster/demo/tests/dwf-test.sh +++ b/tools/cluster/demo/tests/dwf-test.sh @@ -6,7 +6,7 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi wwallet -c owner.json init wwallet -c owner.json request-funds wwallet -c owner.json dwf admin deploy -scaddress=$(cat owner.json | jq .dwf.address -r) +scaddress=$(cat owner.json | jq .sc.dwf.address -r) wwallet -c owner.json send-funds $scaddress IOTA 100 # operating capital wwallet init diff --git a/tools/cluster/demo/tests/fa-test.sh b/tools/cluster/demo/tests/fa-test.sh index 29df3d2db5..750cdd1de2 100644 --- a/tools/cluster/demo/tests/fa-test.sh +++ b/tools/cluster/demo/tests/fa-test.sh @@ -6,7 +6,7 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi wwallet -c owner.json init wwallet -c owner.json request-funds wwallet -c owner.json fa admin deploy -scaddress=$(cat owner.json | jq .fa.address -r) +scaddress=$(cat owner.json | jq .sc.fa.address -r) wwallet -c owner.json send-funds $scaddress IOTA 100 # operating capital wwallet init diff --git a/tools/cluster/demo/tests/fr-test.sh b/tools/cluster/demo/tests/fr-test.sh index cd51c6ba06..9f89c8fb3c 100644 --- a/tools/cluster/demo/tests/fr-test.sh +++ b/tools/cluster/demo/tests/fr-test.sh @@ -7,7 +7,7 @@ wwallet -c owner.json init wwallet -c owner.json request-funds wwallet -c owner.json fr admin deploy wwallet -c owner.json fr admin set-period 10 -scaddress=$(cat owner.json | jq .fr.address -r) +scaddress=$(cat owner.json | jq .sc.fr.address -r) wwallet -c owner.json send-funds $scaddress IOTA 100 # operating capital # check that set-period request has been executed diff --git a/tools/cluster/demo/tests/full-test.sh b/tools/cluster/demo/tests/full-test.sh index 4bc9b64a48..f3cccd3e1e 100644 --- a/tools/cluster/demo/tests/full-test.sh +++ b/tools/cluster/demo/tests/full-test.sh @@ -7,19 +7,19 @@ wwallet -c owner.json init wwallet -c owner.json request-funds wwallet -c owner.json fr admin deploy -fraddress=$(cat owner.json | jq .fr.address -r) +fraddress=$(cat owner.json | jq .sc.fr.address -r) wwallet -c owner.json send-funds $fraddress IOTA 100 # operating capital wwallet -c owner.json fa admin deploy -faaddress=$(cat owner.json | jq .fa.address -r) +faaddress=$(cat owner.json | jq .sc.fa.address -r) wwallet -c owner.json send-funds $faaddress IOTA 100 # operating capital wwallet -c owner.json tr admin deploy -traddress=$(cat owner.json | jq .tr.address -r) +traddress=$(cat owner.json | jq .sc.tr.address -r) wwallet -c owner.json send-funds $traddress IOTA 100 # operating capital wwallet -c owner.json dwf admin deploy -dwfaddress=$(cat owner.json | jq .dwf.address -r) +dwfaddress=$(cat owner.json | jq .sc.dwf.address -r) wwallet -c owner.json send-funds $dwfaddress IOTA 100 # operating capital wwallet init diff --git a/tools/cluster/demo/tests/tr-test.sh b/tools/cluster/demo/tests/tr-test.sh index 817698c403..d52e8147f3 100644 --- a/tools/cluster/demo/tests/tr-test.sh +++ b/tools/cluster/demo/tests/tr-test.sh @@ -6,7 +6,7 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi wwallet -c owner.json init wwallet -c owner.json request-funds wwallet -c owner.json tr admin deploy -scaddress=$(cat owner.json | jq .tr.address -r) +scaddress=$(cat owner.json | jq .sc.tr.address -r) wwallet -c owner.json send-funds $scaddress IOTA 100 # operating capital wwallet init diff --git a/tools/cluster/tests/wasptest2/deploy_test.go b/tools/cluster/tests/wasptest2/deploy_test.go index 8d52e98392..d08ac0b2ac 100644 --- a/tools/cluster/tests/wasptest2/deploy_test.go +++ b/tools/cluster/tests/wasptest2/deploy_test.go @@ -8,7 +8,7 @@ import ( "github.com/iotaledger/wasp/packages/hashing" "github.com/iotaledger/wasp/packages/kv" "github.com/iotaledger/wasp/packages/testutil" - _ "github.com/iotaledger/wasp/packages/vm/examples" + //_ "github.com/iotaledger/wasp/packages/vm/examples" "github.com/iotaledger/wasp/packages/vm/examples/inccounter" "github.com/iotaledger/wasp/packages/vm/examples/tokenregistry" "github.com/iotaledger/wasp/packages/vm/vmconst" diff --git a/tools/wwallet/config/config.go b/tools/wwallet/config/config.go index b0e8e67565..e09c7ccbf0 100644 --- a/tools/wwallet/config/config.go +++ b/tools/wwallet/config/config.go @@ -16,6 +16,7 @@ var configPath string var Verbose bool var WaitForConfirmation bool var Utxodb bool +var SCAlias string const ( hostKindApi = "api" @@ -31,6 +32,7 @@ func InitCommands(commands map[string]func([]string), flags *pflag.FlagSet) { fs.BoolVarP(&Verbose, "verbose", "v", false, "verbose") fs.BoolVarP(&WaitForConfirmation, "wait", "w", false, "wait for confirmation") fs.BoolVarP(&Utxodb, "utxodb", "u", false, "use utxodb") + fs.StringVarP(&SCAlias, "sc", "s", "", "smart contract alias") flags.AddFlagSet(fs) } @@ -144,12 +146,12 @@ func Set(key string, value interface{}) { check(viper.WriteConfig()) } -func SetSCAddress(scName string, address string) { - Set(scName+".address", address) +func SetSCAddress(scAlias string, address string) { + Set("sc."+scAlias+".address", address) } -func TrySCAddress(scName string) *address.Address { - b58 := viper.GetString(scName + ".address") +func TrySCAddress(scAlias string) *address.Address { + b58 := viper.GetString("sc." + scAlias + ".address") if len(b58) == 0 { return nil } @@ -158,10 +160,11 @@ func TrySCAddress(scName string) *address.Address { return &address } -func GetSCAddress(scName string) *address.Address { - address := TrySCAddress(scName) +func GetSCAddress(scAlias string) *address.Address { + address := TrySCAddress(scAlias) if address == nil { - check(fmt.Errorf("call `set .address` or ` admin deploy` first")) + check(fmt.Errorf("call `%s set sc.%s.address` or `%s --sc=%s sc admin deploy` first", + os.Args[0], scAlias, os.Args[0], scAlias)) } return address } diff --git a/tools/wwallet/dashboard/base.go b/tools/wwallet/dashboard/base.go index 0290b2f4a5..8bd27fe137 100644 --- a/tools/wwallet/dashboard/base.go +++ b/tools/wwallet/dashboard/base.go @@ -28,6 +28,7 @@ type NavPage struct { func MakeTemplate(parts ...string) *template.Template { t := template.New("").Funcs(template.FuncMap{ "formatTimestamp": dashboard.FormatTimestamp, + "trim": dashboard.Trim, "exploreAddressUrl": dashboard.ExploreAddressUrl( dashboard.ExploreAddressUrlFromGoshimmerUri(config.GoshimmerApi()), ), @@ -90,7 +91,7 @@ const TplSCInfo = ` Smart contract details

SC address: {{template "address" .Status.SCAddress}}

Program hash: {{.Status.ProgramHash}}

-

Description of the instance: {{.Status.Description}}

+

Description of the instance: {{trim .Status.Description}}

Owner address: {{template "address" .Status.OwnerAddress}}

Minimum node reward (fee): {{.Status.MinimumReward}}

Color: {{.Config.BootupData.Color}}

diff --git a/tools/wwallet/dashboard/server.go b/tools/wwallet/dashboard/server.go index c31b5cf52a..b5f079cbb4 100644 --- a/tools/wwallet/dashboard/server.go +++ b/tools/wwallet/dashboard/server.go @@ -22,7 +22,6 @@ func StartServer(listenAddr string, scs []SCDashboard) { for _, d := range scs { d.AddTemplates(renderer) navPages = append(navPages, NavPage{Title: d.Config().Name, Href: d.Config().Href()}) - sc.LoadBootupData(d.Config()) } if l, ok := e.Logger.(*log.Logger); ok { diff --git a/tools/wwallet/main.go b/tools/wwallet/main.go index 383acbf60c..6868712b26 100644 --- a/tools/wwallet/main.go +++ b/tools/wwallet/main.go @@ -7,6 +7,7 @@ import ( "github.com/iotaledger/wasp/tools/wwallet/config" "github.com/iotaledger/wasp/tools/wwallet/dashboard/dashboardcmd" + "github.com/iotaledger/wasp/tools/wwallet/program" "github.com/iotaledger/wasp/tools/wwallet/sc/dwf/dwfcmd" "github.com/iotaledger/wasp/tools/wwallet/sc/fa/facmd" "github.com/iotaledger/wasp/tools/wwallet/sc/fr/frcmd" @@ -40,12 +41,13 @@ func main() { config.InitCommands(commands, flags) wallet.InitCommands(commands, flags) - frcmd.InitCommands(commands, flags) - facmd.InitCommands(commands, flags) - trcmd.InitCommands(commands, flags) - dwfcmd.InitCommands(commands, flags) + frcmd.InitCommands(commands) + facmd.InitCommands(commands) + trcmd.InitCommands(commands) + dwfcmd.InitCommands(commands) dashboardcmd.InitCommands(commands, flags) sccmd.InitCommands(commands, flags) + program.InitCommands(commands, flags) check(flags.Parse(os.Args[1:])) config.Read() diff --git a/tools/wwallet/program/cmd.go b/tools/wwallet/program/cmd.go new file mode 100644 index 0000000000..09a4827067 --- /dev/null +++ b/tools/wwallet/program/cmd.go @@ -0,0 +1,57 @@ +package program + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/spf13/pflag" +) + +func InitCommands(commands map[string]func([]string), flags *pflag.FlagSet) { + commands["program"] = programCmd +} + +var subcmds = map[string]func([]string){ + "upload": uploadCmd, + "info": infoCmd, +} + +func programCmd(args []string) { + if len(args) < 1 { + usage() + } + subcmd, ok := subcmds[args[0]] + if !ok { + usage() + } + subcmd(args[1:]) +} + +func usage() { + cmdNames := make([]string, 0) + for k := range subcmds { + cmdNames = append(cmdNames, k) + } + + fmt.Printf("Usage: %s program [%s]\n", os.Args[0], strings.Join(cmdNames, "|")) + os.Exit(1) +} + +func check(err error) { + if err != nil { + fmt.Printf("error: %s\n", err) + os.Exit(1) + } +} + +func parseIntList(s string) []int { + committee := make([]int, 0) + for _, ns := range strings.Split(s, ",") { + n, err := strconv.Atoi(ns) + check(err) + committee = append(committee, n) + } + return committee +} diff --git a/tools/wwallet/program/info.go b/tools/wwallet/program/info.go new file mode 100644 index 0000000000..2067615334 --- /dev/null +++ b/tools/wwallet/program/info.go @@ -0,0 +1,39 @@ +package program + +import ( + "fmt" + "os" + + "github.com/iotaledger/wasp/packages/apilib" + "github.com/iotaledger/wasp/packages/hashing" + "github.com/iotaledger/wasp/tools/wwallet/config" +) + +func infoCmd(args []string) { + if len(args) != 2 { + infoUsage() + } + + hash, err := hashing.HashValueFromBase58(args[0]) + check(err) + nodes := parseIntList(args[1]) + + for _, host := range config.CommitteeApi(nodes) { + md, err := apilib.GetProgramMetadata(host, &hash) + check(err) + + fmt.Printf("Node %s:\n", host) + if md == nil { + fmt.Printf(" Program not found\n") + } else { + fmt.Printf(" Description: %s\n", md.Description) + fmt.Printf(" VMType: %s\n", md.VMType) + } + } +} + +func infoUsage() { + fmt.Printf("Usage: %s program info \n", os.Args[0]) + fmt.Printf("Example: %s program info aBcD...wXyZ '0,1,2,3'\n", os.Args[0]) + os.Exit(1) +} diff --git a/tools/wwallet/program/upload.go b/tools/wwallet/program/upload.go new file mode 100644 index 0000000000..4bdb528a2d --- /dev/null +++ b/tools/wwallet/program/upload.go @@ -0,0 +1,35 @@ +package program + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/iotaledger/wasp/packages/apilib" + "github.com/iotaledger/wasp/tools/wwallet/config" +) + +func uploadCmd(args []string) { + if len(args) != 4 { + uploadUsage() + } + + code, err := ioutil.ReadFile(args[0]) + check(err) + vmtype := args[1] + description := args[2] + nodes := parseIntList(args[3]) + + for _, host := range config.CommitteeApi(nodes) { + hash, err := apilib.PutProgram(host, vmtype, description, code) + check(err) + + fmt.Printf("Program uploaded to host %s. Program hash: %s\n", host, hash.String()) + } +} + +func uploadUsage() { + fmt.Printf("Usage: %s program upload \n", os.Args[0]) + fmt.Printf("Example: %s program upload program-code.bin wasm 'Example smart contract' '0,1,2,3'\n", os.Args[0]) + os.Exit(1) +} diff --git a/tools/wwallet/sc/config.go b/tools/wwallet/sc/config.go index 43e8455c3c..c46115a534 100644 --- a/tools/wwallet/sc/config.go +++ b/tools/wwallet/sc/config.go @@ -2,7 +2,6 @@ package sc import ( "fmt" - "github.com/iotaledger/wasp/packages/registry" "os" "strings" "time" @@ -11,43 +10,41 @@ import ( "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme" waspapi "github.com/iotaledger/wasp/packages/apilib" "github.com/iotaledger/wasp/packages/hashing" + "github.com/iotaledger/wasp/packages/registry" "github.com/iotaledger/wasp/tools/wwallet/config" - "github.com/spf13/pflag" "github.com/spf13/viper" ) type Config struct { - ShortName string - Name string - ProgramHash string - Flags *pflag.FlagSet - committeeDefault []int - quorumDefault int - bootupDataLoaded bool - BootupData registry.BootupData + ShortName string + Name string + ProgramHash string + + bootupData *registry.BootupData } -func (c *Config) Href() string { - return "/" + c.ShortName +func (c *Config) Alias() string { + if config.SCAlias != "" { + return config.SCAlias + } + if c.ShortName != "" { + return c.ShortName + } + panic("Which smart contract? (--sc= is required)") } -func (c *Config) HookFlags() *pflag.FlagSet { - c.Flags.IntVar(&c.quorumDefault, c.ShortName+".quorum", 0, "quorum (default 1,2,3,4)") - c.Flags.IntSliceVar(&c.committeeDefault, c.ShortName+".committee", nil, "committee (default 3)") - return c.Flags +func (c *Config) Href() string { + return "/" + c.ShortName } var DefaultCommittee = []int{0, 1, 2, 3} func (c *Config) SetCommittee(indexes []int) { - config.Set(c.ShortName+".committee", indexes) + config.Set("sc."+c.Alias()+".committee", indexes) } func (c *Config) Committee() []int { - if len(c.committeeDefault) > 0 { - return c.committeeDefault - } - r := viper.GetIntSlice(c.ShortName + ".committee") + r := viper.GetIntSlice("sc." + c.Alias() + ".committee") if len(r) > 0 { return r } @@ -55,14 +52,11 @@ func (c *Config) Committee() []int { } func (c *Config) SetQuorum(n uint16) { - config.Set(c.ShortName+".quorum", int(n)) + config.Set("sc."+c.Alias()+".quorum", int(n)) } func (c *Config) Quorum() uint16 { - if c.quorumDefault != 0 { - return uint16(c.quorumDefault) - } - q := viper.GetInt(c.ShortName + ".quorum") + q := viper.GetInt("sc." + c.Alias() + ".quorum") if q != 0 { return uint16(q) } @@ -78,7 +72,7 @@ func (c *Config) HandleSetCmd(args []string) { c.PrintUsage("set ") os.Exit(1) } - config.Set(c.ShortName+"."+args[0], args[1]) + config.Set("sc."+c.Alias()+"."+args[0], args[1]) } func (c *Config) usage(commands map[string]func([]string)) { @@ -88,7 +82,6 @@ func (c *Config) usage(commands map[string]func([]string)) { } c.PrintUsage(fmt.Sprintf("[options] [%s]", strings.Join(cmdNames, "|"))) - c.Flags.PrintDefaults() os.Exit(1) } @@ -104,15 +97,15 @@ func (c *Config) HandleCmd(args []string, commands map[string]func([]string)) { } func (c *Config) SetAddress(address string) { - config.SetSCAddress(c.ShortName, address) + config.SetSCAddress(c.Alias(), address) } func (c *Config) Address() *address.Address { - return config.GetSCAddress(c.ShortName) + return config.GetSCAddress(c.Alias()) } func (c *Config) IsAvailable() bool { - return config.TrySCAddress(c.ShortName) != nil + return config.TrySCAddress(c.Alias()) != nil } func (c *Config) Deploy(sigScheme signaturescheme.SignatureScheme) error { @@ -168,6 +161,16 @@ func Deploy(params *DeployParams) (*address.Address, error) { } fmt.Printf("Initialized %s smart contract\n", params.Description) fmt.Printf("SC Address: %s\n", scAddress) + + if config.SCAlias != "" { + c := Config{ + ProgramHash: params.ProgramHash, + } + c.SetAddress(scAddress.String()) + c.SetCommittee(params.Committee) + c.SetQuorum(params.Quorum) + } + return scAddress, nil } @@ -179,16 +182,15 @@ func (p *DeployParams) progHash() hashing.HashValue { return hash } -func LoadBootupData(cfg *Config) { - if cfg.bootupDataLoaded { - return +func (c *Config) BootupData() *registry.BootupData { + if c.bootupData != nil { + return c.bootupData } - d, exists, err := waspapi.GetSCData(config.WaspApi(), cfg.Address()) + d, exists, err := waspapi.GetSCData(config.WaspApi(), c.Address()) if err != nil || !exists { - //fmt.Printf("++++++++++ GetSCData host = %s, addr = %s exists = %v err = %v\n", - // config.WaspApi(), cfg.Address(), exists, err) - return + panic(fmt.Sprintf("GetSCData host = %s, addr = %s exists = %v err = %v\n", + config.WaspApi(), c.Address(), exists, err)) } - cfg.BootupData = *d - cfg.bootupDataLoaded = true + c.bootupData = d + return c.bootupData } diff --git a/tools/wwallet/sc/dwf/config.go b/tools/wwallet/sc/dwf/config.go index b2d6878aac..cc1c80dd54 100644 --- a/tools/wwallet/sc/dwf/config.go +++ b/tools/wwallet/sc/dwf/config.go @@ -6,14 +6,12 @@ import ( "github.com/iotaledger/wasp/tools/wwallet/config" "github.com/iotaledger/wasp/tools/wwallet/sc" "github.com/iotaledger/wasp/tools/wwallet/wallet" - "github.com/spf13/pflag" ) var Config = &sc.Config{ ShortName: "dwf", Name: "DonateWithFeedback", ProgramHash: dwfimpl.ProgramHash, - Flags: pflag.NewFlagSet("DonateWithFeedback", pflag.ExitOnError), } func Client() *dwfclient.DWFClient { diff --git a/tools/wwallet/sc/dwf/dwfcmd/cmd.go b/tools/wwallet/sc/dwf/dwfcmd/cmd.go index 208533add6..2394c49e9c 100644 --- a/tools/wwallet/sc/dwf/dwfcmd/cmd.go +++ b/tools/wwallet/sc/dwf/dwfcmd/cmd.go @@ -5,12 +5,10 @@ import ( "os" "github.com/iotaledger/wasp/tools/wwallet/sc/dwf" - "github.com/spf13/pflag" ) -func InitCommands(commands map[string]func([]string), flags *pflag.FlagSet) { +func InitCommands(commands map[string]func([]string)) { commands["dwf"] = cmd - flags.AddFlagSet(dwf.Config.HookFlags()) } var subcmds = map[string]func([]string){ diff --git a/tools/wwallet/sc/dwf/dwfdashboard/dwfdashboard.go b/tools/wwallet/sc/dwf/dwfdashboard/dwfdashboard.go index 63a1e10302..1b1c745ec3 100644 --- a/tools/wwallet/sc/dwf/dwfdashboard/dwfdashboard.go +++ b/tools/wwallet/sc/dwf/dwfdashboard/dwfdashboard.go @@ -68,7 +68,7 @@ const tplDwf = `

Log (latest first)

{{range $i, $di := .Status.LastRecordsDesc}}
- {{$di.Seq}}: {{$di.Feedback}} + {{$di.Seq}}: {{trim $di.Feedback}}

Sender: {{template "address" $di.Sender}}

Amount: {{$di.Amount}} IOTAs

When: {{formatTimestamp $di.When}}

diff --git a/tools/wwallet/sc/fa/config.go b/tools/wwallet/sc/fa/config.go index 8741ba2fdc..01e703133a 100644 --- a/tools/wwallet/sc/fa/config.go +++ b/tools/wwallet/sc/fa/config.go @@ -6,14 +6,12 @@ import ( "github.com/iotaledger/wasp/tools/wwallet/config" "github.com/iotaledger/wasp/tools/wwallet/sc" "github.com/iotaledger/wasp/tools/wwallet/wallet" - "github.com/spf13/pflag" ) var Config = &sc.Config{ ShortName: "fa", Name: "FairAuction", ProgramHash: fairauction.ProgramHash, - Flags: pflag.NewFlagSet("FairAuction", pflag.ExitOnError), } func Client() *faclient.FairAuctionClient { diff --git a/tools/wwallet/sc/fa/facmd/cmd.go b/tools/wwallet/sc/fa/facmd/cmd.go index 275249c19b..5ada4732f7 100644 --- a/tools/wwallet/sc/fa/facmd/cmd.go +++ b/tools/wwallet/sc/fa/facmd/cmd.go @@ -5,12 +5,10 @@ import ( "os" "github.com/iotaledger/wasp/tools/wwallet/sc/fa" - "github.com/spf13/pflag" ) -func InitCommands(commands map[string]func([]string), flags *pflag.FlagSet) { +func InitCommands(commands map[string]func([]string)) { commands["fa"] = cmd - flags.AddFlagSet(fa.Config.HookFlags()) } var subcmds = map[string]func([]string){ diff --git a/tools/wwallet/sc/fa/fadashboard/fadashboard.go b/tools/wwallet/sc/fa/fadashboard/fadashboard.go index 943c6f68fe..0f20df0d01 100644 --- a/tools/wwallet/sc/fa/fadashboard/fadashboard.go +++ b/tools/wwallet/sc/fa/fadashboard/fadashboard.go @@ -63,7 +63,7 @@ const tplFairAuction = `
{{range $color, $auction := .Status.Auctions}}
- {{$auction.Description}} + {{trim $auction.Description}}

For sale: {{$auction.NumTokens}} tokens of color {{$color}}

Owner: {{template "address" $auction.AuctionOwner}}

Started at: {{formatTimestamp $auction.WhenStarted}}

diff --git a/tools/wwallet/sc/fr/config.go b/tools/wwallet/sc/fr/config.go index 9dcc97b47a..c3256f3e30 100644 --- a/tools/wwallet/sc/fr/config.go +++ b/tools/wwallet/sc/fr/config.go @@ -6,14 +6,12 @@ import ( "github.com/iotaledger/wasp/tools/wwallet/config" "github.com/iotaledger/wasp/tools/wwallet/sc" "github.com/iotaledger/wasp/tools/wwallet/wallet" - "github.com/spf13/pflag" ) var Config = &sc.Config{ ShortName: "fr", Name: "FairRoulette", ProgramHash: fairroulette.ProgramHash, - Flags: pflag.NewFlagSet("fairroulette", pflag.ExitOnError), } func Client() *frclient.FairRouletteClient { diff --git a/tools/wwallet/sc/fr/frcmd/cmd.go b/tools/wwallet/sc/fr/frcmd/cmd.go index 69fa11cb69..9bd6acb462 100644 --- a/tools/wwallet/sc/fr/frcmd/cmd.go +++ b/tools/wwallet/sc/fr/frcmd/cmd.go @@ -2,12 +2,10 @@ package frcmd import ( "github.com/iotaledger/wasp/tools/wwallet/sc/fr" - "github.com/spf13/pflag" ) -func InitCommands(commands map[string]func([]string), flags *pflag.FlagSet) { +func InitCommands(commands map[string]func([]string)) { commands["fr"] = cmd - flags.AddFlagSet(fr.Config.HookFlags()) } var subcmds = map[string]func([]string){ diff --git a/tools/wwallet/sc/sccmd/deploy.go b/tools/wwallet/sc/sccmd/deploy.go index 05d086236d..887c13205c 100644 --- a/tools/wwallet/sc/sccmd/deploy.go +++ b/tools/wwallet/sc/sccmd/deploy.go @@ -40,6 +40,6 @@ func deployUsage() { fmt.Printf(" %s set %s '%s'\n", os.Args[0], config.CommitteePeeringConfigVar(i), config.CommitteePeering(sc.DefaultCommittee)[i]) fmt.Printf(" %s set %s '%s'\n", os.Args[0], config.CommitteeNanomsgConfigVar(i), config.CommitteeNanomsg(sc.DefaultCommittee)[i]) } - fmt.Printf(" %s sc deploy '0,1,2,3' 3 'FNT6snmmEM28duSg7cQomafbJ5fs596wtuNRn18wfaAz' 'FairRoulette'\n", os.Args[0]) + fmt.Printf(" %s --sc=fr sc deploy '0,1,2,3' 3 'FNT6snmmEM28duSg7cQomafbJ5fs596wtuNRn18wfaAz' 'FairRoulette'\n", os.Args[0]) os.Exit(1) } diff --git a/tools/wwallet/sc/tr/config.go b/tools/wwallet/sc/tr/config.go index 9a1e28f7dd..69438ea7e3 100644 --- a/tools/wwallet/sc/tr/config.go +++ b/tools/wwallet/sc/tr/config.go @@ -6,14 +6,12 @@ import ( "github.com/iotaledger/wasp/tools/wwallet/config" "github.com/iotaledger/wasp/tools/wwallet/sc" "github.com/iotaledger/wasp/tools/wwallet/wallet" - "github.com/spf13/pflag" ) var Config = &sc.Config{ ShortName: "tr", Name: "TokenRegistry", ProgramHash: tokenregistry.ProgramHash, - Flags: pflag.NewFlagSet("tokenregistry", pflag.ExitOnError), } func Client() *trclient.TokenRegistryClient { diff --git a/tools/wwallet/sc/tr/trcmd/cmd.go b/tools/wwallet/sc/tr/trcmd/cmd.go index dec78edba7..670f0cf7e4 100644 --- a/tools/wwallet/sc/tr/trcmd/cmd.go +++ b/tools/wwallet/sc/tr/trcmd/cmd.go @@ -5,12 +5,10 @@ import ( "os" "github.com/iotaledger/wasp/tools/wwallet/sc/tr" - "github.com/spf13/pflag" ) -func InitCommands(commands map[string]func([]string), flags *pflag.FlagSet) { +func InitCommands(commands map[string]func([]string)) { commands["tr"] = cmd - flags.AddFlagSet(tr.Config.HookFlags()) } var subcmds = map[string]func([]string){ diff --git a/tools/wwallet/sc/tr/trdashboard/trdashboard.go b/tools/wwallet/sc/tr/trdashboard/trdashboard.go index 81a0f6a470..32cdc71531 100644 --- a/tools/wwallet/sc/tr/trdashboard/trdashboard.go +++ b/tools/wwallet/sc/tr/trdashboard/trdashboard.go @@ -99,7 +99,7 @@ const tplTokenRegistry = `
{{range $color, $tm := .Status.Registry}}
- {{$tm.Description}} + {{trim $tm.Description}}

Color: {{$color}}

{{template "tmdetails" $tm}}
@@ -114,7 +114,7 @@ const tplTokenRegistry = ` {{if .Color}} {{if .QueryResult}} -

{{.QueryResult.Description}}

+

{{trim .QueryResult.Description}}

Color: {{.Color}}

{{template "tmdetails" .QueryResult}} {{else}}