From 5db3335dce766bd679c54ea44f6df08a7ff74762 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Dec 2013 12:45:52 +0100 Subject: Initial commit --- .ethereum.go.un~ | Bin 0 -> 341697 bytes big.go | 20 ++++++ block.go | 21 ++++++ block_manager.go | 55 +++++++++++++++ ethereum.go | 33 +++++++++ parsing.go | 113 +++++++++++++++++++++++++++++++ parsing_test.go | 42 ++++++++++++ serialization.go | 57 ++++++++++++++++ serialization_test.go | 20 ++++++ transaction.go | 126 ++++++++++++++++++++++++++++++++++ vm.go | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 669 insertions(+) create mode 100644 .ethereum.go.un~ create mode 100644 big.go create mode 100644 block.go create mode 100644 block_manager.go create mode 100644 ethereum.go create mode 100644 parsing.go create mode 100644 parsing_test.go create mode 100644 serialization.go create mode 100644 serialization_test.go create mode 100644 transaction.go create mode 100644 vm.go diff --git a/.ethereum.go.un~ b/.ethereum.go.un~ new file mode 100644 index 000000000..42e42bcb6 Binary files /dev/null and b/.ethereum.go.un~ differ diff --git a/big.go b/big.go new file mode 100644 index 000000000..71e7573e1 --- /dev/null +++ b/big.go @@ -0,0 +1,20 @@ +package main + +import ( + "math/big" +) + +func BigPow(a,b int) *big.Int { + c := new(big.Int) + c.Exp(big.NewInt(int64(a)), big.NewInt(int64(b)), big.NewInt(0)) + + return c +} + +func Big(num string) *big.Int { + n := new(big.Int) + n.SetString(num, 0) + + return n +} + diff --git a/block.go b/block.go new file mode 100644 index 000000000..a0bcf0bd0 --- /dev/null +++ b/block.go @@ -0,0 +1,21 @@ +package main + +import ( + _"fmt" +) + +type Block struct { + transactions []*Transaction +} + +func NewBlock(/* TODO use raw data */transactions []*Transaction) *Block { + block := &Block{ + // Slice of transactions to include in this block + transactions: transactions, + } + + return block +} + +func (block *Block) Update() { +} diff --git a/block_manager.go b/block_manager.go new file mode 100644 index 000000000..4f0ec1cdb --- /dev/null +++ b/block_manager.go @@ -0,0 +1,55 @@ + + // Blocks, blocks will have transactions. + // Transactions/contracts are updated in goroutines + // Each contract should send a message on a channel with usage statistics + // The statics can be used for fee calculation within the block update method + // Statistics{transaction, /* integers */ normal_ops, store_load, extro_balance, crypto, steps} + // The block updater will wait for all goroutines to be finished and update the block accordingly + // in one go and should use minimal IO overhead. + // The actual block updating will happen within a goroutine as well so normal operation may continue + +package main + +import ( + _"fmt" +) + +type BlockManager struct { + vm *Vm +} + +func NewBlockManager() *BlockManager { + bm := &BlockManager{vm: NewVm()} + + return bm +} + +// Process a block. +func (bm *BlockManager) ProcessBlock(block *Block) error { + txCount := len(block.transactions) + lockChan := make(chan bool, txCount) + + for _, tx := range block.transactions { + go bm.ProcessTransaction(tx, lockChan) + } + + // Wait for all Tx to finish processing + for i := 0; i < txCount; i++ { + <- lockChan + } + + return nil +} + +func (bm *BlockManager) ProcessTransaction(tx *Transaction, lockChan chan bool) { + if tx.recipient == 0x0 { + bm.vm.RunTransaction(tx, func(opType OpType) bool { + // TODO calculate fees + + return true // Continue + }) + } + + // Broadcast we're done + lockChan <- true +} diff --git a/ethereum.go b/ethereum.go new file mode 100644 index 000000000..530cbd5c1 --- /dev/null +++ b/ethereum.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" +) + +func main() { + InitFees() + + bm := NewBlockManager() + + tx := NewTransaction(0x0, 20, []string{ + "SET 10 6", + "LD 10 10", + "LT 10 1 20", + "SET 255 7", + "JMPI 20 255", + "STOP", + "SET 30 200", + "LD 30 31", + "SET 255 22", + "JMPI 31 255", + "SET 255 15", + "JMP 255", + }) + tx2 := NewTransaction(0x0, 20, []string{"SET 10 6", "LD 10 10"}) + + blck := NewBlock([]*Transaction{tx2, tx}) + + bm.ProcessBlock( blck ) + + fmt.Printf("rlp encoded Tx %q\n", tx.Serialize()) +} diff --git a/parsing.go b/parsing.go new file mode 100644 index 000000000..d5dcce04f --- /dev/null +++ b/parsing.go @@ -0,0 +1,113 @@ +package main + +import ( + "fmt" + "strings" + "errors" + "math/big" + "strconv" +) + +// Op codes +var OpCodes = map[string]string{ + "STOP": "0", + "ADD": "16", // 0x10 + "SUB": "17", // 0x11 + "MUL": "18", // 0x12 + "DIV": "19", // 0x13 + "SDIV": "20", // 0x14 + "MOD": "21", // 0x15 + "SMOD": "22", // 0x16 + "EXP": "23", // 0x17 + "NEG": "24", // 0x18 + "LT": "32", // 0x20 + "LE": "33", // 0x21 + "GT": "34", // 0x22 + "GE": "35", // 0x23 + "EQ": "36", // 0x24 + "NOT": "37", // 0x25 + "SHA256": "48", // 0x30 + "RIPEMD160": "49", // 0x31 + "ECMUL": "50", // 0x32 + "ECADD": "51", // 0x33 + "SIGN": "52", // 0x34 + "RECOVER": "53", // 0x35 + "COPY": "64", // 0x40 + "ST": "65", // 0x41 + "LD": "66", // 0x42 + "SET": "67", // 0x43 + "JMP": "80", // 0x50 + "JMPI": "81", // 0x51 + "IND": "82", // 0x52 + "EXTRO": "96", // 0x60 + "BALANCE": "97", // 0x61 + "MKTX": "112", // 0x70 + "DATA": "128", // 0x80 + "DATAN": "129", // 0x81 + "MYADDRESS": "144", // 0x90 + "BLKHASH": "145", // 0x91 + "COINBASE": "146", // 0x92 + "SUICIDE": "255", // 0xff +} + + +func CompileInstr(s string) (string, error) { + tokens := strings.Split(s, " ") + if OpCodes[tokens[0]] == "" { + return "", errors.New(fmt.Sprintf("OP not found: %s", tokens[0])) + } + + code := OpCodes[tokens[0]] // Replace op codes with the proper numerical equivalent + op := new(big.Int) + op.SetString(code, 0) + + args := make([]*big.Int, 6) + for i, val := range tokens[1:len(tokens)] { + num := new(big.Int) + num.SetString(val, 0) + args[i] = num + } + + // Big int equation = op + x * 256 + y * 256**2 + z * 256**3 + a * 256**4 + b * 256**5 + c * 256**6 + base := new(big.Int) + x := new(big.Int) + y := new(big.Int) + z := new(big.Int) + a := new(big.Int) + b := new(big.Int) + c := new(big.Int) + + if args[0] != nil { x.Mul(args[0], big.NewInt(256)) } + if args[1] != nil { y.Mul(args[1], BigPow(256, 2)) } + if args[2] != nil { z.Mul(args[2], BigPow(256, 3)) } + if args[3] != nil { a.Mul(args[3], BigPow(256, 4)) } + if args[4] != nil { b.Mul(args[4], BigPow(256, 5)) } + if args[5] != nil { c.Mul(args[5], BigPow(256, 6)) } + + base.Add(op, x) + base.Add(base, y) + base.Add(base, z) + base.Add(base, a) + base.Add(base, b) + base.Add(base, c) + + return base.String(), nil +} + +func Instr(instr string) (int, []string, error) { + base := new(big.Int) + base.SetString(instr, 0) + + args := make([]string, 7) + for i := 0; i < 7; i++ { + // int(int(val) / int(math.Pow(256,float64(i)))) % 256 + exp := BigPow(256, i) + num := new(big.Int) + num.Div(base, exp) + + args[i] = num.Mod(num, big.NewInt(256)).String() + } + op, _ := strconv.Atoi(args[0]) + + return op, args[1:7], nil +} diff --git a/parsing_test.go b/parsing_test.go new file mode 100644 index 000000000..93fe434b9 --- /dev/null +++ b/parsing_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "testing" + "math" +) + +func TestCompile(t *testing.T) { + instr, err := CompileInstr("SET 10 1") + + if err != nil { + t.Error("Failed compiling instruction") + } + + calc := (67 + 10 * 256 + 1 * int64(math.Pow(256,2))) + if Big(instr).Int64() != calc { + t.Error("Expected", calc, ", got:", instr) + } +} + +func TestValidInstr(t *testing.T) { + op, args, err := Instr("68163") + if err != nil { + t.Error("Error decoding instruction") + } + + if op != oSET { + t.Error("Expected op to be 43, got:", op) + } + + if args[0] != "10" { + t.Error("Expect args[0] to be 10, got:", args[0]) + } + + if args[1] != "1" { + t.Error("Expected args[1] to be 1, got:", args[1]) + } +} + +func TestInvalidInstr(t *testing.T) { +} + diff --git a/serialization.go b/serialization.go new file mode 100644 index 000000000..7e2e96beb --- /dev/null +++ b/serialization.go @@ -0,0 +1,57 @@ +package main + +import ( + "math" + "bytes" +) + +func ToBinary(x int, bytes int) string { + if bytes == 0 { + return "" + } else { + return ToBinary(int(x / 256), bytes - 1) + string(x % 256) + } +} + +func NumToVarInt(x int) string { + if x < 253 { + return string(x) + } else if x < int(math.Pow(2,16)) { + return string(253) + ToBinary(x, 2) + } else if x < int(math.Pow(2,32)) { + return string(253) + ToBinary(x, 4) + } else { + return string(253) + ToBinary(x, 8) + } +} + +func RlpEncode(object interface{}) string { + if str, ok := object.(string); ok { + return "\x00" + NumToVarInt(len(str)) + str + } else if slice, ok := object.([]interface{}); ok { + var buffer bytes.Buffer + for _, val := range slice { + if v, ok := val.(string); ok { + buffer.WriteString(RlpEncode(v)) + } else { + buffer.WriteString(RlpEncode(val)) + } + } + + return "\x01" + RlpEncode(len(buffer.String())) + buffer.String() + } else if slice, ok := object.([]string); ok { + + // FIXME this isn't dry. Fix this + var buffer bytes.Buffer + for _, val := range slice { + buffer.WriteString(RlpEncode(val)) + } + return "\x01" + RlpEncode(len(buffer.String())) + buffer.String() + } + + return "" +} + +type RlpSerializer interface { +} + diff --git a/serialization_test.go b/serialization_test.go new file mode 100644 index 000000000..adb1b6e2d --- /dev/null +++ b/serialization_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "testing" + "fmt" +) + +func TestRlpEncode(t *testing.T) { + strRes := "\x00\x03dog" + str := RlpEncode("dog") + if str != strRes { + t.Error(fmt.Sprintf("Expected %q, got %q", strRes, str)) + } + + sliceRes := "\x01\x00\x03dog\x00\x03god\x00\x03cat" + slice := RlpEncode([]string{"dog", "god", "cat"}) + if slice != sliceRes { + t.Error(fmt.Sprintf("Expected %q, got %q", sliceRes, slice)) + } +} diff --git a/transaction.go b/transaction.go new file mode 100644 index 000000000..f78715d8c --- /dev/null +++ b/transaction.go @@ -0,0 +1,126 @@ +package main + +import ( + "math/big" + "fmt" + "encoding/hex" + "crypto/sha256" + _ "bytes" + "strconv" +) + +/* +Transaction Contract Size +------------------------------------------- +sender sender 20 bytes +recipient 0x0 20 bytes +value endowment 4 bytes (uint32) +fee fee 4 bytes (uint32) +d_size o_size 4 bytes (uint32) +data ops * +signature signature 64 bytes +*/ + +var StepFee *big.Int = new(big.Int) +var TxFee *big.Int = new(big.Int) +var MemFee *big.Int = new(big.Int) +var DataFee *big.Int = new(big.Int) +var CryptoFee *big.Int = new(big.Int) +var ExtroFee *big.Int = new(big.Int) + +var Period1Reward *big.Int = new(big.Int) +var Period2Reward *big.Int = new(big.Int) +var Period3Reward *big.Int = new(big.Int) +var Period4Reward *big.Int = new(big.Int) + +type Transaction struct { + sender string + recipient uint32 + value uint32 + fee uint32 + data []string + memory []int + signature string + + addr string +} + +func NewTransaction(to uint32, value uint32, data []string) *Transaction { + tx := Transaction{sender: "1234567890", recipient: to, value: value} + tx.fee = 0//uint32((ContractFee + MemoryFee * float32(len(tx.data))) * 1e8) + + // Serialize the data + tx.data = make([]string, len(data)) + for i, val := range data { + instr, err := CompileInstr(val) + if err != nil { + fmt.Printf("compile error:%d %v", i+1, err) + } + + tx.data[i] = instr + } + + b:= []byte(tx.Serialize()) + hash := sha256.Sum256(b) + tx.addr = hex.EncodeToString(hash[0:19]) + + return &tx +} + +func Uitoa(i uint32) string { + return strconv.FormatUint(uint64(i), 10) +} + +func (tx *Transaction) Serialize() string { + // Prepare the transaction for serialization + preEnc := []interface{}{ + "0", // TODO last Tx + tx.sender, + // XXX In the future there's no need to cast to string because they'll end up being big numbers (strings) + Uitoa(tx.recipient), + Uitoa(tx.value), + Uitoa(tx.fee), + tx.data, + } + + return RlpEncode(preEnc) +} + +func InitFees() { + // Base for 2**60 + b60 := new(big.Int) + b60.Exp(big.NewInt(2), big.NewInt(60), big.NewInt(0)) + // Base for 2**80 + b80 := new(big.Int) + b80.Exp(big.NewInt(2), big.NewInt(80), big.NewInt(0)) + + StepFee.Mul(b60, big.NewInt(4096)) + //fmt.Println("StepFee:", StepFee) + + TxFee.Mul(b60, big.NewInt(524288)) + //fmt.Println("TxFee:", TxFee) + + MemFee.Mul(b60, big.NewInt(262144)) + //fmt.Println("MemFee:", MemFee) + + DataFee.Mul(b60, big.NewInt(16384)) + //fmt.Println("DataFee:", DataFee) + + CryptoFee.Mul(b60, big.NewInt(65536)) + //fmt.Println("CrytoFee:", CryptoFee) + + ExtroFee.Mul(b60, big.NewInt(65536)) + //fmt.Println("ExtroFee:", ExtroFee) + + Period1Reward.Mul(b80, big.NewInt(1024)) + //fmt.Println("Period1Reward:", Period1Reward) + + Period2Reward.Mul(b80, big.NewInt(512)) + //fmt.Println("Period2Reward:", Period2Reward) + + Period3Reward.Mul(b80, big.NewInt(256)) + //fmt.Println("Period3Reward:", Period3Reward) + + Period4Reward.Mul(b80, big.NewInt(128)) + //fmt.Println("Period4Reward:", Period4Reward) +} diff --git a/vm.go b/vm.go new file mode 100644 index 000000000..353f4f39e --- /dev/null +++ b/vm.go @@ -0,0 +1,182 @@ +package main + +import ( + "math" + "math/big" + "fmt" + "strconv" + _ "encoding/hex" +) + +// Op codes +const ( + oSTOP int = 0x00 + oADD int = 0x10 + oSUB int = 0x11 + oMUL int = 0x12 + oDIV int = 0x13 + oSDIV int = 0x14 + oMOD int = 0x15 + oSMOD int = 0x16 + oEXP int = 0x17 + oNEG int = 0x18 + oLT int = 0x20 + oLE int = 0x21 + oGT int = 0x22 + oGE int = 0x23 + oEQ int = 0x24 + oNOT int = 0x25 + oSHA256 int = 0x30 + oRIPEMD160 int = 0x31 + oECMUL int = 0x32 + oECADD int = 0x33 + oSIGN int = 0x34 + oRECOVER int = 0x35 + oCOPY int = 0x40 + oST int = 0x41 + oLD int = 0x42 + oSET int = 0x43 + oJMP int = 0x50 + oJMPI int = 0x51 + oIND int = 0x52 + oEXTRO int = 0x60 + oBALANCE int = 0x61 + oMKTX int = 0x70 + oDATA int = 0x80 + oDATAN int = 0x81 + oMYADDRESS int = 0x90 + oSUICIDE int = 0xff +) + +type OpType int +const ( + tNorm = iota + tData + tExtro + tCrypto +) +type TxCallback func(opType OpType) bool + +type Vm struct { + // Memory stack + stack map[string]string + // Index ptr + iptr int + memory map[string]map[string]string +} + +func NewVm() *Vm { + fmt.Println("init Ethereum VM") + + stackSize := uint(256) + fmt.Println("stack size =", stackSize) + + return &Vm{make(map[string]string), 0, make(map[string]map[string]string)} +} + +func (vm *Vm) RunTransaction(tx *Transaction, cb TxCallback) { + fmt.Printf(` +# processing Tx (%v) +# fee = %f, ops = %d, sender = %s, value = %d +`, tx.addr, float32(tx.fee) / 1e8, len(tx.data), tx.sender, tx.value) + + vm.stack = make(map[string]string) + vm.stack["0"] = tx.sender + vm.stack["1"] = "100" //int(tx.value) + vm.stack["1"] = "1000" //int(tx.fee) + + //vm.memory[tx.addr] = make([]int, 256) + vm.memory[tx.addr] = make(map[string]string) + + // Define instruction 'accessors' for the instruction, which makes it more readable + // also called register values, shorthanded as Rx/y/z. Memory address are shorthanded as Mx/y/z. + // Instructions are shorthanded as Ix/y/z + x := 0; y := 1; z := 2; //a := 3; b := 4; c := 5 +out: + for vm.iptr < len(tx.data) { + // The base big int for all calculations. Use this for any results. + base := new(big.Int) + // XXX Should Instr return big int slice instead of string slice? + op, args, _ := Instr(tx.data[vm.iptr]) + + fmt.Printf("%-3d %d %v\n", vm.iptr, op, args) + + opType := OpType(tNorm) + // Determine the op type (used for calculating fees by the block manager) + switch op { + case oEXTRO, oBALANCE: + opType = tExtro + case oSHA256, oRIPEMD160, oECMUL, oECADD: // TODO add rest + opType = tCrypto + } + + // If the callback yielded a negative result abort execution + if !cb(opType) { break out } + + nptr := vm.iptr + switch op { + case oSTOP: + fmt.Println("exiting (oSTOP), idx =", nptr) + + break out + case oADD: + // (Rx + Ry) % 2 ** 256 + base.Add(Big(vm.stack[args[ x ]]), Big(vm.stack[args[ y ]])) + base.Mod(base, big.NewInt(int64(math.Pow(2, 256)))) + // Set the result to Rz + vm.stack[args[ z ]] = base.String() + case oSUB: + // (Rx - Ry) % 2 ** 256 + base.Sub(Big(vm.stack[args[ x ]]), Big(vm.stack[args[ y ]])) + base.Mod(base, big.NewInt(int64(math.Pow(2, 256)))) + // Set the result to Rz + vm.stack[args[ z ]] = base.String() + case oMUL: + // (Rx * Ry) % 2 ** 256 + base.Mul(Big(vm.stack[args[ x ]]), Big(vm.stack[args[ y ]])) + base.Mod(base, big.NewInt(int64(math.Pow(2, 256)))) + // Set the result to Rz + vm.stack[args[ z ]] = base.String() + case oDIV: + // floor(Rx / Ry) + base.Div(Big(vm.stack[args[ x ]]), Big(vm.stack[args[ y ]])) + // Set the result to Rz + vm.stack[args[ z ]] = base.String() + case oSET: + // Set the (numeric) value at Iy to Rx + vm.stack[args[ x ]] = args[ y ] + case oLD: + // Load the value at Mx to Ry + vm.stack[args[ y ]] = vm.memory[tx.addr][vm.stack[args[ x ]]] + case oLT: + cmp := Big(vm.stack[args[ x ]]).Cmp( Big(vm.stack[args[ y ]]) ) + // Set the result as "boolean" value to Rz + if cmp < 0 { // a < b + vm.stack[args[ z ]] = "1" + } else { + vm.stack[args[ z ]] = "0" + } + case oJMP: + // Set the instruction pointer to the value at Rx + ptr, _ := strconv.Atoi( vm.stack[args[ x ]] ) + nptr = ptr + case oJMPI: + // Set the instruction pointer to the value at Ry if Rx yields true + if vm.stack[args[ x ]] != "0" { + ptr, _ := strconv.Atoi( vm.stack[args[ y ]] ) + nptr = ptr + } + default: + fmt.Println("Error op", op) + break + } + + if vm.iptr == nptr { + vm.iptr++ + } else { + vm.iptr = nptr + fmt.Println("... JMP", nptr, "...") + } + } + fmt.Println("# finished processing Tx\n") +} -- cgit v1.2.3 From f77424d3fe89d6ece9bc5d24f784c648306d611a Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Dec 2013 12:46:02 +0100 Subject: Initial commit --- .ethereum.go.un~ | Bin 341697 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .ethereum.go.un~ diff --git a/.ethereum.go.un~ b/.ethereum.go.un~ deleted file mode 100644 index 42e42bcb6..000000000 Binary files a/.ethereum.go.un~ and /dev/null differ -- cgit v1.2.3 From f201547731aca98ae24a62b816a9957927481a2f Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Dec 2013 12:47:06 +0100 Subject: added git ignore --- .gitignore | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..f725d58d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile ~/.gitignore_global + +/tmp +*/**/*un~ +*un~ +.DS_Store +*/**/.DS_Store + -- cgit v1.2.3 From fe5577f59e0b5b254013263675e7a0989e7cd82a Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Dec 2013 13:29:45 +0100 Subject: Added readme --- README | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 000000000..20e704bf8 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +This is the Go implementation of the Ethereum protocol. It's for from being finished. + +The Python reference implementation can be found at https://github.com/vbuterin/pyethereum + +More information about the protocol: +* http://vitalik.ca/ethereum.html +* http://vitalik.ca/ethereum/spec.html +* http://vitalik.ca/ethereum/patricia.html +* http://vitalik.ca/ethereum/dagger.html +* http://vitalik.ca/ethereum/rlp.html + +# TODO + +Fix this todo file! -- cgit v1.2.3 From 486710c17c71f456e48b40052b1bd392d58e01f3 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Dec 2013 13:30:42 +0100 Subject: for => far --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 20e704bf8..ad6cb58e6 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -This is the Go implementation of the Ethereum protocol. It's for from being finished. +This is the Go implementation of the Ethereum protocol. It's far from being finished. The Python reference implementation can be found at https://github.com/vbuterin/pyethereum -- cgit v1.2.3 From b71e632b467d27710961fce21dcd4453d37c818a Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Dec 2013 13:35:04 +0100 Subject: go get --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index ad6cb58e6..3bba4210f 100644 --- a/README +++ b/README @@ -1,5 +1,7 @@ This is the Go implementation of the Ethereum protocol. It's far from being finished. +`go get https://github.com/obscuren/ethereum` + The Python reference implementation can be found at https://github.com/vbuterin/pyethereum More information about the protocol: -- cgit v1.2.3 From aacfdc7a47507e69c48756221a0ba67459e47553 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Dec 2013 14:07:52 +0100 Subject: moved' --- serialization.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/serialization.go b/serialization.go index 7e2e96beb..613dcdcc5 100644 --- a/serialization.go +++ b/serialization.go @@ -53,5 +53,7 @@ func RlpEncode(object interface{}) string { } type RlpSerializer interface { + MarshalRls() []byte + UnmarshalRls([]byte) } -- cgit v1.2.3 From 2368459814d7dea9706d8f0a914ff4575427ef67 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 26 Dec 2013 14:09:50 +0100 Subject: updated readme --- README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index 3bba4210f..9d08d7f8e 100644 --- a/README +++ b/README @@ -1,8 +1,8 @@ This is the Go implementation of the Ethereum protocol. It's far from being finished. -`go get https://github.com/obscuren/ethereum` + go get https://github.com/ethereum/go-ethereum -The Python reference implementation can be found at https://github.com/vbuterin/pyethereum +The Python reference implementation can be found at https://github.com/ethereum/pyethereum More information about the protocol: * http://vitalik.ca/ethereum.html -- cgit v1.2.3 From ec13db873aa43368bfb155dad03ef537e4635717 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 11:59:00 +0100 Subject: Added travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..69359072d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - 1.2 -- cgit v1.2.3 From b40013ac30006fe5a43abdca574b438cf60db86e Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 12:07:37 +0100 Subject: Updated fees --- transaction.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/transaction.go b/transaction.go index f78715d8c..26d7df258 100644 --- a/transaction.go +++ b/transaction.go @@ -21,12 +21,13 @@ data ops * signature signature 64 bytes */ -var StepFee *big.Int = new(big.Int) -var TxFee *big.Int = new(big.Int) -var MemFee *big.Int = new(big.Int) -var DataFee *big.Int = new(big.Int) -var CryptoFee *big.Int = new(big.Int) -var ExtroFee *big.Int = new(big.Int) +var StepFee *big.Int = new(big.Int) +var TxFee *big.Int = new(big.Int) +var ContractFee *big.Int = new(big.Int) +var MemFee *big.Int = new(big.Int) +var DataFee *big.Int = new(big.Int) +var CryptoFee *big.Int = new(big.Int) +var ExtroFee *big.Int = new(big.Int) var Period1Reward *big.Int = new(big.Int) var Period2Reward *big.Int = new(big.Int) @@ -89,27 +90,30 @@ func (tx *Transaction) Serialize() string { func InitFees() { // Base for 2**60 b60 := new(big.Int) - b60.Exp(big.NewInt(2), big.NewInt(60), big.NewInt(0)) + b60.Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) // Base for 2**80 b80 := new(big.Int) b80.Exp(big.NewInt(2), big.NewInt(80), big.NewInt(0)) - StepFee.Mul(b60, big.NewInt(4096)) + StepFee.Div(b60, big.NewInt(64)) //fmt.Println("StepFee:", StepFee) - TxFee.Mul(b60, big.NewInt(524288)) + TxFee.Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) //fmt.Println("TxFee:", TxFee) - MemFee.Mul(b60, big.NewInt(262144)) + ContractFee.Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) + //fmt.Println("ContractFee:", ContractFee) + + MemFee.Div(b60, big.NewInt(4)) //fmt.Println("MemFee:", MemFee) - DataFee.Mul(b60, big.NewInt(16384)) + DataFee.Div(b60, big.NewInt(16)) //fmt.Println("DataFee:", DataFee) - CryptoFee.Mul(b60, big.NewInt(65536)) + CryptoFee.Div(b60, big.NewInt(16)) //fmt.Println("CrytoFee:", CryptoFee) - ExtroFee.Mul(b60, big.NewInt(65536)) + ExtroFee.Div(b60, big.NewInt(16)) //fmt.Println("ExtroFee:", ExtroFee) Period1Reward.Mul(b80, big.NewInt(1024)) -- cgit v1.2.3 From d5d2efbaf3c3a84f0084058eacd535d077c363e1 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 21:20:47 +0100 Subject: WIP new rlp implementation --- rlp.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 rlp.go diff --git a/rlp.go b/rlp.go new file mode 100644 index 000000000..2c722cf7a --- /dev/null +++ b/rlp.go @@ -0,0 +1,52 @@ +package main + +import ( + _"fmt" + "bytes" + "math" +) + +func EncodeSlice(slice []interface{}) []byte { + var buff bytes.Buffer + + for _, val := range slice { + switch t := val.(type) { + case []interface{}, []string: + buff.Write(Encode(t)) + } + } + + return buff.Bytes() +} + +func Encode(object interface{}) []byte { + var buff bytes.Buffer + + switch t := object.(type) { + case string: + if len(t) < 56 { + buff.WriteString(string(len(t) + 64) + t) + } else { + + } + case uint32, uint64: + var num uint64 + if _num, ok := t.(uint64); ok { + num = _num + } else if _num, ok := t.(uint32); ok { + num = uint64(_num) + } + + if num >= 0 && num < 24 { + buff.WriteString(string(num)) + } else if num <= uint64(math.Pow(2, 256)) { + } + case []byte: + // Cast the byte slice to a string + buff.Write(Encode(string(t))) + case []interface{}: + buff.Write(EncodeSlice(t)) + } + + return buff.Bytes() +} -- cgit v1.2.3 From d8c0b0c89906edada0a30d144ebeb116388b45b1 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 21:21:08 +0100 Subject: Moved string util --- util.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 util.go diff --git a/util.go b/util.go new file mode 100644 index 000000000..fc06673d2 --- /dev/null +++ b/util.go @@ -0,0 +1,23 @@ +package main + +import ( + "strconv" + "crypto/sha256" + "encoding/hex" +) + +func Uitoa(i uint32) string { + return strconv.FormatUint(uint64(i), 10) +} + +func Sha256Hex(data []byte) string { + hash := sha256.Sum256(data) + + return hex.EncodeToString(hash[:]) +} + +func Sha256Bin(data []byte) []byte { + hash := sha256.Sum256(data) + + return hash[:] +} -- cgit v1.2.3 From 8301d154ae4bb6082d588bd42c6e913be63618ef Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 21:22:57 +0100 Subject: Serializing block --- block.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/block.go b/block.go index a0bcf0bd0..f8bf2ce3b 100644 --- a/block.go +++ b/block.go @@ -2,16 +2,29 @@ package main import ( _"fmt" + "time" ) type Block struct { - transactions []*Transaction + RlpSerializer + + number uint32 + prevHash string + uncles []*Block + coinbase string + // state xxx + difficulty int + time time.Time + nonce int + transactions []*Transaction } func NewBlock(/* TODO use raw data */transactions []*Transaction) *Block { block := &Block{ // Slice of transactions to include in this block transactions: transactions, + + time: time.Now(), } return block @@ -19,3 +32,33 @@ func NewBlock(/* TODO use raw data */transactions []*Transaction) *Block { func (block *Block) Update() { } + +func (block *Block) Hash() string { + return Sha256Hex(block.MarshalRlp()) +} + +func (block *Block) MarshalRlp() []byte { + // Encoding method requires []interface{} type. It's actual a slice of strings + encTx := make([]string, len(block.transactions)) + for i, tx := range block.transactions { + encTx[i] = string(tx.MarshalRlp()) + } + + enc := RlpEncode([]interface{}{ + block.number, + block.prevHash, + // Sha of uncles + block.coinbase, + // root state + Sha256Bin([]byte(RlpEncode(encTx))), + block.difficulty, + block.time, + block.nonce, + // extra? + }) + + return []byte(enc) +} + +func (block *Block) UnmarshalRlp(data []byte) { +} -- cgit v1.2.3 From dca9ee79b3a27ba2cc5d026d445ae627a53617b0 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 21:23:40 +0100 Subject: Changed Tx serialization to return bytes instead of a string --- serialization.go | 8 ++++++-- transaction.go | 16 +++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/serialization.go b/serialization.go index 613dcdcc5..5a92a434f 100644 --- a/serialization.go +++ b/serialization.go @@ -28,6 +28,10 @@ func NumToVarInt(x int) string { func RlpEncode(object interface{}) string { if str, ok := object.(string); ok { return "\x00" + NumToVarInt(len(str)) + str + } else if num, ok := object.(uint32); ok { + return RlpEncode(Uitoa(num)) + } else if byt, ok := object.([]byte); ok { + return RlpEncode(string(byt)) } else if slice, ok := object.([]interface{}); ok { var buffer bytes.Buffer for _, val := range slice { @@ -53,7 +57,7 @@ func RlpEncode(object interface{}) string { } type RlpSerializer interface { - MarshalRls() []byte - UnmarshalRls([]byte) + MarshalRlp() []byte + UnmarshalRlp([]byte) } diff --git a/transaction.go b/transaction.go index 26d7df258..5a268b566 100644 --- a/transaction.go +++ b/transaction.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "crypto/sha256" _ "bytes" - "strconv" ) /* @@ -35,14 +34,17 @@ var Period3Reward *big.Int = new(big.Int) var Period4Reward *big.Int = new(big.Int) type Transaction struct { + RlpSerializer + sender string recipient uint32 value uint32 fee uint32 data []string memory []int - signature string + // To be removed + signature string addr string } @@ -61,18 +63,14 @@ func NewTransaction(to uint32, value uint32, data []string) *Transaction { tx.data[i] = instr } - b:= []byte(tx.Serialize()) + b:= []byte(tx.MarshalRlp()) hash := sha256.Sum256(b) tx.addr = hex.EncodeToString(hash[0:19]) return &tx } -func Uitoa(i uint32) string { - return strconv.FormatUint(uint64(i), 10) -} - -func (tx *Transaction) Serialize() string { +func (tx *Transaction) MarshalRlp() []byte { // Prepare the transaction for serialization preEnc := []interface{}{ "0", // TODO last Tx @@ -84,7 +82,7 @@ func (tx *Transaction) Serialize() string { tx.data, } - return RlpEncode(preEnc) + return []byte(RlpEncode(preEnc)) } func InitFees() { -- cgit v1.2.3 From df0fe67fce8050dad60f0d0fca28a78da818832f Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 21:24:06 +0100 Subject: Testing --- ethereum.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ethereum.go b/ethereum.go index 530cbd5c1..78f08c15e 100644 --- a/ethereum.go +++ b/ethereum.go @@ -29,5 +29,7 @@ func main() { bm.ProcessBlock( blck ) - fmt.Printf("rlp encoded Tx %q\n", tx.Serialize()) + //fmt.Printf("rlp encoded Tx %q\n", tx.MarshalRlp()) + fmt.Printf("block enc %q\n", blck.MarshalRlp()) + fmt.Printf("block hash %q\n", blck.Hash()) } -- cgit v1.2.3 From 323ba36869126520c294b6c8acacfa9877cde5c7 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 22:32:55 +0100 Subject: Updated rlp encoding. (requires verification!) --- rlp.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++----------- rlp_test.go | 20 ++++++++++++++ 2 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 rlp_test.go diff --git a/rlp.go b/rlp.go index 2c722cf7a..cce839095 100644 --- a/rlp.go +++ b/rlp.go @@ -6,29 +6,51 @@ import ( "math" ) -func EncodeSlice(slice []interface{}) []byte { - var buff bytes.Buffer +func BinaryLength(n uint64) uint64 { + if n == 0 { return 0 } - for _, val := range slice { - switch t := val.(type) { - case []interface{}, []string: - buff.Write(Encode(t)) - } + return 1 + BinaryLength(n / 256) +} + +func ToBinarySlice(n uint64, length uint64) []uint64 { + if length == 0 { + length = BinaryLength(n) } - return buff.Bytes() + if n == 0 { return make([]uint64, 1) } + + slice := ToBinarySlice(n / 256, 0) + slice = append(slice, n % 256) + + return slice +} + +func ToBin(n uint64, length uint64) string { + var buf bytes.Buffer + for _, val := range ToBinarySlice(n, length) { + buf.WriteString(string(val)) + } + + return buf.String() +} + +func FromBin(data []byte) uint64 { + if len(data) == 0 { return 0 } + + return FromBin(data[:len(data)-1]) * 256 + uint64(data[len(data)-1]) +} + +func Decode(data []byte, pos int) { + char := int(data[pos]) + switch { + case char < 24: + } } func Encode(object interface{}) []byte { var buff bytes.Buffer switch t := object.(type) { - case string: - if len(t) < 56 { - buff.WriteString(string(len(t) + 64) + t) - } else { - - } case uint32, uint64: var num uint64 if _num, ok := t.(uint64); ok { @@ -40,12 +62,49 @@ func Encode(object interface{}) []byte { if num >= 0 && num < 24 { buff.WriteString(string(num)) } else if num <= uint64(math.Pow(2, 256)) { + b := ToBin(num, 0) + buff.WriteString(string(len(b) + 23) + b) + } else { + b := ToBin(num, 0) + b2 := ToBin(uint64(len(b)), 0) + buff.WriteString(string(len(b2) + 55) + b2 + b) } + + case string: + if len(t) < 56 { + buff.WriteString(string(len(t) + 64) + t) + } else { + b2 := ToBin(uint64(len(t)), 0) + buff.WriteString(string(len(b2) + 119) + b2 + t) + } + case []byte: // Cast the byte slice to a string buff.Write(Encode(string(t))) - case []interface{}: - buff.Write(EncodeSlice(t)) + + case []interface{}, []string: + // Inline function for writing the slice header + WriteSliceHeader := func(length int) { + if length < 56 { + buff.WriteString(string(length + 128)) + } else { + b2 := ToBin(uint64(length), 0) + buff.WriteString(string(len(b2) + 183) + b2) + } + } + + // FIXME How can I do this "better"? + if interSlice, ok := t.([]interface{}); ok { + WriteSliceHeader(len(interSlice)) + for _, val := range interSlice { + buff.Write(Encode(val)) + } + } else if stringSlice, ok := t.([]string); ok { + WriteSliceHeader(len(stringSlice)) + for _, val := range stringSlice { + buff.Write(Encode(val)) + } + } } return buff.Bytes() diff --git a/rlp_test.go b/rlp_test.go new file mode 100644 index 000000000..922b70978 --- /dev/null +++ b/rlp_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "testing" + "fmt" +) + +func TestEncode(t *testing.T) { + strRes := "Cdog" + str := string(Encode("dog")) + if str != strRes { + t.Error(fmt.Sprintf("Expected %q, got %q", strRes, str)) + } + + sliceRes := "\u0083CdogCgodCcat" + slice := string(Encode([]string{"dog", "god", "cat"})) + if slice != sliceRes { + t.Error(fmt.Sprintf("Expected %q, got %q", sliceRes, slice)) + } +} -- cgit v1.2.3 From 0f656652e6d76deebcfc7834923adf99eadc050f Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 27 Dec 2013 23:48:44 +0100 Subject: Implemented decoding rlp --- rlp.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- rlp_test.go | 15 ++++++++++++--- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/rlp.go b/rlp.go index cce839095..2ea8ff094 100644 --- a/rlp.go +++ b/rlp.go @@ -1,7 +1,7 @@ package main import ( - _"fmt" + "fmt" "bytes" "math" ) @@ -40,11 +40,53 @@ func FromBin(data []byte) uint64 { return FromBin(data[:len(data)-1]) * 256 + uint64(data[len(data)-1]) } -func Decode(data []byte, pos int) { +func Decode(data []byte, pos int) (interface{}, int) { char := int(data[pos]) + slice := make([]interface{}, 0) switch { case char < 24: + return append(slice, data[pos]), pos + 1 + case char < 56: + b := int(data[pos]) - 23 + return FromBin(data[pos+1 : pos+1+b]), pos + 1 + b + case char < 64: + b := int(data[pos]) - 55 + b2 := int(FromBin(data[pos+1 : pos+1+b])) + return FromBin(data[pos+1+b : pos+1+b+b2]), pos+1+b+b2 + case char < 120: + b := int(data[pos]) - 64 + return data[pos+1:pos+1+b], pos+1+b + case char < 128: + b := int(data[pos]) - 119 + b2 := int(FromBin(data[pos+1 : pos+1+b])) + return data[pos+1+b : pos+1+b+b2], pos+1+b+b2 + case char < 184: + b := int(data[pos]) - 128 + pos++ + for i := 0; i < b; i++ { + var obj interface{} + + obj, pos = Decode(data, pos) + slice = append(slice, obj) + } + return slice, pos + case char < 192: + b := int(data[pos]) - 183 + //b2 := int(FromBin(data[pos+1 : pos+1+b])) (ref imprementation has an unused variable) + pos = pos+1+b + for i := 0; i < b; i++ { + var obj interface{} + + obj, pos = Decode(data, pos) + slice = append(slice, obj) + } + return slice, pos + + default: + panic(fmt.Sprintf("byte not supported: %q", char)) } + + return slice, 0 } func Encode(object interface{}) []byte { @@ -86,10 +128,11 @@ func Encode(object interface{}) []byte { // Inline function for writing the slice header WriteSliceHeader := func(length int) { if length < 56 { - buff.WriteString(string(length + 128)) + buff.WriteByte(byte(length + 128)) } else { b2 := ToBin(uint64(length), 0) - buff.WriteString(string(len(b2) + 183) + b2) + buff.WriteByte(byte(len(b2) + 183)) + buff.WriteString(b2) } } diff --git a/rlp_test.go b/rlp_test.go index 922b70978..c2d5ba263 100644 --- a/rlp_test.go +++ b/rlp_test.go @@ -7,14 +7,23 @@ import ( func TestEncode(t *testing.T) { strRes := "Cdog" - str := string(Encode("dog")) + bytes := Encode("dog") + str := string(bytes) if str != strRes { t.Error(fmt.Sprintf("Expected %q, got %q", strRes, str)) } + dec,_ := Decode(bytes, 0) + fmt.Printf("raw: %v encoded: %q == %v\n", dec, str, "dog") - sliceRes := "\u0083CdogCgodCcat" - slice := string(Encode([]string{"dog", "god", "cat"})) + + sliceRes := "\x83CdogCgodCcat" + strs := []string{"dog", "god", "cat"} + bytes = Encode(strs) + slice := string(bytes) if slice != sliceRes { t.Error(fmt.Sprintf("Expected %q, got %q", sliceRes, slice)) } + + dec,_ = Decode(bytes, 0) + fmt.Printf("raw: %v encoded: %q == %v\n", dec, slice, strs) } -- cgit v1.2.3 From c7dc92e1270c3e2522edc10d9e757d9be48c0789 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 28 Dec 2013 01:46:18 +0100 Subject: Reset stack pointer on run --- vm.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/vm.go b/vm.go index 353f4f39e..dc026f318 100644 --- a/vm.go +++ b/vm.go @@ -60,8 +60,6 @@ type TxCallback func(opType OpType) bool type Vm struct { // Memory stack stack map[string]string - // Index ptr - iptr int memory map[string]map[string]string } @@ -71,7 +69,10 @@ func NewVm() *Vm { stackSize := uint(256) fmt.Println("stack size =", stackSize) - return &Vm{make(map[string]string), 0, make(map[string]map[string]string)} + return &Vm{ + stack: make(map[string]string), + memory: make(map[string]map[string]string), + } } func (vm *Vm) RunTransaction(tx *Transaction, cb TxCallback) { @@ -84,6 +85,8 @@ func (vm *Vm) RunTransaction(tx *Transaction, cb TxCallback) { vm.stack["0"] = tx.sender vm.stack["1"] = "100" //int(tx.value) vm.stack["1"] = "1000" //int(tx.fee) + // Stack pointer + stPtr := 0 //vm.memory[tx.addr] = make([]int, 256) vm.memory[tx.addr] = make(map[string]string) @@ -93,13 +96,13 @@ func (vm *Vm) RunTransaction(tx *Transaction, cb TxCallback) { // Instructions are shorthanded as Ix/y/z x := 0; y := 1; z := 2; //a := 3; b := 4; c := 5 out: - for vm.iptr < len(tx.data) { + for stPtr < len(tx.data) { // The base big int for all calculations. Use this for any results. base := new(big.Int) // XXX Should Instr return big int slice instead of string slice? - op, args, _ := Instr(tx.data[vm.iptr]) + op, args, _ := Instr(tx.data[stPtr]) - fmt.Printf("%-3d %d %v\n", vm.iptr, op, args) + fmt.Printf("%-3d %d %v\n", stPtr, op, args) opType := OpType(tNorm) // Determine the op type (used for calculating fees by the block manager) @@ -113,7 +116,7 @@ out: // If the callback yielded a negative result abort execution if !cb(opType) { break out } - nptr := vm.iptr + nptr := stPtr switch op { case oSTOP: fmt.Println("exiting (oSTOP), idx =", nptr) @@ -171,10 +174,10 @@ out: break } - if vm.iptr == nptr { - vm.iptr++ + if stPtr == nptr { + stPtr++ } else { - vm.iptr = nptr + stPtr = nptr fmt.Println("... JMP", nptr, "...") } } -- cgit v1.2.3 From 8391d3d4f49b55ea719e5524306a0dcf90d6e7ab Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 28 Dec 2013 02:22:42 +0100 Subject: Unmarshalling of transactions --- transaction.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/transaction.go b/transaction.go index 5a268b566..cb7edd89c 100644 --- a/transaction.go +++ b/transaction.go @@ -37,20 +37,22 @@ type Transaction struct { RlpSerializer sender string - recipient uint32 + recipient string value uint32 fee uint32 data []string memory []int + lastTx string // To be removed signature string addr string } -func NewTransaction(to uint32, value uint32, data []string) *Transaction { +func NewTransaction(to string, value uint32, data []string) *Transaction { tx := Transaction{sender: "1234567890", recipient: to, value: value} tx.fee = 0//uint32((ContractFee + MemoryFee * float32(len(tx.data))) * 1e8) + tx.lastTx = "0" // Serialize the data tx.data = make([]string, len(data)) @@ -73,16 +75,67 @@ func NewTransaction(to uint32, value uint32, data []string) *Transaction { func (tx *Transaction) MarshalRlp() []byte { // Prepare the transaction for serialization preEnc := []interface{}{ - "0", // TODO last Tx + tx.lastTx, tx.sender, - // XXX In the future there's no need to cast to string because they'll end up being big numbers (strings) - Uitoa(tx.recipient), - Uitoa(tx.value), - Uitoa(tx.fee), + tx.recipient, + tx.value, + tx.fee, tx.data, } - return []byte(RlpEncode(preEnc)) + return []byte(Encode(preEnc)) +} + +func (tx *Transaction) UnmarshalRlp(data []byte) { + t, _ := Decode(data,0) + if slice, ok := t.([]interface{}); ok { + if lastTx, ok := slice[0].([]byte); ok { + tx.lastTx = string(lastTx) + } + + if sender, ok := slice[1].([]byte); ok { + tx.sender = string(sender) + } + + if recipient, ok := slice[2].([]byte); ok { + tx.recipient = string(recipient) + } + + // If only I knew of a better way. + if value, ok := slice[3].(uint8); ok { + tx.value = uint32(value) + } + if value, ok := slice[3].(uint16); ok { + tx.value = uint32(value) + } + if value, ok := slice[3].(uint32); ok { + tx.value = uint32(value) + } + if value, ok := slice[3].(uint64); ok { + tx.value = uint32(value) + } + if fee, ok := slice[4].(uint8); ok { + tx.fee = uint32(fee) + } + if fee, ok := slice[4].(uint16); ok { + tx.fee = uint32(fee) + } + if fee, ok := slice[4].(uint32); ok { + tx.fee = uint32(fee) + } + if fee, ok := slice[4].(uint64); ok { + tx.fee = uint32(fee) + } + + if data, ok := slice[5].([]interface{}); ok { + tx.data = make([]string, len(data)) + for i, d := range data { + if instr, ok := d.([]byte); ok { + tx.data[i] = string(instr) + } + } + } + } } func InitFees() { -- cgit v1.2.3 From d6460f3de171f018860ea9c3f4a69536c8ef82ac Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 28 Dec 2013 02:23:28 +0100 Subject: Changed 0x0 to \x00 --- block_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block_manager.go b/block_manager.go index 4f0ec1cdb..80b30eff6 100644 --- a/block_manager.go +++ b/block_manager.go @@ -42,7 +42,7 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { } func (bm *BlockManager) ProcessTransaction(tx *Transaction, lockChan chan bool) { - if tx.recipient == 0x0 { + if tx.recipient == "\x00" { bm.vm.RunTransaction(tx, func(opType OpType) bool { // TODO calculate fees -- cgit v1.2.3 From 5a7eae705b83d75cb780279e386f183c4b6e19c6 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 28 Dec 2013 02:24:01 +0100 Subject: Removed slice appending for integers --- rlp.go | 4 ++-- rlp_test.go | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rlp.go b/rlp.go index 2ea8ff094..2c04317ae 100644 --- a/rlp.go +++ b/rlp.go @@ -45,7 +45,7 @@ func Decode(data []byte, pos int) (interface{}, int) { slice := make([]interface{}, 0) switch { case char < 24: - return append(slice, data[pos]), pos + 1 + return data[pos], pos + 1 case char < 56: b := int(data[pos]) - 23 return FromBin(data[pos+1 : pos+1+b]), pos + 1 + b @@ -72,7 +72,7 @@ func Decode(data []byte, pos int) (interface{}, int) { return slice, pos case char < 192: b := int(data[pos]) - 183 - //b2 := int(FromBin(data[pos+1 : pos+1+b])) (ref imprementation has an unused variable) + //b2 := int(FromBin(data[pos+1 : pos+1+b])) (ref implementation has an unused variable) pos = pos+1+b for i := 0; i < b; i++ { var obj interface{} diff --git a/rlp_test.go b/rlp_test.go index c2d5ba263..ae8dcced4 100644 --- a/rlp_test.go +++ b/rlp_test.go @@ -15,7 +15,6 @@ func TestEncode(t *testing.T) { dec,_ := Decode(bytes, 0) fmt.Printf("raw: %v encoded: %q == %v\n", dec, str, "dog") - sliceRes := "\x83CdogCgodCcat" strs := []string{"dog", "god", "cat"} bytes = Encode(strs) @@ -27,3 +26,10 @@ func TestEncode(t *testing.T) { dec,_ = Decode(bytes, 0) fmt.Printf("raw: %v encoded: %q == %v\n", dec, slice, strs) } + +func BenchmarkEncodeDecode(b *testing.B) { + for i := 0; i < b.N; i++ { + bytes := Encode([]string{"dog", "god", "cat"}) + Decode(bytes, 0) + } +} -- cgit v1.2.3 From bb8afba20a56552cef71681f72fb192bcda88c1d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 28 Dec 2013 02:24:16 +0100 Subject: Updated tests --- block.go | 27 +++++++++++++++++---------- ethereum.go | 15 ++++++++++----- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/block.go b/block.go index f8bf2ce3b..f666b09db 100644 --- a/block.go +++ b/block.go @@ -1,7 +1,7 @@ package main import ( - _"fmt" + "fmt" "time" ) @@ -44,21 +44,28 @@ func (block *Block) MarshalRlp() []byte { encTx[i] = string(tx.MarshalRlp()) } - enc := RlpEncode([]interface{}{ + header := []interface{}{ block.number, - block.prevHash, + //block.prevHash, // Sha of uncles - block.coinbase, + //block.coinbase, // root state - Sha256Bin([]byte(RlpEncode(encTx))), - block.difficulty, - block.time, - block.nonce, + //Sha256Bin([]byte(RlpEncode(encTx))), + //block.difficulty, + //block.time, + //block.nonce, // extra? - }) + } - return []byte(enc) + return Encode([]interface{}{header, encTx}) } func (block *Block) UnmarshalRlp(data []byte) { + fmt.Printf("%q\n", data) + t, _ := Decode(data,0) + if slice, ok := t.([]interface{}); ok { + if txes, ok := slice[1].([]interface{}); ok { + fmt.Println(txes[0]) + } + } } diff --git a/ethereum.go b/ethereum.go index 78f08c15e..d3cecdc94 100644 --- a/ethereum.go +++ b/ethereum.go @@ -9,7 +9,7 @@ func main() { bm := NewBlockManager() - tx := NewTransaction(0x0, 20, []string{ + tx := NewTransaction("\x00", 20, []string{ "SET 10 6", "LD 10 10", "LT 10 1 20", @@ -23,13 +23,18 @@ func main() { "SET 255 15", "JMP 255", }) - tx2 := NewTransaction(0x0, 20, []string{"SET 10 6", "LD 10 10"}) + txData := tx.MarshalRlp() + + copyTx := &Transaction{} + copyTx.UnmarshalRlp(txData) + + + tx2 := NewTransaction("\x00", 20, []string{"SET 10 6", "LD 10 10"}) blck := NewBlock([]*Transaction{tx2, tx}) bm.ProcessBlock( blck ) - //fmt.Printf("rlp encoded Tx %q\n", tx.MarshalRlp()) - fmt.Printf("block enc %q\n", blck.MarshalRlp()) - fmt.Printf("block hash %q\n", blck.Hash()) + //t := blck.MarshalRlp() + //blck.UnmarshalRlp(t) } -- cgit v1.2.3 From 95d877f701371108dc18967319d1f7b4e6763caf Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 28 Dec 2013 15:17:26 +0100 Subject: Fixed rlp encoding --- rlp.go | 12 +++++++++++- rlp_test.go | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/rlp.go b/rlp.go index 2c04317ae..bc3b6a151 100644 --- a/rlp.go +++ b/rlp.go @@ -41,25 +41,34 @@ func FromBin(data []byte) uint64 { } func Decode(data []byte, pos int) (interface{}, int) { + if pos > len(data)-1 { + panic(fmt.Sprintf("index out of range %d for data %q, l = %d", pos, data, len(data))) + } + char := int(data[pos]) slice := make([]interface{}, 0) switch { case char < 24: return data[pos], pos + 1 + case char < 56: b := int(data[pos]) - 23 return FromBin(data[pos+1 : pos+1+b]), pos + 1 + b + case char < 64: b := int(data[pos]) - 55 b2 := int(FromBin(data[pos+1 : pos+1+b])) return FromBin(data[pos+1+b : pos+1+b+b2]), pos+1+b+b2 + case char < 120: b := int(data[pos]) - 64 return data[pos+1:pos+1+b], pos+1+b + case char < 128: b := int(data[pos]) - 119 b2 := int(FromBin(data[pos+1 : pos+1+b])) return data[pos+1+b : pos+1+b+b2], pos+1+b+b2 + case char < 184: b := int(data[pos]) - 128 pos++ @@ -70,6 +79,7 @@ func Decode(data []byte, pos int) (interface{}, int) { slice = append(slice, obj) } return slice, pos + case char < 192: b := int(data[pos]) - 183 //b2 := int(FromBin(data[pos+1 : pos+1+b])) (ref implementation has an unused variable) @@ -122,7 +132,7 @@ func Encode(object interface{}) []byte { case []byte: // Cast the byte slice to a string - buff.Write(Encode(string(t))) + //buff.Write(Encode(string(t))) case []interface{}, []string: // Inline function for writing the slice header diff --git a/rlp_test.go b/rlp_test.go index ae8dcced4..6a34aecbc 100644 --- a/rlp_test.go +++ b/rlp_test.go @@ -27,6 +27,27 @@ func TestEncode(t *testing.T) { fmt.Printf("raw: %v encoded: %q == %v\n", dec, slice, strs) } +func TestMultiEncode(t *testing.T) { + inter := []interface{}{ + []interface{}{ + "1","2","3", + }, + []string{ + "string", + "string2", + "\x86A0J1234567890A\x00B20A0\x82F395843F657986", + "\x86A0J1234567890A\x00B20A0\x8cF395843F657986I335612448F524099H16716881A0H13114947G2039362G1507139H16719697G1048387E65360", + }, + "test", + } + + bytes := Encode(inter) + fmt.Printf("%q\n", bytes) + + dec, _ := Decode(bytes, 0) + fmt.Println(dec) +} + func BenchmarkEncodeDecode(b *testing.B) { for i := 0; i < b.N; i++ { bytes := Encode([]string{"dog", "god", "cat"}) -- cgit v1.2.3 From bd582d919bf3dfa68cce10ca506c39a4c2e2ea94 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 28 Dec 2013 15:18:08 +0100 Subject: (un)marshal blocks and transactions --- block.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++++---------- transaction.go | 4 +-- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/block.go b/block.go index f666b09db..5c089137d 100644 --- a/block.go +++ b/block.go @@ -3,19 +3,18 @@ package main import ( "fmt" "time" + _"bytes" ) type Block struct { - RlpSerializer - number uint32 prevHash string uncles []*Block coinbase string // state xxx - difficulty int + difficulty uint32 time time.Time - nonce int + nonce uint32 transactions []*Transaction } @@ -23,6 +22,11 @@ func NewBlock(/* TODO use raw data */transactions []*Transaction) *Block { block := &Block{ // Slice of transactions to include in this block transactions: transactions, + number: 1, + prevHash: "1234", + coinbase: "me", + difficulty: 10, + nonce: 0, time: time.Now(), } @@ -44,28 +48,77 @@ func (block *Block) MarshalRlp() []byte { encTx[i] = string(tx.MarshalRlp()) } + /* I made up the block. It should probably contain different data or types. It sole purpose now is testing */ header := []interface{}{ block.number, - //block.prevHash, + block.prevHash, // Sha of uncles - //block.coinbase, + "", + block.coinbase, // root state - //Sha256Bin([]byte(RlpEncode(encTx))), - //block.difficulty, - //block.time, - //block.nonce, + "", + string(Sha256Bin([]byte(RlpEncode(encTx)))), + block.difficulty, + block.time.String(), + block.nonce, // extra? } - return Encode([]interface{}{header, encTx}) + encoded := Encode([]interface{}{header, encTx}) + + return encoded } func (block *Block) UnmarshalRlp(data []byte) { - fmt.Printf("%q\n", data) t, _ := Decode(data,0) if slice, ok := t.([]interface{}); ok { - if txes, ok := slice[1].([]interface{}); ok { - fmt.Println(txes[0]) + if header, ok := slice[0].([]interface{}); ok { + if number, ok := header[0].(uint8); ok { + block.number = uint32(number) + } + + if prevHash, ok := header[1].([]byte); ok { + block.prevHash = string(prevHash) + } + + // sha of uncles is header[2] + + if coinbase, ok := header[3].([]byte); ok { + block.coinbase = string(coinbase) + } + + // state is header[header[4] + + // sha is header[5] + + // It's either 8bit or 64 + if difficulty, ok := header[6].(uint8); ok { + block.difficulty = uint32(difficulty) + } + if difficulty, ok := header[6].(uint64); ok { + block.difficulty = uint32(difficulty) + } + + if time, ok := header[7].([]byte); ok { + fmt.Sprintf("Time is: ", string(time)) + } + + if nonce, ok := header[8].(uint8); ok { + block.nonce = uint32(nonce) + } + } + + if txSlice, ok := slice[1].([]interface{}); ok { + block.transactions = make([]*Transaction, len(txSlice)) + + for i, tx := range txSlice { + if t, ok := tx.([]byte); ok { + tx := &Transaction{} + tx.UnmarshalRlp(t) + + block.transactions[i] = tx + } + } } } } diff --git a/transaction.go b/transaction.go index cb7edd89c..b6e9eaa8f 100644 --- a/transaction.go +++ b/transaction.go @@ -34,8 +34,6 @@ var Period3Reward *big.Int = new(big.Int) var Period4Reward *big.Int = new(big.Int) type Transaction struct { - RlpSerializer - sender string recipient string value uint32 @@ -83,7 +81,7 @@ func (tx *Transaction) MarshalRlp() []byte { tx.data, } - return []byte(Encode(preEnc)) + return Encode(preEnc) } func (tx *Transaction) UnmarshalRlp(data []byte) { -- cgit v1.2.3 From 5198b7b9ebdfe1ef99627dadb07b87e18a039972 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 28 Dec 2013 15:18:23 +0100 Subject: Test code updated --- ethereum.go | 11 +++++++++-- vm.go | 18 +++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ethereum.go b/ethereum.go index d3cecdc94..3e01f89b2 100644 --- a/ethereum.go +++ b/ethereum.go @@ -4,6 +4,8 @@ import ( "fmt" ) +const Debug = false + func main() { InitFees() @@ -35,6 +37,11 @@ func main() { bm.ProcessBlock( blck ) - //t := blck.MarshalRlp() - //blck.UnmarshalRlp(t) + + t := blck.MarshalRlp() + copyBlock := &Block{} + copyBlock.UnmarshalRlp(t) + + fmt.Println(blck) + fmt.Println(copyBlock) } diff --git a/vm.go b/vm.go index dc026f318..787e29295 100644 --- a/vm.go +++ b/vm.go @@ -64,10 +64,7 @@ type Vm struct { } func NewVm() *Vm { - fmt.Println("init Ethereum VM") - - stackSize := uint(256) - fmt.Println("stack size =", stackSize) + //stackSize := uint(256) return &Vm{ stack: make(map[string]string), @@ -76,10 +73,12 @@ func NewVm() *Vm { } func (vm *Vm) RunTransaction(tx *Transaction, cb TxCallback) { - fmt.Printf(` + if Debug { + fmt.Printf(` # processing Tx (%v) # fee = %f, ops = %d, sender = %s, value = %d -`, tx.addr, float32(tx.fee) / 1e8, len(tx.data), tx.sender, tx.value) + `, tx.addr, float32(tx.fee) / 1e8, len(tx.data), tx.sender, tx.value) + } vm.stack = make(map[string]string) vm.stack["0"] = tx.sender @@ -102,7 +101,9 @@ out: // XXX Should Instr return big int slice instead of string slice? op, args, _ := Instr(tx.data[stPtr]) - fmt.Printf("%-3d %d %v\n", stPtr, op, args) + if Debug { + fmt.Printf("%-3d %d %v\n", stPtr, op, args) + } opType := OpType(tNorm) // Determine the op type (used for calculating fees by the block manager) @@ -178,8 +179,7 @@ out: stPtr++ } else { stPtr = nptr - fmt.Println("... JMP", nptr, "...") + if Debug { fmt.Println("... JMP", nptr, "...") } } } - fmt.Println("# finished processing Tx\n") } -- cgit v1.2.3 From a1c5d5acac542ab877aeec7814338e7638d55dbf Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 29 Dec 2013 01:36:59 +0100 Subject: Comments --- big.go | 6 ++++++ block.go | 22 ++++++++++++++++++---- block_manager.go | 3 +++ ethereum.go | 1 - transaction.go | 1 + 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/big.go b/big.go index 71e7573e1..44bfee2e4 100644 --- a/big.go +++ b/big.go @@ -4,6 +4,9 @@ import ( "math/big" ) +/* + * Returns the power of two integers + */ func BigPow(a,b int) *big.Int { c := new(big.Int) c.Exp(big.NewInt(int64(a)), big.NewInt(int64(b)), big.NewInt(0)) @@ -11,6 +14,9 @@ func BigPow(a,b int) *big.Int { return c } +/* + * Like big.NewInt(uint64); this takes a string instead. + */ func Big(num string) *big.Int { n := new(big.Int) n.SetString(num, 0) diff --git a/block.go b/block.go index 5c089137d..8c4d263fa 100644 --- a/block.go +++ b/block.go @@ -7,17 +7,23 @@ import ( ) type Block struct { + // The number of this block number uint32 + // Hash to the previous block prevHash string + // Uncles of this block uncles []*Block coinbase string // state xxx difficulty uint32 + // Creation time time time.Time nonce uint32 + // List of transactions and/or contracts transactions []*Transaction } +// Creates a new block. This is currently for testing func NewBlock(/* TODO use raw data */transactions []*Transaction) *Block { block := &Block{ // Slice of transactions to include in this block @@ -37,14 +43,16 @@ func NewBlock(/* TODO use raw data */transactions []*Transaction) *Block { func (block *Block) Update() { } +// Returns a hash of the block func (block *Block) Hash() string { return Sha256Hex(block.MarshalRlp()) } func (block *Block) MarshalRlp() []byte { - // Encoding method requires []interface{} type. It's actual a slice of strings + // Marshal the transactions of this block encTx := make([]string, len(block.transactions)) for i, tx := range block.transactions { + // Cast it to a string (safe) encTx[i] = string(tx.MarshalRlp()) } @@ -64,14 +72,16 @@ func (block *Block) MarshalRlp() []byte { // extra? } - encoded := Encode([]interface{}{header, encTx}) - - return encoded + // Encode a slice interface which contains the header and the list of transactions. + return Encode([]interface{}{header, encTx}) } func (block *Block) UnmarshalRlp(data []byte) { t, _ := Decode(data,0) + + // interface slice assertion if slice, ok := t.([]interface{}); ok { + // interface slice assertion if header, ok := slice[0].([]interface{}); ok { if number, ok := header[0].(uint8); ok { block.number = uint32(number) @@ -109,11 +119,15 @@ func (block *Block) UnmarshalRlp(data []byte) { } if txSlice, ok := slice[1].([]interface{}); ok { + // Create transaction slice equal to decoded tx interface slice block.transactions = make([]*Transaction, len(txSlice)) + // Unmarshal transactions for i, tx := range txSlice { if t, ok := tx.([]byte); ok { tx := &Transaction{} + // Use the unmarshaled data to unmarshal the transaction + // t is still decoded. tx.UnmarshalRlp(t) block.transactions[i] = tx diff --git a/block_manager.go b/block_manager.go index 80b30eff6..a60d4340d 100644 --- a/block_manager.go +++ b/block_manager.go @@ -26,9 +26,12 @@ func NewBlockManager() *BlockManager { // Process a block. func (bm *BlockManager) ProcessBlock(block *Block) error { + // Get the tx count. Used to create enough channels to 'join' the go routines txCount := len(block.transactions) + // Locking channel. When it has been fully buffered this method will return lockChan := make(chan bool, txCount) + // Process each transaction/contract for _, tx := range block.transactions { go bm.ProcessTransaction(tx, lockChan) } diff --git a/ethereum.go b/ethereum.go index 3e01f89b2..52e6e3046 100644 --- a/ethereum.go +++ b/ethereum.go @@ -37,7 +37,6 @@ func main() { bm.ProcessBlock( blck ) - t := blck.MarshalRlp() copyBlock := &Block{} copyBlock.UnmarshalRlp(t) diff --git a/transaction.go b/transaction.go index b6e9eaa8f..562593c96 100644 --- a/transaction.go +++ b/transaction.go @@ -125,6 +125,7 @@ func (tx *Transaction) UnmarshalRlp(data []byte) { tx.fee = uint32(fee) } + // Encode the data/instructions if data, ok := slice[5].([]interface{}); ok { tx.data = make([]string, len(data)) for i, d := range data { -- cgit v1.2.3 From 74bc45116aa1c5d0e549f522dccefc58356c1410 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 29 Dec 2013 23:50:43 +0100 Subject: Encoding helpers with tests --- encoding.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ encoding_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 encoding.go create mode 100644 encoding_test.go diff --git a/encoding.go b/encoding.go new file mode 100644 index 000000000..ca30b47c9 --- /dev/null +++ b/encoding.go @@ -0,0 +1,46 @@ +package main + +import ( + "bytes" + "encoding/hex" + "strings" +) + +func CompactEncode(hexSlice []int) string { + terminator := 0 + if hexSlice[len(hexSlice)-1] == 16 { + terminator = 1 + } + + if terminator == 1 { + hexSlice = hexSlice[:len(hexSlice)-1] + } + + oddlen := len(hexSlice) % 2 + flags := 2 * terminator + oddlen + if oddlen != 0 { + hexSlice = append([]int{flags}, hexSlice...) + } else { + hexSlice = append([]int{flags, 0}, hexSlice...) + } + + var buff bytes.Buffer + for i := 0; i < len(hexSlice); i+=2 { + buff.WriteByte(byte(16 * hexSlice[i] + hexSlice[i+1])) + } + + return buff.String() +} + +func CompactHexDecode(str string) []int { + base := "0123456789abcdef" + hexSlice := make([]int, 0) + + enc := hex.EncodeToString([]byte(str)) + for _, v := range enc { + hexSlice = append(hexSlice, strings.IndexByte(base, byte(v))) + } + hexSlice = append(hexSlice, 16) + + return hexSlice +} diff --git a/encoding_test.go b/encoding_test.go new file mode 100644 index 000000000..63f7878bf --- /dev/null +++ b/encoding_test.go @@ -0,0 +1,50 @@ +package main + +import ( + "testing" + "fmt" +) + +func TestCompactEncode(t *testing.T) { + test1 := []int{1,2,3,4,5} + if res := CompactEncode(test1); res != "\x11\x23\x45" { + t.Error(fmt.Sprintf("even compact encode failed. Got: %q", res)) + } + + test2 := []int{0, 1, 2, 3, 4, 5} + if res := CompactEncode(test2); res != "\x00\x01\x23\x45" { + t.Error(fmt.Sprintf("odd compact encode failed. Got: %q", res)) + } + + test3 := []int{0, 15, 1, 12, 11, 8, /*term*/16} + if res := CompactEncode(test3); res != "\x20\x0f\x1c\xb8" { + t.Error(fmt.Sprintf("odd terminated compact encode failed. Got: %q", res)) + } + + test4 := []int{15, 1, 12, 11, 8, /*term*/16} + if res := CompactEncode(test4); res != "\x3f\x1c\xb8" { + t.Error(fmt.Sprintf("even terminated compact encode failed. Got: %q", res)) + } +} + +// Helper function for comparing slices +func CompareIntSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + +func TestCompactHexDecode(t *testing.T) { + exp := []int{7, 6, 6, 5, 7, 2, 6, 2, 16} + res := CompactHexDecode("verb") + + if !CompareIntSlice(res, exp) { + t.Error("Error compact hex decode. Expected", exp, "got", res) + } +} -- cgit v1.2.3 From 0747aa3a3bd78cc9b99ea72cf45e4cc4bfc301f5 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 29 Dec 2013 23:52:46 +0100 Subject: Removed logs from tests and updated rlp encoding to include byte slices --- rlp.go | 2 +- rlp_test.go | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/rlp.go b/rlp.go index bc3b6a151..4db88539d 100644 --- a/rlp.go +++ b/rlp.go @@ -132,7 +132,7 @@ func Encode(object interface{}) []byte { case []byte: // Cast the byte slice to a string - //buff.Write(Encode(string(t))) + buff.Write(Encode(string(t))) case []interface{}, []string: // Inline function for writing the slice header diff --git a/rlp_test.go b/rlp_test.go index 6a34aecbc..68676d030 100644 --- a/rlp_test.go +++ b/rlp_test.go @@ -7,13 +7,14 @@ import ( func TestEncode(t *testing.T) { strRes := "Cdog" + bytes := Encode("dog") + str := string(bytes) if str != strRes { t.Error(fmt.Sprintf("Expected %q, got %q", strRes, str)) } - dec,_ := Decode(bytes, 0) - fmt.Printf("raw: %v encoded: %q == %v\n", dec, str, "dog") + //dec,_ := Decode(bytes, 0) sliceRes := "\x83CdogCgodCcat" strs := []string{"dog", "god", "cat"} @@ -23,8 +24,7 @@ func TestEncode(t *testing.T) { t.Error(fmt.Sprintf("Expected %q, got %q", sliceRes, slice)) } - dec,_ = Decode(bytes, 0) - fmt.Printf("raw: %v encoded: %q == %v\n", dec, slice, strs) + //dec,_ = Decode(bytes, 0) } func TestMultiEncode(t *testing.T) { @@ -42,10 +42,8 @@ func TestMultiEncode(t *testing.T) { } bytes := Encode(inter) - fmt.Printf("%q\n", bytes) - dec, _ := Decode(bytes, 0) - fmt.Println(dec) + Decode(bytes, 0) } func BenchmarkEncodeDecode(b *testing.B) { -- cgit v1.2.3 From 0edcbc695e3e80d2f417467905621505f9971b4f Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 29 Dec 2013 23:53:12 +0100 Subject: Updated serialisation --- block.go | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/block.go b/block.go index 8c4d263fa..9aa3c8bb5 100644 --- a/block.go +++ b/block.go @@ -1,7 +1,7 @@ package main import ( - "fmt" + _"fmt" "time" _"bytes" ) @@ -17,14 +17,24 @@ type Block struct { // state xxx difficulty uint32 // Creation time - time time.Time + time int64 nonce uint32 // List of transactions and/or contracts transactions []*Transaction + + extra string +} + +// New block takes a raw encoded string +func NewBlock(raw []byte) *Block { + block := &Block{} + block.UnmarshalRlp(raw) + + return block } // Creates a new block. This is currently for testing -func NewBlock(/* TODO use raw data */transactions []*Transaction) *Block { +func CreateBlock(/* TODO use raw data */transactions []*Transaction) *Block { block := &Block{ // Slice of transactions to include in this block transactions: transactions, @@ -33,8 +43,7 @@ func NewBlock(/* TODO use raw data */transactions []*Transaction) *Block { coinbase: "me", difficulty: 10, nonce: 0, - - time: time.Now(), + time: time.Now().Unix(), } return block @@ -65,15 +74,18 @@ func (block *Block) MarshalRlp() []byte { block.coinbase, // root state "", - string(Sha256Bin([]byte(RlpEncode(encTx)))), + // Sha of tx + string(Sha256Bin([]byte(Encode(encTx)))), block.difficulty, - block.time.String(), + uint64(block.time), block.nonce, - // extra? + block.extra, } + // TODO + uncles := []interface{}{} // Encode a slice interface which contains the header and the list of transactions. - return Encode([]interface{}{header, encTx}) + return Encode([]interface{}{header, encTx, uncles}) } func (block *Block) UnmarshalRlp(data []byte) { @@ -109,13 +121,21 @@ func (block *Block) UnmarshalRlp(data []byte) { block.difficulty = uint32(difficulty) } - if time, ok := header[7].([]byte); ok { - fmt.Sprintf("Time is: ", string(time)) + // It's either 8bit or 64 + if time, ok := header[7].(uint8); ok { + block.time = int64(time) + } + if time, ok := header[7].(uint64); ok { + block.time = int64(time) } if nonce, ok := header[8].(uint8); ok { block.nonce = uint32(nonce) } + + if extra, ok := header[9].([]byte); ok { + block.extra = string(extra) + } } if txSlice, ok := slice[1].([]interface{}); ok { -- cgit v1.2.3 From ad048e9f445ff96b7bfd75c104ab923e1e06754b Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 29 Dec 2013 23:53:20 +0100 Subject: update test --- ethereum.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ethereum.go b/ethereum.go index 52e6e3046..d9e1fd314 100644 --- a/ethereum.go +++ b/ethereum.go @@ -2,10 +2,27 @@ package main import ( "fmt" + "os" + "os/signal" ) const Debug = false +// Register interrupt handlers so we can stop the server +func RegisterInterupts(s *Server) { + // Buffered chan of one is enough + c := make(chan os.Signal, 1) + // Notify about interrupts for now + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + fmt.Println("Shutting down (%v) ... \n", sig) + + s.Stop() + } + }() +} + func main() { InitFees() @@ -30,17 +47,11 @@ func main() { copyTx := &Transaction{} copyTx.UnmarshalRlp(txData) - tx2 := NewTransaction("\x00", 20, []string{"SET 10 6", "LD 10 10"}) - blck := NewBlock([]*Transaction{tx2, tx}) + blck := CreateBlock([]*Transaction{tx2, tx}) bm.ProcessBlock( blck ) - t := blck.MarshalRlp() - copyBlock := &Block{} - copyBlock.UnmarshalRlp(t) - - fmt.Println(blck) - fmt.Println(copyBlock) + fmt.Println("GenesisBlock:", GenisisBlock, "hashed", GenisisBlock.Hash()) } -- cgit v1.2.3 From a926686445929d091c2d9e019b017600168e9e47 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 29 Dec 2013 23:54:50 +0100 Subject: Added sample server, genesis block, and database interface --- database.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ database_test.go | 43 ++++++++++++++++++++++++++ genesis.go | 36 ++++++++++++++++++++++ server.go | 55 +++++++++++++++++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 database.go create mode 100644 database_test.go create mode 100644 genesis.go create mode 100644 server.go diff --git a/database.go b/database.go new file mode 100644 index 000000000..f74c1dc7f --- /dev/null +++ b/database.go @@ -0,0 +1,94 @@ +package main + +import ( + "path" + "os/user" + "github.com/syndtr/goleveldb/leveldb" + "fmt" +) + +type Database struct { + db *leveldb.DB + trie *Trie +} + +func NewDatabase() (*Database, error) { + // This will eventually have to be something like a resource folder. + // it works on my system for now. Probably won't work on Windows + usr, _ := user.Current() + dbPath := path.Join(usr.HomeDir, ".ethereum", "database") + + // Open the db + db, err := leveldb.OpenFile(dbPath, nil) + if err != nil { + return nil, err + } + + database := &Database{db: db} + + // Bootstrap database. Sets a few defaults; such as the last block + database.Bootstrap() + + return database, nil +} + +func (db *Database) Bootstrap() error { + db.trie = NewTrie(db) + + return nil +} + +func (db *Database) Put(key []byte, value []byte) { + err := db.db.Put(key, value, nil) + if err != nil { + fmt.Println("Error put", err) + } +} + +func (db *Database) Close() { + // Close the leveldb database + db.db.Close() +} + +type Trie struct { + root string + db *Database +} + +func NewTrie(db *Database) *Trie { + return &Trie{db: db, root: ""} +} + +func (t *Trie) Update(key string, value string) { + k := CompactHexDecode(key) + + t.root = t.UpdateState(t.root, k, value) +} + +func (t *Trie) Get(key []byte) ([]byte, error) { + return nil, nil +} + +// Inserts a new sate or delete a state based on the value +func (t *Trie) UpdateState(node, key, value string) string { + if value != "" { + return t.InsertState(node, key, value) + } else { + // delete it + } + + return "" +} + +func (t *Trie) InsertState(node, key, value string) string { + return "" +} + +func (t *Trie) Put(node []byte) []byte { + enc := Encode(node) + sha := Sha256Bin(enc) + + t.db.Put([]byte(sha), enc) + + return sha +} diff --git a/database_test.go b/database_test.go new file mode 100644 index 000000000..c1a6c7d16 --- /dev/null +++ b/database_test.go @@ -0,0 +1,43 @@ +package main + +import ( + "testing" + _"fmt" +) + +func TestTriePut(t *testing.T) { + db, err := NewDatabase() + defer db.Close() + + if err != nil { + t.Error("Error starting db") + } + + key := db.trie.Put([]byte("testing node")) + + data, err := db.db.Get(key, nil) + if err != nil { + t.Error("Nothing at node") + } + + s, _ := Decode(data, 0) + if str, ok := s.([]byte); ok { + if string(str) != "testing node" { + t.Error("Wrong value node", str) + } + } else { + t.Error("Invalid return type") + } +} + +func TestTrieUpdate(t *testing.T) { + db, err := NewDatabase() + defer db.Close() + + if err != nil { + t.Error("Error starting db") + } + + db.trie.Update("test", "test") +} + diff --git a/genesis.go b/genesis.go new file mode 100644 index 000000000..aae9cd1cf --- /dev/null +++ b/genesis.go @@ -0,0 +1,36 @@ +package main + +import ( + "math" +) + +/* + * This is the special genesis block. + */ + +var GenisisHeader = []interface{}{ + // Block number + uint32(0), + // Previous hash (none) + "", + // Sha of uncles + string(Sha256Bin(Encode([]interface{}{}))), + // Coinbase + "", + // Root state + "", + // Sha of transactions + string(Sha256Bin(Encode([]interface{}{}))), + // Difficulty + uint32(math.Pow(2, 36)), + // Time + uint64(1), + // Nonce + uint32(0), + // Extra + "", +} + +var Genesis = []interface{}{ GenisisHeader, []interface{}{}, []interface{}{} } + +var GenisisBlock = NewBlock( Encode(Genesis) ) diff --git a/server.go b/server.go new file mode 100644 index 000000000..0c7488787 --- /dev/null +++ b/server.go @@ -0,0 +1,55 @@ +package main + +import ( + "container/list" + "time" +) + +type Server struct { + // Channel for shutting down the server + shutdownChan chan bool + // DB interface + db *Database + // Peers (NYI) + peers *list.List +} + +func NewServer() (*Server, error) { + db, err := NewDatabase() + if err != nil { + return nil, err + } + + server := &Server{ + shutdownChan: make(chan bool), + db: db, + peers: list.New(), + } + + return server, nil +} + +// Start the server +func (s *Server) Start() { + // For now this function just blocks the main thread + for { + time.Sleep( time.Second ) + } +} + +func (s *Server) Stop() { + // Close the database + defer s.db.Close() + + // Loop thru the peers and close them (if we had them) + for e := s.peers.Front(); e != nil; e = e.Next() { + // peer close etc + } + + s.shutdownChan <- true +} + +// This function will wait for a shutdown and resumes main thread execution +func (s *Server) WaitForShutdown() { + <- s.shutdownChan +} -- cgit v1.2.3 From f17930eb4661721cd0e1b92765448c589441907b Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 30 Dec 2013 01:09:57 +0100 Subject: Split up db and trie and added interface --- database.go | 56 +++++++++------------------------------------------ database_test.go | 38 +---------------------------------- server.go | 4 ++-- trie.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++ trie.test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 86 deletions(-) create mode 100644 trie.go create mode 100644 trie.test.go diff --git a/database.go b/database.go index f74c1dc7f..c056e70af 100644 --- a/database.go +++ b/database.go @@ -7,12 +7,12 @@ import ( "fmt" ) -type Database struct { +type LDBDatabase struct { db *leveldb.DB trie *Trie } -func NewDatabase() (*Database, error) { +func NewLDBDatabase() (*LDBDatabase, error) { // This will eventually have to be something like a resource folder. // it works on my system for now. Probably won't work on Windows usr, _ := user.Current() @@ -24,7 +24,7 @@ func NewDatabase() (*Database, error) { return nil, err } - database := &Database{db: db} + database := &LDBDatabase{db: db} // Bootstrap database. Sets a few defaults; such as the last block database.Bootstrap() @@ -32,63 +32,25 @@ func NewDatabase() (*Database, error) { return database, nil } -func (db *Database) Bootstrap() error { +func (db *LDBDatabase) Bootstrap() error { db.trie = NewTrie(db) return nil } -func (db *Database) Put(key []byte, value []byte) { +func (db *LDBDatabase) Put(key []byte, value []byte) { err := db.db.Put(key, value, nil) if err != nil { fmt.Println("Error put", err) } } -func (db *Database) Close() { - // Close the leveldb database - db.db.Close() -} - -type Trie struct { - root string - db *Database -} - -func NewTrie(db *Database) *Trie { - return &Trie{db: db, root: ""} -} - -func (t *Trie) Update(key string, value string) { - k := CompactHexDecode(key) - - t.root = t.UpdateState(t.root, k, value) -} - -func (t *Trie) Get(key []byte) ([]byte, error) { +func (db *LDBDatabase) Get(key []byte) ([]byte, error) { return nil, nil } -// Inserts a new sate or delete a state based on the value -func (t *Trie) UpdateState(node, key, value string) string { - if value != "" { - return t.InsertState(node, key, value) - } else { - // delete it - } - - return "" -} - -func (t *Trie) InsertState(node, key, value string) string { - return "" +func (db *LDBDatabase) Close() { + // Close the leveldb database + db.db.Close() } -func (t *Trie) Put(node []byte) []byte { - enc := Encode(node) - sha := Sha256Bin(enc) - - t.db.Put([]byte(sha), enc) - - return sha -} diff --git a/database_test.go b/database_test.go index c1a6c7d16..1a46eb95e 100644 --- a/database_test.go +++ b/database_test.go @@ -1,43 +1,7 @@ package main import ( - "testing" + _"testing" _"fmt" ) -func TestTriePut(t *testing.T) { - db, err := NewDatabase() - defer db.Close() - - if err != nil { - t.Error("Error starting db") - } - - key := db.trie.Put([]byte("testing node")) - - data, err := db.db.Get(key, nil) - if err != nil { - t.Error("Nothing at node") - } - - s, _ := Decode(data, 0) - if str, ok := s.([]byte); ok { - if string(str) != "testing node" { - t.Error("Wrong value node", str) - } - } else { - t.Error("Invalid return type") - } -} - -func TestTrieUpdate(t *testing.T) { - db, err := NewDatabase() - defer db.Close() - - if err != nil { - t.Error("Error starting db") - } - - db.trie.Update("test", "test") -} - diff --git a/server.go b/server.go index 0c7488787..c8fb492d5 100644 --- a/server.go +++ b/server.go @@ -9,13 +9,13 @@ type Server struct { // Channel for shutting down the server shutdownChan chan bool // DB interface - db *Database + db *LDBDatabase // Peers (NYI) peers *list.List } func NewServer() (*Server, error) { - db, err := NewDatabase() + db, err := NewLDBDatabase() if err != nil { return nil, err } diff --git a/trie.go b/trie.go new file mode 100644 index 000000000..d9d5dba5a --- /dev/null +++ b/trie.go @@ -0,0 +1,50 @@ +package main + +// Database interface +type Database interface { + Put(key []byte, value []byte) + Get(key []byte) ([]byte, error) +} + +type Trie struct { + root string + db Database +} + +func NewTrie(db Database) *Trie { + return &Trie{db: db, root: ""} +} + +func (t *Trie) Update(key string, value string) { + k := CompactHexDecode(key) + + t.root = t.UpdateState(t.root, k, value) +} + +func (t *Trie) Get(key []byte) ([]byte, error) { + return nil, nil +} + +// Inserts a new sate or delete a state based on the value +func (t *Trie) UpdateState(node string, key []int, value string) string { + if value != "" { + return t.InsertState(node, ""/*key*/, value) + } else { + // delete it + } + + return "" +} + +func (t *Trie) InsertState(node, key, value string) string { + return "" +} + +func (t *Trie) Put(node []byte) []byte { + enc := Encode(node) + sha := Sha256Bin(enc) + + t.db.Put([]byte(sha), enc) + + return sha +} diff --git a/trie.test.go b/trie.test.go new file mode 100644 index 000000000..0a6054cf0 --- /dev/null +++ b/trie.test.go @@ -0,0 +1,61 @@ +package main + +import ( + "testing" +) + +type MemDatabase struct { + db map[string][]byte + trie *Trie +} + +func NewMemDatabase() (*MemDatabase, error) { + db := &MemDatabase{db: make(map[string][]byte)} + + db.trie = NewTrie(db) + + return db, nil +} + +func (db *MemDatabase) Put(key []byte, value []byte) { + db.db[string(key)] = value +} + +func (db *MemDatabase) Get(key []byte) ([]byte, error) { + return db.db[string(key)], nil +} + +func TestTriePut(t *testing.T) { + db, err := NewMemDatabase() + + if err != nil { + t.Error("Error starting db") + } + + key := db.trie.Put([]byte("testing node")) + + data, err := db.Get(key) + if err != nil { + t.Error("Nothing at node") + } + + s, _ := Decode(data, 0) + if str, ok := s.([]byte); ok { + if string(str) != "testing node" { + t.Error("Wrong value node", str) + } + } else { + t.Error("Invalid return type") + } +} + +func TestTrieUpdate(t *testing.T) { + db, err := NewMemDatabase() + + if err != nil { + t.Error("Error starting db") + } + + db.trie.Update("test", "test") +} + -- cgit v1.2.3 From 276fa6c799b08bc41efd2d26a83eef678e8c943b Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 03:06:39 +0100 Subject: Working Trie --- trie.go | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- trie.test.go | 61 --------------------- trie_test.go | 69 ++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 72 deletions(-) delete mode 100644 trie.test.go create mode 100644 trie_test.go diff --git a/trie.go b/trie.go index d9d5dba5a..dc83c7cfe 100644 --- a/trie.go +++ b/trie.go @@ -1,5 +1,9 @@ package main +import ( + "fmt" +) + // Database interface type Database interface { Put(key []byte, value []byte) @@ -11,24 +15,79 @@ type Trie struct { db Database } -func NewTrie(db Database) *Trie { +func NewTrie(db Database, root string) *Trie { return &Trie{db: db, root: ""} } +func (t *Trie) Put(node interface{}) []byte { + //if s, ok := node.([]string); ok { + // PrintSlice(s) + //} + enc := Encode(node) + sha := Sha256Bin(enc) + + t.db.Put([]byte(sha), enc) + + return sha +} + func (t *Trie) Update(key string, value string) { k := CompactHexDecode(key) t.root = t.UpdateState(t.root, k, value) } -func (t *Trie) Get(key []byte) ([]byte, error) { - return nil, nil +func (t *Trie) Get(key string) string { + k := CompactHexDecode(key) + + return t.GetState(t.root, k) +} + +// Returns the state of an object +func (t *Trie) GetState(node string, key []int) string { + //if Debug { fmt.Println("get =", key) } + + // Return the node if key is empty (= found) + if len(key) == 0 || node == "" { + return node + } + + // Fetch the encoded node from the db + n, err := t.db.Get([]byte(node)) + if err != nil { fmt.Println("Error in GetState for node", node, "with key", key); return "" } + + // Decode it + currentNode := DecodeNode(n) + + if len(currentNode) == 0 { + return "" + } else if len(currentNode) == 2 { + // Decode the key + k := CompactDecode(currentNode[0]) + v := currentNode[1] + + //fmt.Println(k, key) + //fmt.Printf("k1:%v\nk2:%v\n", k, key[:len(k)-1]) + + //fmt.Println(len(key), ">=", len(k)-1, "&&", k, key[:len(k)]) + if len(key) >= len(k) && CompareIntSlice(k, key[:len(k)]) { + return t.GetState(v, key[len(k):]) + } else { + return "" + } + } else if len(currentNode) == 17 { + return t.GetState(currentNode[key[0]], key[1:]) + } + + // It shouldn't come this far + fmt.Println("GetState unexpected return") + return "" } // Inserts a new sate or delete a state based on the value func (t *Trie) UpdateState(node string, key []int, value string) string { if value != "" { - return t.InsertState(node, ""/*key*/, value) + return t.InsertState(node, key, value) } else { // delete it } @@ -36,15 +95,104 @@ func (t *Trie) UpdateState(node string, key []int, value string) string { return "" } -func (t *Trie) InsertState(node, key, value string) string { - return "" +func DecodeNode(data []byte) []string { + dec, _ := Decode(data, 0) + if slice, ok := dec.([]interface{}); ok { + strSlice := make([]string, len(slice)) + + for i, s := range slice { + if str, ok := s.([]byte); ok { + strSlice[i] = string(str) + } + } + + return strSlice + } + + return nil } -func (t *Trie) Put(node []byte) []byte { - enc := Encode(node) - sha := Sha256Bin(enc) +func (t *Trie) PrintNode(n string) { + data, _ := t.db.Get([]byte(n)) + d := DecodeNode(data) + PrintSlice(d) +} - t.db.Put([]byte(sha), enc) +func PrintSlice(slice []string) { + fmt.Printf("[") + for i, val := range slice { + fmt.Printf("%q", val) + if i != len(slice)-1 { fmt.Printf(",") } + } + fmt.Printf("]\n") +} - return sha +func (t *Trie) InsertState(node string, key []int, value string) string { + //if Debug { fmt.Println("insrt", key, value, "node:", node) } + + if len(key) == 0 { + return value + } + + //fmt.Println(node) + // Root node! + if node == "" { + newNode := []string{ CompactEncode(key), value } + + return string(t.Put(newNode)) + } + + // Fetch the encoded node from the db + n, err := t.db.Get([]byte(node)) + if err != nil { fmt.Println("Error InsertState", err); return "" } + + // Decode it + currentNode := DecodeNode(n) + // Check for "special" 2 slice type node + if len(currentNode) == 2 { + // Decode the key + k := CompactDecode(currentNode[0]) + v := currentNode[1] + + // Matching key pair (ie. there's already an object with this key) + if CompareIntSlice(k, key) { + return string(t.Put([]string{ CompactEncode(key), value })) + } + + var newHash string + matchingLength := MatchingNibbleLength(key, k) + if matchingLength == len(k) { + // Insert the hash, creating a new node + newHash = t.InsertState(v, key[matchingLength:], value) + } else { + // Expand the 2 length slice to a 17 length slice + oldNode := t.InsertState("", k[matchingLength+1:], v) + newNode := t.InsertState("", key[matchingLength+1:], value) + // Create an expanded slice + scaledSlice := make([]string, 17) + // Set the copied and new node + scaledSlice[k[matchingLength]] = oldNode + scaledSlice[key[matchingLength]] = newNode + + newHash = string(t.Put(scaledSlice)) + } + + if matchingLength == 0 { + // End of the chain, return + return newHash + } else { + newNode := []string{ CompactEncode(key[:matchingLength]), newHash } + return string(t.Put(newNode)) + } + } else { + // Copy the current node over to the new node and replace the first nibble in the key + newNode := make([]string, 17); copy(newNode, currentNode) + newNode[key[0]] = t.InsertState(currentNode[key[0]], key[1:], value) + + return string(t.Put(newNode)) + } + + return "" } + + diff --git a/trie.test.go b/trie.test.go deleted file mode 100644 index 0a6054cf0..000000000 --- a/trie.test.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "testing" -) - -type MemDatabase struct { - db map[string][]byte - trie *Trie -} - -func NewMemDatabase() (*MemDatabase, error) { - db := &MemDatabase{db: make(map[string][]byte)} - - db.trie = NewTrie(db) - - return db, nil -} - -func (db *MemDatabase) Put(key []byte, value []byte) { - db.db[string(key)] = value -} - -func (db *MemDatabase) Get(key []byte) ([]byte, error) { - return db.db[string(key)], nil -} - -func TestTriePut(t *testing.T) { - db, err := NewMemDatabase() - - if err != nil { - t.Error("Error starting db") - } - - key := db.trie.Put([]byte("testing node")) - - data, err := db.Get(key) - if err != nil { - t.Error("Nothing at node") - } - - s, _ := Decode(data, 0) - if str, ok := s.([]byte); ok { - if string(str) != "testing node" { - t.Error("Wrong value node", str) - } - } else { - t.Error("Invalid return type") - } -} - -func TestTrieUpdate(t *testing.T) { - db, err := NewMemDatabase() - - if err != nil { - t.Error("Error starting db") - } - - db.trie.Update("test", "test") -} - diff --git a/trie_test.go b/trie_test.go new file mode 100644 index 000000000..dac2333c9 --- /dev/null +++ b/trie_test.go @@ -0,0 +1,69 @@ +package main + +import ( + "testing" + "encoding/hex" +) + +type MemDatabase struct { + db map[string][]byte +} + +func NewMemDatabase() (*MemDatabase, error) { + db := &MemDatabase{db: make(map[string][]byte)} + + return db, nil +} + +func (db *MemDatabase) Put(key []byte, value []byte) { + db.db[string(key)] = value +} + +func (db *MemDatabase) Get(key []byte) ([]byte, error) { + return db.db[string(key)], nil +} + +func TestTriePut(t *testing.T) { + db, err := NewMemDatabase() + trie := NewTrie(db, "") + + if err != nil { + t.Error("Error starting db") + } + + key := trie.Put([]byte("testing node")) + + data, err := db.Get(key) + if err != nil { + t.Error("Nothing at node") + } + + s, _ := Decode(data, 0) + if str, ok := s.([]byte); ok { + if string(str) != "testing node" { + t.Error("Wrong value node", str) + } + } else { + t.Error("Invalid return type") + } +} + +func TestTrieUpdate(t *testing.T) { + db, err := NewMemDatabase() + trie := NewTrie(db, "") + + if err != nil { + t.Error("Error starting db") + } + + + trie.Update("doe", "reindeer") + trie.Update("dog", "puppy") + trie.Update("dogglesworth", "cat") + root := hex.EncodeToString([]byte(trie.root)) + req := "e378927bfc1bd4f01a2e8d9f59bd18db8a208bb493ac0b00f93ce51d4d2af76c" + if root != req { + t.Error("trie.root do not match, expected", req, "got", root) + } +} + -- cgit v1.2.3 From 34d62c380ef6327c170639af6221a82610efa25c Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 03:06:52 +0100 Subject: Encoding helpers for trie --- encoding.go | 16 ++++++++++++++++ encoding_test.go | 12 ------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/encoding.go b/encoding.go index ca30b47c9..d77303817 100644 --- a/encoding.go +++ b/encoding.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "strings" + _"fmt" ) func CompactEncode(hexSlice []int) string { @@ -32,6 +33,21 @@ func CompactEncode(hexSlice []int) string { return buff.String() } +func CompactDecode(str string) []int { + base := CompactHexDecode(str) + base = base[:len(base)-1] + if base[0] >= 2 {// && base[len(base)-1] != 16 { + base = append(base, 16) + } + if base[0] % 2 == 1 { + base = base[1:] + } else { + base = base[2:] + } + + return base +} + func CompactHexDecode(str string) []int { base := "0123456789abcdef" hexSlice := make([]int, 0) diff --git a/encoding_test.go b/encoding_test.go index 63f7878bf..b66f702ac 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -27,18 +27,6 @@ func TestCompactEncode(t *testing.T) { } } -// Helper function for comparing slices -func CompareIntSlice(a, b []int) bool { - if len(a) != len(b) { - return false - } - for i, v := range a { - if v != b[i] { - return false - } - } - return true -} func TestCompactHexDecode(t *testing.T) { exp := []int{7, 6, 6, 5, 7, 2, 6, 2, 16} -- cgit v1.2.3 From 79eaa6f2bae8c8354807a9b32b889295b0de4b6a Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 03:07:04 +0100 Subject: Removed old serialization --- serialization.go | 63 --------------------------------------------------- serialization_test.go | 20 ---------------- 2 files changed, 83 deletions(-) delete mode 100644 serialization.go delete mode 100644 serialization_test.go diff --git a/serialization.go b/serialization.go deleted file mode 100644 index 5a92a434f..000000000 --- a/serialization.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "math" - "bytes" -) - -func ToBinary(x int, bytes int) string { - if bytes == 0 { - return "" - } else { - return ToBinary(int(x / 256), bytes - 1) + string(x % 256) - } -} - -func NumToVarInt(x int) string { - if x < 253 { - return string(x) - } else if x < int(math.Pow(2,16)) { - return string(253) + ToBinary(x, 2) - } else if x < int(math.Pow(2,32)) { - return string(253) + ToBinary(x, 4) - } else { - return string(253) + ToBinary(x, 8) - } -} - -func RlpEncode(object interface{}) string { - if str, ok := object.(string); ok { - return "\x00" + NumToVarInt(len(str)) + str - } else if num, ok := object.(uint32); ok { - return RlpEncode(Uitoa(num)) - } else if byt, ok := object.([]byte); ok { - return RlpEncode(string(byt)) - } else if slice, ok := object.([]interface{}); ok { - var buffer bytes.Buffer - for _, val := range slice { - if v, ok := val.(string); ok { - buffer.WriteString(RlpEncode(v)) - } else { - buffer.WriteString(RlpEncode(val)) - } - } - - return "\x01" + RlpEncode(len(buffer.String())) + buffer.String() - } else if slice, ok := object.([]string); ok { - - // FIXME this isn't dry. Fix this - var buffer bytes.Buffer - for _, val := range slice { - buffer.WriteString(RlpEncode(val)) - } - return "\x01" + RlpEncode(len(buffer.String())) + buffer.String() - } - - return "" -} - -type RlpSerializer interface { - MarshalRlp() []byte - UnmarshalRlp([]byte) -} - diff --git a/serialization_test.go b/serialization_test.go deleted file mode 100644 index adb1b6e2d..000000000 --- a/serialization_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "testing" - "fmt" -) - -func TestRlpEncode(t *testing.T) { - strRes := "\x00\x03dog" - str := RlpEncode("dog") - if str != strRes { - t.Error(fmt.Sprintf("Expected %q, got %q", strRes, str)) - } - - sliceRes := "\x01\x00\x03dog\x00\x03god\x00\x03cat" - slice := RlpEncode([]string{"dog", "god", "cat"}) - if slice != sliceRes { - t.Error(fmt.Sprintf("Expected %q, got %q", sliceRes, slice)) - } -} -- cgit v1.2.3 From 35e4d746418386508247319beeeb50619204af03 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 03:07:29 +0100 Subject: Added helper methods for slice comparison and nibble matching --- util.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/util.go b/util.go index fc06673d2..512a6f3d7 100644 --- a/util.go +++ b/util.go @@ -4,6 +4,8 @@ import ( "strconv" "crypto/sha256" "encoding/hex" + _"fmt" + _"math" ) func Uitoa(i uint32) string { @@ -21,3 +23,28 @@ func Sha256Bin(data []byte) []byte { return hash[:] } + +// Helper function for comparing slices +func CompareIntSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + +// Returns the amount of nibbles that match each other from 0 ... +func MatchingNibbleLength(a, b []int) int { + i := 0 + for CompareIntSlice(a[:i+1], b[:i+1]) && i < len(b) { + i+=1 + } + + //fmt.Println(a, b, i-1) + + return i +} -- cgit v1.2.3 From 52952e274d791991c6c368d135234068968981bc Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 03:07:49 +0100 Subject: Updated database interface and eth test code --- database.go | 2 +- ethereum.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/database.go b/database.go index c056e70af..b147756b6 100644 --- a/database.go +++ b/database.go @@ -33,7 +33,7 @@ func NewLDBDatabase() (*LDBDatabase, error) { } func (db *LDBDatabase) Bootstrap() error { - db.trie = NewTrie(db) + //db.trie = NewTrie(db) return nil } diff --git a/ethereum.go b/ethereum.go index d9e1fd314..e260e6c0e 100644 --- a/ethereum.go +++ b/ethereum.go @@ -6,7 +6,7 @@ import ( "os/signal" ) -const Debug = false +const Debug = true // Register interrupt handlers so we can stop the server func RegisterInterupts(s *Server) { -- cgit v1.2.3 From 5da78427d0d5910c2ea0c0fc6ca84078f327e933 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 13:36:48 +0100 Subject: Added db query interface and moved memory database --- db_query_interface.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ memory_database.go | 25 +++++++++++++ testing.go | 36 +++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 db_query_interface.go create mode 100644 memory_database.go create mode 100644 testing.go diff --git a/db_query_interface.go b/db_query_interface.go new file mode 100644 index 000000000..1bcf1e72d --- /dev/null +++ b/db_query_interface.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "bufio" + "strings" + "os" + "errors" + "encoding/hex" +) + +type DbInterface struct { + db *MemDatabase + trie *Trie +} + +func NewDBInterface() *DbInterface { + db, _ := NewMemDatabase() + trie := NewTrie(db, "") + + return &DbInterface{db: db, trie: trie} +} + +func (i *DbInterface) ValidateInput(action string, argumentLength int) error { + err := false + var expArgCount int + + switch { + case action == "update" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "get" && argumentLength != 1: + err = true + expArgCount = 1 + case (action == "quit" || action == "exit") && argumentLength != 0: + err = true + expArgCount = 0 + } + + if err { + return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) + } else { + return nil + } +} + +func (i *DbInterface) ParseInput(input string) bool { + scanner := bufio.NewScanner(strings.NewReader(input)) + scanner.Split(bufio.ScanWords) + + count := 0 + var tokens []string + for scanner.Scan() { + count++ + tokens = append(tokens, scanner.Text()) + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading input:", err) + } + + if len(tokens) == 0 { return true } + + err := i.ValidateInput(tokens[0], count-1) + if err != nil { + fmt.Println(err) + } else { + switch tokens[0] { + case "update": + i.trie.Update(tokens[1], tokens[2]) + + fmt.Println(hex.EncodeToString([]byte(i.trie.root))) + case "get": + fmt.Println(i.trie.Get(tokens[1])) + case "exit", "quit", "q": + return false + default: + fmt.Println("Unknown command:", tokens[0]) + } + } + + return true +} + +func (i *DbInterface) Start() { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("db >>> ") + str, _, err := reader.ReadLine() + if err != nil { + fmt.Println("Error reading input", err) + } else { + if !i.ParseInput(string(str)) { + return + } + } + } +} diff --git a/memory_database.go b/memory_database.go new file mode 100644 index 000000000..fc40f76f3 --- /dev/null +++ b/memory_database.go @@ -0,0 +1,25 @@ +package main + +import ( +) + +/* + * This is a test memory database. Do not use for any production it does not get persisted + */ +type MemDatabase struct { + db map[string][]byte +} + +func NewMemDatabase() (*MemDatabase, error) { + db := &MemDatabase{db: make(map[string][]byte)} + + return db, nil +} + +func (db *MemDatabase) Put(key []byte, value []byte) { + db.db[string(key)] = value +} + +func (db *MemDatabase) Get(key []byte) ([]byte, error) { + return db.db[string(key)], nil +} diff --git a/testing.go b/testing.go new file mode 100644 index 000000000..5d0b818a9 --- /dev/null +++ b/testing.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" +) + +func Testing() { + bm := NewBlockManager() + + tx := NewTransaction("\x00", 20, []string{ + "SET 10 6", + "LD 10 10", + "LT 10 1 20", + "SET 255 7", + "JMPI 20 255", + "STOP", + "SET 30 200", + "LD 30 31", + "SET 255 22", + "JMPI 31 255", + "SET 255 15", + "JMP 255", + }) + txData := tx.MarshalRlp() + + copyTx := &Transaction{} + copyTx.UnmarshalRlp(txData) + + tx2 := NewTransaction("\x00", 20, []string{"SET 10 6", "LD 10 10"}) + + blck := CreateBlock([]*Transaction{tx2, tx}) + + bm.ProcessBlock( blck ) + + fmt.Println("GenesisBlock:", GenisisBlock, "hashed", GenisisBlock.Hash()) +} -- cgit v1.2.3 From 584f9be7f423b84e3dbae43edd41d071a600622c Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 13:37:00 +0100 Subject: Moved some testing code --- ethereum.go | 42 +++++++++++++++--------------------------- trie_test.go | 18 ------------------ 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/ethereum.go b/ethereum.go index e260e6c0e..ac9690e41 100644 --- a/ethereum.go +++ b/ethereum.go @@ -4,10 +4,18 @@ import ( "fmt" "os" "os/signal" + "flag" ) const Debug = true +var StartDBQueryInterface bool +func Init() { + flag.BoolVar(&StartDBQueryInterface, "db", false, "start db query interface") + + flag.Parse() +} + // Register interrupt handlers so we can stop the server func RegisterInterupts(s *Server) { // Buffered chan of one is enough @@ -26,32 +34,12 @@ func RegisterInterupts(s *Server) { func main() { InitFees() - bm := NewBlockManager() - - tx := NewTransaction("\x00", 20, []string{ - "SET 10 6", - "LD 10 10", - "LT 10 1 20", - "SET 255 7", - "JMPI 20 255", - "STOP", - "SET 30 200", - "LD 30 31", - "SET 255 22", - "JMPI 31 255", - "SET 255 15", - "JMP 255", - }) - txData := tx.MarshalRlp() - - copyTx := &Transaction{} - copyTx.UnmarshalRlp(txData) - - tx2 := NewTransaction("\x00", 20, []string{"SET 10 6", "LD 10 10"}) - - blck := CreateBlock([]*Transaction{tx2, tx}) - - bm.ProcessBlock( blck ) + Init() - fmt.Println("GenesisBlock:", GenisisBlock, "hashed", GenisisBlock.Hash()) + if StartDBQueryInterface { + dbInterface := NewDBInterface() + dbInterface.Start() + } else { + Testing() + } } diff --git a/trie_test.go b/trie_test.go index dac2333c9..6dbe040ee 100644 --- a/trie_test.go +++ b/trie_test.go @@ -5,24 +5,6 @@ import ( "encoding/hex" ) -type MemDatabase struct { - db map[string][]byte -} - -func NewMemDatabase() (*MemDatabase, error) { - db := &MemDatabase{db: make(map[string][]byte)} - - return db, nil -} - -func (db *MemDatabase) Put(key []byte, value []byte) { - db.db[string(key)] = value -} - -func (db *MemDatabase) Get(key []byte) ([]byte, error) { - return db.db[string(key)], nil -} - func TestTriePut(t *testing.T) { db, err := NewMemDatabase() trie := NewTrie(db, "") -- cgit v1.2.3 From df6f7e8a0e43fbee90af8531c4dd4279b8f1e120 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 13:52:26 +0100 Subject: updated readme --- README | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README b/README index 9d08d7f8e..92ca75b5a 100644 --- a/README +++ b/README @@ -1,16 +1,11 @@ This is the Go implementation of the Ethereum protocol. It's far from being finished. - go get https://github.com/ethereum/go-ethereum +```go get https://github.com/ethereum/go-ethereum``` -The Python reference implementation can be found at https://github.com/ethereum/pyethereum +** The Python reference implementation can be found at https://github.com/ethereum/pyethereum ** -More information about the protocol: -* http://vitalik.ca/ethereum.html -* http://vitalik.ca/ethereum/spec.html -* http://vitalik.ca/ethereum/patricia.html -* http://vitalik.ca/ethereum/dagger.html -* http://vitalik.ca/ethereum/rlp.html +## Command line options -# TODO +-db will launch the db query tool. -Fix this todo file! +Supported arguments `update` and `get` -- cgit v1.2.3 From 30f3b4d4e435aed02033d97d69361c78db7d6d7f Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 14:06:00 +0100 Subject: Added a few more comments and cleaned up code --- trie.go | 111 +++++++++++++++++++++++++++++++++------------------------------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/trie.go b/trie.go index dc83c7cfe..83a3073ae 100644 --- a/trie.go +++ b/trie.go @@ -10,6 +10,38 @@ type Database interface { Get(key []byte) ([]byte, error) } +/* + * Trie helper functions + */ +// Helper function for printing out the raw contents of a slice +func PrintSlice(slice []string) { + fmt.Printf("[") + for i, val := range slice { + fmt.Printf("%q", val) + if i != len(slice)-1 { fmt.Printf(",") } + } + fmt.Printf("]\n") +} + +// RLP Decodes a node in to a [2] or [17] string slice +func DecodeNode(data []byte) []string { + dec, _ := Decode(data, 0) + if slice, ok := dec.([]interface{}); ok { + strSlice := make([]string, len(slice)) + + for i, s := range slice { + if str, ok := s.([]byte); ok { + strSlice[i] = string(str) + } + } + + return strSlice + } + + return nil +} + +// A (modified) Radix Trie implementation type Trie struct { root string db Database @@ -19,18 +51,9 @@ func NewTrie(db Database, root string) *Trie { return &Trie{db: db, root: ""} } -func (t *Trie) Put(node interface{}) []byte { - //if s, ok := node.([]string); ok { - // PrintSlice(s) - //} - enc := Encode(node) - sha := Sha256Bin(enc) - - t.db.Put([]byte(sha), enc) - - return sha -} - +/* + * Public (query) interface functions + */ func (t *Trie) Update(key string, value string) { k := CompactHexDecode(key) @@ -43,10 +66,29 @@ func (t *Trie) Get(key string) string { return t.GetState(t.root, k) } +/* + * State functions (shouldn't be needed directly). + */ + +// Wrapper around the regular db "Put" which generates a key and value +func (t *Trie) Put(node interface{}) []byte { + enc := Encode(node) + sha := Sha256Bin(enc) + + t.db.Put([]byte(sha), enc) + + return sha +} + +// Helper function for printing a node (using fetch, decode and slice printing) +func (t *Trie) PrintNode(n string) { + data, _ := t.db.Get([]byte(n)) + d := DecodeNode(data) + PrintSlice(d) +} + // Returns the state of an object func (t *Trie) GetState(node string, key []int) string { - //if Debug { fmt.Println("get =", key) } - // Return the node if key is empty (= found) if len(key) == 0 || node == "" { return node @@ -66,10 +108,6 @@ func (t *Trie) GetState(node string, key []int) string { k := CompactDecode(currentNode[0]) v := currentNode[1] - //fmt.Println(k, key) - //fmt.Printf("k1:%v\nk2:%v\n", k, key[:len(k)-1]) - - //fmt.Println(len(key), ">=", len(k)-1, "&&", k, key[:len(k)]) if len(key) >= len(k) && CompareIntSlice(k, key[:len(k)]) { return t.GetState(v, key[len(k):]) } else { @@ -95,47 +133,13 @@ func (t *Trie) UpdateState(node string, key []int, value string) string { return "" } -func DecodeNode(data []byte) []string { - dec, _ := Decode(data, 0) - if slice, ok := dec.([]interface{}); ok { - strSlice := make([]string, len(slice)) - - for i, s := range slice { - if str, ok := s.([]byte); ok { - strSlice[i] = string(str) - } - } - - return strSlice - } - - return nil -} - -func (t *Trie) PrintNode(n string) { - data, _ := t.db.Get([]byte(n)) - d := DecodeNode(data) - PrintSlice(d) -} - -func PrintSlice(slice []string) { - fmt.Printf("[") - for i, val := range slice { - fmt.Printf("%q", val) - if i != len(slice)-1 { fmt.Printf(",") } - } - fmt.Printf("]\n") -} func (t *Trie) InsertState(node string, key []int, value string) string { - //if Debug { fmt.Println("insrt", key, value, "node:", node) } - if len(key) == 0 { return value } - //fmt.Println(node) - // Root node! + // New node if node == "" { newNode := []string{ CompactEncode(key), value } @@ -195,4 +199,3 @@ func (t *Trie) InsertState(node string, key []int, value string) string { return "" } - -- cgit v1.2.3 From 65f00656f224546b1b3ed3cd8217236963f770df Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 14:11:56 +0100 Subject: Help text --- db_query_interface.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/db_query_interface.go b/db_query_interface.go index 1bcf1e72d..572dd6684 100644 --- a/db_query_interface.go +++ b/db_query_interface.go @@ -32,9 +32,6 @@ func (i *DbInterface) ValidateInput(action string, argumentLength int) error { case action == "get" && argumentLength != 1: err = true expArgCount = 1 - case (action == "quit" || action == "exit") && argumentLength != 0: - err = true - expArgCount = 0 } if err { @@ -73,6 +70,11 @@ func (i *DbInterface) ParseInput(input string) bool { fmt.Println(i.trie.Get(tokens[1])) case "exit", "quit", "q": return false + case "help": + fmt.Println(`query commands: +update KEY VALUE +get KEY +`) default: fmt.Println("Unknown command:", tokens[0]) } @@ -82,6 +84,7 @@ func (i *DbInterface) ParseInput(input string) bool { } func (i *DbInterface) Start() { + fmt.Printf("DB Query tool. Type (help) for help\n") reader := bufio.NewReader(os.Stdin) for { fmt.Printf("db >>> ") -- cgit v1.2.3 From 61d67f2ae965a9a1113084f2352e2c2dd97ab9a7 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 14:12:30 +0100 Subject: moved to markdown --- README | 11 ----------- README.md | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index 92ca75b5a..000000000 --- a/README +++ /dev/null @@ -1,11 +0,0 @@ -This is the Go implementation of the Ethereum protocol. It's far from being finished. - -```go get https://github.com/ethereum/go-ethereum``` - -** The Python reference implementation can be found at https://github.com/ethereum/pyethereum ** - -## Command line options - --db will launch the db query tool. - -Supported arguments `update` and `get` diff --git a/README.md b/README.md new file mode 100644 index 000000000..92ca75b5a --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +This is the Go implementation of the Ethereum protocol. It's far from being finished. + +```go get https://github.com/ethereum/go-ethereum``` + +** The Python reference implementation can be found at https://github.com/ethereum/pyethereum ** + +## Command line options + +-db will launch the db query tool. + +Supported arguments `update` and `get` -- cgit v1.2.3 From 5b3d4fae6e03e5471a10c653fc0b016cc5e5dcfa Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 1 Jan 2014 15:49:38 +0100 Subject: Work in progress external test runner --- db_query_interface.go | 12 +++++++++--- test_runner.go | 35 +++++++++++++++++++++++++++++++++++ test_runner_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 test_runner.go create mode 100644 test_runner_test.go diff --git a/db_query_interface.go b/db_query_interface.go index 572dd6684..11137e4c2 100644 --- a/db_query_interface.go +++ b/db_query_interface.go @@ -68,12 +68,18 @@ func (i *DbInterface) ParseInput(input string) bool { fmt.Println(hex.EncodeToString([]byte(i.trie.root))) case "get": fmt.Println(i.trie.Get(tokens[1])) + case "root": + fmt.Println(hex.EncodeToString([]byte(i.trie.root))) + case "rawroot": + fmt.Println(i.trie.root) case "exit", "quit", "q": return false case "help": - fmt.Println(`query commands: -update KEY VALUE -get KEY + fmt.Printf(`QUERY COMMANDS: +update KEY VALUE - Updates/Creates a new value for the given key +get KEY - Retrieves the given key +root - Prints the hex encoded merkle root +rawroot - Prints the raw merkle root `) default: fmt.Println("Unknown command:", tokens[0]) diff --git a/test_runner.go b/test_runner.go new file mode 100644 index 000000000..da93533dd --- /dev/null +++ b/test_runner.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "testing" + "encoding/json" +) + +type TestSource struct { + Inputs map[string]string + Expectation string +} + +func NewTestSource(source string) *TestSource { + s := &TestSource{} + err := json.Unmarshal([]byte(source), s) + if err != nil { + fmt.Println(err) + } + + return s +} + +type TestRunner struct { + source *TestSource +} + +func NewTestRunner(t *testing.T) *TestRunner { + return &TestRunner{} +} + +func (runner *TestRunner) RunFromString(input string, Cb func(*TestSource)) { + source := NewTestSource(input) + Cb(source) +} diff --git a/test_runner_test.go b/test_runner_test.go new file mode 100644 index 000000000..190bf3caf --- /dev/null +++ b/test_runner_test.go @@ -0,0 +1,31 @@ +package main + +import ( + _"fmt" + "testing" + "encoding/hex" +) + +var testsource = `{"Inputs":{ + "doe": "reindeer", + "dog": "puppy", + "dogglesworth": "cat" + }, + "Expectation":"e378927bfc1bd4f01a2e8d9f59bd18db8a208bb493ac0b00f93ce51d4d2af76c" +}` + +func TestTestRunner(t *testing.T) { + db, _ := NewMemDatabase() + trie := NewTrie(db, "") + + runner := NewTestRunner(t) + runner.RunFromString(testsource, func(source *TestSource) { + for key, value := range source.Inputs { + trie.Update(key, value) + } + + if hex.EncodeToString([]byte(trie.root)) != source.Expectation { + t.Error("trie root did not match") + } + }) +} -- cgit v1.2.3 From 9df4c745119b3ed10a7ad17887e8dd9cac249af7 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 2 Jan 2014 23:02:24 +0100 Subject: WIP rewrite vm --- block.go | 38 ++++++++++++++++++++++++++----- block_manager.go | 6 ++--- parsing.go | 3 +++ parsing_test.go | 13 ++--------- testing.go | 23 ++++++++----------- trie_test.go | 8 +++++++ vm.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- vm_test.go | 27 ++++++++++++++++++++++ 8 files changed, 150 insertions(+), 36 deletions(-) create mode 100644 vm_test.go diff --git a/block.go b/block.go index 9aa3c8bb5..146328471 100644 --- a/block.go +++ b/block.go @@ -15,6 +15,7 @@ type Block struct { uncles []*Block coinbase string // state xxx + state *Trie difficulty uint32 // Creation time time int64 @@ -34,7 +35,7 @@ func NewBlock(raw []byte) *Block { } // Creates a new block. This is currently for testing -func CreateBlock(/* TODO use raw data */transactions []*Transaction) *Block { +func CreateTestBlock(/* TODO use raw data */transactions []*Transaction) *Block { block := &Block{ // Slice of transactions to include in this block transactions: transactions, @@ -49,12 +50,32 @@ func CreateBlock(/* TODO use raw data */transactions []*Transaction) *Block { return block } +func CreateBlock(root string, num int, prevHash string, base string, difficulty int, nonce int, extra string, txes []*Transaction) *Block { + block := &Block{ + // Slice of transactions to include in this block + transactions: txes, + number: uint32(num), + prevHash: prevHash, + coinbase: base, + difficulty: uint32(difficulty), + nonce: uint32(nonce), + time: time.Now().Unix(), + extra: extra, + } + block.state = NewTrie(Db, root) + for _, tx := range txes { + block.state.Update(tx.recipient, string(tx.MarshalRlp())) + } + + return block +} + func (block *Block) Update() { } // Returns a hash of the block -func (block *Block) Hash() string { - return Sha256Hex(block.MarshalRlp()) +func (block *Block) Hash() []byte { + return Sha256Bin(block.MarshalRlp()) } func (block *Block) MarshalRlp() []byte { @@ -73,7 +94,7 @@ func (block *Block) MarshalRlp() []byte { "", block.coinbase, // root state - "", + block.state.root, // Sha of tx string(Sha256Bin([]byte(Encode(encTx)))), block.difficulty, @@ -99,7 +120,7 @@ func (block *Block) UnmarshalRlp(data []byte) { block.number = uint32(number) } - if prevHash, ok := header[1].([]byte); ok { + if prevHash, ok := header[1].([]uint8); ok { block.prevHash = string(prevHash) } @@ -109,7 +130,12 @@ func (block *Block) UnmarshalRlp(data []byte) { block.coinbase = string(coinbase) } - // state is header[header[4] + if state, ok := header[4].([]uint8); ok { + // XXX The database is currently a global variable defined in testing.go + // This will eventually go away and the database will grabbed from the public server + // interface + block.state = NewTrie(Db, string(state)) + } // sha is header[5] diff --git a/block_manager.go b/block_manager.go index a60d4340d..0ef5d1108 100644 --- a/block_manager.go +++ b/block_manager.go @@ -33,7 +33,7 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { // Process each transaction/contract for _, tx := range block.transactions { - go bm.ProcessTransaction(tx, lockChan) + go bm.ProcessTransaction(tx, block, lockChan) } // Wait for all Tx to finish processing @@ -44,9 +44,9 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { return nil } -func (bm *BlockManager) ProcessTransaction(tx *Transaction, lockChan chan bool) { +func (bm *BlockManager) ProcessTransaction(tx *Transaction, block *Block, lockChan chan bool) { if tx.recipient == "\x00" { - bm.vm.RunTransaction(tx, func(opType OpType) bool { + bm.vm.RunTransaction(tx, block, func(opType OpType) bool { // TODO calculate fees return true // Continue diff --git a/parsing.go b/parsing.go index d5dcce04f..765950a4c 100644 --- a/parsing.go +++ b/parsing.go @@ -11,6 +11,8 @@ import ( // Op codes var OpCodes = map[string]string{ "STOP": "0", + "PSH": "30", // 0x30 + /* "ADD": "16", // 0x10 "SUB": "17", // 0x11 "MUL": "18", // 0x12 @@ -48,6 +50,7 @@ var OpCodes = map[string]string{ "BLKHASH": "145", // 0x91 "COINBASE": "146", // 0x92 "SUICIDE": "255", // 0xff + */ } diff --git a/parsing_test.go b/parsing_test.go index 93fe434b9..fa319e7bf 100644 --- a/parsing_test.go +++ b/parsing_test.go @@ -19,22 +19,13 @@ func TestCompile(t *testing.T) { } func TestValidInstr(t *testing.T) { + /* op, args, err := Instr("68163") if err != nil { t.Error("Error decoding instruction") } + */ - if op != oSET { - t.Error("Expected op to be 43, got:", op) - } - - if args[0] != "10" { - t.Error("Expect args[0] to be 10, got:", args[0]) - } - - if args[1] != "1" { - t.Error("Expected args[1] to be 1, got:", args[1]) - } } func TestInvalidInstr(t *testing.T) { diff --git a/testing.go b/testing.go index 5d0b818a9..07e8c362f 100644 --- a/testing.go +++ b/testing.go @@ -4,22 +4,17 @@ import ( "fmt" ) +// This will eventually go away +var Db *MemDatabase + func Testing() { + db, _ := NewMemDatabase() + Db = db + bm := NewBlockManager() tx := NewTransaction("\x00", 20, []string{ - "SET 10 6", - "LD 10 10", - "LT 10 1 20", - "SET 255 7", - "JMPI 20 255", - "STOP", - "SET 30 200", - "LD 30 31", - "SET 255 22", - "JMPI 31 255", - "SET 255 15", - "JMP 255", + "PSH 10", }) txData := tx.MarshalRlp() @@ -28,9 +23,9 @@ func Testing() { tx2 := NewTransaction("\x00", 20, []string{"SET 10 6", "LD 10 10"}) - blck := CreateBlock([]*Transaction{tx2, tx}) + blck := CreateTestBlock([]*Transaction{tx2, tx}) bm.ProcessBlock( blck ) - fmt.Println("GenesisBlock:", GenisisBlock, "hashed", GenisisBlock.Hash()) + fmt.Println("GenesisBlock:", GenisisBlock, "hash", string(GenisisBlock.Hash())) } diff --git a/trie_test.go b/trie_test.go index 6dbe040ee..599a5f47c 100644 --- a/trie_test.go +++ b/trie_test.go @@ -3,6 +3,7 @@ package main import ( "testing" "encoding/hex" + _"fmt" ) func TestTriePut(t *testing.T) { @@ -41,6 +42,13 @@ func TestTrieUpdate(t *testing.T) { trie.Update("doe", "reindeer") trie.Update("dog", "puppy") + /* + data, _ := db.Get([]byte(trie.root)) + data, _ = db.Get([]byte(DecodeNode(data)[1])) + data, _ = db.Get([]byte(DecodeNode(data)[7])) + PrintSlice(DecodeNode(data)) + */ + trie.Update("dogglesworth", "cat") root := hex.EncodeToString([]byte(trie.root)) req := "e378927bfc1bd4f01a2e8d9f59bd18db8a208bb493ac0b00f93ce51d4d2af76c" diff --git a/vm.go b/vm.go index 787e29295..f38e7be06 100644 --- a/vm.go +++ b/vm.go @@ -1,16 +1,18 @@ package main import ( - "math" + _"math" "math/big" "fmt" - "strconv" + _"strconv" _ "encoding/hex" ) // Op codes const ( oSTOP int = 0x00 + oPSH int = 0x30 + /* oADD int = 0x10 oSUB int = 0x11 oMUL int = 0x12 @@ -46,6 +48,7 @@ const ( oDATAN int = 0x81 oMYADDRESS int = 0x90 oSUICIDE int = 0xff + */ ) type OpType int @@ -57,6 +60,66 @@ const ( ) type TxCallback func(opType OpType) bool +// Simple push/pop stack mechanism +type Stack struct { + data []string +} +func NewStack() *Stack { + return &Stack{} +} +func (st *Stack) Pop() string { + s := len(st.data) + + str := st.data[s] + st.data = st.data[:s-1] + + return str +} + +func (st *Stack) Push(d string) { + st.data = append(st.data, d) +} + +type Vm struct { + // Stack + stack *Stack +} + +func NewVm() *Vm { + return &Vm{ + stack: NewStack(), + } +} + +func (vm *Vm) RunTransaction(tx *Transaction, block *Block, cb TxCallback) { + // Instruction pointer + iptr := 0 + + // Index pointer for the memory + memIndex := 0 + + fmt.Printf("# op arg\n") + for iptr < len(tx.data) { + memIndex++ + // The base big int for all calculations. Use this for any results. + base := new(big.Int) + base.SetString("0",0) // so it doesn't whine about it + // XXX Should Instr return big int slice instead of string slice? + op, args, _ := Instr(tx.data[iptr]) + + if Debug { + fmt.Printf("%-3d %-4d %v\n", iptr, op, args) + } + + switch op { + case oPSH: + } + // Increment instruction pointer + iptr++ + } +} + +/* type Vm struct { // Memory stack stack map[string]string @@ -183,3 +246,4 @@ out: } } } +*/ diff --git a/vm_test.go b/vm_test.go new file mode 100644 index 000000000..d0bcda2ca --- /dev/null +++ b/vm_test.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "testing" + _"encoding/hex" +) + + +func TestVm(t *testing.T) { + db, _ := NewMemDatabase() + Db = db + + tx := NewTransaction("\x00", 20, []string{ + "PSH 10", + }) + + block := CreateBlock("", 0, "", "", 0, 0, "", []*Transaction{tx}) + db.Put(block.Hash(), block.MarshalRlp()) + + bm := NewBlockManager() + bm.ProcessBlock( block ) + tx1 := &Transaction{} + tx1.UnmarshalRlp([]byte(block.state.Get(tx.recipient))) + fmt.Println(tx1) +} + -- cgit v1.2.3 From 7cd41ac45aed7ee22ef02f8abedf83a2914c4807 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 3 Jan 2014 00:43:49 +0100 Subject: Wip VM. Created contracts --- block.go | 26 +++++++++++++++++++++++++- block_manager.go | 10 ++++------ contract.go | 40 ++++++++++++++++++++++++++++++++++++++++ transaction.go | 11 ++++++----- trie.go | 2 +- vm_test.go | 9 ++++----- 6 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 contract.go diff --git a/block.go b/block.go index 146328471..dd329f8ac 100644 --- a/block.go +++ b/block.go @@ -4,6 +4,7 @@ import ( _"fmt" "time" _"bytes" + _"encoding/hex" ) type Block struct { @@ -63,13 +64,36 @@ func CreateBlock(root string, num int, prevHash string, base string, difficulty extra: extra, } block.state = NewTrie(Db, root) + for _, tx := range txes { - block.state.Update(tx.recipient, string(tx.MarshalRlp())) + // Create contract if there's no recipient + if tx.recipient == "" { + addr := tx.Hash() + + contract := NewContract(tx.value, []byte("")) + block.state.Update(string(addr), string(contract.MarshalRlp())) + for i, val := range tx.data { + contract.state.Update(string(Encode(i)), val) + } + block.UpdateContract(addr, contract) + } } return block } +func (block *Block) GetContract(addr []byte) *Contract { + data := block.state.Get(string(addr)) + contract := &Contract{} + contract.UnmarshalRlp([]byte(data)) + + return contract +} + +func (block *Block) UpdateContract(addr []byte, contract *Contract) { + block.state.Update(string(addr), string(contract.MarshalRlp())) +} + func (block *Block) Update() { } diff --git a/block_manager.go b/block_manager.go index 0ef5d1108..f3aa29ba4 100644 --- a/block_manager.go +++ b/block_manager.go @@ -45,13 +45,11 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { } func (bm *BlockManager) ProcessTransaction(tx *Transaction, block *Block, lockChan chan bool) { - if tx.recipient == "\x00" { - bm.vm.RunTransaction(tx, block, func(opType OpType) bool { - // TODO calculate fees + bm.vm.RunTransaction(tx, block, func(opType OpType) bool { + // TODO calculate fees - return true // Continue - }) - } + return true // Continue + }) // Broadcast we're done lockChan <- true diff --git a/contract.go b/contract.go new file mode 100644 index 000000000..e95e50b74 --- /dev/null +++ b/contract.go @@ -0,0 +1,40 @@ +package main + +import ( +) + +type Contract struct { + active int + amount uint32 // ??? + state *Trie +} +func NewContract(amount uint32, root []byte) *Contract { + contract := &Contract{active: 1, amount: amount} + contract.state = NewTrie(Db, string(root)) + + return contract +} +func (c *Contract) MarshalRlp() []byte { + // Prepare the transaction for serialization + preEnc := []interface{}{uint32(c.active), c.amount, c.state.root} + + return Encode(preEnc) +} + +func (c *Contract) UnmarshalRlp(data []byte) { + t, _ := Decode(data, 0) + + if slice, ok := t.([]interface{}); ok { + if active, ok := slice[0].(uint8); ok { + c.active = int(active) + } + + if amount, ok := slice[1].(uint8); ok { + c.amount = uint32(amount) + } + + if root, ok := slice[2].([]uint8); ok { + c.state = NewTrie(Db, string(root)) + } + } +} diff --git a/transaction.go b/transaction.go index 562593c96..dc5204f9b 100644 --- a/transaction.go +++ b/transaction.go @@ -3,8 +3,8 @@ package main import ( "math/big" "fmt" - "encoding/hex" - "crypto/sha256" + _"encoding/hex" + _"crypto/sha256" _ "bytes" ) @@ -63,13 +63,14 @@ func NewTransaction(to string, value uint32, data []string) *Transaction { tx.data[i] = instr } - b:= []byte(tx.MarshalRlp()) - hash := sha256.Sum256(b) - tx.addr = hex.EncodeToString(hash[0:19]) return &tx } +func (tx *Transaction) Hash() []byte { + return Sha256Bin(tx.MarshalRlp()) +} + func (tx *Transaction) MarshalRlp() []byte { // Prepare the transaction for serialization preEnc := []interface{}{ diff --git a/trie.go b/trie.go index 83a3073ae..442cc08b8 100644 --- a/trie.go +++ b/trie.go @@ -48,7 +48,7 @@ type Trie struct { } func NewTrie(db Database, root string) *Trie { - return &Trie{db: db, root: ""} + return &Trie{db: db, root: root} } /* diff --git a/vm_test.go b/vm_test.go index d0bcda2ca..00b8809a1 100644 --- a/vm_test.go +++ b/vm_test.go @@ -3,7 +3,6 @@ package main import ( "fmt" "testing" - _"encoding/hex" ) @@ -11,7 +10,7 @@ func TestVm(t *testing.T) { db, _ := NewMemDatabase() Db = db - tx := NewTransaction("\x00", 20, []string{ + tx := NewTransaction("", 20, []string{ "PSH 10", }) @@ -20,8 +19,8 @@ func TestVm(t *testing.T) { bm := NewBlockManager() bm.ProcessBlock( block ) - tx1 := &Transaction{} - tx1.UnmarshalRlp([]byte(block.state.Get(tx.recipient))) - fmt.Println(tx1) + contract := block.GetContract(tx.Hash()) + fmt.Println(contract) + fmt.Println("it is", contract.state.Get(string(Encode(0)))) } -- cgit v1.2.3 From 78d18b134ffbd4d28d49ef3fe51774a202bb20df Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 3 Jan 2014 13:40:12 +0100 Subject: Pop, push, load for vm --- vm.go | 38 ++++++++++++++++++++++++++++---------- vm_test.go | 20 +++++++++++++------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/vm.go b/vm.go index f38e7be06..d7e3f77cf 100644 --- a/vm.go +++ b/vm.go @@ -6,12 +6,15 @@ import ( "fmt" _"strconv" _ "encoding/hex" + "strconv" ) // Op codes const ( oSTOP int = 0x00 - oPSH int = 0x30 + oPUSH int = 0x30 + oPOP int = 0x31 + oLOAD int = 0x36 /* oADD int = 0x10 oSUB int = 0x11 @@ -70,7 +73,7 @@ func NewStack() *Stack { func (st *Stack) Pop() string { s := len(st.data) - str := st.data[s] + str := st.data[s-1] st.data = st.data[:s-1] return str @@ -91,30 +94,45 @@ func NewVm() *Vm { } } -func (vm *Vm) RunTransaction(tx *Transaction, block *Block, cb TxCallback) { +func (vm *Vm) ProcContract(tx *Transaction, block *Block, cb TxCallback) { // Instruction pointer iptr := 0 - // Index pointer for the memory - memIndex := 0 + contract := block.GetContract(tx.Hash()) + if contract == nil { + fmt.Println("Contract not found") + return + } fmt.Printf("# op arg\n") - for iptr < len(tx.data) { - memIndex++ +out: + for { // The base big int for all calculations. Use this for any results. base := new(big.Int) base.SetString("0",0) // so it doesn't whine about it // XXX Should Instr return big int slice instead of string slice? - op, args, _ := Instr(tx.data[iptr]) + // Get the next instruction from the contract + op, args, _ := Instr(contract.state.Get(string(Encode(uint32(iptr))))) if Debug { fmt.Printf("%-3d %-4d %v\n", iptr, op, args) } switch op { - case oPSH: + case oPUSH: + // Get the next entry and pushes the value on the stack + iptr++ + vm.stack.Push(contract.state.Get(string(Encode(uint32(iptr))))) + case oPOP: + // Pop current value of the stack + vm.stack.Pop() + case oLOAD: + // Load instruction X on the stack + i, _ := strconv.Atoi(vm.stack.Pop()) + vm.stack.Push(contract.state.Get(string(Encode(uint32(i))))) + case oSTOP: + break out } - // Increment instruction pointer iptr++ } } diff --git a/vm_test.go b/vm_test.go index 00b8809a1..fe0358a42 100644 --- a/vm_test.go +++ b/vm_test.go @@ -1,7 +1,7 @@ package main import ( - "fmt" + _"fmt" "testing" ) @@ -10,17 +10,23 @@ func TestVm(t *testing.T) { db, _ := NewMemDatabase() Db = db - tx := NewTransaction("", 20, []string{ - "PSH 10", + ctrct := NewTransaction("", 20, []string{ + "PUSH", + "1a2f2e", + "PUSH", + "hallo", + "POP", // POP hallo + "PUSH", + "3", + "LOAD", // Load hallo back on the stack + "STOP", }) + tx := NewTransaction("1e8a42ea8cce13", 100, []string{}) - block := CreateBlock("", 0, "", "", 0, 0, "", []*Transaction{tx}) + block := CreateBlock("", 0, "", "", 0, 0, "", []*Transaction{ctrct, tx}) db.Put(block.Hash(), block.MarshalRlp()) bm := NewBlockManager() bm.ProcessBlock( block ) - contract := block.GetContract(tx.Hash()) - fmt.Println(contract) - fmt.Println("it is", contract.state.Get(string(Encode(0)))) } -- cgit v1.2.3 From 055407c835328c8b9628c73ddd7ba872d787c09d Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 3 Jan 2014 13:40:25 +0100 Subject: VM Recovery --- block_manager.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/block_manager.go b/block_manager.go index f3aa29ba4..ae9803c8e 100644 --- a/block_manager.go +++ b/block_manager.go @@ -11,7 +11,7 @@ package main import ( - _"fmt" + "fmt" ) type BlockManager struct { @@ -33,7 +33,13 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { // Process each transaction/contract for _, tx := range block.transactions { - go bm.ProcessTransaction(tx, block, lockChan) + // If there's no recipient, it's a contract + if tx.recipient == "" { + go bm.ProcessContract(tx, block, lockChan) + } else { + // "finish" tx which isn't a contract + lockChan <- true + } } // Wait for all Tx to finish processing @@ -44,8 +50,18 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { return nil } -func (bm *BlockManager) ProcessTransaction(tx *Transaction, block *Block, lockChan chan bool) { - bm.vm.RunTransaction(tx, block, func(opType OpType) bool { +func (bm *BlockManager) ProcessContract(tx *Transaction, block *Block, lockChan chan bool) { + // Recovering function in case the VM had any errors + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered from VM execution with err =", r) + // Let the channel know where done even though it failed (so the execution may resume normally) + lockChan <- true + } + }() + + // Process contract + bm.vm.ProcContract(tx, block, func(opType OpType) bool { // TODO calculate fees return true // Continue -- cgit v1.2.3 From 9581faf5fdacf4a135bcdf77d6f0fbe0d6eecba8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 3 Jan 2014 13:40:38 +0100 Subject: Parsing for block and tx --- block.go | 2 +- parsing.go | 9 ++++++--- transaction.go | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/block.go b/block.go index dd329f8ac..39858b0fd 100644 --- a/block.go +++ b/block.go @@ -73,7 +73,7 @@ func CreateBlock(root string, num int, prevHash string, base string, difficulty contract := NewContract(tx.value, []byte("")) block.state.Update(string(addr), string(contract.MarshalRlp())) for i, val := range tx.data { - contract.state.Update(string(Encode(i)), val) + contract.state.Update(string(Encode(uint32(i))), val) } block.UpdateContract(addr, contract) } diff --git a/parsing.go b/parsing.go index 765950a4c..4c3f1187e 100644 --- a/parsing.go +++ b/parsing.go @@ -11,8 +11,11 @@ import ( // Op codes var OpCodes = map[string]string{ "STOP": "0", - "PSH": "30", // 0x30 - /* + "PUSH": "48", // 0x30 + "POP": "49", // 0x31 + "LOAD": "54", // 0x36 + + /* OLD VM OPCODES "ADD": "16", // 0x10 "SUB": "17", // 0x11 "MUL": "18", // 0x12 @@ -57,7 +60,7 @@ var OpCodes = map[string]string{ func CompileInstr(s string) (string, error) { tokens := strings.Split(s, " ") if OpCodes[tokens[0]] == "" { - return "", errors.New(fmt.Sprintf("OP not found: %s", tokens[0])) + return s, errors.New(fmt.Sprintf("OP not found: %s", tokens[0])) } code := OpCodes[tokens[0]] // Replace op codes with the proper numerical equivalent diff --git a/transaction.go b/transaction.go index dc5204f9b..1cfa6d729 100644 --- a/transaction.go +++ b/transaction.go @@ -57,7 +57,7 @@ func NewTransaction(to string, value uint32, data []string) *Transaction { for i, val := range data { instr, err := CompileInstr(val) if err != nil { - fmt.Printf("compile error:%d %v", i+1, err) + fmt.Printf("compile error:%d %v\n", i+1, err) } tx.data[i] = instr -- cgit v1.2.3 From f97716ebe287349729821d826141e73a6e55702e Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 4 Jan 2014 00:30:19 +0100 Subject: Added new big from []bytes --- big.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/big.go b/big.go index 44bfee2e4..b0fbcb64f 100644 --- a/big.go +++ b/big.go @@ -24,3 +24,12 @@ func Big(num string) *big.Int { return n } +/* + * Like big.NewInt(uint64); this takes a byte buffer instead. + */ +func BigD(data []byte) *big.Int { + n := new(big.Int) + n.SetBytes(data) + + return n +} -- cgit v1.2.3 From 15bf5ee4db6485619fb942ae288bd9659d4d6d73 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 4 Jan 2014 00:31:06 +0100 Subject: Contract creation, updating and fee processing --- block.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/block.go b/block.go index 39858b0fd..00317573c 100644 --- a/block.go +++ b/block.go @@ -1,7 +1,7 @@ package main import ( - _"fmt" + "fmt" "time" _"bytes" _"encoding/hex" @@ -73,7 +73,8 @@ func CreateBlock(root string, num int, prevHash string, base string, difficulty contract := NewContract(tx.value, []byte("")) block.state.Update(string(addr), string(contract.MarshalRlp())) for i, val := range tx.data { - contract.state.Update(string(Encode(uint32(i))), val) + contract.state.Update(string(NumberToBytes(uint64(i), 32)), val) + //contract.state.Update(string(Encode(uint32(i))), val) } block.UpdateContract(addr, contract) } @@ -84,6 +85,10 @@ func CreateBlock(root string, num int, prevHash string, base string, difficulty func (block *Block) GetContract(addr []byte) *Contract { data := block.state.Get(string(addr)) + if data == "" { + return nil + } + contract := &Contract{} contract.UnmarshalRlp([]byte(data)) @@ -94,7 +99,27 @@ func (block *Block) UpdateContract(addr []byte, contract *Contract) { block.state.Update(string(addr), string(contract.MarshalRlp())) } -func (block *Block) Update() { + +func (block *Block) PayFee(addr []byte, fee uint64) bool { + contract := block.GetContract(addr) + // If we can't pay the fee return + if contract == nil || contract.amount < fee { + fmt.Println("Contract has insufficient funds", contract.amount, fee) + return false + } + + contract.amount -= fee + block.state.Update(string(addr), string(contract.MarshalRlp())) + + data := block.state.Get(string(block.coinbase)) + println(data) + // Get the ether (coinbase) and add the fee (gief fee to miner) + ether := NewEtherFromData([]byte(data)) + ether.amount += fee + + block.state.Update(string(block.coinbase), string(ether.MarshalRlp())) + + return true } // Returns a hash of the block -- cgit v1.2.3 From a42ebd9ed62ffe5ba30e29001a380a4bd185be6d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 4 Jan 2014 00:31:42 +0100 Subject: block --- block_manager.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/block_manager.go b/block_manager.go index ae9803c8e..6c1a0fe58 100644 --- a/block_manager.go +++ b/block_manager.go @@ -62,7 +62,10 @@ func (bm *BlockManager) ProcessContract(tx *Transaction, block *Block, lockChan // Process contract bm.vm.ProcContract(tx, block, func(opType OpType) bool { - // TODO calculate fees + // TODO turn on once big ints are in place + //if !block.PayFee(tx.Hash(), StepFee.Uint64()) { + // return false + //} return true // Continue }) -- cgit v1.2.3 From ebef4e103b4601e477a787ace068dbf181acc39c Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 4 Jan 2014 00:32:01 +0100 Subject: Ether --- contract.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/contract.go b/contract.go index e95e50b74..778f3578d 100644 --- a/contract.go +++ b/contract.go @@ -1,36 +1,42 @@ package main import ( + _"fmt" ) type Contract struct { - active int - amount uint32 // ??? + t uint32 // contract is always 1 + amount uint64 // ??? state *Trie } -func NewContract(amount uint32, root []byte) *Contract { - contract := &Contract{active: 1, amount: amount} + +func NewContract(amount uint64, root []byte) *Contract { + contract := &Contract{t: 1, amount: amount} contract.state = NewTrie(Db, string(root)) return contract } -func (c *Contract) MarshalRlp() []byte { - // Prepare the transaction for serialization - preEnc := []interface{}{uint32(c.active), c.amount, c.state.root} - return Encode(preEnc) +func (c *Contract) MarshalRlp() []byte { + return Encode([]interface{}{c.t, c.amount, c.state.root}) } func (c *Contract) UnmarshalRlp(data []byte) { t, _ := Decode(data, 0) if slice, ok := t.([]interface{}); ok { - if active, ok := slice[0].(uint8); ok { - c.active = int(active) + if t, ok := slice[0].(uint8); ok { + c.t = uint32(t) } if amount, ok := slice[1].(uint8); ok { - c.amount = uint32(amount) + c.amount = uint64(amount) + } else if amount, ok := slice[1].(uint16); ok { + c.amount = uint64(amount) + } else if amount, ok := slice[1].(uint32); ok { + c.amount = uint64(amount) + } else if amount, ok := slice[1].(uint64); ok { + c.amount = amount } if root, ok := slice[2].([]uint8); ok { @@ -38,3 +44,38 @@ func (c *Contract) UnmarshalRlp(data []byte) { } } } + +type Ether struct { + t uint32 + amount uint64 + nonce string +} + +func NewEtherFromData(data []byte) *Ether { + ether := &Ether{} + ether.UnmarshalRlp(data) + + return ether +} + +func (e *Ether) MarshalRlp() []byte { + return Encode([]interface{}{e.t, e.amount, e.nonce}) +} + +func (e *Ether) UnmarshalRlp(data []byte) { + t, _ := Decode(data, 0) + + if slice, ok := t.([]interface{}); ok { + if t, ok := slice[0].(uint8); ok { + e.t = uint32(t) + } + + if amount, ok := slice[1].(uint8); ok { + e.amount = uint64(amount) + } + + if nonce, ok := slice[2].([]uint8); ok { + e.nonce = string(nonce) + } + } +} -- cgit v1.2.3 From 6ab61f2c524686e479f9546d5ce2529f3b8eb7fe Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 4 Jan 2014 00:32:13 +0100 Subject: Added byte helpers --- bytes.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 bytes.go diff --git a/bytes.go b/bytes.go new file mode 100644 index 000000000..6bf381343 --- /dev/null +++ b/bytes.go @@ -0,0 +1,27 @@ +package main + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +func NumberToBytes(num uint64, bits int) []byte { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, num) + if err != nil { + fmt.Println("binary.Write failed:", err) + } + + return buf.Bytes()[buf.Len()-(bits / 8):] +} + +func BytesToNumber(b []byte) (number uint64) { + buf := bytes.NewReader(b) + err := binary.Read(buf, binary.LittleEndian, &number) + if err != nil { + fmt.Println("binary.Read failed:", err) + } + + return +} -- cgit v1.2.3 From 2d3c3674faa2c9c15d4ab27c7dc6b3e07a532780 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 4 Jan 2014 00:32:52 +0100 Subject: Updated stack based vm --- parsing.go | 67 +++++-------- vm.go | 320 ++++++++++++++++++++++++++++--------------------------------- vm_test.go | 59 ++++++++++-- 3 files changed, 221 insertions(+), 225 deletions(-) diff --git a/parsing.go b/parsing.go index 4c3f1187e..b97d77f3e 100644 --- a/parsing.go +++ b/parsing.go @@ -10,50 +10,29 @@ import ( // Op codes var OpCodes = map[string]string{ - "STOP": "0", - "PUSH": "48", // 0x30 - "POP": "49", // 0x31 - "LOAD": "54", // 0x36 - - /* OLD VM OPCODES - "ADD": "16", // 0x10 - "SUB": "17", // 0x11 - "MUL": "18", // 0x12 - "DIV": "19", // 0x13 - "SDIV": "20", // 0x14 - "MOD": "21", // 0x15 - "SMOD": "22", // 0x16 - "EXP": "23", // 0x17 - "NEG": "24", // 0x18 - "LT": "32", // 0x20 - "LE": "33", // 0x21 - "GT": "34", // 0x22 - "GE": "35", // 0x23 - "EQ": "36", // 0x24 - "NOT": "37", // 0x25 - "SHA256": "48", // 0x30 - "RIPEMD160": "49", // 0x31 - "ECMUL": "50", // 0x32 - "ECADD": "51", // 0x33 - "SIGN": "52", // 0x34 - "RECOVER": "53", // 0x35 - "COPY": "64", // 0x40 - "ST": "65", // 0x41 - "LD": "66", // 0x42 - "SET": "67", // 0x43 - "JMP": "80", // 0x50 - "JMPI": "81", // 0x51 - "IND": "82", // 0x52 - "EXTRO": "96", // 0x60 - "BALANCE": "97", // 0x61 - "MKTX": "112", // 0x70 - "DATA": "128", // 0x80 - "DATAN": "129", // 0x81 - "MYADDRESS": "144", // 0x90 - "BLKHASH": "145", // 0x91 - "COINBASE": "146", // 0x92 - "SUICIDE": "255", // 0xff - */ + "STOP": "0", + "ADD": "1", + "MUL": "2", + "SUB": "3", + "DIV": "4", + "SDIV": "5", + "MOD": "6", + "SMOD": "7", + "EXP": "8", + "NEG": "9", + "LT": "10", + "LE": "11", + "GT": "12", + "GE": "13", + "EQ": "14", + "NOT": "15", + "MYADDRESS": "16", + "TXSENDER": "17", + + + "PUSH": "48", + "POP": "49", + "LOAD": "54", } diff --git a/vm.go b/vm.go index d7e3f77cf..9e1e83b0b 100644 --- a/vm.go +++ b/vm.go @@ -12,46 +12,28 @@ import ( // Op codes const ( oSTOP int = 0x00 + oADD int = 0x01 + oMUL int = 0x02 + oSUB int = 0x03 + oDIV int = 0x04 + oSDIV int = 0x05 + oMOD int = 0x06 + oSMOD int = 0x07 + oEXP int = 0x08 + oNEG int = 0x09 + oLT int = 0x0a + oLE int = 0x0b + oGT int = 0x0c + oGE int = 0x0d + oEQ int = 0x0e + oNOT int = 0x0f + oMYADDRESS int = 0x10 + oTXSENDER int = 0x11 + + oPUSH int = 0x30 oPOP int = 0x31 oLOAD int = 0x36 - /* - oADD int = 0x10 - oSUB int = 0x11 - oMUL int = 0x12 - oDIV int = 0x13 - oSDIV int = 0x14 - oMOD int = 0x15 - oSMOD int = 0x16 - oEXP int = 0x17 - oNEG int = 0x18 - oLT int = 0x20 - oLE int = 0x21 - oGT int = 0x22 - oGE int = 0x23 - oEQ int = 0x24 - oNOT int = 0x25 - oSHA256 int = 0x30 - oRIPEMD160 int = 0x31 - oECMUL int = 0x32 - oECADD int = 0x33 - oSIGN int = 0x34 - oRECOVER int = 0x35 - oCOPY int = 0x40 - oST int = 0x41 - oLD int = 0x42 - oSET int = 0x43 - oJMP int = 0x50 - oJMPI int = 0x51 - oIND int = 0x52 - oEXTRO int = 0x60 - oBALANCE int = 0x61 - oMKTX int = 0x70 - oDATA int = 0x80 - oDATAN int = 0x81 - oMYADDRESS int = 0x90 - oSUICIDE int = 0xff - */ ) type OpType int @@ -79,9 +61,21 @@ func (st *Stack) Pop() string { return str } +func (st *Stack) Popn() (*big.Int, *big.Int) { + s := len(st.data) + + strs := st.data[s-2:] + st.data = st.data[:s-2] + + return Big(strs[0]), Big(strs[1]) +} + func (st *Stack) Push(d string) { st.data = append(st.data, d) } +func (st *Stack) Print() { + fmt.Println(st.data) +} type Vm struct { // Stack @@ -96,7 +90,7 @@ func NewVm() *Vm { func (vm *Vm) ProcContract(tx *Transaction, block *Block, cb TxCallback) { // Instruction pointer - iptr := 0 + pc := 0 contract := block.GetContract(tx.Hash()) if contract == nil { @@ -104,164 +98,144 @@ func (vm *Vm) ProcContract(tx *Transaction, block *Block, cb TxCallback) { return } + Pow256 := BigPow(2, 256) + fmt.Printf("# op arg\n") out: for { // The base big int for all calculations. Use this for any results. base := new(big.Int) - base.SetString("0",0) // so it doesn't whine about it // XXX Should Instr return big int slice instead of string slice? // Get the next instruction from the contract - op, args, _ := Instr(contract.state.Get(string(Encode(uint32(iptr))))) + //op, _, _ := Instr(contract.state.Get(string(Encode(uint32(pc))))) + op, _, _ := Instr(contract.state.Get(string(NumberToBytes(uint64(pc), 32)))) + + if !cb(0) { break } if Debug { - fmt.Printf("%-3d %-4d %v\n", iptr, op, args) + fmt.Printf("%-3d %-4d\n", pc, op) } switch op { + case oADD: + x, y := vm.stack.Popn() + // (x + y) % 2 ** 256 + base.Add(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + vm.stack.Push(base.String()) + case oSUB: + x, y := vm.stack.Popn() + // (x - y) % 2 ** 256 + base.Sub(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + vm.stack.Push(base.String()) + case oMUL: + x, y := vm.stack.Popn() + // (x * y) % 2 ** 256 + base.Mul(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + vm.stack.Push(base.String()) + case oDIV: + x, y := vm.stack.Popn() + // floor(x / y) + base.Div(x, y) + // Pop result back on the stack + vm.stack.Push(base.String()) + case oSDIV: + x, y := vm.stack.Popn() + // n > 2**255 + if x.Cmp(Pow256) > 0 { x.Sub(Pow256, x) } + if y.Cmp(Pow256) > 0 { y.Sub(Pow256, y) } + z := new(big.Int) + z.Div(x, y) + if z.Cmp(Pow256) > 0 { z.Sub(Pow256, z) } + // Push result on to the stack + vm.stack.Push(z.String()) + case oMOD: + x, y := vm.stack.Popn() + base.Mod(x, y) + vm.stack.Push(base.String()) + case oSMOD: + x, y := vm.stack.Popn() + // n > 2**255 + if x.Cmp(Pow256) > 0 { x.Sub(Pow256, x) } + if y.Cmp(Pow256) > 0 { y.Sub(Pow256, y) } + z := new(big.Int) + z.Mod(x, y) + if z.Cmp(Pow256) > 0 { z.Sub(Pow256, z) } + // Push result on to the stack + vm.stack.Push(z.String()) + case oEXP: + x, y := vm.stack.Popn() + base.Exp(x, y, Pow256) + + vm.stack.Push(base.String()) + case oNEG: + base.Sub(Pow256, Big(vm.stack.Pop())) + vm.stack.Push(base.String()) + case oLT: + x, y := vm.stack.Popn() + // x < y + if x.Cmp(y) < 0 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oLE: + x, y := vm.stack.Popn() + // x <= y + if x.Cmp(y) < 1 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oGT: + x, y := vm.stack.Popn() + // x > y + if x.Cmp(y) > 0 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oGE: + x, y := vm.stack.Popn() + // x >= y + if x.Cmp(y) > -1 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oNOT: + x, y := vm.stack.Popn() + // x != y + if x.Cmp(y) != 0 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oMYADDRESS: + vm.stack.Push(string(tx.Hash())) + case oTXSENDER: + vm.stack.Push(tx.sender) case oPUSH: // Get the next entry and pushes the value on the stack - iptr++ - vm.stack.Push(contract.state.Get(string(Encode(uint32(iptr))))) + pc++ + vm.stack.Push(contract.state.Get(string(NumberToBytes(uint64(pc), 32)))) case oPOP: // Pop current value of the stack vm.stack.Pop() case oLOAD: // Load instruction X on the stack i, _ := strconv.Atoi(vm.stack.Pop()) - vm.stack.Push(contract.state.Get(string(Encode(uint32(i))))) + vm.stack.Push(contract.state.Get(string(NumberToBytes(uint64(i), 32)))) case oSTOP: break out } - iptr++ + pc++ } -} -/* -type Vm struct { - // Memory stack - stack map[string]string - memory map[string]map[string]string -} - -func NewVm() *Vm { - //stackSize := uint(256) - - return &Vm{ - stack: make(map[string]string), - memory: make(map[string]map[string]string), - } -} - -func (vm *Vm) RunTransaction(tx *Transaction, cb TxCallback) { - if Debug { - fmt.Printf(` -# processing Tx (%v) -# fee = %f, ops = %d, sender = %s, value = %d - `, tx.addr, float32(tx.fee) / 1e8, len(tx.data), tx.sender, tx.value) - } - - vm.stack = make(map[string]string) - vm.stack["0"] = tx.sender - vm.stack["1"] = "100" //int(tx.value) - vm.stack["1"] = "1000" //int(tx.fee) - // Stack pointer - stPtr := 0 - - //vm.memory[tx.addr] = make([]int, 256) - vm.memory[tx.addr] = make(map[string]string) - - // Define instruction 'accessors' for the instruction, which makes it more readable - // also called register values, shorthanded as Rx/y/z. Memory address are shorthanded as Mx/y/z. - // Instructions are shorthanded as Ix/y/z - x := 0; y := 1; z := 2; //a := 3; b := 4; c := 5 -out: - for stPtr < len(tx.data) { - // The base big int for all calculations. Use this for any results. - base := new(big.Int) - // XXX Should Instr return big int slice instead of string slice? - op, args, _ := Instr(tx.data[stPtr]) - - if Debug { - fmt.Printf("%-3d %d %v\n", stPtr, op, args) - } - - opType := OpType(tNorm) - // Determine the op type (used for calculating fees by the block manager) - switch op { - case oEXTRO, oBALANCE: - opType = tExtro - case oSHA256, oRIPEMD160, oECMUL, oECADD: // TODO add rest - opType = tCrypto - } - - // If the callback yielded a negative result abort execution - if !cb(opType) { break out } - - nptr := stPtr - switch op { - case oSTOP: - fmt.Println("exiting (oSTOP), idx =", nptr) - - break out - case oADD: - // (Rx + Ry) % 2 ** 256 - base.Add(Big(vm.stack[args[ x ]]), Big(vm.stack[args[ y ]])) - base.Mod(base, big.NewInt(int64(math.Pow(2, 256)))) - // Set the result to Rz - vm.stack[args[ z ]] = base.String() - case oSUB: - // (Rx - Ry) % 2 ** 256 - base.Sub(Big(vm.stack[args[ x ]]), Big(vm.stack[args[ y ]])) - base.Mod(base, big.NewInt(int64(math.Pow(2, 256)))) - // Set the result to Rz - vm.stack[args[ z ]] = base.String() - case oMUL: - // (Rx * Ry) % 2 ** 256 - base.Mul(Big(vm.stack[args[ x ]]), Big(vm.stack[args[ y ]])) - base.Mod(base, big.NewInt(int64(math.Pow(2, 256)))) - // Set the result to Rz - vm.stack[args[ z ]] = base.String() - case oDIV: - // floor(Rx / Ry) - base.Div(Big(vm.stack[args[ x ]]), Big(vm.stack[args[ y ]])) - // Set the result to Rz - vm.stack[args[ z ]] = base.String() - case oSET: - // Set the (numeric) value at Iy to Rx - vm.stack[args[ x ]] = args[ y ] - case oLD: - // Load the value at Mx to Ry - vm.stack[args[ y ]] = vm.memory[tx.addr][vm.stack[args[ x ]]] - case oLT: - cmp := Big(vm.stack[args[ x ]]).Cmp( Big(vm.stack[args[ y ]]) ) - // Set the result as "boolean" value to Rz - if cmp < 0 { // a < b - vm.stack[args[ z ]] = "1" - } else { - vm.stack[args[ z ]] = "0" - } - case oJMP: - // Set the instruction pointer to the value at Rx - ptr, _ := strconv.Atoi( vm.stack[args[ x ]] ) - nptr = ptr - case oJMPI: - // Set the instruction pointer to the value at Ry if Rx yields true - if vm.stack[args[ x ]] != "0" { - ptr, _ := strconv.Atoi( vm.stack[args[ y ]] ) - nptr = ptr - } - default: - fmt.Println("Error op", op) - break - } - - if stPtr == nptr { - stPtr++ - } else { - stPtr = nptr - if Debug { fmt.Println("... JMP", nptr, "...") } - } - } + vm.stack.Print() } -*/ diff --git a/vm_test.go b/vm_test.go index fe0358a42..efcd875cd 100644 --- a/vm_test.go +++ b/vm_test.go @@ -7,23 +7,66 @@ import ( func TestVm(t *testing.T) { + InitFees() + db, _ := NewMemDatabase() Db = db - ctrct := NewTransaction("", 20, []string{ - "PUSH", - "1a2f2e", - "PUSH", - "hallo", + ctrct := NewTransaction("", 200000000, []string{ + "PUSH", "1a2f2e", + "PUSH", "hallo", "POP", // POP hallo - "PUSH", - "3", + "PUSH", "3", "LOAD", // Load hallo back on the stack + + "PUSH", "1", + "PUSH", "2", + "ADD", + + "PUSH", "2", + "PUSH", "1", + "SUB", + + "PUSH", "100000000000000000000000", + "PUSH", "10000000000000", + "SDIV", + + "PUSH", "105", + "PUSH", "200", + "MOD", + + "PUSH", "100000000000000000000000", + "PUSH", "10000000000000", + "SMOD", + + "PUSH", "5", + "PUSH", "10", + "LT", + + "PUSH", "5", + "PUSH", "5", + "LE", + + "PUSH", "50", + "PUSH", "5", + "GT", + + "PUSH", "5", + "PUSH", "5", + "GE", + + "PUSH", "10", + "PUSH", "10", + "NOT", + + "MYADDRESS", + "TXSENDER", + "STOP", }) tx := NewTransaction("1e8a42ea8cce13", 100, []string{}) - block := CreateBlock("", 0, "", "", 0, 0, "", []*Transaction{ctrct, tx}) + block := CreateBlock("", 0, "", "c014ba53", 0, 0, "", []*Transaction{ctrct, tx}) db.Put(block.Hash(), block.MarshalRlp()) bm := NewBlockManager() -- cgit v1.2.3 From 2c90c0df65132bf65abbcf4e4caecb89b671b93f Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 4 Jan 2014 00:33:41 +0100 Subject: Updated fee --- transaction.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/transaction.go b/transaction.go index 1cfa6d729..f5c848a31 100644 --- a/transaction.go +++ b/transaction.go @@ -34,9 +34,10 @@ var Period3Reward *big.Int = new(big.Int) var Period4Reward *big.Int = new(big.Int) type Transaction struct { + nonce string sender string recipient string - value uint32 + value uint64 fee uint32 data []string memory []int @@ -47,8 +48,9 @@ type Transaction struct { addr string } -func NewTransaction(to string, value uint32, data []string) *Transaction { +func NewTransaction(to string, value uint64, data []string) *Transaction { tx := Transaction{sender: "1234567890", recipient: to, value: value} + tx.nonce = "0" tx.fee = 0//uint32((ContractFee + MemoryFee * float32(len(tx.data))) * 1e8) tx.lastTx = "0" @@ -102,16 +104,16 @@ func (tx *Transaction) UnmarshalRlp(data []byte) { // If only I knew of a better way. if value, ok := slice[3].(uint8); ok { - tx.value = uint32(value) + tx.value = uint64(value) } if value, ok := slice[3].(uint16); ok { - tx.value = uint32(value) + tx.value = uint64(value) } if value, ok := slice[3].(uint32); ok { - tx.value = uint32(value) + tx.value = uint64(value) } if value, ok := slice[3].(uint64); ok { - tx.value = uint32(value) + tx.value = uint64(value) } if fee, ok := slice[4].(uint8); ok { tx.fee = uint32(fee) @@ -146,7 +148,8 @@ func InitFees() { b80 := new(big.Int) b80.Exp(big.NewInt(2), big.NewInt(80), big.NewInt(0)) - StepFee.Div(b60, big.NewInt(64)) + StepFee.Exp(big.NewInt(10), big.NewInt(16), big.NewInt(0)) + //StepFee.Div(b60, big.NewInt(64)) //fmt.Println("StepFee:", StepFee) TxFee.Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) -- cgit v1.2.3 From 53a30740eea7d3b3010337166e53a62ebff508f3 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 5 Jan 2014 01:54:15 +0100 Subject: Contract checking method --- block_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block_manager.go b/block_manager.go index 6c1a0fe58..0f7a64cf8 100644 --- a/block_manager.go +++ b/block_manager.go @@ -34,7 +34,7 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { // Process each transaction/contract for _, tx := range block.transactions { // If there's no recipient, it's a contract - if tx.recipient == "" { + if tx.IsContract() { go bm.ProcessContract(tx, block, lockChan) } else { // "finish" tx which isn't a contract -- cgit v1.2.3 From 92c3471445d9a46389ddab893a159ce273707a95 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 5 Jan 2014 01:55:03 +0100 Subject: secp256k1 method signing --- transaction.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/transaction.go b/transaction.go index f5c848a31..f38f0fed3 100644 --- a/transaction.go +++ b/transaction.go @@ -3,6 +3,7 @@ package main import ( "math/big" "fmt" + "github.com/obscuren/secp256k1-go" _"encoding/hex" _"crypto/sha256" _ "bytes" @@ -73,6 +74,28 @@ func (tx *Transaction) Hash() []byte { return Sha256Bin(tx.MarshalRlp()) } +func (tx *Transaction) IsContract() bool { + return tx.recipient == "" +} + +func (tx *Transaction) Signature() []byte { + hash := Sha256Bin(tx.MarshalRlp()) + sec := Sha256Bin([]byte("myprivkey")) + + sig, _ := secp256k1.Sign(hash, sec) + + return sig +} + +func (tx *Transaction) PublicKey() []byte { + hash := Sha256Bin(tx.MarshalRlp()) + sig := tx.Signature() + + pubkey, _ := secp256k1.RecoverPubkey(hash, sig) + + return pubkey +} + func (tx *Transaction) MarshalRlp() []byte { // Prepare the transaction for serialization preEnc := []interface{}{ -- cgit v1.2.3 From 8204e9cd6eefdca9e9248be6cf97466fa5fb433d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 5 Jan 2014 20:14:53 +0100 Subject: Changed hashing, signature and key generation. --- transaction.go | 95 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/transaction.go b/transaction.go index f38f0fed3..a3279aa33 100644 --- a/transaction.go +++ b/transaction.go @@ -43,10 +43,8 @@ type Transaction struct { data []string memory []int lastTx string - - // To be removed - signature string - addr string + v uint32 + r, s []byte } func NewTransaction(to string, value uint64, data []string) *Transaction { @@ -60,18 +58,28 @@ func NewTransaction(to string, value uint64, data []string) *Transaction { for i, val := range data { instr, err := CompileInstr(val) if err != nil { - fmt.Printf("compile error:%d %v\n", i+1, err) + //fmt.Printf("compile error:%d %v\n", i+1, err) } tx.data[i] = instr } + tx.SetVRS() + return &tx } func (tx *Transaction) Hash() []byte { - return Sha256Bin(tx.MarshalRlp()) + preEnc := []interface{}{ + tx.nonce, + tx.recipient, + tx.value, + tx.fee, + tx.data, + } + + return Sha256Bin(Encode(preEnc)) } func (tx *Transaction) IsContract() bool { @@ -79,7 +87,7 @@ func (tx *Transaction) IsContract() bool { } func (tx *Transaction) Signature() []byte { - hash := Sha256Bin(tx.MarshalRlp()) + hash := tx.Hash() sec := Sha256Bin([]byte("myprivkey")) sig, _ := secp256k1.Sign(hash, sec) @@ -96,15 +104,34 @@ func (tx *Transaction) PublicKey() []byte { return pubkey } +func (tx *Transaction) Address() []byte { + pubk := tx.PublicKey() + // 1 is the marker 04 + key := pubk[1:65] + + return Sha256Bin(key)[12:] +} + +func (tx *Transaction) SetVRS() { + // Add 27 so we get either 27 or 28 (for positive and negative) + tx.v = uint32(tx.Signature()[64]) + 27 + + pubk := tx.PublicKey()[1:65] + tx.r = pubk[:32] + tx.s = pubk[32:64] +} + func (tx *Transaction) MarshalRlp() []byte { // Prepare the transaction for serialization preEnc := []interface{}{ - tx.lastTx, - tx.sender, + tx.nonce, tx.recipient, tx.value, tx.fee, tx.data, + tx.v, + tx.r, + tx.s, } return Encode(preEnc) @@ -113,46 +140,43 @@ func (tx *Transaction) MarshalRlp() []byte { func (tx *Transaction) UnmarshalRlp(data []byte) { t, _ := Decode(data,0) if slice, ok := t.([]interface{}); ok { - if lastTx, ok := slice[0].([]byte); ok { - tx.lastTx = string(lastTx) + fmt.Printf("NONCE %T\n", slice[3]) + if nonce, ok := slice[0].(uint8); ok { + tx.nonce = string(nonce) } - if sender, ok := slice[1].([]byte); ok { - tx.sender = string(sender) - } - - if recipient, ok := slice[2].([]byte); ok { + if recipient, ok := slice[1].([]byte); ok { tx.recipient = string(recipient) } // If only I knew of a better way. - if value, ok := slice[3].(uint8); ok { + if value, ok := slice[2].(uint8); ok { tx.value = uint64(value) } - if value, ok := slice[3].(uint16); ok { + if value, ok := slice[2].(uint16); ok { tx.value = uint64(value) } - if value, ok := slice[3].(uint32); ok { + if value, ok := slice[2].(uint32); ok { tx.value = uint64(value) } - if value, ok := slice[3].(uint64); ok { + if value, ok := slice[2].(uint64); ok { tx.value = uint64(value) } - if fee, ok := slice[4].(uint8); ok { + if fee, ok := slice[3].(uint8); ok { tx.fee = uint32(fee) } - if fee, ok := slice[4].(uint16); ok { + if fee, ok := slice[3].(uint16); ok { tx.fee = uint32(fee) } - if fee, ok := slice[4].(uint32); ok { + if fee, ok := slice[3].(uint32); ok { tx.fee = uint32(fee) } - if fee, ok := slice[4].(uint64); ok { + if fee, ok := slice[3].(uint64); ok { tx.fee = uint32(fee) } // Encode the data/instructions - if data, ok := slice[5].([]interface{}); ok { + if data, ok := slice[4].([]interface{}); ok { tx.data = make([]string, len(data)) for i, d := range data { if instr, ok := d.([]byte); ok { @@ -160,6 +184,27 @@ func (tx *Transaction) UnmarshalRlp(data []byte) { } } } + + // vrs + fmt.Printf("v %T\n", slice[5]) + if v, ok := slice[5].(uint8); ok { + tx.v = uint32(v) + } + if v, ok := slice[5].(uint16); ok { + tx.v = uint32(v) + } + if v, ok := slice[5].(uint32); ok { + tx.v = uint32(v) + } + if v, ok := slice[5].(uint64); ok { + tx.v = uint32(v) + } + if r, ok := slice[6].([]byte); ok { + tx.r = r + } + if s, ok := slice[7].([]byte); ok { + tx.s = s + } } } -- cgit v1.2.3 From 79c89c0bcda7ed35ec5570eb597e427631848174 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 5 Jan 2014 20:15:05 +0100 Subject: Removed logging --- vm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm.go b/vm.go index 9e1e83b0b..a727d58e5 100644 --- a/vm.go +++ b/vm.go @@ -100,7 +100,7 @@ func (vm *Vm) ProcContract(tx *Transaction, block *Block, cb TxCallback) { Pow256 := BigPow(2, 256) - fmt.Printf("# op arg\n") + //fmt.Printf("# op arg\n") out: for { // The base big int for all calculations. Use this for any results. @@ -113,7 +113,7 @@ out: if !cb(0) { break } if Debug { - fmt.Printf("%-3d %-4d\n", pc, op) + //fmt.Printf("%-3d %-4d\n", pc, op) } switch op { -- cgit v1.2.3 From 40dc4d0259b3bec4e8ac11dc4dd4daeaf663fa7c Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 5 Jan 2014 20:15:13 +0100 Subject: Testing --- testing.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing.go b/testing.go index 07e8c362f..64c34ca70 100644 --- a/testing.go +++ b/testing.go @@ -13,13 +13,13 @@ func Testing() { bm := NewBlockManager() - tx := NewTransaction("\x00", 20, []string{ - "PSH 10", - }) + tx := NewTransaction("\x00", 20, []string{"PSH 10"}) txData := tx.MarshalRlp() copyTx := &Transaction{} copyTx.UnmarshalRlp(txData) + fmt.Println(tx) + fmt.Println(copyTx) tx2 := NewTransaction("\x00", 20, []string{"SET 10 6", "LD 10 10"}) -- cgit v1.2.3 From 1676930a1644f06dafc66e2b5595eb343ffebc07 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 5 Jan 2014 20:41:01 +0100 Subject: updated testing" --- parsing_test.go | 4 ++-- testing.go | 3 ++- transaction.go | 4 +--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/parsing_test.go b/parsing_test.go index fa319e7bf..6aa9e240c 100644 --- a/parsing_test.go +++ b/parsing_test.go @@ -6,13 +6,13 @@ import ( ) func TestCompile(t *testing.T) { - instr, err := CompileInstr("SET 10 1") + instr, err := CompileInstr("PUSH") if err != nil { t.Error("Failed compiling instruction") } - calc := (67 + 10 * 256 + 1 * int64(math.Pow(256,2))) + calc := (48 + 0 * 256 + 0 * int64(math.Pow(256,2))) if Big(instr).Int64() != calc { t.Error("Expected", calc, ", got:", instr) } diff --git a/testing.go b/testing.go index 64c34ca70..3762e4dc5 100644 --- a/testing.go +++ b/testing.go @@ -13,8 +13,9 @@ func Testing() { bm := NewBlockManager() - tx := NewTransaction("\x00", 20, []string{"PSH 10"}) + tx := NewTransaction("\x00", 20, []string{"PUSH"}) txData := tx.MarshalRlp() + fmt.Printf("%q\n", txData) copyTx := &Transaction{} copyTx.UnmarshalRlp(txData) diff --git a/transaction.go b/transaction.go index a3279aa33..168d58566 100644 --- a/transaction.go +++ b/transaction.go @@ -2,7 +2,7 @@ package main import ( "math/big" - "fmt" + _"fmt" "github.com/obscuren/secp256k1-go" _"encoding/hex" _"crypto/sha256" @@ -140,7 +140,6 @@ func (tx *Transaction) MarshalRlp() []byte { func (tx *Transaction) UnmarshalRlp(data []byte) { t, _ := Decode(data,0) if slice, ok := t.([]interface{}); ok { - fmt.Printf("NONCE %T\n", slice[3]) if nonce, ok := slice[0].(uint8); ok { tx.nonce = string(nonce) } @@ -186,7 +185,6 @@ func (tx *Transaction) UnmarshalRlp(data []byte) { } // vrs - fmt.Printf("v %T\n", slice[5]) if v, ok := slice[5].(uint8); ok { tx.v = uint32(v) } -- cgit v1.2.3 From b6f7859c3dbf2d63933eab542c8ef3b3fdcbb863 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 5 Jan 2014 20:45:32 +0100 Subject: travis --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 92ca75b5a..7449c439a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Build +Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) + This is the Go implementation of the Ethereum protocol. It's far from being finished. ```go get https://github.com/ethereum/go-ethereum``` -- cgit v1.2.3 From 9f133a92d0853102863b77dd7c884d1462cf73a4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 8 Jan 2014 23:41:03 +0100 Subject: First dagger impl --- dagger.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 dagger.go diff --git a/dagger.go b/dagger.go new file mode 100644 index 000000000..750a781b0 --- /dev/null +++ b/dagger.go @@ -0,0 +1,110 @@ +package main + +import ( + "math/big" + "fmt" + "math/rand" + "time" + "github.com/obscuren/sha3" +) + +type Dagger struct { + hash *big.Int + xn *big.Int +} + +func (dag *Dagger) Search(diff *big.Int) *big.Int { + dag.hash = big.NewInt(0) + + obj := BigPow(2, 256) + obj = obj.Div(obj, diff) + + fmt.Println("diff", diff, "< objective", obj) + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + rnd := big.NewInt(r.Int63()) + fmt.Println("init rnd =", rnd) + + for i := 0; i < 1000; i++ { + if dag.Eval(rnd).Cmp(obj) < 0 { + fmt.Println("Found result! i = ", i) + return rnd + } + + rnd = rnd.Add(rnd, big.NewInt(1)) + } + + return big.NewInt(0) +} + +func (dag *Dagger) Node(L uint64, i uint64) *big.Int { + if L == i { + return dag.hash + } + + var m *big.Int + if L == 9 { + m = big.NewInt(16) + } else { + m = big.NewInt(3) + } + + sha := sha3.NewKeccak224() + sha.Reset() + d := sha3.NewKeccak224() + b := new(big.Int) + ret := new(big.Int) + + for k := 0; k < int(m.Uint64()); k++ { + d.Reset() + d.Write(dag.hash.Bytes()) + d.Write(dag.xn.Bytes()) + d.Write(big.NewInt(int64(L)).Bytes()) + d.Write(big.NewInt(int64(i)).Bytes()) + d.Write(big.NewInt(int64(k)).Bytes()) + + b.SetBytes(d.Sum(nil)) + pk := b.Uint64() & ((1 << ((L - 1) * 3)) - 1) + sha.Write(dag.Node(L - 1, pk).Bytes()) + } + + ret.SetBytes(sha.Sum(nil)) + + return ret +} + +func (dag *Dagger) Eval(N *big.Int) *big.Int { + pow := BigPow(2, 26) + dag.xn = N.Div(N, pow) + + sha := sha3.NewKeccak224() + sha.Reset() + ret := new(big.Int) + + doneChan := make(chan bool, 3) + + for k := 0; k < 4; k++ { + go func(_k int) { + d := sha3.NewKeccak224() + b := new(big.Int) + + d.Reset() + d.Write(dag.hash.Bytes()) + d.Write(dag.xn.Bytes()) + d.Write(N.Bytes()) + d.Write(big.NewInt(int64(_k)).Bytes()) + + b.SetBytes(d.Sum(nil)) + pk := (b.Uint64() & 0x1ffffff) + + sha.Write(dag.Node(9, pk).Bytes()) + doneChan <- true + }(k) + } + + for k := 0; k < 4; k++ { + <- doneChan + } + + return ret.SetBytes(sha.Sum(nil)) +} -- cgit v1.2.3 From 0929f59ec260e2a29863b8764959b271a7f75cc8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 8 Jan 2014 23:42:11 +0100 Subject: Updated marshalling --- block.go | 93 ++++++++++---------------------------------- contract.go | 24 ++---------- genesis.go | 2 - rlp.go | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++ transaction.go | 120 ++++++++++++++++++--------------------------------------- 5 files changed, 168 insertions(+), 176 deletions(-) diff --git a/block.go b/block.go index 00317573c..6ae3cc832 100644 --- a/block.go +++ b/block.go @@ -74,7 +74,6 @@ func CreateBlock(root string, num int, prevHash string, base string, difficulty block.state.Update(string(addr), string(contract.MarshalRlp())) for i, val := range tx.data { contract.state.Update(string(NumberToBytes(uint64(i), 32)), val) - //contract.state.Update(string(Encode(uint32(i))), val) } block.UpdateContract(addr, contract) } @@ -105,6 +104,7 @@ func (block *Block) PayFee(addr []byte, fee uint64) bool { // If we can't pay the fee return if contract == nil || contract.amount < fee { fmt.Println("Contract has insufficient funds", contract.amount, fee) + return false } @@ -112,7 +112,7 @@ func (block *Block) PayFee(addr []byte, fee uint64) bool { block.state.Update(string(addr), string(contract.MarshalRlp())) data := block.state.Get(string(block.coinbase)) - println(data) + // Get the ether (coinbase) and add the fee (gief fee to miner) ether := NewEtherFromData([]byte(data)) ether.amount += fee @@ -159,75 +159,24 @@ func (block *Block) MarshalRlp() []byte { } func (block *Block) UnmarshalRlp(data []byte) { - t, _ := Decode(data,0) - - // interface slice assertion - if slice, ok := t.([]interface{}); ok { - // interface slice assertion - if header, ok := slice[0].([]interface{}); ok { - if number, ok := header[0].(uint8); ok { - block.number = uint32(number) - } - - if prevHash, ok := header[1].([]uint8); ok { - block.prevHash = string(prevHash) - } - - // sha of uncles is header[2] - - if coinbase, ok := header[3].([]byte); ok { - block.coinbase = string(coinbase) - } - - if state, ok := header[4].([]uint8); ok { - // XXX The database is currently a global variable defined in testing.go - // This will eventually go away and the database will grabbed from the public server - // interface - block.state = NewTrie(Db, string(state)) - } - - // sha is header[5] - - // It's either 8bit or 64 - if difficulty, ok := header[6].(uint8); ok { - block.difficulty = uint32(difficulty) - } - if difficulty, ok := header[6].(uint64); ok { - block.difficulty = uint32(difficulty) - } - - // It's either 8bit or 64 - if time, ok := header[7].(uint8); ok { - block.time = int64(time) - } - if time, ok := header[7].(uint64); ok { - block.time = int64(time) - } - - if nonce, ok := header[8].(uint8); ok { - block.nonce = uint32(nonce) - } - - if extra, ok := header[9].([]byte); ok { - block.extra = string(extra) - } - } - - if txSlice, ok := slice[1].([]interface{}); ok { - // Create transaction slice equal to decoded tx interface slice - block.transactions = make([]*Transaction, len(txSlice)) - - // Unmarshal transactions - for i, tx := range txSlice { - if t, ok := tx.([]byte); ok { - tx := &Transaction{} - // Use the unmarshaled data to unmarshal the transaction - // t is still decoded. - tx.UnmarshalRlp(t) - - block.transactions[i] = tx - } - } - } + decoder := NewRlpDecoder(data) + + header := decoder.Get(0) + block.number = uint32(header.Get(0).AsUint()) + block.prevHash = header.Get(1).AsString() + // sha of uncles is header[2] + block.coinbase = header.Get(3).AsString() + block.state = NewTrie(Db, header.Get(4).AsString()) + block.difficulty = uint32(header.Get(5).AsUint()) + block.time = int64(header.Get(6).AsUint()) + block.nonce = uint32(header.Get(7).AsUint()) + block.extra = header.Get(8).AsString() + + txes := decoder.Get(1) + block.transactions = make([]*Transaction, txes.Length()) + for i := 0; i < txes.Length(); i++ { + tx := &Transaction{} + tx.UnmarshalRlp(txes.Get(i).AsBytes()) + block.transactions[i] = tx } } diff --git a/contract.go b/contract.go index 778f3578d..a54643f59 100644 --- a/contract.go +++ b/contract.go @@ -22,27 +22,11 @@ func (c *Contract) MarshalRlp() []byte { } func (c *Contract) UnmarshalRlp(data []byte) { - t, _ := Decode(data, 0) - - if slice, ok := t.([]interface{}); ok { - if t, ok := slice[0].(uint8); ok { - c.t = uint32(t) - } + decoder := NewRlpDecoder(data) - if amount, ok := slice[1].(uint8); ok { - c.amount = uint64(amount) - } else if amount, ok := slice[1].(uint16); ok { - c.amount = uint64(amount) - } else if amount, ok := slice[1].(uint32); ok { - c.amount = uint64(amount) - } else if amount, ok := slice[1].(uint64); ok { - c.amount = amount - } - - if root, ok := slice[2].([]uint8); ok { - c.state = NewTrie(Db, string(root)) - } - } + c.t = uint32(decoder.Get(0).AsUint()) + c.amount = decoder.Get(1).AsUint() + c.state = NewTrie(Db, decoder.Get(2).AsString()) } type Ether struct { diff --git a/genesis.go b/genesis.go index aae9cd1cf..21b8e9998 100644 --- a/genesis.go +++ b/genesis.go @@ -32,5 +32,3 @@ var GenisisHeader = []interface{}{ } var Genesis = []interface{}{ GenisisHeader, []interface{}{}, []interface{}{} } - -var GenisisBlock = NewBlock( Encode(Genesis) ) diff --git a/rlp.go b/rlp.go index 4db88539d..5366632f4 100644 --- a/rlp.go +++ b/rlp.go @@ -4,8 +4,110 @@ import ( "fmt" "bytes" "math" + "math/big" ) +type RlpEncoder struct { + rlpData []byte +} +func NewRlpEncoder() *RlpEncoder { + encoder := &RlpEncoder{} + + return encoder +} +func (coder *RlpEncoder) EncodeData(rlpData []interface{}) []byte { + return nil +} + +// Data attributes are returned by the rlp decoder. The data attributes represents +// one item within the rlp data structure. It's responsible for all the casting +// It always returns something valid +type RlpDataAttribute struct { + dataAttrib interface{} +} + +func NewRlpDataAttribute(attrib interface{}) *RlpDataAttribute { + return &RlpDataAttribute{dataAttrib: attrib} +} + +func (attr *RlpDataAttribute) Length() int { + if data, ok := attr.dataAttrib.([]interface{}); ok { + return len(data) + } + + return 0 +} + +func (attr *RlpDataAttribute) AsUint() uint64 { + if value, ok := attr.dataAttrib.(uint8); ok { + return uint64(value) + } else if value, ok := attr.dataAttrib.(uint16); ok { + return uint64(value) + } else if value, ok := attr.dataAttrib.(uint32); ok { + return uint64(value) + } else if value, ok := attr.dataAttrib.(uint64); ok { + return value + } + + return 0 +} + +func (attr *RlpDataAttribute) AsBigInt() *big.Int { + if a, ok := attr.dataAttrib.([]byte); ok { + return Big(string(a)) + } + + return big.NewInt(0) +} + +func (attr *RlpDataAttribute) AsString() string { + if a, ok := attr.dataAttrib.([]byte); ok { + return string(a) + } + + return "" +} + +func (attr *RlpDataAttribute) AsBytes() []byte { + if a, ok := attr.dataAttrib.([]byte); ok { + return a + } + + return make([]byte, 0) +} + +// Threat the attribute as a slice +func (attr *RlpDataAttribute) Get(idx int) *RlpDataAttribute { + if d, ok := attr.dataAttrib.([]interface{}); ok { + // Guard for oob + if len(d) < idx { + return NewRlpDataAttribute(nil) + } + + return NewRlpDataAttribute(d[idx]) + } + + // If this wasn't a slice you probably shouldn't be using this function + return NewRlpDataAttribute(nil) +} + +type RlpDecoder struct { + rlpData interface{} +} +func NewRlpDecoder(rlpData []byte) *RlpDecoder { + decoder := &RlpDecoder{} + // Decode the data + data, _ := Decode(rlpData,0) + decoder.rlpData = data + + return decoder +} + +func (dec *RlpDecoder) Get(idx int) *RlpDataAttribute { + return NewRlpDataAttribute(dec.rlpData).Get(idx) +} + +/// Raw methods func BinaryLength(n uint64) uint64 { if n == 0 { return 0 } @@ -122,6 +224,9 @@ func Encode(object interface{}) []byte { buff.WriteString(string(len(b2) + 55) + b2 + b) } + case *big.Int: + buff.Write(Encode(t.String())) + case string: if len(t) < 56 { buff.WriteString(string(len(t) + 64) + t) diff --git a/transaction.go b/transaction.go index 168d58566..90e0d9869 100644 --- a/transaction.go +++ b/transaction.go @@ -2,7 +2,7 @@ package main import ( "math/big" - _"fmt" + "fmt" "github.com/obscuren/secp256k1-go" _"encoding/hex" _"crypto/sha256" @@ -64,7 +64,8 @@ func NewTransaction(to string, value uint64, data []string) *Transaction { tx.data[i] = instr } - tx.SetVRS() + tx.Sign([]byte("privkey")) + tx.Sender() return &tx @@ -86,9 +87,9 @@ func (tx *Transaction) IsContract() bool { return tx.recipient == "" } -func (tx *Transaction) Signature() []byte { +func (tx *Transaction) Signature(key []byte) []byte { hash := tx.Hash() - sec := Sha256Bin([]byte("myprivkey")) + sec := Sha256Bin(key) sig, _ := secp256k1.Sign(hash, sec) @@ -96,29 +97,33 @@ func (tx *Transaction) Signature() []byte { } func (tx *Transaction) PublicKey() []byte { - hash := Sha256Bin(tx.MarshalRlp()) - sig := tx.Signature() + hash := Sha256Bin(tx.Hash()) + sig := append(tx.r, tx.s...) pubkey, _ := secp256k1.RecoverPubkey(hash, sig) return pubkey } -func (tx *Transaction) Address() []byte { - pubk := tx.PublicKey() - // 1 is the marker 04 - key := pubk[1:65] +func (tx *Transaction) Sender() []byte { + pubkey := tx.PublicKey() - return Sha256Bin(key)[12:] + // Validate the returned key. + // Return nil if public key isn't in full format (04 = full, 03 = compact) + if pubkey[0] != 4 { + return nil + } + + return Sha256Bin(pubkey[1:65])[12:] } -func (tx *Transaction) SetVRS() { - // Add 27 so we get either 27 or 28 (for positive and negative) - tx.v = uint32(tx.Signature()[64]) + 27 +func (tx *Transaction) Sign(privk []byte) { + sig := tx.Signature(privk) - pubk := tx.PublicKey()[1:65] - tx.r = pubk[:32] - tx.s = pubk[32:64] + // Add 27 so we get either 27 or 28 (for positive and negative) + tx.v = uint32(sig[64]) + 27 + tx.r = sig[:32] + tx.s = sig[32:65] } func (tx *Transaction) MarshalRlp() []byte { @@ -138,72 +143,23 @@ func (tx *Transaction) MarshalRlp() []byte { } func (tx *Transaction) UnmarshalRlp(data []byte) { - t, _ := Decode(data,0) - if slice, ok := t.([]interface{}); ok { - if nonce, ok := slice[0].(uint8); ok { - tx.nonce = string(nonce) - } - - if recipient, ok := slice[1].([]byte); ok { - tx.recipient = string(recipient) - } - - // If only I knew of a better way. - if value, ok := slice[2].(uint8); ok { - tx.value = uint64(value) - } - if value, ok := slice[2].(uint16); ok { - tx.value = uint64(value) - } - if value, ok := slice[2].(uint32); ok { - tx.value = uint64(value) - } - if value, ok := slice[2].(uint64); ok { - tx.value = uint64(value) - } - if fee, ok := slice[3].(uint8); ok { - tx.fee = uint32(fee) - } - if fee, ok := slice[3].(uint16); ok { - tx.fee = uint32(fee) - } - if fee, ok := slice[3].(uint32); ok { - tx.fee = uint32(fee) - } - if fee, ok := slice[3].(uint64); ok { - tx.fee = uint32(fee) - } - - // Encode the data/instructions - if data, ok := slice[4].([]interface{}); ok { - tx.data = make([]string, len(data)) - for i, d := range data { - if instr, ok := d.([]byte); ok { - tx.data[i] = string(instr) - } - } - } - - // vrs - if v, ok := slice[5].(uint8); ok { - tx.v = uint32(v) - } - if v, ok := slice[5].(uint16); ok { - tx.v = uint32(v) - } - if v, ok := slice[5].(uint32); ok { - tx.v = uint32(v) - } - if v, ok := slice[5].(uint64); ok { - tx.v = uint32(v) - } - if r, ok := slice[6].([]byte); ok { - tx.r = r - } - if s, ok := slice[7].([]byte); ok { - tx.s = s - } + decoder := NewRlpDecoder(data) + + tx.nonce = decoder.Get(0).AsString() + tx.recipient = decoder.Get(0).AsString() + tx.value = decoder.Get(2).AsUint() + tx.fee = uint32(decoder.Get(3).AsUint()) + + d := decoder.Get(4) + tx.data = make([]string, d.Length()) + fmt.Println(d.Get(0)) + for i := 0; i < d.Length(); i++ { + tx.data[i] = d.Get(i).AsString() } + + tx.v = uint32(decoder.Get(5).AsUint()) + tx.r = decoder.Get(6).AsBytes() + tx.s = decoder.Get(7).AsBytes() } func InitFees() { -- cgit v1.2.3 From 4bc9b1fcb3591f60d6c3d3c03c710e210918917d Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 8 Jan 2014 23:42:23 +0100 Subject: Updated readme --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7449c439a..05276a113 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,23 @@ -[![Build -Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) +Ethereum +======== -This is the Go implementation of the Ethereum protocol. It's far from being finished. +[![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) + +Ethereum Go + +Deps +==== + +Ethereum Go makes use of a modified `secp256k1-go` and therefor GMP. + +Install +======= ```go get https://github.com/ethereum/go-ethereum``` -** The Python reference implementation can be found at https://github.com/ethereum/pyethereum ** -## Command line options +Command line options +==================== -db will launch the db query tool. -- cgit v1.2.3 From 83bb4a3f265ec9a4fa75388dd6fa212a79a9b218 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 8 Jan 2014 23:42:35 +0100 Subject: added few commands --- db_query_interface.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db_query_interface.go b/db_query_interface.go index 11137e4c2..007f4bf28 100644 --- a/db_query_interface.go +++ b/db_query_interface.go @@ -72,6 +72,8 @@ func (i *DbInterface) ParseInput(input string) bool { fmt.Println(hex.EncodeToString([]byte(i.trie.root))) case "rawroot": fmt.Println(i.trie.root) + case "print": + i.db.Print() case "exit", "quit", "q": return false case "help": -- cgit v1.2.3 From 9f42835a0204ba2508895c1d259340bcda843974 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 8 Jan 2014 23:43:07 +0100 Subject: WIP Block chain --- block_manager.go | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/block_manager.go b/block_manager.go index 0f7a64cf8..134ca0e75 100644 --- a/block_manager.go +++ b/block_manager.go @@ -1,21 +1,26 @@ - - // Blocks, blocks will have transactions. - // Transactions/contracts are updated in goroutines - // Each contract should send a message on a channel with usage statistics - // The statics can be used for fee calculation within the block update method - // Statistics{transaction, /* integers */ normal_ops, store_load, extro_balance, crypto, steps} - // The block updater will wait for all goroutines to be finished and update the block accordingly - // in one go and should use minimal IO overhead. - // The actual block updating will happen within a goroutine as well so normal operation may continue - package main import ( "fmt" ) +type BlockChain struct { + lastBlock *Block + + genesisBlock *Block +} + +func NewBlockChain() *BlockChain { + bc := &BlockChain{} + bc.genesisBlock = NewBlock( Encode(Genesis) ) + + return bc +} + type BlockManager struct { vm *Vm + + blockChain *BlockChain } func NewBlockManager() *BlockManager { @@ -26,6 +31,11 @@ func NewBlockManager() *BlockManager { // Process a block. func (bm *BlockManager) ProcessBlock(block *Block) error { + // TODO Validation (Or move to other part of the application) + if err := bm.ValidateBlock(block); err != nil { + return err + } + // Get the tx count. Used to create enough channels to 'join' the go routines txCount := len(block.transactions) // Locking channel. When it has been fully buffered this method will return @@ -50,6 +60,10 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { return nil } +func (bm *BlockManager) ValidateBlock(block *Block) error { + return nil +} + func (bm *BlockManager) ProcessContract(tx *Transaction, block *Block, lockChan chan bool) { // Recovering function in case the VM had any errors defer func() { -- cgit v1.2.3 From 92b6667bd1cf7aad4a00331d761d8a92b03a7cae Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 8 Jan 2014 23:43:20 +0100 Subject: Minor update --- ethereum.go | 27 ++++++++++++++++++-- memory_database.go | 9 +++++++ server.go | 15 +++++++++--- testing.go | 12 ++++----- trie.go | 22 +++++++++-------- util.go | 13 ++++++++++ vm.go | 72 +++++++++++++++++++++++++++++++++++++----------------- 7 files changed, 126 insertions(+), 44 deletions(-) diff --git a/ethereum.go b/ethereum.go index ac9690e41..b10415fd1 100644 --- a/ethereum.go +++ b/ethereum.go @@ -5,13 +5,16 @@ import ( "os" "os/signal" "flag" + "runtime" ) const Debug = true var StartDBQueryInterface bool +var StartMining bool func Init() { flag.BoolVar(&StartDBQueryInterface, "db", false, "start db query interface") + flag.BoolVar(&StartMining, "mine", false, "start dagger mining") flag.Parse() } @@ -24,7 +27,7 @@ func RegisterInterupts(s *Server) { signal.Notify(c, os.Interrupt) go func() { for sig := range c { - fmt.Println("Shutting down (%v) ... \n", sig) + fmt.Printf("Shutting down (%v) ... \n", sig) s.Stop() } @@ -32,6 +35,8 @@ func RegisterInterupts(s *Server) { } func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) + InitFees() Init() @@ -39,7 +44,25 @@ func main() { if StartDBQueryInterface { dbInterface := NewDBInterface() dbInterface.Start() + } else if StartMining { + dagger := &Dagger{} + seed := dagger.Search(BigPow(2, 36)) + + fmt.Println("dagger res = ", seed) } else { - Testing() + fmt.Println("[DBUG]: Starting Ethereum") + server, err := NewServer() + + if err != nil { + fmt.Println("error NewServer:", err) + return + } + + RegisterInterupts(server) + + server.Start() + + // Wait for shutdown + server.WaitForShutdown() } } diff --git a/memory_database.go b/memory_database.go index fc40f76f3..a8c74bb46 100644 --- a/memory_database.go +++ b/memory_database.go @@ -1,6 +1,7 @@ package main import ( + "fmt" ) /* @@ -23,3 +24,11 @@ func (db *MemDatabase) Put(key []byte, value []byte) { func (db *MemDatabase) Get(key []byte) ([]byte, error) { return db.db[string(key)], nil } + +func (db *MemDatabase) Print() { + for key, val := range db.db { + fmt.Printf("%x(%d):", key, len(key)) + decoded := DecodeNode(val) + PrintSlice(decoded) + } +} diff --git a/server.go b/server.go index c8fb492d5..d7718a8a6 100644 --- a/server.go +++ b/server.go @@ -5,11 +5,15 @@ import ( "time" ) +var Db *LDBDatabase + type Server struct { // Channel for shutting down the server shutdownChan chan bool // DB interface db *LDBDatabase + // Block manager for processing new blocks and managing the block chain + blockManager *BlockManager // Peers (NYI) peers *list.List } @@ -20,8 +24,11 @@ func NewServer() (*Server, error) { return nil, err } + Db = db + server := &Server{ shutdownChan: make(chan bool), + blockManager: NewBlockManager(), db: db, peers: list.New(), } @@ -32,9 +39,11 @@ func NewServer() (*Server, error) { // Start the server func (s *Server) Start() { // For now this function just blocks the main thread - for { - time.Sleep( time.Second ) - } + go func() { + for { + time.Sleep( time.Second ) + } + }() } func (s *Server) Stop() { diff --git a/testing.go b/testing.go index 3762e4dc5..9b7b7b3ce 100644 --- a/testing.go +++ b/testing.go @@ -1,7 +1,8 @@ package main +/* import ( - "fmt" + _"fmt" ) // This will eventually go away @@ -15,18 +16,17 @@ func Testing() { tx := NewTransaction("\x00", 20, []string{"PUSH"}) txData := tx.MarshalRlp() - fmt.Printf("%q\n", txData) + //fmt.Printf("%q\n", txData) copyTx := &Transaction{} copyTx.UnmarshalRlp(txData) - fmt.Println(tx) - fmt.Println(copyTx) + //fmt.Println(tx) + //fmt.Println(copyTx) tx2 := NewTransaction("\x00", 20, []string{"SET 10 6", "LD 10 10"}) blck := CreateTestBlock([]*Transaction{tx2, tx}) bm.ProcessBlock( blck ) - - fmt.Println("GenesisBlock:", GenisisBlock, "hash", string(GenisisBlock.Hash())) } +*/ diff --git a/trie.go b/trie.go index 442cc08b8..d66699eeb 100644 --- a/trie.go +++ b/trie.go @@ -36,6 +36,8 @@ func DecodeNode(data []byte) []string { } return strSlice + } else { + fmt.Printf("It wasn't a []. It's a %T\n", dec) } return nil @@ -70,16 +72,6 @@ func (t *Trie) Get(key string) string { * State functions (shouldn't be needed directly). */ -// Wrapper around the regular db "Put" which generates a key and value -func (t *Trie) Put(node interface{}) []byte { - enc := Encode(node) - sha := Sha256Bin(enc) - - t.db.Put([]byte(sha), enc) - - return sha -} - // Helper function for printing a node (using fetch, decode and slice printing) func (t *Trie) PrintNode(n string) { data, _ := t.db.Get([]byte(n)) @@ -133,6 +125,16 @@ func (t *Trie) UpdateState(node string, key []int, value string) string { return "" } +// Wrapper around the regular db "Put" which generates a key and value +func (t *Trie) Put(node interface{}) []byte { + enc := Encode(node) + var sha []byte + sha = Sha256Bin(enc) + + t.db.Put([]byte(sha), enc) + + return sha +} func (t *Trie) InsertState(node string, key []int, value string) string { if len(key) == 0 { diff --git a/util.go b/util.go index 512a6f3d7..24e5455f4 100644 --- a/util.go +++ b/util.go @@ -6,6 +6,7 @@ import ( "encoding/hex" _"fmt" _"math" + "github.com/obscuren/sha3" ) func Uitoa(i uint32) string { @@ -24,6 +25,14 @@ func Sha256Bin(data []byte) []byte { return hash[:] } +func Sha3Bin(data []byte) []byte { + d := sha3.NewKeccak224() + d.Reset() + d.Write(data) + + return d.Sum(nil) +} + // Helper function for comparing slices func CompareIntSlice(a, b []int) bool { if len(a) != len(b) { @@ -48,3 +57,7 @@ func MatchingNibbleLength(a, b []int) int { return i } + +func Hex(d []byte) string { + return hex.EncodeToString(d) +} diff --git a/vm.go b/vm.go index a727d58e5..5b70f3544 100644 --- a/vm.go +++ b/vm.go @@ -11,29 +11,55 @@ import ( // Op codes const ( - oSTOP int = 0x00 - oADD int = 0x01 - oMUL int = 0x02 - oSUB int = 0x03 - oDIV int = 0x04 - oSDIV int = 0x05 - oMOD int = 0x06 - oSMOD int = 0x07 - oEXP int = 0x08 - oNEG int = 0x09 - oLT int = 0x0a - oLE int = 0x0b - oGT int = 0x0c - oGE int = 0x0d - oEQ int = 0x0e - oNOT int = 0x0f - oMYADDRESS int = 0x10 - oTXSENDER int = 0x11 - - - oPUSH int = 0x30 - oPOP int = 0x31 - oLOAD int = 0x36 + oSTOP int = 0x00 + oADD int = 0x01 + oMUL int = 0x02 + oSUB int = 0x03 + oDIV int = 0x04 + oSDIV int = 0x05 + oMOD int = 0x06 + oSMOD int = 0x07 + oEXP int = 0x08 + oNEG int = 0x09 + oLT int = 0x0a + oLE int = 0x0b + oGT int = 0x0c + oGE int = 0x0d + oEQ int = 0x0e + oNOT int = 0x0f + oMYADDRESS int = 0x10 + oTXSENDER int = 0x11 + oTXVALUE int = 0x12 + oTXFEE int = 0x13 + oTXDATAN int = 0x14 + oTXDATA int = 0x15 + oBLK_PREVHASH int = 0x16 + oBLK_COINBASE int = 0x17 + oBLK_TIMESTAMP int = 0x18 + oBLK_NUMBER int = 0x19 + oBLK_DIFFICULTY int = 0x1a + oSHA256 int = 0x20 + oRIPEMD160 int = 0x21 + oECMUL int = 0x22 + oECADD int = 0x23 + oECSIGN int = 0x24 + oECRECOVER int = 0x25 + oECVALID int = 0x26 + oPUSH int = 0x30 + oPOP int = 0x31 + oDUP int = 0x32 + oDUPN int = 0x33 + oSWAP int = 0x34 + oSWAPN int = 0x35 + oLOAD int = 0x36 + oSTORE int = 0x37 + oJMP int = 0x40 + oJMPI int = 0x41 + oIND int = 0x42 + oEXTRO int = 0x50 + oBALANCE int = 0x51 + oMKTX int = 0x60 + oSUICIDE int = 0xff ) type OpType int -- cgit v1.2.3 From d2b3071b4d820b9530b3de891078970ea036767c Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 9 Jan 2014 11:57:35 +0100 Subject: db console is now a general console. --- dagger.go | 45 +++++++++++++++++++++++++++++++++------------ db_query_interface.go | 5 +++++ ethereum.go | 7 +++---- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/dagger.go b/dagger.go index 750a781b0..b161ef733 100644 --- a/dagger.go +++ b/dagger.go @@ -6,6 +6,7 @@ import ( "math/rand" "time" "github.com/obscuren/sha3" + "hash" ) type Dagger struct { @@ -27,8 +28,11 @@ func (dag *Dagger) Search(diff *big.Int) *big.Int { for i := 0; i < 1000; i++ { if dag.Eval(rnd).Cmp(obj) < 0 { - fmt.Println("Found result! i = ", i) + fmt.Println("Found result! nonce = ", rnd) + return rnd + } else { + fmt.Println("Not found :( nonce = ", rnd) } rnd = rnd.Add(rnd, big.NewInt(1)) @@ -37,6 +41,16 @@ func (dag *Dagger) Search(diff *big.Int) *big.Int { return big.NewInt(0) } +func DaggerVerify(hash, diff, nonce *big.Int) bool { + dagger := &Dagger{} + dagger.hash = hash + + obj := BigPow(2, 256) + obj = obj.Div(obj, diff) + + return dagger.Eval(nonce).Cmp(obj) < 0 +} + func (dag *Dagger) Node(L uint64, i uint64) *big.Int { if L == i { return dag.hash @@ -63,16 +77,21 @@ func (dag *Dagger) Node(L uint64, i uint64) *big.Int { d.Write(big.NewInt(int64(i)).Bytes()) d.Write(big.NewInt(int64(k)).Bytes()) - b.SetBytes(d.Sum(nil)) + b.SetBytes(Sum(d)) pk := b.Uint64() & ((1 << ((L - 1) * 3)) - 1) sha.Write(dag.Node(L - 1, pk).Bytes()) } - ret.SetBytes(sha.Sum(nil)) + ret.SetBytes(Sum(sha)) return ret } +func Sum(sha hash.Hash) []byte { + in := make([]byte, 32) + return sha.Sum(in) +} + func (dag *Dagger) Eval(N *big.Int) *big.Int { pow := BigPow(2, 26) dag.xn = N.Div(N, pow) @@ -81,10 +100,11 @@ func (dag *Dagger) Eval(N *big.Int) *big.Int { sha.Reset() ret := new(big.Int) - doneChan := make(chan bool, 3) + //doneChan := make(chan bool, 3) for k := 0; k < 4; k++ { - go func(_k int) { + //go func(_k int) { + _k := k d := sha3.NewKeccak224() b := new(big.Int) @@ -94,17 +114,18 @@ func (dag *Dagger) Eval(N *big.Int) *big.Int { d.Write(N.Bytes()) d.Write(big.NewInt(int64(_k)).Bytes()) - b.SetBytes(d.Sum(nil)) + b.SetBytes(Sum(d)) pk := (b.Uint64() & 0x1ffffff) sha.Write(dag.Node(9, pk).Bytes()) - doneChan <- true - }(k) + //doneChan <- true + //}(k) } - for k := 0; k < 4; k++ { - <- doneChan - } + //for k := 0; k < 4; k++ { + // <- doneChan + //} + - return ret.SetBytes(sha.Sum(nil)) + return ret.SetBytes(Sum(sha)) } diff --git a/db_query_interface.go b/db_query_interface.go index 007f4bf28..ec1d7feb5 100644 --- a/db_query_interface.go +++ b/db_query_interface.go @@ -32,6 +32,9 @@ func (i *DbInterface) ValidateInput(action string, argumentLength int) error { case action == "get" && argumentLength != 1: err = true expArgCount = 1 + case action == "dag" && argumentLength != 2: + err = true + expArgCount = 2 } if err { @@ -74,6 +77,8 @@ func (i *DbInterface) ParseInput(input string) bool { fmt.Println(i.trie.root) case "print": i.db.Print() + case "dag": + fmt.Println(DaggerVerify(Big(tokens[1]), BigPow(2, 36), Big(tokens[2]))) case "exit", "quit", "q": return false case "help": diff --git a/ethereum.go b/ethereum.go index b10415fd1..b31ff751d 100644 --- a/ethereum.go +++ b/ethereum.go @@ -6,6 +6,7 @@ import ( "os/signal" "flag" "runtime" + _"math/big" ) const Debug = true @@ -13,7 +14,7 @@ const Debug = true var StartDBQueryInterface bool var StartMining bool func Init() { - flag.BoolVar(&StartDBQueryInterface, "db", false, "start db query interface") + flag.BoolVar(&StartDBQueryInterface, "c", false, "console interface") flag.BoolVar(&StartMining, "mine", false, "start dagger mining") flag.Parse() @@ -46,9 +47,7 @@ func main() { dbInterface.Start() } else if StartMining { dagger := &Dagger{} - seed := dagger.Search(BigPow(2, 36)) - - fmt.Println("dagger res = ", seed) + dagger.Search(BigPow(2, 36)) } else { fmt.Println("[DBUG]: Starting Ethereum") server, err := NewServer() -- cgit v1.2.3 From d895f83136eccc7e440792fdb45fd4d40876d82e Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 9 Jan 2014 16:19:16 +0100 Subject: Dagger improvements --- dagger.go | 52 ++++++++++++++++++++++++++++++++++++++++------------ dagger_test.go | 17 +++++++++++++++++ ethereum.go | 13 +++++++------ vm_test.go | 3 ++- 4 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 dagger_test.go diff --git a/dagger.go b/dagger.go index b161ef733..9fef78a36 100644 --- a/dagger.go +++ b/dagger.go @@ -14,7 +14,33 @@ type Dagger struct { xn *big.Int } +var Found bool +func (dag *Dagger) Find(obj *big.Int, resChan chan int64) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + for i := 0; i < 1000; i++ { + rnd := r.Int63() + + if dag.Eval(big.NewInt(rnd)).Cmp(obj) < 0 { + // Post back result on the channel + resChan <- rnd + // Notify other threads we've found a valid nonce + Found = true + } else { + fmt.Printf(".") + } + + // Break out if found + if Found { break } + } + + resChan <- 0 +} + func (dag *Dagger) Search(diff *big.Int) *big.Int { + // TODO fix multi threading. Somehow it results in the wrong nonce + amountOfRoutines := 1 + dag.hash = big.NewInt(0) obj := BigPow(2, 256) @@ -22,23 +48,25 @@ func (dag *Dagger) Search(diff *big.Int) *big.Int { fmt.Println("diff", diff, "< objective", obj) - r := rand.New(rand.NewSource(time.Now().UnixNano())) - rnd := big.NewInt(r.Int63()) - fmt.Println("init rnd =", rnd) + Found = false + resChan := make(chan int64, 3) + var res int64 - for i := 0; i < 1000; i++ { - if dag.Eval(rnd).Cmp(obj) < 0 { - fmt.Println("Found result! nonce = ", rnd) + for k := 0; k < amountOfRoutines; k++ { + go dag.Find(obj, resChan) + } - return rnd - } else { - fmt.Println("Not found :( nonce = ", rnd) + // Wait for each go routine to finish + for k := 0; k < amountOfRoutines; k++ { + // Get the result from the channel. 0 = quit + if r := <- resChan; r != 0 { + res = r } - - rnd = rnd.Add(rnd, big.NewInt(1)) } - return big.NewInt(0) + fmt.Println("\n") + + return big.NewInt(res) } func DaggerVerify(hash, diff, nonce *big.Int) bool { diff --git a/dagger_test.go b/dagger_test.go new file mode 100644 index 000000000..7953ec1da --- /dev/null +++ b/dagger_test.go @@ -0,0 +1,17 @@ +package main + +import ( + "testing" + "math/big" +) + +func BenchmarkDaggerSearch(b *testing.B) { + hash := big.NewInt(0) + diff := BigPow(2, 36) + o := big.NewInt(0) // nonce doesn't matter. We're only testing against speed, not validity + + // Reset timer so the big generation isn't included in the benchmark + b.ResetTimer() + // Validate + DaggerVerify(hash, diff, o) +} diff --git a/ethereum.go b/ethereum.go index b31ff751d..e3e5005eb 100644 --- a/ethereum.go +++ b/ethereum.go @@ -11,10 +11,10 @@ import ( const Debug = true -var StartDBQueryInterface bool +var StartConsole bool var StartMining bool func Init() { - flag.BoolVar(&StartDBQueryInterface, "c", false, "console interface") + flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "mine", false, "start dagger mining") flag.Parse() @@ -42,12 +42,13 @@ func main() { Init() - if StartDBQueryInterface { - dbInterface := NewDBInterface() - dbInterface.Start() + if StartConsole { + console := NewConsole() + console.Start() } else if StartMining { dagger := &Dagger{} - dagger.Search(BigPow(2, 36)) + res := dagger.Search(BigPow(2, 36)) + fmt.Println("nonce =", res) } else { fmt.Println("[DBUG]: Starting Ethereum") server, err := NewServer() diff --git a/vm_test.go b/vm_test.go index efcd875cd..cb70220e1 100644 --- a/vm_test.go +++ b/vm_test.go @@ -1,5 +1,6 @@ package main +/* import ( _"fmt" "testing" @@ -72,4 +73,4 @@ func TestVm(t *testing.T) { bm := NewBlockManager() bm.ProcessBlock( block ) } - +*/ -- cgit v1.2.3 From 01740695cda7fe27c53f0fa078732fa5a15a88a5 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 9 Jan 2014 16:19:44 +0100 Subject: moved db to dev console --- db_query_interface.go | 113 ------------------------------------------------- dev_console.go | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 113 deletions(-) delete mode 100644 db_query_interface.go create mode 100644 dev_console.go diff --git a/db_query_interface.go b/db_query_interface.go deleted file mode 100644 index ec1d7feb5..000000000 --- a/db_query_interface.go +++ /dev/null @@ -1,113 +0,0 @@ -package main - -import ( - "fmt" - "bufio" - "strings" - "os" - "errors" - "encoding/hex" -) - -type DbInterface struct { - db *MemDatabase - trie *Trie -} - -func NewDBInterface() *DbInterface { - db, _ := NewMemDatabase() - trie := NewTrie(db, "") - - return &DbInterface{db: db, trie: trie} -} - -func (i *DbInterface) ValidateInput(action string, argumentLength int) error { - err := false - var expArgCount int - - switch { - case action == "update" && argumentLength != 2: - err = true - expArgCount = 2 - case action == "get" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "dag" && argumentLength != 2: - err = true - expArgCount = 2 - } - - if err { - return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) - } else { - return nil - } -} - -func (i *DbInterface) ParseInput(input string) bool { - scanner := bufio.NewScanner(strings.NewReader(input)) - scanner.Split(bufio.ScanWords) - - count := 0 - var tokens []string - for scanner.Scan() { - count++ - tokens = append(tokens, scanner.Text()) - } - if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "reading input:", err) - } - - if len(tokens) == 0 { return true } - - err := i.ValidateInput(tokens[0], count-1) - if err != nil { - fmt.Println(err) - } else { - switch tokens[0] { - case "update": - i.trie.Update(tokens[1], tokens[2]) - - fmt.Println(hex.EncodeToString([]byte(i.trie.root))) - case "get": - fmt.Println(i.trie.Get(tokens[1])) - case "root": - fmt.Println(hex.EncodeToString([]byte(i.trie.root))) - case "rawroot": - fmt.Println(i.trie.root) - case "print": - i.db.Print() - case "dag": - fmt.Println(DaggerVerify(Big(tokens[1]), BigPow(2, 36), Big(tokens[2]))) - case "exit", "quit", "q": - return false - case "help": - fmt.Printf(`QUERY COMMANDS: -update KEY VALUE - Updates/Creates a new value for the given key -get KEY - Retrieves the given key -root - Prints the hex encoded merkle root -rawroot - Prints the raw merkle root -`) - default: - fmt.Println("Unknown command:", tokens[0]) - } - } - - return true -} - -func (i *DbInterface) Start() { - fmt.Printf("DB Query tool. Type (help) for help\n") - reader := bufio.NewReader(os.Stdin) - for { - fmt.Printf("db >>> ") - str, _, err := reader.ReadLine() - if err != nil { - fmt.Println("Error reading input", err) - } else { - if !i.ParseInput(string(str)) { - return - } - } - } -} diff --git a/dev_console.go b/dev_console.go new file mode 100644 index 000000000..65166c0c1 --- /dev/null +++ b/dev_console.go @@ -0,0 +1,115 @@ +package main + +import ( + "fmt" + "bufio" + "strings" + "os" + "errors" + "encoding/hex" +) + +type Console struct { + db *MemDatabase + trie *Trie +} + +func NewConsole() *Console { + db, _ := NewMemDatabase() + trie := NewTrie(db, "") + + return &Console{db: db, trie: trie} +} + +func (i *Console) ValidateInput(action string, argumentLength int) error { + err := false + var expArgCount int + + switch { + case action == "update" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "get" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "dag" && argumentLength != 2: + err = true + expArgCount = 2 + } + + if err { + return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) + } else { + return nil + } +} + +func (i *Console) ParseInput(input string) bool { + scanner := bufio.NewScanner(strings.NewReader(input)) + scanner.Split(bufio.ScanWords) + + count := 0 + var tokens []string + for scanner.Scan() { + count++ + tokens = append(tokens, scanner.Text()) + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading input:", err) + } + + if len(tokens) == 0 { return true } + + err := i.ValidateInput(tokens[0], count-1) + if err != nil { + fmt.Println(err) + } else { + switch tokens[0] { + case "update": + i.trie.Update(tokens[1], tokens[2]) + + fmt.Println(hex.EncodeToString([]byte(i.trie.root))) + case "get": + fmt.Println(i.trie.Get(tokens[1])) + case "root": + fmt.Println(hex.EncodeToString([]byte(i.trie.root))) + case "rawroot": + fmt.Println(i.trie.root) + case "print": + i.db.Print() + case "dag": + fmt.Println(DaggerVerify(Big(tokens[1]), BigPow(2, 36), Big(tokens[2]))) + case "exit", "quit", "q": + return false + case "help": + fmt.Printf( "COMMANDS:\n"+ + "\033[1m= DB =\033[0m\n"+ + "update KEY VALUE - Updates/Creates a new value for the given key\n"+ + "get KEY - Retrieves the given key\n"+ + "root - Prints the hex encoded merkle root\n"+ + "rawroot - Prints the raw merkle root\n"+ + "\033[1m= Dagger =\033[0m\n"+ + "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n") + default: + fmt.Println("Unknown command:", tokens[0]) + } + } + + return true +} + +func (i *Console) Start() { + fmt.Printf("Eth Console. Type (help) for help\n") + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("eth >>> ") + str, _, err := reader.ReadLine() + if err != nil { + fmt.Println("Error reading input", err) + } else { + if !i.ParseInput(string(str)) { + return + } + } + } +} -- cgit v1.2.3 From 849408dda60fe32d7abb78d103b09ca0bc7b5a60 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 9 Jan 2014 23:15:51 +0100 Subject: Peer handling --- peer.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ server.go | 20 ++++++++++++-- 2 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 peer.go diff --git a/peer.go b/peer.go new file mode 100644 index 000000000..0c8d38772 --- /dev/null +++ b/peer.go @@ -0,0 +1,93 @@ +package main + +import ( + "net" + "errors" + "log" +) + +type InMsg struct { + msgType string // Specifies how the encoded data should be interpreted + data []byte // RLP encoded data +} + +func ReadMessage(conn net.Conn) (*InMsg, error) { + buff := make([]byte, 4069) + + // Wait for a message from this peer + n, err := conn.Read(buff) + if err != nil { + return nil, err + } else if n == 0 { + return nil, errors.New("Empty message received") + } + + // Read the header (MAX n) + decoder := NewRlpDecoder(buff[:n]) + t := decoder.Get(0).AsString() + if t == "" { + return nil, errors.New("Data contained no data type") + } + + return &InMsg{msgType: t, data: decoder.Get(1).AsBytes()}, nil +} + +type OutMsg struct { + data []byte +} + +type Peer struct { + server *Server + conn net.Conn + outputQueue chan OutMsg + quit chan bool +} + +func NewPeer(conn net.Conn, server *Server) *Peer { + return &Peer{ + outputQueue: make(chan OutMsg, 1), // Buffered chan of 1 is enough + quit: make(chan bool), + + server: server, + conn: conn, + } +} + +// Outputs any RLP encoded data to the peer +func (p *Peer) QueueMessage(data []byte) { + p.outputQueue <- OutMsg{data: data} +} + +func (p *Peer) HandleOutbound() { +out: + for { + switch { + case <- p.quit: + break out + } + } +} + +func (p *Peer) HandleInbound() { + defer p.conn.Close() + +out: + for { + msg, err := ReadMessage(p.conn) + if err != nil { + log.Println(err) + + break out + } + + log.Println(msg) + } + + // Notify the out handler we're quiting + p.quit <- true +} + +func (p *Peer) Start() { + go p.HandleOutbound() + go p.HandleInbound() +} diff --git a/server.go b/server.go index d7718a8a6..feaf61076 100644 --- a/server.go +++ b/server.go @@ -2,7 +2,8 @@ package main import ( "container/list" - "time" + "net" + "log" ) var Db *LDBDatabase @@ -36,12 +37,27 @@ func NewServer() (*Server, error) { return server, nil } +func (s *Server) AddPeer(conn net.Conn) { + s.peers.PushBack(NewPeer(conn, s)) +} + // Start the server func (s *Server) Start() { // For now this function just blocks the main thread + ln, err := net.Listen("tcp", ":12345") + if err != nil { + log.Fatal(err) + } + go func() { for { - time.Sleep( time.Second ) + conn, err := ln.Accept() + if err != nil { + log.Println(err) + continue + } + + go s.AddPeer(conn) } }() } -- cgit v1.2.3 From bd0abe2a8187c0ae948bba6a90cbaac07f479cc8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 00:39:16 +0100 Subject: Updated server and peers and mining processing --- dagger.go | 46 ++++++++++++++-------------------------------- ethereum.go | 31 ++++++++++++++++++++++++------- peer.go | 40 +++++++++++++++++++++++++++++++--------- server.go | 45 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 112 insertions(+), 50 deletions(-) diff --git a/dagger.go b/dagger.go index 9fef78a36..8509232a2 100644 --- a/dagger.go +++ b/dagger.go @@ -2,7 +2,6 @@ package main import ( "math/big" - "fmt" "math/rand" "time" "github.com/obscuren/sha3" @@ -26,8 +25,6 @@ func (dag *Dagger) Find(obj *big.Int, resChan chan int64) { resChan <- rnd // Notify other threads we've found a valid nonce Found = true - } else { - fmt.Printf(".") } // Break out if found @@ -37,17 +34,15 @@ func (dag *Dagger) Find(obj *big.Int, resChan chan int64) { resChan <- 0 } -func (dag *Dagger) Search(diff *big.Int) *big.Int { +func (dag *Dagger) Search(hash, diff *big.Int) *big.Int { // TODO fix multi threading. Somehow it results in the wrong nonce amountOfRoutines := 1 - dag.hash = big.NewInt(0) + dag.hash = hash obj := BigPow(2, 256) obj = obj.Div(obj, diff) - fmt.Println("diff", diff, "< objective", obj) - Found = false resChan := make(chan int64, 3) var res int64 @@ -64,8 +59,6 @@ func (dag *Dagger) Search(diff *big.Int) *big.Int { } } - fmt.Println("\n") - return big.NewInt(res) } @@ -128,32 +121,21 @@ func (dag *Dagger) Eval(N *big.Int) *big.Int { sha.Reset() ret := new(big.Int) - //doneChan := make(chan bool, 3) - for k := 0; k < 4; k++ { - //go func(_k int) { - _k := k - d := sha3.NewKeccak224() - b := new(big.Int) - - d.Reset() - d.Write(dag.hash.Bytes()) - d.Write(dag.xn.Bytes()) - d.Write(N.Bytes()) - d.Write(big.NewInt(int64(_k)).Bytes()) - - b.SetBytes(Sum(d)) - pk := (b.Uint64() & 0x1ffffff) - - sha.Write(dag.Node(9, pk).Bytes()) - //doneChan <- true - //}(k) - } + d := sha3.NewKeccak224() + b := new(big.Int) - //for k := 0; k < 4; k++ { - // <- doneChan - //} + d.Reset() + d.Write(dag.hash.Bytes()) + d.Write(dag.xn.Bytes()) + d.Write(N.Bytes()) + d.Write(big.NewInt(int64(k)).Bytes()) + b.SetBytes(Sum(d)) + pk := (b.Uint64() & 0x1ffffff) + + sha.Write(dag.Node(9, pk).Bytes()) + } return ret.SetBytes(Sum(sha)) } diff --git a/ethereum.go b/ethereum.go index e3e5005eb..6ceb0249d 100644 --- a/ethereum.go +++ b/ethereum.go @@ -6,6 +6,7 @@ import ( "os/signal" "flag" "runtime" + "log" _"math/big" ) @@ -45,23 +46,39 @@ func main() { if StartConsole { console := NewConsole() console.Start() - } else if StartMining { - dagger := &Dagger{} - res := dagger.Search(BigPow(2, 36)) - fmt.Println("nonce =", res) - } else { - fmt.Println("[DBUG]: Starting Ethereum") + } else{ + log.Println("Starting Ethereum") server, err := NewServer() if err != nil { - fmt.Println("error NewServer:", err) + log.Println(err) return } RegisterInterupts(server) + if StartMining { + log.Println("Mining started") + dagger := &Dagger{} + + go func() { + for { + res := dagger.Search(Big("0"), BigPow(2, 36)) + server.Broadcast("foundblock", res.Bytes()) + } + }() + } + server.Start() + err = server.ConnectToPeer("localhost:12345") + if err != nil { + log.Println(err) + server.Stop() + return + } + + // Wait for shutdown server.WaitForShutdown() } diff --git a/peer.go b/peer.go index 0c8d38772..15e0fdcf1 100644 --- a/peer.go +++ b/peer.go @@ -11,6 +11,11 @@ type InMsg struct { data []byte // RLP encoded data } +type OutMsg struct { + msgType string + data []byte +} + func ReadMessage(conn net.Conn) (*InMsg, error) { buff := make([]byte, 4069) @@ -23,6 +28,7 @@ func ReadMessage(conn net.Conn) (*InMsg, error) { } // Read the header (MAX n) + // XXX The data specification is made up. This will change once more details have been released on the specification of the format decoder := NewRlpDecoder(buff[:n]) t := decoder.Get(0).AsString() if t == "" { @@ -32,10 +38,6 @@ func ReadMessage(conn net.Conn) (*InMsg, error) { return &InMsg{msgType: t, data: decoder.Get(1).AsBytes()}, nil } -type OutMsg struct { - data []byte -} - type Peer struct { server *Server conn net.Conn @@ -54,22 +56,34 @@ func NewPeer(conn net.Conn, server *Server) *Peer { } // Outputs any RLP encoded data to the peer -func (p *Peer) QueueMessage(data []byte) { - p.outputQueue <- OutMsg{data: data} +func (p *Peer) QueueMessage(msgType string, data []byte) { + p.outputQueue <- OutMsg{msgType: msgType, data: data} } func (p *Peer) HandleOutbound() { out: for { - switch { + select { + case msg := <-p.outputQueue: + p.WriteMessage(msg) + case <- p.quit: break out } } } +func (p *Peer) WriteMessage(msg OutMsg) { + encoded := Encode([]interface{}{ msg.msgType, msg.data }) + _, err := p.conn.Write(encoded) + if err != nil { + log.Println(err) + p.Stop() + } +} + func (p *Peer) HandleInbound() { - defer p.conn.Close() + defer p.Stop() out: for { @@ -80,7 +94,9 @@ out: break out } - log.Println(msg) + // TODO + data, _ := Decode(msg.data, 0) + log.Printf("%s, %s\n", msg.msgType, data) } // Notify the out handler we're quiting @@ -91,3 +107,9 @@ func (p *Peer) Start() { go p.HandleOutbound() go p.HandleInbound() } + +func (p *Peer) Stop() { + defer p.conn.Close() + + p.quit <- true +} diff --git a/server.go b/server.go index feaf61076..c2903cb59 100644 --- a/server.go +++ b/server.go @@ -4,6 +4,7 @@ import ( "container/list" "net" "log" + _"time" ) var Db *LDBDatabase @@ -38,7 +39,36 @@ func NewServer() (*Server, error) { } func (s *Server) AddPeer(conn net.Conn) { - s.peers.PushBack(NewPeer(conn, s)) + peer := NewPeer(conn, s) + s.peers.PushBack(peer) + peer.Start() + + log.Println("Peer connected ::", conn.RemoteAddr()) +} + +func (s *Server) ConnectToPeer(addr string) error { + conn, err := net.Dial("tcp", addr) + + if err != nil { + return err + } + + peer := NewPeer(conn, s) + s.peers.PushBack(peer) + peer.Start() + + + log.Println("Connected to peer ::", conn.RemoteAddr()) + + return nil +} + +func (s *Server) Broadcast(msgType string, data []byte) { + for e := s.peers.Front(); e != nil; e = e.Next() { + if peer, ok := e.Value.(*Peer); ok { + peer.QueueMessage(msgType, data) + } + } } // Start the server @@ -60,6 +90,15 @@ func (s *Server) Start() { go s.AddPeer(conn) } }() + + // TMP + //go func() { + // for { + // s.Broadcast("block", Encode("blockdata")) +// +// time.Sleep(100 * time.Millisecond) +// } +// }() } func (s *Server) Stop() { @@ -68,7 +107,9 @@ func (s *Server) Stop() { // Loop thru the peers and close them (if we had them) for e := s.peers.Front(); e != nil; e = e.Next() { - // peer close etc + if peer, ok := e.Value.(*Peer); ok { + peer.Stop() + } } s.shutdownChan <- true -- cgit v1.2.3 From ce40e51ae53af9679e11f8ced934ae8ee3b2db6f Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 00:44:45 +0100 Subject: Encoding --- ethereum.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum.go b/ethereum.go index 6ceb0249d..a935f903e 100644 --- a/ethereum.go +++ b/ethereum.go @@ -64,7 +64,7 @@ func main() { go func() { for { res := dagger.Search(Big("0"), BigPow(2, 36)) - server.Broadcast("foundblock", res.Bytes()) + server.Broadcast("block", Encode(res.String())) } }() } -- cgit v1.2.3 From 19addcb51a1d100db483be98ee660b73dd5070d7 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 10:58:46 +0100 Subject: Comments --- peer.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/peer.go b/peer.go index 15e0fdcf1..a855fe2b9 100644 --- a/peer.go +++ b/peer.go @@ -31,6 +31,7 @@ func ReadMessage(conn net.Conn) (*InMsg, error) { // XXX The data specification is made up. This will change once more details have been released on the specification of the format decoder := NewRlpDecoder(buff[:n]) t := decoder.Get(0).AsString() + // If the msgdata contains no data we throw an error and disconnect the peer if t == "" { return nil, errors.New("Data contained no data type") } @@ -39,9 +40,13 @@ func ReadMessage(conn net.Conn) (*InMsg, error) { } type Peer struct { + // Server interface server *Server + // Net connection conn net.Conn + // Output queue which is used to communicate and handle messages outputQueue chan OutMsg + // Quit channel quit chan bool } @@ -60,21 +65,28 @@ func (p *Peer) QueueMessage(msgType string, data []byte) { p.outputQueue <- OutMsg{msgType: msgType, data: data} } +// Outbound message handler. Outbound messages are handled here func (p *Peer) HandleOutbound() { out: for { select { + // Main message queue. All outbound messages are processed through here case msg := <-p.outputQueue: + // TODO Message checking and handle accordingly p.WriteMessage(msg) + // Break out of the for loop if a quit message is posted case <- p.quit: break out } } } +// Write a message to the peer. func (p *Peer) WriteMessage(msg OutMsg) { + // Encode the type and the (RLP encoded) data for sending over the wire encoded := Encode([]interface{}{ msg.msgType, msg.data }) + // Write to the connection _, err := p.conn.Write(encoded) if err != nil { log.Println(err) @@ -82,11 +94,13 @@ func (p *Peer) WriteMessage(msg OutMsg) { } } +// Inbound handler. Inbound messages are received here and passed to the appropriate methods func (p *Peer) HandleInbound() { defer p.Stop() out: for { + // Wait for a message from the peer msg, err := ReadMessage(p.conn) if err != nil { log.Println(err) @@ -104,7 +118,9 @@ out: } func (p *Peer) Start() { + // Run the outbound handler in a new goroutine go p.HandleOutbound() + // Run the inbound handler in a new goroutine go p.HandleInbound() } -- cgit v1.2.3 From 164d7c4f0b8d8c35f7a50c32e0fdfb2aae8b6ebe Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 10:58:55 +0100 Subject: before install --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 69359072d..f10dc0606 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +before_install: udo apt-get install libgmp3-dev language: go go: - 1.2 -- cgit v1.2.3 From a9c16803d239a007a865d97485cd96dffd2b77a9 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 10:59:07 +0100 Subject: Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05276a113..c7201a4ce 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,6 @@ Install Command line options ==================== --db will launch the db query tool. +-c will launch the developer console Supported arguments `update` and `get` -- cgit v1.2.3 From ba385ccdf15f8379c54d65061c3d62353baffc2b Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 10:59:57 +0100 Subject: sudo not udo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f10dc0606..32740f91a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -before_install: udo apt-get install libgmp3-dev +before_install: sudo apt-get install libgmp3-dev language: go go: - 1.2 -- cgit v1.2.3 From fbc59b7323394fc95e0d87f20375c5e1cbec2170 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 11:05:28 +0100 Subject: Updated read me and changed mine argument to m --- README.md | 5 ++--- ethereum.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c7201a4ce..4b797b5a7 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,5 @@ Install Command line options ==================== --c will launch the developer console - -Supported arguments `update` and `get` +-c launch the developer console +-m start mining fake blocks and broadcast fake messages to the net diff --git a/ethereum.go b/ethereum.go index a935f903e..aee8ebc30 100644 --- a/ethereum.go +++ b/ethereum.go @@ -16,7 +16,7 @@ var StartConsole bool var StartMining bool func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") - flag.BoolVar(&StartMining, "mine", false, "start dagger mining") + flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.Parse() } -- cgit v1.2.3 From dbd28a52525581b59fb334f2aafa3b2b56fb6ba8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 11:18:54 +0100 Subject: 224 to 256 --- dagger.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dagger.go b/dagger.go index 8509232a2..2c1976840 100644 --- a/dagger.go +++ b/dagger.go @@ -84,9 +84,9 @@ func (dag *Dagger) Node(L uint64, i uint64) *big.Int { m = big.NewInt(3) } - sha := sha3.NewKeccak224() + sha := sha3.NewKeccak256() sha.Reset() - d := sha3.NewKeccak224() + d := sha3.NewKeccak256() b := new(big.Int) ret := new(big.Int) @@ -117,12 +117,12 @@ func (dag *Dagger) Eval(N *big.Int) *big.Int { pow := BigPow(2, 26) dag.xn = N.Div(N, pow) - sha := sha3.NewKeccak224() + sha := sha3.NewKeccak256() sha.Reset() ret := new(big.Int) for k := 0; k < 4; k++ { - d := sha3.NewKeccak224() + d := sha3.NewKeccak256() b := new(big.Int) d.Reset() -- cgit v1.2.3 From f6fa4f88797030b8e83066cb262a68958953974e Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 21:18:56 +0100 Subject: WIP Splitting ethereum up in submodules --- block.go | 20 ++++++++++++++----- peer.go | 69 ++++++++++++++-------------------------------------------------- 2 files changed, 30 insertions(+), 59 deletions(-) diff --git a/block.go b/block.go index 6ae3cc832..2287a9d55 100644 --- a/block.go +++ b/block.go @@ -51,7 +51,15 @@ func CreateTestBlock(/* TODO use raw data */transactions []*Transaction) *Block return block } -func CreateBlock(root string, num int, prevHash string, base string, difficulty int, nonce int, extra string, txes []*Transaction) *Block { +func CreateBlock(root string, + num int, + prevHash string, + base string, + difficulty int, + nonce int, + extra string, + txes []*Transaction) *Block { + block := &Block{ // Slice of transactions to include in this block transactions: txes, @@ -134,8 +142,11 @@ func (block *Block) MarshalRlp() []byte { // Cast it to a string (safe) encTx[i] = string(tx.MarshalRlp()) } + // TODO + uncles := []interface{}{} - /* I made up the block. It should probably contain different data or types. It sole purpose now is testing */ + // I made up the block. It should probably contain different data or types. + // It sole purpose now is testing header := []interface{}{ block.number, block.prevHash, @@ -152,9 +163,8 @@ func (block *Block) MarshalRlp() []byte { block.extra, } - // TODO - uncles := []interface{}{} - // Encode a slice interface which contains the header and the list of transactions. + // Encode a slice interface which contains the header and the list of + // transactions. return Encode([]interface{}{header, encTx, uncles}) } diff --git a/peer.go b/peer.go index a855fe2b9..d47af73de 100644 --- a/peer.go +++ b/peer.go @@ -2,57 +2,24 @@ package main import ( "net" - "errors" "log" + "github.com/ethereum/ethwire-go" ) -type InMsg struct { - msgType string // Specifies how the encoded data should be interpreted - data []byte // RLP encoded data -} - -type OutMsg struct { - msgType string - data []byte -} - -func ReadMessage(conn net.Conn) (*InMsg, error) { - buff := make([]byte, 4069) - - // Wait for a message from this peer - n, err := conn.Read(buff) - if err != nil { - return nil, err - } else if n == 0 { - return nil, errors.New("Empty message received") - } - - // Read the header (MAX n) - // XXX The data specification is made up. This will change once more details have been released on the specification of the format - decoder := NewRlpDecoder(buff[:n]) - t := decoder.Get(0).AsString() - // If the msgdata contains no data we throw an error and disconnect the peer - if t == "" { - return nil, errors.New("Data contained no data type") - } - - return &InMsg{msgType: t, data: decoder.Get(1).AsBytes()}, nil -} - type Peer struct { // Server interface server *Server // Net connection conn net.Conn // Output queue which is used to communicate and handle messages - outputQueue chan OutMsg + outputQueue chan ethwire.InOutMsg // Quit channel quit chan bool } func NewPeer(conn net.Conn, server *Server) *Peer { return &Peer{ - outputQueue: make(chan OutMsg, 1), // Buffered chan of 1 is enough + outputQueue: make(chan ethwire.InOutMsg, 1), // Buffered chan of 1 is enough quit: make(chan bool), server: server, @@ -62,7 +29,7 @@ func NewPeer(conn net.Conn, server *Server) *Peer { // Outputs any RLP encoded data to the peer func (p *Peer) QueueMessage(msgType string, data []byte) { - p.outputQueue <- OutMsg{msgType: msgType, data: data} + p.outputQueue <- ethwire.InOutMsg{MsgType: msgType, Data: data} } // Outbound message handler. Outbound messages are handled here @@ -73,7 +40,13 @@ out: // Main message queue. All outbound messages are processed through here case msg := <-p.outputQueue: // TODO Message checking and handle accordingly - p.WriteMessage(msg) + err := ethwire.WriteMessage(p.conn, msg) + if err != nil { + log.Println(err) + + // Stop the client if there was an error writing to it + p.Stop() + } // Break out of the for loop if a quit message is posted case <- p.quit: @@ -82,18 +55,6 @@ out: } } -// Write a message to the peer. -func (p *Peer) WriteMessage(msg OutMsg) { - // Encode the type and the (RLP encoded) data for sending over the wire - encoded := Encode([]interface{}{ msg.msgType, msg.data }) - // Write to the connection - _, err := p.conn.Write(encoded) - if err != nil { - log.Println(err) - p.Stop() - } -} - // Inbound handler. Inbound messages are received here and passed to the appropriate methods func (p *Peer) HandleInbound() { defer p.Stop() @@ -101,7 +62,7 @@ func (p *Peer) HandleInbound() { out: for { // Wait for a message from the peer - msg, err := ReadMessage(p.conn) + msg, err := ethwire.ReadMessage(p.conn) if err != nil { log.Println(err) @@ -109,8 +70,8 @@ out: } // TODO - data, _ := Decode(msg.data, 0) - log.Printf("%s, %s\n", msg.msgType, data) + data, _ := Decode(msg.Data, 0) + log.Printf("%s, %s\n", msg.MsgType, data) } // Notify the out handler we're quiting @@ -125,7 +86,7 @@ func (p *Peer) Start() { } func (p *Peer) Stop() { - defer p.conn.Close() + p.conn.Close() p.quit <- true } -- cgit v1.2.3 From 8bbf879cb31e9cb28700773ed788421f9935ac36 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 10 Jan 2014 22:44:53 +0100 Subject: Moving the ethgo to individual packages --- big.go | 35 --------- block.go | 192 ------------------------------------------------- block_manager.go | 17 ++--- bytes.go | 27 ------- contract.go | 65 ----------------- dagger.go | 7 +- database.go | 56 --------------- database_test.go | 7 -- dev_console.go | 20 +++--- encoding.go | 62 ---------------- encoding_test.go | 38 ---------- ethereum.go | 6 +- genesis.go | 34 --------- memory_database.go | 34 --------- parsing.go | 98 ------------------------- parsing_test.go | 33 --------- rlp.go | 3 +- server.go | 10 +-- transaction.go | 206 ----------------------------------------------------- trie.go | 203 ---------------------------------------------------- trie_test.go | 59 --------------- util.go | 63 ---------------- vm.go | 22 +++--- 23 files changed, 46 insertions(+), 1251 deletions(-) delete mode 100644 big.go delete mode 100644 block.go delete mode 100644 bytes.go delete mode 100644 contract.go delete mode 100644 database.go delete mode 100644 database_test.go delete mode 100644 encoding.go delete mode 100644 encoding_test.go delete mode 100644 genesis.go delete mode 100644 memory_database.go delete mode 100644 parsing.go delete mode 100644 parsing_test.go delete mode 100644 transaction.go delete mode 100644 trie.go delete mode 100644 trie_test.go delete mode 100644 util.go diff --git a/big.go b/big.go deleted file mode 100644 index b0fbcb64f..000000000 --- a/big.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "math/big" -) - -/* - * Returns the power of two integers - */ -func BigPow(a,b int) *big.Int { - c := new(big.Int) - c.Exp(big.NewInt(int64(a)), big.NewInt(int64(b)), big.NewInt(0)) - - return c -} - -/* - * Like big.NewInt(uint64); this takes a string instead. - */ -func Big(num string) *big.Int { - n := new(big.Int) - n.SetString(num, 0) - - return n -} - -/* - * Like big.NewInt(uint64); this takes a byte buffer instead. - */ -func BigD(data []byte) *big.Int { - n := new(big.Int) - n.SetBytes(data) - - return n -} diff --git a/block.go b/block.go deleted file mode 100644 index 2287a9d55..000000000 --- a/block.go +++ /dev/null @@ -1,192 +0,0 @@ -package main - -import ( - "fmt" - "time" - _"bytes" - _"encoding/hex" -) - -type Block struct { - // The number of this block - number uint32 - // Hash to the previous block - prevHash string - // Uncles of this block - uncles []*Block - coinbase string - // state xxx - state *Trie - difficulty uint32 - // Creation time - time int64 - nonce uint32 - // List of transactions and/or contracts - transactions []*Transaction - - extra string -} - -// New block takes a raw encoded string -func NewBlock(raw []byte) *Block { - block := &Block{} - block.UnmarshalRlp(raw) - - return block -} - -// Creates a new block. This is currently for testing -func CreateTestBlock(/* TODO use raw data */transactions []*Transaction) *Block { - block := &Block{ - // Slice of transactions to include in this block - transactions: transactions, - number: 1, - prevHash: "1234", - coinbase: "me", - difficulty: 10, - nonce: 0, - time: time.Now().Unix(), - } - - return block -} - -func CreateBlock(root string, - num int, - prevHash string, - base string, - difficulty int, - nonce int, - extra string, - txes []*Transaction) *Block { - - block := &Block{ - // Slice of transactions to include in this block - transactions: txes, - number: uint32(num), - prevHash: prevHash, - coinbase: base, - difficulty: uint32(difficulty), - nonce: uint32(nonce), - time: time.Now().Unix(), - extra: extra, - } - block.state = NewTrie(Db, root) - - for _, tx := range txes { - // Create contract if there's no recipient - if tx.recipient == "" { - addr := tx.Hash() - - contract := NewContract(tx.value, []byte("")) - block.state.Update(string(addr), string(contract.MarshalRlp())) - for i, val := range tx.data { - contract.state.Update(string(NumberToBytes(uint64(i), 32)), val) - } - block.UpdateContract(addr, contract) - } - } - - return block -} - -func (block *Block) GetContract(addr []byte) *Contract { - data := block.state.Get(string(addr)) - if data == "" { - return nil - } - - contract := &Contract{} - contract.UnmarshalRlp([]byte(data)) - - return contract -} - -func (block *Block) UpdateContract(addr []byte, contract *Contract) { - block.state.Update(string(addr), string(contract.MarshalRlp())) -} - - -func (block *Block) PayFee(addr []byte, fee uint64) bool { - contract := block.GetContract(addr) - // If we can't pay the fee return - if contract == nil || contract.amount < fee { - fmt.Println("Contract has insufficient funds", contract.amount, fee) - - return false - } - - contract.amount -= fee - block.state.Update(string(addr), string(contract.MarshalRlp())) - - data := block.state.Get(string(block.coinbase)) - - // Get the ether (coinbase) and add the fee (gief fee to miner) - ether := NewEtherFromData([]byte(data)) - ether.amount += fee - - block.state.Update(string(block.coinbase), string(ether.MarshalRlp())) - - return true -} - -// Returns a hash of the block -func (block *Block) Hash() []byte { - return Sha256Bin(block.MarshalRlp()) -} - -func (block *Block) MarshalRlp() []byte { - // Marshal the transactions of this block - encTx := make([]string, len(block.transactions)) - for i, tx := range block.transactions { - // Cast it to a string (safe) - encTx[i] = string(tx.MarshalRlp()) - } - // TODO - uncles := []interface{}{} - - // I made up the block. It should probably contain different data or types. - // It sole purpose now is testing - header := []interface{}{ - block.number, - block.prevHash, - // Sha of uncles - "", - block.coinbase, - // root state - block.state.root, - // Sha of tx - string(Sha256Bin([]byte(Encode(encTx)))), - block.difficulty, - uint64(block.time), - block.nonce, - block.extra, - } - - // Encode a slice interface which contains the header and the list of - // transactions. - return Encode([]interface{}{header, encTx, uncles}) -} - -func (block *Block) UnmarshalRlp(data []byte) { - decoder := NewRlpDecoder(data) - - header := decoder.Get(0) - block.number = uint32(header.Get(0).AsUint()) - block.prevHash = header.Get(1).AsString() - // sha of uncles is header[2] - block.coinbase = header.Get(3).AsString() - block.state = NewTrie(Db, header.Get(4).AsString()) - block.difficulty = uint32(header.Get(5).AsUint()) - block.time = int64(header.Get(6).AsUint()) - block.nonce = uint32(header.Get(7).AsUint()) - block.extra = header.Get(8).AsString() - - txes := decoder.Get(1) - block.transactions = make([]*Transaction, txes.Length()) - for i := 0; i < txes.Length(); i++ { - tx := &Transaction{} - tx.UnmarshalRlp(txes.Get(i).AsBytes()) - block.transactions[i] = tx - } -} diff --git a/block_manager.go b/block_manager.go index 134ca0e75..59430dca7 100644 --- a/block_manager.go +++ b/block_manager.go @@ -2,17 +2,18 @@ package main import ( "fmt" + "github.com/ethereum/ethutil-go" ) type BlockChain struct { - lastBlock *Block + lastBlock *ethutil.Block - genesisBlock *Block + genesisBlock *ethutil.Block } func NewBlockChain() *BlockChain { bc := &BlockChain{} - bc.genesisBlock = NewBlock( Encode(Genesis) ) + bc.genesisBlock = ethutil.NewBlock( ethutil.Encode(ethutil.Genesis) ) return bc } @@ -30,19 +31,19 @@ func NewBlockManager() *BlockManager { } // Process a block. -func (bm *BlockManager) ProcessBlock(block *Block) error { +func (bm *BlockManager) ProcessBlock(block *ethutil.Block) error { // TODO Validation (Or move to other part of the application) if err := bm.ValidateBlock(block); err != nil { return err } // Get the tx count. Used to create enough channels to 'join' the go routines - txCount := len(block.transactions) + txCount := len(block.Transactions()) // Locking channel. When it has been fully buffered this method will return lockChan := make(chan bool, txCount) // Process each transaction/contract - for _, tx := range block.transactions { + for _, tx := range block.Transactions() { // If there's no recipient, it's a contract if tx.IsContract() { go bm.ProcessContract(tx, block, lockChan) @@ -60,11 +61,11 @@ func (bm *BlockManager) ProcessBlock(block *Block) error { return nil } -func (bm *BlockManager) ValidateBlock(block *Block) error { +func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { return nil } -func (bm *BlockManager) ProcessContract(tx *Transaction, block *Block, lockChan chan bool) { +func (bm *BlockManager) ProcessContract(tx *ethutil.Transaction, block *ethutil.Block, lockChan chan bool) { // Recovering function in case the VM had any errors defer func() { if r := recover(); r != nil { diff --git a/bytes.go b/bytes.go deleted file mode 100644 index 6bf381343..000000000 --- a/bytes.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "bytes" - "encoding/binary" - "fmt" -) - -func NumberToBytes(num uint64, bits int) []byte { - buf := new(bytes.Buffer) - err := binary.Write(buf, binary.BigEndian, num) - if err != nil { - fmt.Println("binary.Write failed:", err) - } - - return buf.Bytes()[buf.Len()-(bits / 8):] -} - -func BytesToNumber(b []byte) (number uint64) { - buf := bytes.NewReader(b) - err := binary.Read(buf, binary.LittleEndian, &number) - if err != nil { - fmt.Println("binary.Read failed:", err) - } - - return -} diff --git a/contract.go b/contract.go deleted file mode 100644 index a54643f59..000000000 --- a/contract.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - _"fmt" -) - -type Contract struct { - t uint32 // contract is always 1 - amount uint64 // ??? - state *Trie -} - -func NewContract(amount uint64, root []byte) *Contract { - contract := &Contract{t: 1, amount: amount} - contract.state = NewTrie(Db, string(root)) - - return contract -} - -func (c *Contract) MarshalRlp() []byte { - return Encode([]interface{}{c.t, c.amount, c.state.root}) -} - -func (c *Contract) UnmarshalRlp(data []byte) { - decoder := NewRlpDecoder(data) - - c.t = uint32(decoder.Get(0).AsUint()) - c.amount = decoder.Get(1).AsUint() - c.state = NewTrie(Db, decoder.Get(2).AsString()) -} - -type Ether struct { - t uint32 - amount uint64 - nonce string -} - -func NewEtherFromData(data []byte) *Ether { - ether := &Ether{} - ether.UnmarshalRlp(data) - - return ether -} - -func (e *Ether) MarshalRlp() []byte { - return Encode([]interface{}{e.t, e.amount, e.nonce}) -} - -func (e *Ether) UnmarshalRlp(data []byte) { - t, _ := Decode(data, 0) - - if slice, ok := t.([]interface{}); ok { - if t, ok := slice[0].(uint8); ok { - e.t = uint32(t) - } - - if amount, ok := slice[1].(uint8); ok { - e.amount = uint64(amount) - } - - if nonce, ok := slice[2].([]uint8); ok { - e.nonce = string(nonce) - } - } -} diff --git a/dagger.go b/dagger.go index 2c1976840..5aa5dd755 100644 --- a/dagger.go +++ b/dagger.go @@ -6,6 +6,7 @@ import ( "time" "github.com/obscuren/sha3" "hash" + "github.com/ethereum/ethutil-go" ) type Dagger struct { @@ -40,7 +41,7 @@ func (dag *Dagger) Search(hash, diff *big.Int) *big.Int { dag.hash = hash - obj := BigPow(2, 256) + obj := ethutil.BigPow(2, 256) obj = obj.Div(obj, diff) Found = false @@ -66,7 +67,7 @@ func DaggerVerify(hash, diff, nonce *big.Int) bool { dagger := &Dagger{} dagger.hash = hash - obj := BigPow(2, 256) + obj := ethutil.BigPow(2, 256) obj = obj.Div(obj, diff) return dagger.Eval(nonce).Cmp(obj) < 0 @@ -114,7 +115,7 @@ func Sum(sha hash.Hash) []byte { } func (dag *Dagger) Eval(N *big.Int) *big.Int { - pow := BigPow(2, 26) + pow := ethutil.BigPow(2, 26) dag.xn = N.Div(N, pow) sha := sha3.NewKeccak256() diff --git a/database.go b/database.go deleted file mode 100644 index b147756b6..000000000 --- a/database.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "path" - "os/user" - "github.com/syndtr/goleveldb/leveldb" - "fmt" -) - -type LDBDatabase struct { - db *leveldb.DB - trie *Trie -} - -func NewLDBDatabase() (*LDBDatabase, error) { - // This will eventually have to be something like a resource folder. - // it works on my system for now. Probably won't work on Windows - usr, _ := user.Current() - dbPath := path.Join(usr.HomeDir, ".ethereum", "database") - - // Open the db - db, err := leveldb.OpenFile(dbPath, nil) - if err != nil { - return nil, err - } - - database := &LDBDatabase{db: db} - - // Bootstrap database. Sets a few defaults; such as the last block - database.Bootstrap() - - return database, nil -} - -func (db *LDBDatabase) Bootstrap() error { - //db.trie = NewTrie(db) - - return nil -} - -func (db *LDBDatabase) Put(key []byte, value []byte) { - err := db.db.Put(key, value, nil) - if err != nil { - fmt.Println("Error put", err) - } -} - -func (db *LDBDatabase) Get(key []byte) ([]byte, error) { - return nil, nil -} - -func (db *LDBDatabase) Close() { - // Close the leveldb database - db.db.Close() -} - diff --git a/database_test.go b/database_test.go deleted file mode 100644 index 1a46eb95e..000000000 --- a/database_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import ( - _"testing" - _"fmt" -) - diff --git a/dev_console.go b/dev_console.go index 65166c0c1..27fc2e65e 100644 --- a/dev_console.go +++ b/dev_console.go @@ -7,16 +7,18 @@ import ( "os" "errors" "encoding/hex" + "github.com/ethereum/ethdb-go" + "github.com/ethereum/ethutil-go" ) type Console struct { - db *MemDatabase - trie *Trie + db *ethdb.MemDatabase + trie *ethutil.Trie } func NewConsole() *Console { - db, _ := NewMemDatabase() - trie := NewTrie(db, "") + db, _ := ethdb.NewMemDatabase() + trie := ethutil.NewTrie(db, "") return &Console{db: db, trie: trie} } @@ -68,17 +70,19 @@ func (i *Console) ParseInput(input string) bool { case "update": i.trie.Update(tokens[1], tokens[2]) - fmt.Println(hex.EncodeToString([]byte(i.trie.root))) + fmt.Println(hex.EncodeToString([]byte(i.trie.Root))) case "get": fmt.Println(i.trie.Get(tokens[1])) case "root": - fmt.Println(hex.EncodeToString([]byte(i.trie.root))) + fmt.Println(hex.EncodeToString([]byte(i.trie.Root))) case "rawroot": - fmt.Println(i.trie.root) + fmt.Println(i.trie.Root) case "print": i.db.Print() case "dag": - fmt.Println(DaggerVerify(Big(tokens[1]), BigPow(2, 36), Big(tokens[2]))) + fmt.Println(DaggerVerify( ethutil.Big(tokens[1]), // hash + ethutil.BigPow(2, 36), // diff + ethutil.Big(tokens[2])))// nonce case "exit", "quit", "q": return false case "help": diff --git a/encoding.go b/encoding.go deleted file mode 100644 index d77303817..000000000 --- a/encoding.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "bytes" - "encoding/hex" - "strings" - _"fmt" -) - -func CompactEncode(hexSlice []int) string { - terminator := 0 - if hexSlice[len(hexSlice)-1] == 16 { - terminator = 1 - } - - if terminator == 1 { - hexSlice = hexSlice[:len(hexSlice)-1] - } - - oddlen := len(hexSlice) % 2 - flags := 2 * terminator + oddlen - if oddlen != 0 { - hexSlice = append([]int{flags}, hexSlice...) - } else { - hexSlice = append([]int{flags, 0}, hexSlice...) - } - - var buff bytes.Buffer - for i := 0; i < len(hexSlice); i+=2 { - buff.WriteByte(byte(16 * hexSlice[i] + hexSlice[i+1])) - } - - return buff.String() -} - -func CompactDecode(str string) []int { - base := CompactHexDecode(str) - base = base[:len(base)-1] - if base[0] >= 2 {// && base[len(base)-1] != 16 { - base = append(base, 16) - } - if base[0] % 2 == 1 { - base = base[1:] - } else { - base = base[2:] - } - - return base -} - -func CompactHexDecode(str string) []int { - base := "0123456789abcdef" - hexSlice := make([]int, 0) - - enc := hex.EncodeToString([]byte(str)) - for _, v := range enc { - hexSlice = append(hexSlice, strings.IndexByte(base, byte(v))) - } - hexSlice = append(hexSlice, 16) - - return hexSlice -} diff --git a/encoding_test.go b/encoding_test.go deleted file mode 100644 index b66f702ac..000000000 --- a/encoding_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "testing" - "fmt" -) - -func TestCompactEncode(t *testing.T) { - test1 := []int{1,2,3,4,5} - if res := CompactEncode(test1); res != "\x11\x23\x45" { - t.Error(fmt.Sprintf("even compact encode failed. Got: %q", res)) - } - - test2 := []int{0, 1, 2, 3, 4, 5} - if res := CompactEncode(test2); res != "\x00\x01\x23\x45" { - t.Error(fmt.Sprintf("odd compact encode failed. Got: %q", res)) - } - - test3 := []int{0, 15, 1, 12, 11, 8, /*term*/16} - if res := CompactEncode(test3); res != "\x20\x0f\x1c\xb8" { - t.Error(fmt.Sprintf("odd terminated compact encode failed. Got: %q", res)) - } - - test4 := []int{15, 1, 12, 11, 8, /*term*/16} - if res := CompactEncode(test4); res != "\x3f\x1c\xb8" { - t.Error(fmt.Sprintf("even terminated compact encode failed. Got: %q", res)) - } -} - - -func TestCompactHexDecode(t *testing.T) { - exp := []int{7, 6, 6, 5, 7, 2, 6, 2, 16} - res := CompactHexDecode("verb") - - if !CompareIntSlice(res, exp) { - t.Error("Error compact hex decode. Expected", exp, "got", res) - } -} diff --git a/ethereum.go b/ethereum.go index aee8ebc30..96b67f970 100644 --- a/ethereum.go +++ b/ethereum.go @@ -7,7 +7,7 @@ import ( "flag" "runtime" "log" - _"math/big" + "github.com/ethereum/ethutil-go" ) const Debug = true @@ -39,7 +39,7 @@ func RegisterInterupts(s *Server) { func main() { runtime.GOMAXPROCS(runtime.NumCPU()) - InitFees() + ethutil.InitFees() Init() @@ -63,7 +63,7 @@ func main() { go func() { for { - res := dagger.Search(Big("0"), BigPow(2, 36)) + res := dagger.Search(ethutil.Big("0"), ethutil.BigPow(2, 36)) server.Broadcast("block", Encode(res.String())) } }() diff --git a/genesis.go b/genesis.go deleted file mode 100644 index 21b8e9998..000000000 --- a/genesis.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "math" -) - -/* - * This is the special genesis block. - */ - -var GenisisHeader = []interface{}{ - // Block number - uint32(0), - // Previous hash (none) - "", - // Sha of uncles - string(Sha256Bin(Encode([]interface{}{}))), - // Coinbase - "", - // Root state - "", - // Sha of transactions - string(Sha256Bin(Encode([]interface{}{}))), - // Difficulty - uint32(math.Pow(2, 36)), - // Time - uint64(1), - // Nonce - uint32(0), - // Extra - "", -} - -var Genesis = []interface{}{ GenisisHeader, []interface{}{}, []interface{}{} } diff --git a/memory_database.go b/memory_database.go deleted file mode 100644 index a8c74bb46..000000000 --- a/memory_database.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "fmt" -) - -/* - * This is a test memory database. Do not use for any production it does not get persisted - */ -type MemDatabase struct { - db map[string][]byte -} - -func NewMemDatabase() (*MemDatabase, error) { - db := &MemDatabase{db: make(map[string][]byte)} - - return db, nil -} - -func (db *MemDatabase) Put(key []byte, value []byte) { - db.db[string(key)] = value -} - -func (db *MemDatabase) Get(key []byte) ([]byte, error) { - return db.db[string(key)], nil -} - -func (db *MemDatabase) Print() { - for key, val := range db.db { - fmt.Printf("%x(%d):", key, len(key)) - decoded := DecodeNode(val) - PrintSlice(decoded) - } -} diff --git a/parsing.go b/parsing.go deleted file mode 100644 index b97d77f3e..000000000 --- a/parsing.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "fmt" - "strings" - "errors" - "math/big" - "strconv" -) - -// Op codes -var OpCodes = map[string]string{ - "STOP": "0", - "ADD": "1", - "MUL": "2", - "SUB": "3", - "DIV": "4", - "SDIV": "5", - "MOD": "6", - "SMOD": "7", - "EXP": "8", - "NEG": "9", - "LT": "10", - "LE": "11", - "GT": "12", - "GE": "13", - "EQ": "14", - "NOT": "15", - "MYADDRESS": "16", - "TXSENDER": "17", - - - "PUSH": "48", - "POP": "49", - "LOAD": "54", -} - - -func CompileInstr(s string) (string, error) { - tokens := strings.Split(s, " ") - if OpCodes[tokens[0]] == "" { - return s, errors.New(fmt.Sprintf("OP not found: %s", tokens[0])) - } - - code := OpCodes[tokens[0]] // Replace op codes with the proper numerical equivalent - op := new(big.Int) - op.SetString(code, 0) - - args := make([]*big.Int, 6) - for i, val := range tokens[1:len(tokens)] { - num := new(big.Int) - num.SetString(val, 0) - args[i] = num - } - - // Big int equation = op + x * 256 + y * 256**2 + z * 256**3 + a * 256**4 + b * 256**5 + c * 256**6 - base := new(big.Int) - x := new(big.Int) - y := new(big.Int) - z := new(big.Int) - a := new(big.Int) - b := new(big.Int) - c := new(big.Int) - - if args[0] != nil { x.Mul(args[0], big.NewInt(256)) } - if args[1] != nil { y.Mul(args[1], BigPow(256, 2)) } - if args[2] != nil { z.Mul(args[2], BigPow(256, 3)) } - if args[3] != nil { a.Mul(args[3], BigPow(256, 4)) } - if args[4] != nil { b.Mul(args[4], BigPow(256, 5)) } - if args[5] != nil { c.Mul(args[5], BigPow(256, 6)) } - - base.Add(op, x) - base.Add(base, y) - base.Add(base, z) - base.Add(base, a) - base.Add(base, b) - base.Add(base, c) - - return base.String(), nil -} - -func Instr(instr string) (int, []string, error) { - base := new(big.Int) - base.SetString(instr, 0) - - args := make([]string, 7) - for i := 0; i < 7; i++ { - // int(int(val) / int(math.Pow(256,float64(i)))) % 256 - exp := BigPow(256, i) - num := new(big.Int) - num.Div(base, exp) - - args[i] = num.Mod(num, big.NewInt(256)).String() - } - op, _ := strconv.Atoi(args[0]) - - return op, args[1:7], nil -} diff --git a/parsing_test.go b/parsing_test.go deleted file mode 100644 index 6aa9e240c..000000000 --- a/parsing_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "testing" - "math" -) - -func TestCompile(t *testing.T) { - instr, err := CompileInstr("PUSH") - - if err != nil { - t.Error("Failed compiling instruction") - } - - calc := (48 + 0 * 256 + 0 * int64(math.Pow(256,2))) - if Big(instr).Int64() != calc { - t.Error("Expected", calc, ", got:", instr) - } -} - -func TestValidInstr(t *testing.T) { - /* - op, args, err := Instr("68163") - if err != nil { - t.Error("Error decoding instruction") - } - */ - -} - -func TestInvalidInstr(t *testing.T) { -} - diff --git a/rlp.go b/rlp.go index 5366632f4..cee9da613 100644 --- a/rlp.go +++ b/rlp.go @@ -5,6 +5,7 @@ import ( "bytes" "math" "math/big" + "github.com/ethereum/ethutil-go" ) type RlpEncoder struct { @@ -54,7 +55,7 @@ func (attr *RlpDataAttribute) AsUint() uint64 { func (attr *RlpDataAttribute) AsBigInt() *big.Int { if a, ok := attr.dataAttrib.([]byte); ok { - return Big(string(a)) + return ethutil.Big(string(a)) } return big.NewInt(0) diff --git a/server.go b/server.go index c2903cb59..d81fe1bce 100644 --- a/server.go +++ b/server.go @@ -5,15 +5,15 @@ import ( "net" "log" _"time" + "github.com/ethereum/ethdb-go" + "github.com/ethereum/ethutil-go" ) -var Db *LDBDatabase - type Server struct { // Channel for shutting down the server shutdownChan chan bool // DB interface - db *LDBDatabase + db *ethdb.LDBDatabase // Block manager for processing new blocks and managing the block chain blockManager *BlockManager // Peers (NYI) @@ -21,12 +21,12 @@ type Server struct { } func NewServer() (*Server, error) { - db, err := NewLDBDatabase() + db, err := ethdb.NewLDBDatabase() if err != nil { return nil, err } - Db = db + ethutil.SetConfig(db) server := &Server{ shutdownChan: make(chan bool), diff --git a/transaction.go b/transaction.go deleted file mode 100644 index 90e0d9869..000000000 --- a/transaction.go +++ /dev/null @@ -1,206 +0,0 @@ -package main - -import ( - "math/big" - "fmt" - "github.com/obscuren/secp256k1-go" - _"encoding/hex" - _"crypto/sha256" - _ "bytes" -) - -/* -Transaction Contract Size -------------------------------------------- -sender sender 20 bytes -recipient 0x0 20 bytes -value endowment 4 bytes (uint32) -fee fee 4 bytes (uint32) -d_size o_size 4 bytes (uint32) -data ops * -signature signature 64 bytes -*/ - -var StepFee *big.Int = new(big.Int) -var TxFee *big.Int = new(big.Int) -var ContractFee *big.Int = new(big.Int) -var MemFee *big.Int = new(big.Int) -var DataFee *big.Int = new(big.Int) -var CryptoFee *big.Int = new(big.Int) -var ExtroFee *big.Int = new(big.Int) - -var Period1Reward *big.Int = new(big.Int) -var Period2Reward *big.Int = new(big.Int) -var Period3Reward *big.Int = new(big.Int) -var Period4Reward *big.Int = new(big.Int) - -type Transaction struct { - nonce string - sender string - recipient string - value uint64 - fee uint32 - data []string - memory []int - lastTx string - v uint32 - r, s []byte -} - -func NewTransaction(to string, value uint64, data []string) *Transaction { - tx := Transaction{sender: "1234567890", recipient: to, value: value} - tx.nonce = "0" - tx.fee = 0//uint32((ContractFee + MemoryFee * float32(len(tx.data))) * 1e8) - tx.lastTx = "0" - - // Serialize the data - tx.data = make([]string, len(data)) - for i, val := range data { - instr, err := CompileInstr(val) - if err != nil { - //fmt.Printf("compile error:%d %v\n", i+1, err) - } - - tx.data[i] = instr - } - - tx.Sign([]byte("privkey")) - tx.Sender() - - - return &tx -} - -func (tx *Transaction) Hash() []byte { - preEnc := []interface{}{ - tx.nonce, - tx.recipient, - tx.value, - tx.fee, - tx.data, - } - - return Sha256Bin(Encode(preEnc)) -} - -func (tx *Transaction) IsContract() bool { - return tx.recipient == "" -} - -func (tx *Transaction) Signature(key []byte) []byte { - hash := tx.Hash() - sec := Sha256Bin(key) - - sig, _ := secp256k1.Sign(hash, sec) - - return sig -} - -func (tx *Transaction) PublicKey() []byte { - hash := Sha256Bin(tx.Hash()) - sig := append(tx.r, tx.s...) - - pubkey, _ := secp256k1.RecoverPubkey(hash, sig) - - return pubkey -} - -func (tx *Transaction) Sender() []byte { - pubkey := tx.PublicKey() - - // Validate the returned key. - // Return nil if public key isn't in full format (04 = full, 03 = compact) - if pubkey[0] != 4 { - return nil - } - - return Sha256Bin(pubkey[1:65])[12:] -} - -func (tx *Transaction) Sign(privk []byte) { - sig := tx.Signature(privk) - - // Add 27 so we get either 27 or 28 (for positive and negative) - tx.v = uint32(sig[64]) + 27 - tx.r = sig[:32] - tx.s = sig[32:65] -} - -func (tx *Transaction) MarshalRlp() []byte { - // Prepare the transaction for serialization - preEnc := []interface{}{ - tx.nonce, - tx.recipient, - tx.value, - tx.fee, - tx.data, - tx.v, - tx.r, - tx.s, - } - - return Encode(preEnc) -} - -func (tx *Transaction) UnmarshalRlp(data []byte) { - decoder := NewRlpDecoder(data) - - tx.nonce = decoder.Get(0).AsString() - tx.recipient = decoder.Get(0).AsString() - tx.value = decoder.Get(2).AsUint() - tx.fee = uint32(decoder.Get(3).AsUint()) - - d := decoder.Get(4) - tx.data = make([]string, d.Length()) - fmt.Println(d.Get(0)) - for i := 0; i < d.Length(); i++ { - tx.data[i] = d.Get(i).AsString() - } - - tx.v = uint32(decoder.Get(5).AsUint()) - tx.r = decoder.Get(6).AsBytes() - tx.s = decoder.Get(7).AsBytes() -} - -func InitFees() { - // Base for 2**60 - b60 := new(big.Int) - b60.Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) - // Base for 2**80 - b80 := new(big.Int) - b80.Exp(big.NewInt(2), big.NewInt(80), big.NewInt(0)) - - StepFee.Exp(big.NewInt(10), big.NewInt(16), big.NewInt(0)) - //StepFee.Div(b60, big.NewInt(64)) - //fmt.Println("StepFee:", StepFee) - - TxFee.Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) - //fmt.Println("TxFee:", TxFee) - - ContractFee.Exp(big.NewInt(2), big.NewInt(64), big.NewInt(0)) - //fmt.Println("ContractFee:", ContractFee) - - MemFee.Div(b60, big.NewInt(4)) - //fmt.Println("MemFee:", MemFee) - - DataFee.Div(b60, big.NewInt(16)) - //fmt.Println("DataFee:", DataFee) - - CryptoFee.Div(b60, big.NewInt(16)) - //fmt.Println("CrytoFee:", CryptoFee) - - ExtroFee.Div(b60, big.NewInt(16)) - //fmt.Println("ExtroFee:", ExtroFee) - - Period1Reward.Mul(b80, big.NewInt(1024)) - //fmt.Println("Period1Reward:", Period1Reward) - - Period2Reward.Mul(b80, big.NewInt(512)) - //fmt.Println("Period2Reward:", Period2Reward) - - Period3Reward.Mul(b80, big.NewInt(256)) - //fmt.Println("Period3Reward:", Period3Reward) - - Period4Reward.Mul(b80, big.NewInt(128)) - //fmt.Println("Period4Reward:", Period4Reward) -} diff --git a/trie.go b/trie.go deleted file mode 100644 index d66699eeb..000000000 --- a/trie.go +++ /dev/null @@ -1,203 +0,0 @@ -package main - -import ( - "fmt" -) - -// Database interface -type Database interface { - Put(key []byte, value []byte) - Get(key []byte) ([]byte, error) -} - -/* - * Trie helper functions - */ -// Helper function for printing out the raw contents of a slice -func PrintSlice(slice []string) { - fmt.Printf("[") - for i, val := range slice { - fmt.Printf("%q", val) - if i != len(slice)-1 { fmt.Printf(",") } - } - fmt.Printf("]\n") -} - -// RLP Decodes a node in to a [2] or [17] string slice -func DecodeNode(data []byte) []string { - dec, _ := Decode(data, 0) - if slice, ok := dec.([]interface{}); ok { - strSlice := make([]string, len(slice)) - - for i, s := range slice { - if str, ok := s.([]byte); ok { - strSlice[i] = string(str) - } - } - - return strSlice - } else { - fmt.Printf("It wasn't a []. It's a %T\n", dec) - } - - return nil -} - -// A (modified) Radix Trie implementation -type Trie struct { - root string - db Database -} - -func NewTrie(db Database, root string) *Trie { - return &Trie{db: db, root: root} -} - -/* - * Public (query) interface functions - */ -func (t *Trie) Update(key string, value string) { - k := CompactHexDecode(key) - - t.root = t.UpdateState(t.root, k, value) -} - -func (t *Trie) Get(key string) string { - k := CompactHexDecode(key) - - return t.GetState(t.root, k) -} - -/* - * State functions (shouldn't be needed directly). - */ - -// Helper function for printing a node (using fetch, decode and slice printing) -func (t *Trie) PrintNode(n string) { - data, _ := t.db.Get([]byte(n)) - d := DecodeNode(data) - PrintSlice(d) -} - -// Returns the state of an object -func (t *Trie) GetState(node string, key []int) string { - // Return the node if key is empty (= found) - if len(key) == 0 || node == "" { - return node - } - - // Fetch the encoded node from the db - n, err := t.db.Get([]byte(node)) - if err != nil { fmt.Println("Error in GetState for node", node, "with key", key); return "" } - - // Decode it - currentNode := DecodeNode(n) - - if len(currentNode) == 0 { - return "" - } else if len(currentNode) == 2 { - // Decode the key - k := CompactDecode(currentNode[0]) - v := currentNode[1] - - if len(key) >= len(k) && CompareIntSlice(k, key[:len(k)]) { - return t.GetState(v, key[len(k):]) - } else { - return "" - } - } else if len(currentNode) == 17 { - return t.GetState(currentNode[key[0]], key[1:]) - } - - // It shouldn't come this far - fmt.Println("GetState unexpected return") - return "" -} - -// Inserts a new sate or delete a state based on the value -func (t *Trie) UpdateState(node string, key []int, value string) string { - if value != "" { - return t.InsertState(node, key, value) - } else { - // delete it - } - - return "" -} - -// Wrapper around the regular db "Put" which generates a key and value -func (t *Trie) Put(node interface{}) []byte { - enc := Encode(node) - var sha []byte - sha = Sha256Bin(enc) - - t.db.Put([]byte(sha), enc) - - return sha -} - -func (t *Trie) InsertState(node string, key []int, value string) string { - if len(key) == 0 { - return value - } - - // New node - if node == "" { - newNode := []string{ CompactEncode(key), value } - - return string(t.Put(newNode)) - } - - // Fetch the encoded node from the db - n, err := t.db.Get([]byte(node)) - if err != nil { fmt.Println("Error InsertState", err); return "" } - - // Decode it - currentNode := DecodeNode(n) - // Check for "special" 2 slice type node - if len(currentNode) == 2 { - // Decode the key - k := CompactDecode(currentNode[0]) - v := currentNode[1] - - // Matching key pair (ie. there's already an object with this key) - if CompareIntSlice(k, key) { - return string(t.Put([]string{ CompactEncode(key), value })) - } - - var newHash string - matchingLength := MatchingNibbleLength(key, k) - if matchingLength == len(k) { - // Insert the hash, creating a new node - newHash = t.InsertState(v, key[matchingLength:], value) - } else { - // Expand the 2 length slice to a 17 length slice - oldNode := t.InsertState("", k[matchingLength+1:], v) - newNode := t.InsertState("", key[matchingLength+1:], value) - // Create an expanded slice - scaledSlice := make([]string, 17) - // Set the copied and new node - scaledSlice[k[matchingLength]] = oldNode - scaledSlice[key[matchingLength]] = newNode - - newHash = string(t.Put(scaledSlice)) - } - - if matchingLength == 0 { - // End of the chain, return - return newHash - } else { - newNode := []string{ CompactEncode(key[:matchingLength]), newHash } - return string(t.Put(newNode)) - } - } else { - // Copy the current node over to the new node and replace the first nibble in the key - newNode := make([]string, 17); copy(newNode, currentNode) - newNode[key[0]] = t.InsertState(currentNode[key[0]], key[1:], value) - - return string(t.Put(newNode)) - } - - return "" -} - diff --git a/trie_test.go b/trie_test.go deleted file mode 100644 index 599a5f47c..000000000 --- a/trie_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "testing" - "encoding/hex" - _"fmt" -) - -func TestTriePut(t *testing.T) { - db, err := NewMemDatabase() - trie := NewTrie(db, "") - - if err != nil { - t.Error("Error starting db") - } - - key := trie.Put([]byte("testing node")) - - data, err := db.Get(key) - if err != nil { - t.Error("Nothing at node") - } - - s, _ := Decode(data, 0) - if str, ok := s.([]byte); ok { - if string(str) != "testing node" { - t.Error("Wrong value node", str) - } - } else { - t.Error("Invalid return type") - } -} - -func TestTrieUpdate(t *testing.T) { - db, err := NewMemDatabase() - trie := NewTrie(db, "") - - if err != nil { - t.Error("Error starting db") - } - - - trie.Update("doe", "reindeer") - trie.Update("dog", "puppy") - /* - data, _ := db.Get([]byte(trie.root)) - data, _ = db.Get([]byte(DecodeNode(data)[1])) - data, _ = db.Get([]byte(DecodeNode(data)[7])) - PrintSlice(DecodeNode(data)) - */ - - trie.Update("dogglesworth", "cat") - root := hex.EncodeToString([]byte(trie.root)) - req := "e378927bfc1bd4f01a2e8d9f59bd18db8a208bb493ac0b00f93ce51d4d2af76c" - if root != req { - t.Error("trie.root do not match, expected", req, "got", root) - } -} - diff --git a/util.go b/util.go deleted file mode 100644 index 24e5455f4..000000000 --- a/util.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "strconv" - "crypto/sha256" - "encoding/hex" - _"fmt" - _"math" - "github.com/obscuren/sha3" -) - -func Uitoa(i uint32) string { - return strconv.FormatUint(uint64(i), 10) -} - -func Sha256Hex(data []byte) string { - hash := sha256.Sum256(data) - - return hex.EncodeToString(hash[:]) -} - -func Sha256Bin(data []byte) []byte { - hash := sha256.Sum256(data) - - return hash[:] -} - -func Sha3Bin(data []byte) []byte { - d := sha3.NewKeccak224() - d.Reset() - d.Write(data) - - return d.Sum(nil) -} - -// Helper function for comparing slices -func CompareIntSlice(a, b []int) bool { - if len(a) != len(b) { - return false - } - for i, v := range a { - if v != b[i] { - return false - } - } - return true -} - -// Returns the amount of nibbles that match each other from 0 ... -func MatchingNibbleLength(a, b []int) int { - i := 0 - for CompareIntSlice(a[:i+1], b[:i+1]) && i < len(b) { - i+=1 - } - - //fmt.Println(a, b, i-1) - - return i -} - -func Hex(d []byte) string { - return hex.EncodeToString(d) -} diff --git a/vm.go b/vm.go index 5b70f3544..5605cb7c7 100644 --- a/vm.go +++ b/vm.go @@ -1,12 +1,10 @@ package main import ( - _"math" "math/big" "fmt" - _"strconv" - _ "encoding/hex" "strconv" + "github.com/ethereum/ethutil-go" ) // Op codes @@ -93,7 +91,7 @@ func (st *Stack) Popn() (*big.Int, *big.Int) { strs := st.data[s-2:] st.data = st.data[:s-2] - return Big(strs[0]), Big(strs[1]) + return ethutil.Big(strs[0]), ethutil.Big(strs[1]) } func (st *Stack) Push(d string) { @@ -114,7 +112,8 @@ func NewVm() *Vm { } } -func (vm *Vm) ProcContract(tx *Transaction, block *Block, cb TxCallback) { +func (vm *Vm) ProcContract( tx *ethutil.Transaction, + block *ethutil.Block, cb TxCallback) { // Instruction pointer pc := 0 @@ -124,7 +123,7 @@ func (vm *Vm) ProcContract(tx *Transaction, block *Block, cb TxCallback) { return } - Pow256 := BigPow(2, 256) + Pow256 := ethutil.BigPow(2, 256) //fmt.Printf("# op arg\n") out: @@ -134,7 +133,8 @@ out: // XXX Should Instr return big int slice instead of string slice? // Get the next instruction from the contract //op, _, _ := Instr(contract.state.Get(string(Encode(uint32(pc))))) - op, _, _ := Instr(contract.state.Get(string(NumberToBytes(uint64(pc), 32)))) + nb := ethutil.NumberToBytes(uint64(pc), 32) + op, _, _ := ethutil.Instr(contract.State().Get(string(nb))) if !cb(0) { break } @@ -200,7 +200,7 @@ out: vm.stack.Push(base.String()) case oNEG: - base.Sub(Pow256, Big(vm.stack.Pop())) + base.Sub(Pow256, ethutil.Big(vm.stack.Pop())) vm.stack.Push(base.String()) case oLT: x, y := vm.stack.Popn() @@ -245,18 +245,18 @@ out: case oMYADDRESS: vm.stack.Push(string(tx.Hash())) case oTXSENDER: - vm.stack.Push(tx.sender) + vm.stack.Push(string(tx.Sender())) case oPUSH: // Get the next entry and pushes the value on the stack pc++ - vm.stack.Push(contract.state.Get(string(NumberToBytes(uint64(pc), 32)))) + vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(pc), 32)))) case oPOP: // Pop current value of the stack vm.stack.Pop() case oLOAD: // Load instruction X on the stack i, _ := strconv.Atoi(vm.stack.Pop()) - vm.stack.Push(contract.state.Get(string(NumberToBytes(uint64(i), 32)))) + vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32)))) case oSTOP: break out } -- cgit v1.2.3 From 9571a512861d4a44c36d368f4baa15b2aa81c37d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 11 Jan 2014 15:27:08 +0100 Subject: gofmt no coding standards --- README.md | 5 + block_manager.go | 120 +++++++------- dagger.go | 199 +++++++++++------------ dagger_test.go | 18 +-- dev_console.go | 186 +++++++++++----------- ethereum.go | 132 +++++++-------- peer.go | 118 +++++++------- rlp.go | 388 ++++++++++++++++++++++---------------------- rlp_test.go | 74 ++++----- server.go | 161 ++++++++++--------- test_runner.go | 30 ++-- test_runner_test.go | 28 ++-- testing.go | 1 + vm.go | 451 +++++++++++++++++++++++++++------------------------- 14 files changed, 973 insertions(+), 938 deletions(-) diff --git a/README.md b/README.md index 4b797b5a7..37e8505f1 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,8 @@ Command line options -c launch the developer console -m start mining fake blocks and broadcast fake messages to the net + +Contribution +============ + +See CONTRIB.md diff --git a/block_manager.go b/block_manager.go index 59430dca7..968d9a139 100644 --- a/block_manager.go +++ b/block_manager.go @@ -1,90 +1,90 @@ package main import ( - "fmt" - "github.com/ethereum/ethutil-go" + "fmt" + "github.com/ethereum/ethutil-go" ) type BlockChain struct { - lastBlock *ethutil.Block + lastBlock *ethutil.Block - genesisBlock *ethutil.Block + genesisBlock *ethutil.Block } func NewBlockChain() *BlockChain { - bc := &BlockChain{} - bc.genesisBlock = ethutil.NewBlock( ethutil.Encode(ethutil.Genesis) ) + bc := &BlockChain{} + bc.genesisBlock = ethutil.NewBlock(ethutil.Encode(ethutil.Genesis)) - return bc + return bc } type BlockManager struct { - vm *Vm + vm *Vm - blockChain *BlockChain + blockChain *BlockChain } func NewBlockManager() *BlockManager { - bm := &BlockManager{vm: NewVm()} + bm := &BlockManager{vm: NewVm()} - return bm + return bm } // Process a block. func (bm *BlockManager) ProcessBlock(block *ethutil.Block) error { - // TODO Validation (Or move to other part of the application) - if err := bm.ValidateBlock(block); err != nil { - return err - } - - // Get the tx count. Used to create enough channels to 'join' the go routines - txCount := len(block.Transactions()) - // Locking channel. When it has been fully buffered this method will return - lockChan := make(chan bool, txCount) - - // Process each transaction/contract - for _, tx := range block.Transactions() { - // If there's no recipient, it's a contract - if tx.IsContract() { - go bm.ProcessContract(tx, block, lockChan) - } else { - // "finish" tx which isn't a contract - lockChan <- true - } - } - - // Wait for all Tx to finish processing - for i := 0; i < txCount; i++ { - <- lockChan - } - - return nil + // TODO Validation (Or move to other part of the application) + if err := bm.ValidateBlock(block); err != nil { + return err + } + + // Get the tx count. Used to create enough channels to 'join' the go routines + txCount := len(block.Transactions()) + // Locking channel. When it has been fully buffered this method will return + lockChan := make(chan bool, txCount) + + // Process each transaction/contract + for _, tx := range block.Transactions() { + // If there's no recipient, it's a contract + if tx.IsContract() { + go bm.ProcessContract(tx, block, lockChan) + } else { + // "finish" tx which isn't a contract + lockChan <- true + } + } + + // Wait for all Tx to finish processing + for i := 0; i < txCount; i++ { + <-lockChan + } + + return nil } func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { - return nil + return nil } func (bm *BlockManager) ProcessContract(tx *ethutil.Transaction, block *ethutil.Block, lockChan chan bool) { - // Recovering function in case the VM had any errors - defer func() { - if r := recover(); r != nil { - fmt.Println("Recovered from VM execution with err =", r) - // Let the channel know where done even though it failed (so the execution may resume normally) - lockChan <- true - } - }() - - // Process contract - bm.vm.ProcContract(tx, block, func(opType OpType) bool { - // TODO turn on once big ints are in place - //if !block.PayFee(tx.Hash(), StepFee.Uint64()) { - // return false - //} - - return true // Continue - }) - - // Broadcast we're done - lockChan <- true + // Recovering function in case the VM had any errors + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered from VM execution with err =", r) + // Let the channel know where done even though it failed (so the execution may resume normally) + lockChan <- true + } + }() + + // Process contract + bm.vm.ProcContract(tx, block, func(opType OpType) bool { + // TODO turn on once big ints are in place + //if !block.PayFee(tx.Hash(), StepFee.Uint64()) { + // return false + //} + + return true // Continue + }) + + // Broadcast we're done + lockChan <- true } diff --git a/dagger.go b/dagger.go index 5aa5dd755..70b0b4692 100644 --- a/dagger.go +++ b/dagger.go @@ -1,142 +1,145 @@ package main import ( - "math/big" - "math/rand" - "time" - "github.com/obscuren/sha3" - "hash" - "github.com/ethereum/ethutil-go" + "github.com/ethereum/ethutil-go" + "github.com/obscuren/sha3" + "hash" + "math/big" + "math/rand" + "time" ) type Dagger struct { - hash *big.Int - xn *big.Int + hash *big.Int + xn *big.Int } var Found bool + func (dag *Dagger) Find(obj *big.Int, resChan chan int64) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) + r := rand.New(rand.NewSource(time.Now().UnixNano())) - for i := 0; i < 1000; i++ { - rnd := r.Int63() + for i := 0; i < 1000; i++ { + rnd := r.Int63() - if dag.Eval(big.NewInt(rnd)).Cmp(obj) < 0 { - // Post back result on the channel - resChan <- rnd - // Notify other threads we've found a valid nonce - Found = true - } + if dag.Eval(big.NewInt(rnd)).Cmp(obj) < 0 { + // Post back result on the channel + resChan <- rnd + // Notify other threads we've found a valid nonce + Found = true + } - // Break out if found - if Found { break } - } + // Break out if found + if Found { + break + } + } - resChan <- 0 + resChan <- 0 } func (dag *Dagger) Search(hash, diff *big.Int) *big.Int { - // TODO fix multi threading. Somehow it results in the wrong nonce - amountOfRoutines := 1 + // TODO fix multi threading. Somehow it results in the wrong nonce + amountOfRoutines := 1 - dag.hash = hash + dag.hash = hash - obj := ethutil.BigPow(2, 256) - obj = obj.Div(obj, diff) + obj := ethutil.BigPow(2, 256) + obj = obj.Div(obj, diff) - Found = false - resChan := make(chan int64, 3) - var res int64 + Found = false + resChan := make(chan int64, 3) + var res int64 - for k := 0; k < amountOfRoutines; k++ { - go dag.Find(obj, resChan) - } + for k := 0; k < amountOfRoutines; k++ { + go dag.Find(obj, resChan) + } - // Wait for each go routine to finish - for k := 0; k < amountOfRoutines; k++ { - // Get the result from the channel. 0 = quit - if r := <- resChan; r != 0 { - res = r - } - } + // Wait for each go routine to finish + for k := 0; k < amountOfRoutines; k++ { + // Get the result from the channel. 0 = quit + if r := <-resChan; r != 0 { + res = r + } + } - return big.NewInt(res) + return big.NewInt(res) } func DaggerVerify(hash, diff, nonce *big.Int) bool { - dagger := &Dagger{} - dagger.hash = hash + dagger := &Dagger{} + dagger.hash = hash - obj := ethutil.BigPow(2, 256) - obj = obj.Div(obj, diff) + obj := ethutil.BigPow(2, 256) + obj = obj.Div(obj, diff) - return dagger.Eval(nonce).Cmp(obj) < 0 + return dagger.Eval(nonce).Cmp(obj) < 0 } func (dag *Dagger) Node(L uint64, i uint64) *big.Int { - if L == i { - return dag.hash - } - - var m *big.Int - if L == 9 { - m = big.NewInt(16) - } else { - m = big.NewInt(3) - } - - sha := sha3.NewKeccak256() - sha.Reset() - d := sha3.NewKeccak256() - b := new(big.Int) - ret := new(big.Int) - - for k := 0; k < int(m.Uint64()); k++ { - d.Reset() - d.Write(dag.hash.Bytes()) - d.Write(dag.xn.Bytes()) - d.Write(big.NewInt(int64(L)).Bytes()) - d.Write(big.NewInt(int64(i)).Bytes()) - d.Write(big.NewInt(int64(k)).Bytes()) - - b.SetBytes(Sum(d)) - pk := b.Uint64() & ((1 << ((L - 1) * 3)) - 1) - sha.Write(dag.Node(L - 1, pk).Bytes()) - } - - ret.SetBytes(Sum(sha)) - - return ret + if L == i { + return dag.hash + } + + var m *big.Int + if L == 9 { + m = big.NewInt(16) + } else { + m = big.NewInt(3) + } + + sha := sha3.NewKeccak256() + sha.Reset() + d := sha3.NewKeccak256() + b := new(big.Int) + ret := new(big.Int) + + for k := 0; k < int(m.Uint64()); k++ { + d.Reset() + d.Write(dag.hash.Bytes()) + d.Write(dag.xn.Bytes()) + d.Write(big.NewInt(int64(L)).Bytes()) + d.Write(big.NewInt(int64(i)).Bytes()) + d.Write(big.NewInt(int64(k)).Bytes()) + + b.SetBytes(Sum(d)) + pk := b.Uint64() & ((1 << ((L - 1) * 3)) - 1) + sha.Write(dag.Node(L-1, pk).Bytes()) + } + + ret.SetBytes(Sum(sha)) + + return ret } func Sum(sha hash.Hash) []byte { - in := make([]byte, 32) - return sha.Sum(in) + in := make([]byte, 32) + return sha.Sum(in) } func (dag *Dagger) Eval(N *big.Int) *big.Int { - pow := ethutil.BigPow(2, 26) - dag.xn = N.Div(N, pow) + pow := ethutil.BigPow(2, 26) + dag.xn = N.Div(N, pow) - sha := sha3.NewKeccak256() - sha.Reset() - ret := new(big.Int) + sha := sha3.NewKeccak256() + sha.Reset() + ret := new(big.Int) - for k := 0; k < 4; k++ { - d := sha3.NewKeccak256() - b := new(big.Int) + for k := 0; k < 4; k++ { + d := sha3.NewKeccak256() + b := new(big.Int) - d.Reset() - d.Write(dag.hash.Bytes()) - d.Write(dag.xn.Bytes()) - d.Write(N.Bytes()) - d.Write(big.NewInt(int64(k)).Bytes()) + d.Reset() + d.Write(dag.hash.Bytes()) + d.Write(dag.xn.Bytes()) + d.Write(N.Bytes()) + d.Write(big.NewInt(int64(k)).Bytes()) - b.SetBytes(Sum(d)) - pk := (b.Uint64() & 0x1ffffff) + b.SetBytes(Sum(d)) + pk := (b.Uint64() & 0x1ffffff) - sha.Write(dag.Node(9, pk).Bytes()) - } + sha.Write(dag.Node(9, pk).Bytes()) + } - return ret.SetBytes(Sum(sha)) + return ret.SetBytes(Sum(sha)) } diff --git a/dagger_test.go b/dagger_test.go index 7953ec1da..58cdd6afd 100644 --- a/dagger_test.go +++ b/dagger_test.go @@ -1,17 +1,17 @@ package main import ( - "testing" - "math/big" + "math/big" + "testing" ) func BenchmarkDaggerSearch(b *testing.B) { - hash := big.NewInt(0) - diff := BigPow(2, 36) - o := big.NewInt(0) // nonce doesn't matter. We're only testing against speed, not validity + hash := big.NewInt(0) + diff := BigPow(2, 36) + o := big.NewInt(0) // nonce doesn't matter. We're only testing against speed, not validity - // Reset timer so the big generation isn't included in the benchmark - b.ResetTimer() - // Validate - DaggerVerify(hash, diff, o) + // Reset timer so the big generation isn't included in the benchmark + b.ResetTimer() + // Validate + DaggerVerify(hash, diff, o) } diff --git a/dev_console.go b/dev_console.go index 27fc2e65e..923c483c2 100644 --- a/dev_console.go +++ b/dev_console.go @@ -1,119 +1,121 @@ package main import ( - "fmt" - "bufio" - "strings" - "os" - "errors" - "encoding/hex" - "github.com/ethereum/ethdb-go" - "github.com/ethereum/ethutil-go" + "bufio" + "encoding/hex" + "errors" + "fmt" + "github.com/ethereum/ethdb-go" + "github.com/ethereum/ethutil-go" + "os" + "strings" ) type Console struct { - db *ethdb.MemDatabase - trie *ethutil.Trie + db *ethdb.MemDatabase + trie *ethutil.Trie } func NewConsole() *Console { - db, _ := ethdb.NewMemDatabase() - trie := ethutil.NewTrie(db, "") + db, _ := ethdb.NewMemDatabase() + trie := ethutil.NewTrie(db, "") - return &Console{db: db, trie: trie} + return &Console{db: db, trie: trie} } func (i *Console) ValidateInput(action string, argumentLength int) error { - err := false - var expArgCount int + err := false + var expArgCount int - switch { - case action == "update" && argumentLength != 2: - err = true - expArgCount = 2 - case action == "get" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "dag" && argumentLength != 2: - err = true - expArgCount = 2 - } + switch { + case action == "update" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "get" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "dag" && argumentLength != 2: + err = true + expArgCount = 2 + } - if err { - return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) - } else { - return nil - } + if err { + return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) + } else { + return nil + } } func (i *Console) ParseInput(input string) bool { - scanner := bufio.NewScanner(strings.NewReader(input)) - scanner.Split(bufio.ScanWords) + scanner := bufio.NewScanner(strings.NewReader(input)) + scanner.Split(bufio.ScanWords) - count := 0 - var tokens []string - for scanner.Scan() { - count++ - tokens = append(tokens, scanner.Text()) - } - if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "reading input:", err) - } + count := 0 + var tokens []string + for scanner.Scan() { + count++ + tokens = append(tokens, scanner.Text()) + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading input:", err) + } - if len(tokens) == 0 { return true } + if len(tokens) == 0 { + return true + } - err := i.ValidateInput(tokens[0], count-1) - if err != nil { - fmt.Println(err) - } else { - switch tokens[0] { - case "update": - i.trie.Update(tokens[1], tokens[2]) + err := i.ValidateInput(tokens[0], count-1) + if err != nil { + fmt.Println(err) + } else { + switch tokens[0] { + case "update": + i.trie.Update(tokens[1], tokens[2]) - fmt.Println(hex.EncodeToString([]byte(i.trie.Root))) - case "get": - fmt.Println(i.trie.Get(tokens[1])) - case "root": - fmt.Println(hex.EncodeToString([]byte(i.trie.Root))) - case "rawroot": - fmt.Println(i.trie.Root) - case "print": - i.db.Print() - case "dag": - fmt.Println(DaggerVerify( ethutil.Big(tokens[1]), // hash - ethutil.BigPow(2, 36), // diff - ethutil.Big(tokens[2])))// nonce - case "exit", "quit", "q": - return false - case "help": - fmt.Printf( "COMMANDS:\n"+ - "\033[1m= DB =\033[0m\n"+ - "update KEY VALUE - Updates/Creates a new value for the given key\n"+ - "get KEY - Retrieves the given key\n"+ - "root - Prints the hex encoded merkle root\n"+ - "rawroot - Prints the raw merkle root\n"+ - "\033[1m= Dagger =\033[0m\n"+ - "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n") - default: - fmt.Println("Unknown command:", tokens[0]) - } - } + fmt.Println(hex.EncodeToString([]byte(i.trie.Root))) + case "get": + fmt.Println(i.trie.Get(tokens[1])) + case "root": + fmt.Println(hex.EncodeToString([]byte(i.trie.Root))) + case "rawroot": + fmt.Println(i.trie.Root) + case "print": + i.db.Print() + case "dag": + fmt.Println(DaggerVerify(ethutil.Big(tokens[1]), // hash + ethutil.BigPow(2, 36), // diff + ethutil.Big(tokens[2]))) // nonce + case "exit", "quit", "q": + return false + case "help": + fmt.Printf("COMMANDS:\n" + + "\033[1m= DB =\033[0m\n" + + "update KEY VALUE - Updates/Creates a new value for the given key\n" + + "get KEY - Retrieves the given key\n" + + "root - Prints the hex encoded merkle root\n" + + "rawroot - Prints the raw merkle root\n" + + "\033[1m= Dagger =\033[0m\n" + + "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n") + default: + fmt.Println("Unknown command:", tokens[0]) + } + } - return true + return true } func (i *Console) Start() { - fmt.Printf("Eth Console. Type (help) for help\n") - reader := bufio.NewReader(os.Stdin) - for { - fmt.Printf("eth >>> ") - str, _, err := reader.ReadLine() - if err != nil { - fmt.Println("Error reading input", err) - } else { - if !i.ParseInput(string(str)) { - return - } - } - } + fmt.Printf("Eth Console. Type (help) for help\n") + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("eth >>> ") + str, _, err := reader.ReadLine() + if err != nil { + fmt.Println("Error reading input", err) + } else { + if !i.ParseInput(string(str)) { + return + } + } + } } diff --git a/ethereum.go b/ethereum.go index 96b67f970..dd5b44308 100644 --- a/ethereum.go +++ b/ethereum.go @@ -1,85 +1,85 @@ package main import ( - "fmt" - "os" - "os/signal" - "flag" - "runtime" - "log" - "github.com/ethereum/ethutil-go" + "flag" + "fmt" + "github.com/ethereum/ethutil-go" + "log" + "os" + "os/signal" + "runtime" ) const Debug = true var StartConsole bool var StartMining bool + func Init() { - flag.BoolVar(&StartConsole, "c", false, "debug and testing console") - flag.BoolVar(&StartMining, "m", false, "start dagger mining") + flag.BoolVar(&StartConsole, "c", false, "debug and testing console") + flag.BoolVar(&StartMining, "m", false, "start dagger mining") - flag.Parse() + flag.Parse() } // Register interrupt handlers so we can stop the server func RegisterInterupts(s *Server) { - // Buffered chan of one is enough - c := make(chan os.Signal, 1) - // Notify about interrupts for now - signal.Notify(c, os.Interrupt) - go func() { - for sig := range c { - fmt.Printf("Shutting down (%v) ... \n", sig) - - s.Stop() - } - }() + // Buffered chan of one is enough + c := make(chan os.Signal, 1) + // Notify about interrupts for now + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + fmt.Printf("Shutting down (%v) ... \n", sig) + + s.Stop() + } + }() } func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) - - ethutil.InitFees() - - Init() - - if StartConsole { - console := NewConsole() - console.Start() - } else{ - log.Println("Starting Ethereum") - server, err := NewServer() - - if err != nil { - log.Println(err) - return - } - - RegisterInterupts(server) - - if StartMining { - log.Println("Mining started") - dagger := &Dagger{} - - go func() { - for { - res := dagger.Search(ethutil.Big("0"), ethutil.BigPow(2, 36)) - server.Broadcast("block", Encode(res.String())) - } - }() - } - - server.Start() - - err = server.ConnectToPeer("localhost:12345") - if err != nil { - log.Println(err) - server.Stop() - return - } - - - // Wait for shutdown - server.WaitForShutdown() - } + runtime.GOMAXPROCS(runtime.NumCPU()) + + ethutil.InitFees() + + Init() + + if StartConsole { + console := NewConsole() + console.Start() + } else { + log.Println("Starting Ethereum") + server, err := NewServer() + + if err != nil { + log.Println(err) + return + } + + RegisterInterupts(server) + + if StartMining { + log.Println("Mining started") + dagger := &Dagger{} + + go func() { + for { + res := dagger.Search(ethutil.Big("0"), ethutil.BigPow(2, 36)) + server.Broadcast("block", Encode(res.String())) + } + }() + } + + server.Start() + + err = server.ConnectToPeer("localhost:12345") + if err != nil { + log.Println(err) + server.Stop() + return + } + + // Wait for shutdown + server.WaitForShutdown() + } } diff --git a/peer.go b/peer.go index d47af73de..a9f88b1e1 100644 --- a/peer.go +++ b/peer.go @@ -1,92 +1,92 @@ package main import ( - "net" - "log" - "github.com/ethereum/ethwire-go" + "github.com/ethereum/ethwire-go" + "log" + "net" ) type Peer struct { - // Server interface - server *Server - // Net connection - conn net.Conn - // Output queue which is used to communicate and handle messages - outputQueue chan ethwire.InOutMsg - // Quit channel - quit chan bool + // Server interface + server *Server + // Net connection + conn net.Conn + // Output queue which is used to communicate and handle messages + outputQueue chan ethwire.InOutMsg + // Quit channel + quit chan bool } func NewPeer(conn net.Conn, server *Server) *Peer { - return &Peer{ - outputQueue: make(chan ethwire.InOutMsg, 1), // Buffered chan of 1 is enough - quit: make(chan bool), + return &Peer{ + outputQueue: make(chan ethwire.InOutMsg, 1), // Buffered chan of 1 is enough + quit: make(chan bool), - server: server, - conn: conn, - } + server: server, + conn: conn, + } } // Outputs any RLP encoded data to the peer func (p *Peer) QueueMessage(msgType string, data []byte) { - p.outputQueue <- ethwire.InOutMsg{MsgType: msgType, Data: data} + p.outputQueue <- ethwire.InOutMsg{MsgType: msgType, Data: data} } // Outbound message handler. Outbound messages are handled here func (p *Peer) HandleOutbound() { out: - for { - select { - // Main message queue. All outbound messages are processed through here - case msg := <-p.outputQueue: - // TODO Message checking and handle accordingly - err := ethwire.WriteMessage(p.conn, msg) - if err != nil { - log.Println(err) - - // Stop the client if there was an error writing to it - p.Stop() - } - - // Break out of the for loop if a quit message is posted - case <- p.quit: - break out - } - } + for { + select { + // Main message queue. All outbound messages are processed through here + case msg := <-p.outputQueue: + // TODO Message checking and handle accordingly + err := ethwire.WriteMessage(p.conn, msg) + if err != nil { + log.Println(err) + + // Stop the client if there was an error writing to it + p.Stop() + } + + // Break out of the for loop if a quit message is posted + case <-p.quit: + break out + } + } } // Inbound handler. Inbound messages are received here and passed to the appropriate methods func (p *Peer) HandleInbound() { - defer p.Stop() + defer p.Stop() out: - for { - // Wait for a message from the peer - msg, err := ethwire.ReadMessage(p.conn) - if err != nil { - log.Println(err) - - break out - } - - // TODO - data, _ := Decode(msg.Data, 0) - log.Printf("%s, %s\n", msg.MsgType, data) - } - - // Notify the out handler we're quiting - p.quit <- true + for { + // Wait for a message from the peer + msg, err := ethwire.ReadMessage(p.conn) + if err != nil { + log.Println(err) + + break out + } + + // TODO + data, _ := Decode(msg.Data, 0) + log.Printf("%s, %s\n", msg.MsgType, data) + } + + // Notify the out handler we're quiting + p.quit <- true } func (p *Peer) Start() { - // Run the outbound handler in a new goroutine - go p.HandleOutbound() - // Run the inbound handler in a new goroutine - go p.HandleInbound() + // Run the outbound handler in a new goroutine + go p.HandleOutbound() + // Run the inbound handler in a new goroutine + go p.HandleInbound() } func (p *Peer) Stop() { - p.conn.Close() + p.conn.Close() - p.quit <- true + p.quit <- true } diff --git a/rlp.go b/rlp.go index cee9da613..91ec50164 100644 --- a/rlp.go +++ b/rlp.go @@ -1,270 +1,278 @@ package main import ( - "fmt" - "bytes" - "math" - "math/big" - "github.com/ethereum/ethutil-go" + "bytes" + "fmt" + "github.com/ethereum/ethutil-go" + "math" + "math/big" ) type RlpEncoder struct { - rlpData []byte + rlpData []byte } + func NewRlpEncoder() *RlpEncoder { - encoder := &RlpEncoder{} + encoder := &RlpEncoder{} - return encoder + return encoder } func (coder *RlpEncoder) EncodeData(rlpData []interface{}) []byte { - return nil + return nil } // Data attributes are returned by the rlp decoder. The data attributes represents // one item within the rlp data structure. It's responsible for all the casting // It always returns something valid type RlpDataAttribute struct { - dataAttrib interface{} + dataAttrib interface{} } func NewRlpDataAttribute(attrib interface{}) *RlpDataAttribute { - return &RlpDataAttribute{dataAttrib: attrib} + return &RlpDataAttribute{dataAttrib: attrib} } func (attr *RlpDataAttribute) Length() int { - if data, ok := attr.dataAttrib.([]interface{}); ok { - return len(data) - } + if data, ok := attr.dataAttrib.([]interface{}); ok { + return len(data) + } - return 0 + return 0 } func (attr *RlpDataAttribute) AsUint() uint64 { - if value, ok := attr.dataAttrib.(uint8); ok { - return uint64(value) - } else if value, ok := attr.dataAttrib.(uint16); ok { - return uint64(value) - } else if value, ok := attr.dataAttrib.(uint32); ok { - return uint64(value) - } else if value, ok := attr.dataAttrib.(uint64); ok { - return value - } - - return 0 + if value, ok := attr.dataAttrib.(uint8); ok { + return uint64(value) + } else if value, ok := attr.dataAttrib.(uint16); ok { + return uint64(value) + } else if value, ok := attr.dataAttrib.(uint32); ok { + return uint64(value) + } else if value, ok := attr.dataAttrib.(uint64); ok { + return value + } + + return 0 } func (attr *RlpDataAttribute) AsBigInt() *big.Int { - if a, ok := attr.dataAttrib.([]byte); ok { - return ethutil.Big(string(a)) - } + if a, ok := attr.dataAttrib.([]byte); ok { + return ethutil.Big(string(a)) + } - return big.NewInt(0) + return big.NewInt(0) } func (attr *RlpDataAttribute) AsString() string { - if a, ok := attr.dataAttrib.([]byte); ok { - return string(a) - } + if a, ok := attr.dataAttrib.([]byte); ok { + return string(a) + } - return "" + return "" } func (attr *RlpDataAttribute) AsBytes() []byte { - if a, ok := attr.dataAttrib.([]byte); ok { - return a - } + if a, ok := attr.dataAttrib.([]byte); ok { + return a + } - return make([]byte, 0) + return make([]byte, 0) } // Threat the attribute as a slice func (attr *RlpDataAttribute) Get(idx int) *RlpDataAttribute { - if d, ok := attr.dataAttrib.([]interface{}); ok { - // Guard for oob - if len(d) < idx { - return NewRlpDataAttribute(nil) - } + if d, ok := attr.dataAttrib.([]interface{}); ok { + // Guard for oob + if len(d) < idx { + return NewRlpDataAttribute(nil) + } - return NewRlpDataAttribute(d[idx]) - } + return NewRlpDataAttribute(d[idx]) + } - // If this wasn't a slice you probably shouldn't be using this function - return NewRlpDataAttribute(nil) + // If this wasn't a slice you probably shouldn't be using this function + return NewRlpDataAttribute(nil) } type RlpDecoder struct { - rlpData interface{} + rlpData interface{} } + func NewRlpDecoder(rlpData []byte) *RlpDecoder { - decoder := &RlpDecoder{} - // Decode the data - data, _ := Decode(rlpData,0) - decoder.rlpData = data + decoder := &RlpDecoder{} + // Decode the data + data, _ := Decode(rlpData, 0) + decoder.rlpData = data - return decoder + return decoder } func (dec *RlpDecoder) Get(idx int) *RlpDataAttribute { - return NewRlpDataAttribute(dec.rlpData).Get(idx) + return NewRlpDataAttribute(dec.rlpData).Get(idx) } /// Raw methods func BinaryLength(n uint64) uint64 { - if n == 0 { return 0 } + if n == 0 { + return 0 + } - return 1 + BinaryLength(n / 256) + return 1 + BinaryLength(n/256) } func ToBinarySlice(n uint64, length uint64) []uint64 { - if length == 0 { - length = BinaryLength(n) - } + if length == 0 { + length = BinaryLength(n) + } - if n == 0 { return make([]uint64, 1) } + if n == 0 { + return make([]uint64, 1) + } - slice := ToBinarySlice(n / 256, 0) - slice = append(slice, n % 256) + slice := ToBinarySlice(n/256, 0) + slice = append(slice, n%256) - return slice + return slice } func ToBin(n uint64, length uint64) string { - var buf bytes.Buffer - for _, val := range ToBinarySlice(n, length) { - buf.WriteString(string(val)) - } + var buf bytes.Buffer + for _, val := range ToBinarySlice(n, length) { + buf.WriteString(string(val)) + } - return buf.String() + return buf.String() } func FromBin(data []byte) uint64 { - if len(data) == 0 { return 0 } + if len(data) == 0 { + return 0 + } - return FromBin(data[:len(data)-1]) * 256 + uint64(data[len(data)-1]) + return FromBin(data[:len(data)-1])*256 + uint64(data[len(data)-1]) } func Decode(data []byte, pos int) (interface{}, int) { - if pos > len(data)-1 { - panic(fmt.Sprintf("index out of range %d for data %q, l = %d", pos, data, len(data))) - } - - char := int(data[pos]) - slice := make([]interface{}, 0) - switch { - case char < 24: - return data[pos], pos + 1 - - case char < 56: - b := int(data[pos]) - 23 - return FromBin(data[pos+1 : pos+1+b]), pos + 1 + b - - case char < 64: - b := int(data[pos]) - 55 - b2 := int(FromBin(data[pos+1 : pos+1+b])) - return FromBin(data[pos+1+b : pos+1+b+b2]), pos+1+b+b2 - - case char < 120: - b := int(data[pos]) - 64 - return data[pos+1:pos+1+b], pos+1+b - - case char < 128: - b := int(data[pos]) - 119 - b2 := int(FromBin(data[pos+1 : pos+1+b])) - return data[pos+1+b : pos+1+b+b2], pos+1+b+b2 - - case char < 184: - b := int(data[pos]) - 128 - pos++ - for i := 0; i < b; i++ { - var obj interface{} - - obj, pos = Decode(data, pos) - slice = append(slice, obj) - } - return slice, pos - - case char < 192: - b := int(data[pos]) - 183 - //b2 := int(FromBin(data[pos+1 : pos+1+b])) (ref implementation has an unused variable) - pos = pos+1+b - for i := 0; i < b; i++ { - var obj interface{} - - obj, pos = Decode(data, pos) - slice = append(slice, obj) - } - return slice, pos - - default: - panic(fmt.Sprintf("byte not supported: %q", char)) - } - - return slice, 0 + if pos > len(data)-1 { + panic(fmt.Sprintf("index out of range %d for data %q, l = %d", pos, data, len(data))) + } + + char := int(data[pos]) + slice := make([]interface{}, 0) + switch { + case char < 24: + return data[pos], pos + 1 + + case char < 56: + b := int(data[pos]) - 23 + return FromBin(data[pos+1 : pos+1+b]), pos + 1 + b + + case char < 64: + b := int(data[pos]) - 55 + b2 := int(FromBin(data[pos+1 : pos+1+b])) + return FromBin(data[pos+1+b : pos+1+b+b2]), pos + 1 + b + b2 + + case char < 120: + b := int(data[pos]) - 64 + return data[pos+1 : pos+1+b], pos + 1 + b + + case char < 128: + b := int(data[pos]) - 119 + b2 := int(FromBin(data[pos+1 : pos+1+b])) + return data[pos+1+b : pos+1+b+b2], pos + 1 + b + b2 + + case char < 184: + b := int(data[pos]) - 128 + pos++ + for i := 0; i < b; i++ { + var obj interface{} + + obj, pos = Decode(data, pos) + slice = append(slice, obj) + } + return slice, pos + + case char < 192: + b := int(data[pos]) - 183 + //b2 := int(FromBin(data[pos+1 : pos+1+b])) (ref implementation has an unused variable) + pos = pos + 1 + b + for i := 0; i < b; i++ { + var obj interface{} + + obj, pos = Decode(data, pos) + slice = append(slice, obj) + } + return slice, pos + + default: + panic(fmt.Sprintf("byte not supported: %q", char)) + } + + return slice, 0 } func Encode(object interface{}) []byte { - var buff bytes.Buffer - - switch t := object.(type) { - case uint32, uint64: - var num uint64 - if _num, ok := t.(uint64); ok { - num = _num - } else if _num, ok := t.(uint32); ok { - num = uint64(_num) - } - - if num >= 0 && num < 24 { - buff.WriteString(string(num)) - } else if num <= uint64(math.Pow(2, 256)) { - b := ToBin(num, 0) - buff.WriteString(string(len(b) + 23) + b) - } else { - b := ToBin(num, 0) - b2 := ToBin(uint64(len(b)), 0) - buff.WriteString(string(len(b2) + 55) + b2 + b) - } - - case *big.Int: - buff.Write(Encode(t.String())) - - case string: - if len(t) < 56 { - buff.WriteString(string(len(t) + 64) + t) - } else { - b2 := ToBin(uint64(len(t)), 0) - buff.WriteString(string(len(b2) + 119) + b2 + t) - } - - case []byte: - // Cast the byte slice to a string - buff.Write(Encode(string(t))) - - case []interface{}, []string: - // Inline function for writing the slice header - WriteSliceHeader := func(length int) { - if length < 56 { - buff.WriteByte(byte(length + 128)) - } else { - b2 := ToBin(uint64(length), 0) - buff.WriteByte(byte(len(b2) + 183)) - buff.WriteString(b2) - } - } - - // FIXME How can I do this "better"? - if interSlice, ok := t.([]interface{}); ok { - WriteSliceHeader(len(interSlice)) - for _, val := range interSlice { - buff.Write(Encode(val)) - } - } else if stringSlice, ok := t.([]string); ok { - WriteSliceHeader(len(stringSlice)) - for _, val := range stringSlice { - buff.Write(Encode(val)) - } - } - } - - return buff.Bytes() + var buff bytes.Buffer + + switch t := object.(type) { + case uint32, uint64: + var num uint64 + if _num, ok := t.(uint64); ok { + num = _num + } else if _num, ok := t.(uint32); ok { + num = uint64(_num) + } + + if num >= 0 && num < 24 { + buff.WriteString(string(num)) + } else if num <= uint64(math.Pow(2, 256)) { + b := ToBin(num, 0) + buff.WriteString(string(len(b)+23) + b) + } else { + b := ToBin(num, 0) + b2 := ToBin(uint64(len(b)), 0) + buff.WriteString(string(len(b2)+55) + b2 + b) + } + + case *big.Int: + buff.Write(Encode(t.String())) + + case string: + if len(t) < 56 { + buff.WriteString(string(len(t)+64) + t) + } else { + b2 := ToBin(uint64(len(t)), 0) + buff.WriteString(string(len(b2)+119) + b2 + t) + } + + case []byte: + // Cast the byte slice to a string + buff.Write(Encode(string(t))) + + case []interface{}, []string: + // Inline function for writing the slice header + WriteSliceHeader := func(length int) { + if length < 56 { + buff.WriteByte(byte(length + 128)) + } else { + b2 := ToBin(uint64(length), 0) + buff.WriteByte(byte(len(b2) + 183)) + buff.WriteString(b2) + } + } + + // FIXME How can I do this "better"? + if interSlice, ok := t.([]interface{}); ok { + WriteSliceHeader(len(interSlice)) + for _, val := range interSlice { + buff.Write(Encode(val)) + } + } else if stringSlice, ok := t.([]string); ok { + WriteSliceHeader(len(stringSlice)) + for _, val := range stringSlice { + buff.Write(Encode(val)) + } + } + } + + return buff.Bytes() } diff --git a/rlp_test.go b/rlp_test.go index 68676d030..65cf34b39 100644 --- a/rlp_test.go +++ b/rlp_test.go @@ -1,54 +1,54 @@ package main import ( - "testing" - "fmt" + "fmt" + "testing" ) func TestEncode(t *testing.T) { - strRes := "Cdog" + strRes := "Cdog" - bytes := Encode("dog") + bytes := Encode("dog") - str := string(bytes) - if str != strRes { - t.Error(fmt.Sprintf("Expected %q, got %q", strRes, str)) - } - //dec,_ := Decode(bytes, 0) + str := string(bytes) + if str != strRes { + t.Error(fmt.Sprintf("Expected %q, got %q", strRes, str)) + } + //dec,_ := Decode(bytes, 0) - sliceRes := "\x83CdogCgodCcat" - strs := []string{"dog", "god", "cat"} - bytes = Encode(strs) - slice := string(bytes) - if slice != sliceRes { - t.Error(fmt.Sprintf("Expected %q, got %q", sliceRes, slice)) - } + sliceRes := "\x83CdogCgodCcat" + strs := []string{"dog", "god", "cat"} + bytes = Encode(strs) + slice := string(bytes) + if slice != sliceRes { + t.Error(fmt.Sprintf("Expected %q, got %q", sliceRes, slice)) + } - //dec,_ = Decode(bytes, 0) + //dec,_ = Decode(bytes, 0) } func TestMultiEncode(t *testing.T) { - inter := []interface{}{ - []interface{}{ - "1","2","3", - }, - []string{ - "string", - "string2", - "\x86A0J1234567890A\x00B20A0\x82F395843F657986", - "\x86A0J1234567890A\x00B20A0\x8cF395843F657986I335612448F524099H16716881A0H13114947G2039362G1507139H16719697G1048387E65360", - }, - "test", - } - - bytes := Encode(inter) - - Decode(bytes, 0) + inter := []interface{}{ + []interface{}{ + "1", "2", "3", + }, + []string{ + "string", + "string2", + "\x86A0J1234567890A\x00B20A0\x82F395843F657986", + "\x86A0J1234567890A\x00B20A0\x8cF395843F657986I335612448F524099H16716881A0H13114947G2039362G1507139H16719697G1048387E65360", + }, + "test", + } + + bytes := Encode(inter) + + Decode(bytes, 0) } func BenchmarkEncodeDecode(b *testing.B) { - for i := 0; i < b.N; i++ { - bytes := Encode([]string{"dog", "god", "cat"}) - Decode(bytes, 0) - } + for i := 0; i < b.N; i++ { + bytes := Encode([]string{"dog", "god", "cat"}) + Decode(bytes, 0) + } } diff --git a/server.go b/server.go index d81fe1bce..5373e2418 100644 --- a/server.go +++ b/server.go @@ -1,121 +1,120 @@ package main import ( - "container/list" - "net" - "log" - _"time" - "github.com/ethereum/ethdb-go" - "github.com/ethereum/ethutil-go" + "container/list" + "github.com/ethereum/ethdb-go" + "github.com/ethereum/ethutil-go" + "log" + "net" + _ "time" ) type Server struct { - // Channel for shutting down the server - shutdownChan chan bool - // DB interface - db *ethdb.LDBDatabase - // Block manager for processing new blocks and managing the block chain - blockManager *BlockManager - // Peers (NYI) - peers *list.List + // Channel for shutting down the server + shutdownChan chan bool + // DB interface + db *ethdb.LDBDatabase + // Block manager for processing new blocks and managing the block chain + blockManager *BlockManager + // Peers (NYI) + peers *list.List } func NewServer() (*Server, error) { - db, err := ethdb.NewLDBDatabase() - if err != nil { - return nil, err - } + db, err := ethdb.NewLDBDatabase() + if err != nil { + return nil, err + } - ethutil.SetConfig(db) + ethutil.SetConfig(db) - server := &Server{ - shutdownChan: make(chan bool), - blockManager: NewBlockManager(), - db: db, - peers: list.New(), - } + server := &Server{ + shutdownChan: make(chan bool), + blockManager: NewBlockManager(), + db: db, + peers: list.New(), + } - return server, nil + return server, nil } func (s *Server) AddPeer(conn net.Conn) { - peer := NewPeer(conn, s) - s.peers.PushBack(peer) - peer.Start() + peer := NewPeer(conn, s) + s.peers.PushBack(peer) + peer.Start() - log.Println("Peer connected ::", conn.RemoteAddr()) + log.Println("Peer connected ::", conn.RemoteAddr()) } func (s *Server) ConnectToPeer(addr string) error { - conn, err := net.Dial("tcp", addr) + conn, err := net.Dial("tcp", addr) - if err != nil { - return err - } + if err != nil { + return err + } - peer := NewPeer(conn, s) - s.peers.PushBack(peer) - peer.Start() + peer := NewPeer(conn, s) + s.peers.PushBack(peer) + peer.Start() + log.Println("Connected to peer ::", conn.RemoteAddr()) - log.Println("Connected to peer ::", conn.RemoteAddr()) - - return nil + return nil } func (s *Server) Broadcast(msgType string, data []byte) { - for e := s.peers.Front(); e != nil; e = e.Next() { - if peer, ok := e.Value.(*Peer); ok { - peer.QueueMessage(msgType, data) - } - } + for e := s.peers.Front(); e != nil; e = e.Next() { + if peer, ok := e.Value.(*Peer); ok { + peer.QueueMessage(msgType, data) + } + } } // Start the server func (s *Server) Start() { - // For now this function just blocks the main thread - ln, err := net.Listen("tcp", ":12345") - if err != nil { - log.Fatal(err) - } - - go func() { - for { - conn, err := ln.Accept() - if err != nil { - log.Println(err) - continue - } - - go s.AddPeer(conn) - } - }() - - // TMP - //go func() { - // for { - // s.Broadcast("block", Encode("blockdata")) -// -// time.Sleep(100 * time.Millisecond) -// } -// }() + // For now this function just blocks the main thread + ln, err := net.Listen("tcp", ":12345") + if err != nil { + log.Fatal(err) + } + + go func() { + for { + conn, err := ln.Accept() + if err != nil { + log.Println(err) + continue + } + + go s.AddPeer(conn) + } + }() + + // TMP + //go func() { + // for { + // s.Broadcast("block", Encode("blockdata")) + // + // time.Sleep(100 * time.Millisecond) + // } + // }() } func (s *Server) Stop() { - // Close the database - defer s.db.Close() + // Close the database + defer s.db.Close() - // Loop thru the peers and close them (if we had them) - for e := s.peers.Front(); e != nil; e = e.Next() { - if peer, ok := e.Value.(*Peer); ok { - peer.Stop() - } - } + // Loop thru the peers and close them (if we had them) + for e := s.peers.Front(); e != nil; e = e.Next() { + if peer, ok := e.Value.(*Peer); ok { + peer.Stop() + } + } - s.shutdownChan <- true + s.shutdownChan <- true } // This function will wait for a shutdown and resumes main thread execution func (s *Server) WaitForShutdown() { - <- s.shutdownChan + <-s.shutdownChan } diff --git a/test_runner.go b/test_runner.go index da93533dd..e8a1698ce 100644 --- a/test_runner.go +++ b/test_runner.go @@ -1,35 +1,35 @@ package main import ( - "fmt" - "testing" - "encoding/json" + "encoding/json" + "fmt" + "testing" ) type TestSource struct { - Inputs map[string]string - Expectation string + Inputs map[string]string + Expectation string } func NewTestSource(source string) *TestSource { - s := &TestSource{} - err := json.Unmarshal([]byte(source), s) - if err != nil { - fmt.Println(err) - } + s := &TestSource{} + err := json.Unmarshal([]byte(source), s) + if err != nil { + fmt.Println(err) + } - return s + return s } type TestRunner struct { - source *TestSource + source *TestSource } func NewTestRunner(t *testing.T) *TestRunner { - return &TestRunner{} + return &TestRunner{} } func (runner *TestRunner) RunFromString(input string, Cb func(*TestSource)) { - source := NewTestSource(input) - Cb(source) + source := NewTestSource(input) + Cb(source) } diff --git a/test_runner_test.go b/test_runner_test.go index 190bf3caf..5abe20002 100644 --- a/test_runner_test.go +++ b/test_runner_test.go @@ -1,9 +1,9 @@ package main import ( - _"fmt" - "testing" - "encoding/hex" + "encoding/hex" + _ "fmt" + "testing" ) var testsource = `{"Inputs":{ @@ -15,17 +15,17 @@ var testsource = `{"Inputs":{ }` func TestTestRunner(t *testing.T) { - db, _ := NewMemDatabase() - trie := NewTrie(db, "") + db, _ := NewMemDatabase() + trie := NewTrie(db, "") - runner := NewTestRunner(t) - runner.RunFromString(testsource, func(source *TestSource) { - for key, value := range source.Inputs { - trie.Update(key, value) - } + runner := NewTestRunner(t) + runner.RunFromString(testsource, func(source *TestSource) { + for key, value := range source.Inputs { + trie.Update(key, value) + } - if hex.EncodeToString([]byte(trie.root)) != source.Expectation { - t.Error("trie root did not match") - } - }) + if hex.EncodeToString([]byte(trie.root)) != source.Expectation { + t.Error("trie root did not match") + } + }) } diff --git a/testing.go b/testing.go index 9b7b7b3ce..5e2aec02b 100644 --- a/testing.go +++ b/testing.go @@ -1,4 +1,5 @@ package main + /* import ( diff --git a/vm.go b/vm.go index 5605cb7c7..96a3dfa05 100644 --- a/vm.go +++ b/vm.go @@ -1,267 +1,284 @@ package main import ( - "math/big" - "fmt" - "strconv" - "github.com/ethereum/ethutil-go" + "fmt" + "github.com/ethereum/ethutil-go" + "math/big" + "strconv" ) // Op codes const ( - oSTOP int = 0x00 - oADD int = 0x01 - oMUL int = 0x02 - oSUB int = 0x03 - oDIV int = 0x04 - oSDIV int = 0x05 - oMOD int = 0x06 - oSMOD int = 0x07 - oEXP int = 0x08 - oNEG int = 0x09 - oLT int = 0x0a - oLE int = 0x0b - oGT int = 0x0c - oGE int = 0x0d - oEQ int = 0x0e - oNOT int = 0x0f - oMYADDRESS int = 0x10 - oTXSENDER int = 0x11 - oTXVALUE int = 0x12 - oTXFEE int = 0x13 - oTXDATAN int = 0x14 - oTXDATA int = 0x15 - oBLK_PREVHASH int = 0x16 - oBLK_COINBASE int = 0x17 - oBLK_TIMESTAMP int = 0x18 - oBLK_NUMBER int = 0x19 - oBLK_DIFFICULTY int = 0x1a - oSHA256 int = 0x20 - oRIPEMD160 int = 0x21 - oECMUL int = 0x22 - oECADD int = 0x23 - oECSIGN int = 0x24 - oECRECOVER int = 0x25 - oECVALID int = 0x26 - oPUSH int = 0x30 - oPOP int = 0x31 - oDUP int = 0x32 - oDUPN int = 0x33 - oSWAP int = 0x34 - oSWAPN int = 0x35 - oLOAD int = 0x36 - oSTORE int = 0x37 - oJMP int = 0x40 - oJMPI int = 0x41 - oIND int = 0x42 - oEXTRO int = 0x50 - oBALANCE int = 0x51 - oMKTX int = 0x60 - oSUICIDE int = 0xff + oSTOP int = 0x00 + oADD int = 0x01 + oMUL int = 0x02 + oSUB int = 0x03 + oDIV int = 0x04 + oSDIV int = 0x05 + oMOD int = 0x06 + oSMOD int = 0x07 + oEXP int = 0x08 + oNEG int = 0x09 + oLT int = 0x0a + oLE int = 0x0b + oGT int = 0x0c + oGE int = 0x0d + oEQ int = 0x0e + oNOT int = 0x0f + oMYADDRESS int = 0x10 + oTXSENDER int = 0x11 + oTXVALUE int = 0x12 + oTXFEE int = 0x13 + oTXDATAN int = 0x14 + oTXDATA int = 0x15 + oBLK_PREVHASH int = 0x16 + oBLK_COINBASE int = 0x17 + oBLK_TIMESTAMP int = 0x18 + oBLK_NUMBER int = 0x19 + oBLK_DIFFICULTY int = 0x1a + oSHA256 int = 0x20 + oRIPEMD160 int = 0x21 + oECMUL int = 0x22 + oECADD int = 0x23 + oECSIGN int = 0x24 + oECRECOVER int = 0x25 + oECVALID int = 0x26 + oPUSH int = 0x30 + oPOP int = 0x31 + oDUP int = 0x32 + oDUPN int = 0x33 + oSWAP int = 0x34 + oSWAPN int = 0x35 + oLOAD int = 0x36 + oSTORE int = 0x37 + oJMP int = 0x40 + oJMPI int = 0x41 + oIND int = 0x42 + oEXTRO int = 0x50 + oBALANCE int = 0x51 + oMKTX int = 0x60 + oSUICIDE int = 0xff ) type OpType int + const ( - tNorm = iota - tData - tExtro - tCrypto + tNorm = iota + tData + tExtro + tCrypto ) + type TxCallback func(opType OpType) bool // Simple push/pop stack mechanism type Stack struct { - data []string + data []string } + func NewStack() *Stack { - return &Stack{} + return &Stack{} } func (st *Stack) Pop() string { - s := len(st.data) + s := len(st.data) - str := st.data[s-1] - st.data = st.data[:s-1] + str := st.data[s-1] + st.data = st.data[:s-1] - return str + return str } func (st *Stack) Popn() (*big.Int, *big.Int) { - s := len(st.data) + s := len(st.data) - strs := st.data[s-2:] - st.data = st.data[:s-2] + strs := st.data[s-2:] + st.data = st.data[:s-2] - return ethutil.Big(strs[0]), ethutil.Big(strs[1]) + return ethutil.Big(strs[0]), ethutil.Big(strs[1]) } func (st *Stack) Push(d string) { - st.data = append(st.data, d) + st.data = append(st.data, d) } func (st *Stack) Print() { - fmt.Println(st.data) + fmt.Println(st.data) } type Vm struct { - // Stack - stack *Stack + // Stack + stack *Stack } func NewVm() *Vm { - return &Vm{ - stack: NewStack(), - } + return &Vm{ + stack: NewStack(), + } } -func (vm *Vm) ProcContract( tx *ethutil.Transaction, - block *ethutil.Block, cb TxCallback) { - // Instruction pointer - pc := 0 +func (vm *Vm) ProcContract(tx *ethutil.Transaction, + block *ethutil.Block, cb TxCallback) { + // Instruction pointer + pc := 0 - contract := block.GetContract(tx.Hash()) - if contract == nil { - fmt.Println("Contract not found") - return - } + contract := block.GetContract(tx.Hash()) + if contract == nil { + fmt.Println("Contract not found") + return + } - Pow256 := ethutil.BigPow(2, 256) + Pow256 := ethutil.BigPow(2, 256) - //fmt.Printf("# op arg\n") + //fmt.Printf("# op arg\n") out: - for { - // The base big int for all calculations. Use this for any results. - base := new(big.Int) - // XXX Should Instr return big int slice instead of string slice? - // Get the next instruction from the contract - //op, _, _ := Instr(contract.state.Get(string(Encode(uint32(pc))))) - nb := ethutil.NumberToBytes(uint64(pc), 32) - op, _, _ := ethutil.Instr(contract.State().Get(string(nb))) + for { + // The base big int for all calculations. Use this for any results. + base := new(big.Int) + // XXX Should Instr return big int slice instead of string slice? + // Get the next instruction from the contract + //op, _, _ := Instr(contract.state.Get(string(Encode(uint32(pc))))) + nb := ethutil.NumberToBytes(uint64(pc), 32) + op, _, _ := ethutil.Instr(contract.State().Get(string(nb))) - if !cb(0) { break } + if !cb(0) { + break + } - if Debug { - //fmt.Printf("%-3d %-4d\n", pc, op) - } + if Debug { + //fmt.Printf("%-3d %-4d\n", pc, op) + } - switch op { - case oADD: - x, y := vm.stack.Popn() - // (x + y) % 2 ** 256 - base.Add(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - vm.stack.Push(base.String()) - case oSUB: - x, y := vm.stack.Popn() - // (x - y) % 2 ** 256 - base.Sub(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - vm.stack.Push(base.String()) - case oMUL: - x, y := vm.stack.Popn() - // (x * y) % 2 ** 256 - base.Mul(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - vm.stack.Push(base.String()) - case oDIV: - x, y := vm.stack.Popn() - // floor(x / y) - base.Div(x, y) - // Pop result back on the stack - vm.stack.Push(base.String()) - case oSDIV: - x, y := vm.stack.Popn() - // n > 2**255 - if x.Cmp(Pow256) > 0 { x.Sub(Pow256, x) } - if y.Cmp(Pow256) > 0 { y.Sub(Pow256, y) } - z := new(big.Int) - z.Div(x, y) - if z.Cmp(Pow256) > 0 { z.Sub(Pow256, z) } - // Push result on to the stack - vm.stack.Push(z.String()) - case oMOD: - x, y := vm.stack.Popn() - base.Mod(x, y) - vm.stack.Push(base.String()) - case oSMOD: - x, y := vm.stack.Popn() - // n > 2**255 - if x.Cmp(Pow256) > 0 { x.Sub(Pow256, x) } - if y.Cmp(Pow256) > 0 { y.Sub(Pow256, y) } - z := new(big.Int) - z.Mod(x, y) - if z.Cmp(Pow256) > 0 { z.Sub(Pow256, z) } - // Push result on to the stack - vm.stack.Push(z.String()) - case oEXP: - x, y := vm.stack.Popn() - base.Exp(x, y, Pow256) + switch op { + case oADD: + x, y := vm.stack.Popn() + // (x + y) % 2 ** 256 + base.Add(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + vm.stack.Push(base.String()) + case oSUB: + x, y := vm.stack.Popn() + // (x - y) % 2 ** 256 + base.Sub(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + vm.stack.Push(base.String()) + case oMUL: + x, y := vm.stack.Popn() + // (x * y) % 2 ** 256 + base.Mul(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + vm.stack.Push(base.String()) + case oDIV: + x, y := vm.stack.Popn() + // floor(x / y) + base.Div(x, y) + // Pop result back on the stack + vm.stack.Push(base.String()) + case oSDIV: + x, y := vm.stack.Popn() + // n > 2**255 + if x.Cmp(Pow256) > 0 { + x.Sub(Pow256, x) + } + if y.Cmp(Pow256) > 0 { + y.Sub(Pow256, y) + } + z := new(big.Int) + z.Div(x, y) + if z.Cmp(Pow256) > 0 { + z.Sub(Pow256, z) + } + // Push result on to the stack + vm.stack.Push(z.String()) + case oMOD: + x, y := vm.stack.Popn() + base.Mod(x, y) + vm.stack.Push(base.String()) + case oSMOD: + x, y := vm.stack.Popn() + // n > 2**255 + if x.Cmp(Pow256) > 0 { + x.Sub(Pow256, x) + } + if y.Cmp(Pow256) > 0 { + y.Sub(Pow256, y) + } + z := new(big.Int) + z.Mod(x, y) + if z.Cmp(Pow256) > 0 { + z.Sub(Pow256, z) + } + // Push result on to the stack + vm.stack.Push(z.String()) + case oEXP: + x, y := vm.stack.Popn() + base.Exp(x, y, Pow256) - vm.stack.Push(base.String()) - case oNEG: - base.Sub(Pow256, ethutil.Big(vm.stack.Pop())) - vm.stack.Push(base.String()) - case oLT: - x, y := vm.stack.Popn() - // x < y - if x.Cmp(y) < 0 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oLE: - x, y := vm.stack.Popn() - // x <= y - if x.Cmp(y) < 1 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oGT: - x, y := vm.stack.Popn() - // x > y - if x.Cmp(y) > 0 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oGE: - x, y := vm.stack.Popn() - // x >= y - if x.Cmp(y) > -1 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oNOT: - x, y := vm.stack.Popn() - // x != y - if x.Cmp(y) != 0 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oMYADDRESS: - vm.stack.Push(string(tx.Hash())) - case oTXSENDER: - vm.stack.Push(string(tx.Sender())) - case oPUSH: - // Get the next entry and pushes the value on the stack - pc++ - vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(pc), 32)))) - case oPOP: - // Pop current value of the stack - vm.stack.Pop() - case oLOAD: - // Load instruction X on the stack - i, _ := strconv.Atoi(vm.stack.Pop()) - vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32)))) - case oSTOP: - break out - } - pc++ - } + vm.stack.Push(base.String()) + case oNEG: + base.Sub(Pow256, ethutil.Big(vm.stack.Pop())) + vm.stack.Push(base.String()) + case oLT: + x, y := vm.stack.Popn() + // x < y + if x.Cmp(y) < 0 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oLE: + x, y := vm.stack.Popn() + // x <= y + if x.Cmp(y) < 1 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oGT: + x, y := vm.stack.Popn() + // x > y + if x.Cmp(y) > 0 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oGE: + x, y := vm.stack.Popn() + // x >= y + if x.Cmp(y) > -1 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oNOT: + x, y := vm.stack.Popn() + // x != y + if x.Cmp(y) != 0 { + vm.stack.Push("1") + } else { + vm.stack.Push("0") + } + case oMYADDRESS: + vm.stack.Push(string(tx.Hash())) + case oTXSENDER: + vm.stack.Push(string(tx.Sender())) + case oPUSH: + // Get the next entry and pushes the value on the stack + pc++ + vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(pc), 32)))) + case oPOP: + // Pop current value of the stack + vm.stack.Pop() + case oLOAD: + // Load instruction X on the stack + i, _ := strconv.Atoi(vm.stack.Pop()) + vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32)))) + case oSTOP: + break out + } + pc++ + } - vm.stack.Print() + vm.stack.Print() } -- cgit v1.2.3 From d6f7372a3c5b002c6e385b5916bd5f81463c37df Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 11 Jan 2014 15:41:05 +0100 Subject: Coding style --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 37e8505f1..a9bb8b260 100644 --- a/README.md +++ b/README.md @@ -25,4 +25,27 @@ Command line options Contribution ============ -See CONTRIB.md +If you'd like to contribute to Ethereum Go please fork, fix, commit and +send a pull request. Commits who do not comply with the coding standards +are ignored. + +Coding standards +================ + +Sources should be formatted according to the [Go Formatting +Style](http://golang.org/doc/effective_go.html#formatting). + +Unless structs fields are supposed to be directly accesible, provide +Getters and hide the fields through Go's exporting facility. + +Don't "overcomment", meaning that your and my mom doesn't have to read +the source code. + +*wrong* + +```go +// Check if the value at x is greater than y +if x > y { + // It's greater! +} +``` -- cgit v1.2.3 From 66dd8b6fbaa675cbac8dc0bf4085d5a708a612d7 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 11 Jan 2014 15:53:27 +0100 Subject: sub packages --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a9bb8b260..a7d2a0e66 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,14 @@ Ethereum [![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) -Ethereum Go +Ethereum Go (c) 0255c78815d4f056f84c96de438ed9e38c69c0f8af24f5032248be5a79fe9071c3 + + +Ethereum Go is split up in several sub packages. Please refer to each +individual package for more information. + 1. [ethutil](https://github.com/ethereum/ethutil-go) + 2. [ethdb](https://github.com/ethereum/ethdb-go) + 3. [ethwire](https://github.com/ethereum/ethwire-go) Deps ==== @@ -13,7 +20,7 @@ Ethereum Go makes use of a modified `secp256k1-go` and therefor GMP. Install ======= -```go get https://github.com/ethereum/go-ethereum``` +```go get -u -t https://github.com/ethereum/go-ethereum``` Command line options @@ -49,3 +56,8 @@ if x > y { // It's greater! } ``` + +Copy +==== + +69bce990a619e747b4f57483724b0e8a1732bb3b44ccf70b0dd6abd272af94550fc9d8b21232d33ebf30d38a148612f68e936094b4daeb9ea7174088a439070401 0255c78815d4f056f84c96de438ed9e38c69c0f8af24f5032248be5a79fe9071c3 -- cgit v1.2.3 From e058f3bc0a9a184f3a145d88646063cfaf8071e6 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 11 Jan 2014 15:55:12 +0100 Subject: sub packages --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a7d2a0e66..23fdda2e5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Ethereum [![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) -Ethereum Go (c) 0255c78815d4f056f84c96de438ed9e38c69c0f8af24f5032248be5a79fe9071c3 +Ethereum Go (c) [0255c7881](https://github.com/ethereum/go-ethereum#copy) Ethereum Go is split up in several sub packages. Please refer to each @@ -57,7 +57,6 @@ if x > y { } ``` -Copy -==== +### Copy 69bce990a619e747b4f57483724b0e8a1732bb3b44ccf70b0dd6abd272af94550fc9d8b21232d33ebf30d38a148612f68e936094b4daeb9ea7174088a439070401 0255c78815d4f056f84c96de438ed9e38c69c0f8af24f5032248be5a79fe9071c3 -- cgit v1.2.3 From dfb8e65ca74e566345ca40d63b1f359d922f0b9e Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 01:51:07 +0100 Subject: RLP changes --- rlp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rlp.go b/rlp.go index 91ec50164..f5e630398 100644 --- a/rlp.go +++ b/rlp.go @@ -40,6 +40,7 @@ func (attr *RlpDataAttribute) Length() int { return 0 } + func (attr *RlpDataAttribute) AsUint() uint64 { if value, ok := attr.dataAttrib.(uint8); ok { return uint64(value) -- cgit v1.2.3 From bee05d52bfb9f555b7f9c7b625524594bff97826 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 01:51:52 +0100 Subject: Block verification, TD calculations and overall block processing --- block_manager.go | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 5 deletions(-) diff --git a/block_manager.go b/block_manager.go index 968d9a139..04d45f434 100644 --- a/block_manager.go +++ b/block_manager.go @@ -3,40 +3,63 @@ package main import ( "fmt" "github.com/ethereum/ethutil-go" + "errors" + "log" + "math/big" ) type BlockChain struct { - lastBlock *ethutil.Block + LastBlock *ethutil.Block genesisBlock *ethutil.Block + + TD *big.Int } func NewBlockChain() *BlockChain { bc := &BlockChain{} bc.genesisBlock = ethutil.NewBlock(ethutil.Encode(ethutil.Genesis)) + // Set the last know difficulty (might be 0x0 as initial value, Genesis) + bc.TD = new(big.Int) + bc.TD.SetBytes(ethutil.Config.Db.LastKnownTD()) + return bc } +func (bc *BlockChain) HasBlock(hash string) bool { + return bc.LastBlock.State().Get(hash) != "" +} + type BlockManager struct { + // Ethereum virtual machine for processing contracts vm *Vm - - blockChain *BlockChain + // The block chain :) + bc *BlockChain } func NewBlockManager() *BlockManager { - bm := &BlockManager{vm: NewVm()} + bm := &BlockManager{ + vm: NewVm(), + bc: NewBlockChain(), + } return bm } // Process a block. func (bm *BlockManager) ProcessBlock(block *ethutil.Block) error { - // TODO Validation (Or move to other part of the application) + // Block validation if err := bm.ValidateBlock(block); err != nil { return err } + // I'm not sure, but I don't know if there should be thrown + // any errors at this time. + if err := bm.AccumelateRewards(block); err != nil { + return err + } + // Get the tx count. Used to create enough channels to 'join' the go routines txCount := len(block.Transactions()) // Locking channel. When it has been fully buffered this method will return @@ -58,10 +81,80 @@ func (bm *BlockManager) ProcessBlock(block *ethutil.Block) error { <-lockChan } + if bm.CalculateTD(block) { + ethutil.Config.Db.Put(block.Hash(), block.MarshalRlp()) + bm.bc.LastBlock = block + } + return nil } +func (bm *BlockManager) CalculateTD(block *ethutil.Block) bool { + uncleDiff := new(big.Int) + for _, uncle := range block.Uncles { + uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty) + } + + // TD(genesis_block) = 0 and TD(B) = TD(B.parent) + sum(u.difficulty for u in B.uncles) + B.difficulty + td := new(big.Int) + td = td.Add(bm.bc.TD, uncleDiff) + td = td.Add(td, block.Difficulty) + + // The new TD will only be accepted if the new difficulty is + // is greater than the previous. + if td.Cmp(bm.bc.TD) > 0 { + bm.bc.LastBlock = block + // Set the new total difficulty back to the block chain + bm.bc.TD = td + + return true + } + + return false +} + +// Validates the current block. Returns an error if the block was invalid, +// an uncle or anything that isn't on the current block chain func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { + // TODO + // 1. Check if the nonce of the block is valid + // 2. Check if the difficulty is correct + + // Check if we have the parent hash, if it isn't known we discard it + // Reasons might be catching up or simply an invalid block + if bm.bc.HasBlock(block.PrevHash) { + // Check each uncle's previous hash. In order for it to be valid + // is if it has the same block hash as the current + for _, uncle := range block.Uncles { + if uncle.PrevHash != block.PrevHash { + if Debug { + log.Printf("Uncle prvhash mismatch %x %x\n", block.PrevHash, uncle.PrevHash) + } + + return errors.New("Mismatching Prvhash from uncle") + } + } + } else { + return errors.New("Block's parent unknown") + } + + + return nil +} + +func (bm *BlockManager) AccumelateRewards(block *ethutil.Block) error { + // Get the coinbase rlp data + d := block.State().Get(block.Coinbase) + + ether := ethutil.NewEtherFromData([]byte(d)) + + // Reward amount of ether to the coinbase address + ether.AddFee(ethutil.CalculateBlockReward(block, len(block.Uncles))) + block.State().Update(block.Coinbase, string(ether.MarshalRlp())) + + // TODO Reward each uncle + + return nil } -- cgit v1.2.3 From 54bce64e3a8e50b6553364f2f01b38382ffbc4df Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 02:36:41 +0100 Subject: Validations reordering & added nonce validation --- block_manager.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/block_manager.go b/block_manager.go index 04d45f434..21162f36b 100644 --- a/block_manager.go +++ b/block_manager.go @@ -114,30 +114,34 @@ func (bm *BlockManager) CalculateTD(block *ethutil.Block) bool { } // Validates the current block. Returns an error if the block was invalid, -// an uncle or anything that isn't on the current block chain +// an uncle or anything that isn't on the current block chain. +// Validation validates easy over difficult (dagger takes longer time = difficult) func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { // TODO - // 1. Check if the nonce of the block is valid // 2. Check if the difficulty is correct // Check if we have the parent hash, if it isn't known we discard it // Reasons might be catching up or simply an invalid block - if bm.bc.HasBlock(block.PrevHash) { - // Check each uncle's previous hash. In order for it to be valid - // is if it has the same block hash as the current - for _, uncle := range block.Uncles { - if uncle.PrevHash != block.PrevHash { - if Debug { - log.Printf("Uncle prvhash mismatch %x %x\n", block.PrevHash, uncle.PrevHash) - } - - return errors.New("Mismatching Prvhash from uncle") + if !bm.bc.HasBlock(block.PrevHash) { + return errors.New("Block's parent unknown") + } + + // Check each uncle's previous hash. In order for it to be valid + // is if it has the same block hash as the current + for _, uncle := range block.Uncles { + if uncle.PrevHash != block.PrevHash { + if Debug { + log.Printf("Uncle prvhash mismatch %x %x\n", block.PrevHash, uncle.PrevHash) } + + return errors.New("Mismatching Prvhash from uncle") } - } else { - return errors.New("Block's parent unknown") } + // Verify the nonce of the block. Return an error if it's not valid + if !DaggerVerify(block.Hash(), block.Nonce) { + return errors.New("Block's nonce is invalid") + } return nil } -- cgit v1.2.3 From 406adb4563d4664e4ce63a7786ea57f797c42e47 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 02:39:17 +0100 Subject: Fixed dagger verification --- block_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block_manager.go b/block_manager.go index 21162f36b..1a73a45f2 100644 --- a/block_manager.go +++ b/block_manager.go @@ -139,7 +139,7 @@ func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { } // Verify the nonce of the block. Return an error if it's not valid - if !DaggerVerify(block.Hash(), block.Nonce) { + if !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { return errors.New("Block's nonce is invalid") } -- cgit v1.2.3 From c3fabfe00a81b548dceb8ecf1281087e51c5fddf Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 16:50:09 +0100 Subject: Fixed validation --- block_manager.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/block_manager.go b/block_manager.go index 1a73a45f2..0c7410022 100644 --- a/block_manager.go +++ b/block_manager.go @@ -24,11 +24,20 @@ func NewBlockChain() *BlockChain { bc.TD = new(big.Int) bc.TD.SetBytes(ethutil.Config.Db.LastKnownTD()) + + // TODO get last block from the database + //bc.LastBlock = bc.genesisBlock + return bc } func (bc *BlockChain) HasBlock(hash string) bool { - return bc.LastBlock.State().Get(hash) != "" + data, _ := ethutil.Config.Db.Get([]byte(hash)) + return len(data) != 0 +} + +func (bc *BlockChain) GenesisBlock() *ethutil.Block { + return bc.genesisBlock } type BlockManager struct { @@ -107,6 +116,10 @@ func (bm *BlockManager) CalculateTD(block *ethutil.Block) bool { // Set the new total difficulty back to the block chain bm.bc.TD = td + if Debug { + log.Println("TD(block) =", td) + } + return true } @@ -122,7 +135,8 @@ func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { // Check if we have the parent hash, if it isn't known we discard it // Reasons might be catching up or simply an invalid block - if !bm.bc.HasBlock(block.PrevHash) { + if bm.bc.LastBlock != nil && block.PrevHash == "" && + !bm.bc.HasBlock(block.PrevHash) { return errors.New("Block's parent unknown") } @@ -139,10 +153,13 @@ func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { } // Verify the nonce of the block. Return an error if it's not valid - if !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { + if bm.bc.LastBlock != nil && block.PrevHash == "" && + !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { return errors.New("Block's nonce is invalid") } + log.Println("Block validation PASSED") + return nil } -- cgit v1.2.3 From 12c0e827032f2c8de12bac0738116a51b3816cdb Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 16:50:33 +0100 Subject: Verion acknowledgement --- peer.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/peer.go b/peer.go index a9f88b1e1..2dda1f94f 100644 --- a/peer.go +++ b/peer.go @@ -2,6 +2,7 @@ package main import ( "github.com/ethereum/ethwire-go" + "github.com/ethereum/ethutil-go" "log" "net" ) @@ -12,24 +13,26 @@ type Peer struct { // Net connection conn net.Conn // Output queue which is used to communicate and handle messages - outputQueue chan ethwire.InOutMsg + outputQueue chan *ethwire.InOutMsg // Quit channel quit chan bool + + inbound bool // Determines whether it's an inbound or outbound peer } -func NewPeer(conn net.Conn, server *Server) *Peer { +func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { return &Peer{ - outputQueue: make(chan ethwire.InOutMsg, 1), // Buffered chan of 1 is enough + outputQueue: make(chan *ethwire.InOutMsg, 1), // Buffered chan of 1 is enough quit: make(chan bool), - server: server, conn: conn, + inbound: inbound, } } // Outputs any RLP encoded data to the peer -func (p *Peer) QueueMessage(msgType string, data []byte) { - p.outputQueue <- ethwire.InOutMsg{MsgType: msgType, Data: data} +func (p *Peer) QueueMessage(msg *ethwire.InOutMsg) { + p.outputQueue <- msg//ethwire.InOutMsg{MsgType: msgType, Nonce: ethutil.RandomUint64(), Data: data} } // Outbound message handler. Outbound messages are handled here @@ -69,9 +72,22 @@ out: break out } - // TODO - data, _ := Decode(msg.Data, 0) - log.Printf("%s, %s\n", msg.MsgType, data) + if Debug { + log.Printf("Received %s\n", msg.MsgType) + } + + // TODO Hash data and check if for existence (= ignore) + + switch msg.MsgType { + case "verack": + // Version message + p.handleVersionAck(msg) + case "block": + err := p.server.blockManager.ProcessBlock(ethutil.NewBlock(msg.Data)) + if err != nil { + log.Println(err) + } + } } // Notify the out handler we're quiting @@ -79,6 +95,15 @@ out: } func (p *Peer) Start() { + if !p.inbound { + err := p.pushVersionAck() + if err != nil { + log.Printf("Peer can't send outbound version ack", err) + + p.Stop() + } + } + // Run the outbound handler in a new goroutine go p.HandleOutbound() // Run the inbound handler in a new goroutine @@ -90,3 +115,34 @@ func (p *Peer) Stop() { p.quit <- true } + +func (p *Peer) pushVersionAck() error { + msg := ethwire.NewMessage("verack", p.server.Nonce, []byte("01")) + + p.QueueMessage(msg) + + return nil +} + +func (p *Peer) handleVersionAck(msg *ethwire.InOutMsg) { + // Detect self connect + if msg.Nonce == p.server.Nonce { + log.Println("Peer connected to self, disconnecting") + + p.Stop() + return + } + + log.Println("mnonce", msg.Nonce, "snonce", p.server.Nonce) + + // If this is an inbound connection send an ack back + if p.inbound { + err := p.pushVersionAck() + if err != nil { + log.Println("Peer can't send ack back") + + p.Stop() + } + } +} + -- cgit v1.2.3 From fedbd9a962b2e800afe2e191d768f47172172741 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 16:50:43 +0100 Subject: Rlp integer fix --- rlp.go | 279 ------------------------------------------------------------ rlp_test.go | 54 ------------ 2 files changed, 333 deletions(-) delete mode 100644 rlp.go delete mode 100644 rlp_test.go diff --git a/rlp.go b/rlp.go deleted file mode 100644 index f5e630398..000000000 --- a/rlp.go +++ /dev/null @@ -1,279 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "github.com/ethereum/ethutil-go" - "math" - "math/big" -) - -type RlpEncoder struct { - rlpData []byte -} - -func NewRlpEncoder() *RlpEncoder { - encoder := &RlpEncoder{} - - return encoder -} -func (coder *RlpEncoder) EncodeData(rlpData []interface{}) []byte { - return nil -} - -// Data attributes are returned by the rlp decoder. The data attributes represents -// one item within the rlp data structure. It's responsible for all the casting -// It always returns something valid -type RlpDataAttribute struct { - dataAttrib interface{} -} - -func NewRlpDataAttribute(attrib interface{}) *RlpDataAttribute { - return &RlpDataAttribute{dataAttrib: attrib} -} - -func (attr *RlpDataAttribute) Length() int { - if data, ok := attr.dataAttrib.([]interface{}); ok { - return len(data) - } - - return 0 -} - - -func (attr *RlpDataAttribute) AsUint() uint64 { - if value, ok := attr.dataAttrib.(uint8); ok { - return uint64(value) - } else if value, ok := attr.dataAttrib.(uint16); ok { - return uint64(value) - } else if value, ok := attr.dataAttrib.(uint32); ok { - return uint64(value) - } else if value, ok := attr.dataAttrib.(uint64); ok { - return value - } - - return 0 -} - -func (attr *RlpDataAttribute) AsBigInt() *big.Int { - if a, ok := attr.dataAttrib.([]byte); ok { - return ethutil.Big(string(a)) - } - - return big.NewInt(0) -} - -func (attr *RlpDataAttribute) AsString() string { - if a, ok := attr.dataAttrib.([]byte); ok { - return string(a) - } - - return "" -} - -func (attr *RlpDataAttribute) AsBytes() []byte { - if a, ok := attr.dataAttrib.([]byte); ok { - return a - } - - return make([]byte, 0) -} - -// Threat the attribute as a slice -func (attr *RlpDataAttribute) Get(idx int) *RlpDataAttribute { - if d, ok := attr.dataAttrib.([]interface{}); ok { - // Guard for oob - if len(d) < idx { - return NewRlpDataAttribute(nil) - } - - return NewRlpDataAttribute(d[idx]) - } - - // If this wasn't a slice you probably shouldn't be using this function - return NewRlpDataAttribute(nil) -} - -type RlpDecoder struct { - rlpData interface{} -} - -func NewRlpDecoder(rlpData []byte) *RlpDecoder { - decoder := &RlpDecoder{} - // Decode the data - data, _ := Decode(rlpData, 0) - decoder.rlpData = data - - return decoder -} - -func (dec *RlpDecoder) Get(idx int) *RlpDataAttribute { - return NewRlpDataAttribute(dec.rlpData).Get(idx) -} - -/// Raw methods -func BinaryLength(n uint64) uint64 { - if n == 0 { - return 0 - } - - return 1 + BinaryLength(n/256) -} - -func ToBinarySlice(n uint64, length uint64) []uint64 { - if length == 0 { - length = BinaryLength(n) - } - - if n == 0 { - return make([]uint64, 1) - } - - slice := ToBinarySlice(n/256, 0) - slice = append(slice, n%256) - - return slice -} - -func ToBin(n uint64, length uint64) string { - var buf bytes.Buffer - for _, val := range ToBinarySlice(n, length) { - buf.WriteString(string(val)) - } - - return buf.String() -} - -func FromBin(data []byte) uint64 { - if len(data) == 0 { - return 0 - } - - return FromBin(data[:len(data)-1])*256 + uint64(data[len(data)-1]) -} - -func Decode(data []byte, pos int) (interface{}, int) { - if pos > len(data)-1 { - panic(fmt.Sprintf("index out of range %d for data %q, l = %d", pos, data, len(data))) - } - - char := int(data[pos]) - slice := make([]interface{}, 0) - switch { - case char < 24: - return data[pos], pos + 1 - - case char < 56: - b := int(data[pos]) - 23 - return FromBin(data[pos+1 : pos+1+b]), pos + 1 + b - - case char < 64: - b := int(data[pos]) - 55 - b2 := int(FromBin(data[pos+1 : pos+1+b])) - return FromBin(data[pos+1+b : pos+1+b+b2]), pos + 1 + b + b2 - - case char < 120: - b := int(data[pos]) - 64 - return data[pos+1 : pos+1+b], pos + 1 + b - - case char < 128: - b := int(data[pos]) - 119 - b2 := int(FromBin(data[pos+1 : pos+1+b])) - return data[pos+1+b : pos+1+b+b2], pos + 1 + b + b2 - - case char < 184: - b := int(data[pos]) - 128 - pos++ - for i := 0; i < b; i++ { - var obj interface{} - - obj, pos = Decode(data, pos) - slice = append(slice, obj) - } - return slice, pos - - case char < 192: - b := int(data[pos]) - 183 - //b2 := int(FromBin(data[pos+1 : pos+1+b])) (ref implementation has an unused variable) - pos = pos + 1 + b - for i := 0; i < b; i++ { - var obj interface{} - - obj, pos = Decode(data, pos) - slice = append(slice, obj) - } - return slice, pos - - default: - panic(fmt.Sprintf("byte not supported: %q", char)) - } - - return slice, 0 -} - -func Encode(object interface{}) []byte { - var buff bytes.Buffer - - switch t := object.(type) { - case uint32, uint64: - var num uint64 - if _num, ok := t.(uint64); ok { - num = _num - } else if _num, ok := t.(uint32); ok { - num = uint64(_num) - } - - if num >= 0 && num < 24 { - buff.WriteString(string(num)) - } else if num <= uint64(math.Pow(2, 256)) { - b := ToBin(num, 0) - buff.WriteString(string(len(b)+23) + b) - } else { - b := ToBin(num, 0) - b2 := ToBin(uint64(len(b)), 0) - buff.WriteString(string(len(b2)+55) + b2 + b) - } - - case *big.Int: - buff.Write(Encode(t.String())) - - case string: - if len(t) < 56 { - buff.WriteString(string(len(t)+64) + t) - } else { - b2 := ToBin(uint64(len(t)), 0) - buff.WriteString(string(len(b2)+119) + b2 + t) - } - - case []byte: - // Cast the byte slice to a string - buff.Write(Encode(string(t))) - - case []interface{}, []string: - // Inline function for writing the slice header - WriteSliceHeader := func(length int) { - if length < 56 { - buff.WriteByte(byte(length + 128)) - } else { - b2 := ToBin(uint64(length), 0) - buff.WriteByte(byte(len(b2) + 183)) - buff.WriteString(b2) - } - } - - // FIXME How can I do this "better"? - if interSlice, ok := t.([]interface{}); ok { - WriteSliceHeader(len(interSlice)) - for _, val := range interSlice { - buff.Write(Encode(val)) - } - } else if stringSlice, ok := t.([]string); ok { - WriteSliceHeader(len(stringSlice)) - for _, val := range stringSlice { - buff.Write(Encode(val)) - } - } - } - - return buff.Bytes() -} diff --git a/rlp_test.go b/rlp_test.go deleted file mode 100644 index 65cf34b39..000000000 --- a/rlp_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "fmt" - "testing" -) - -func TestEncode(t *testing.T) { - strRes := "Cdog" - - bytes := Encode("dog") - - str := string(bytes) - if str != strRes { - t.Error(fmt.Sprintf("Expected %q, got %q", strRes, str)) - } - //dec,_ := Decode(bytes, 0) - - sliceRes := "\x83CdogCgodCcat" - strs := []string{"dog", "god", "cat"} - bytes = Encode(strs) - slice := string(bytes) - if slice != sliceRes { - t.Error(fmt.Sprintf("Expected %q, got %q", sliceRes, slice)) - } - - //dec,_ = Decode(bytes, 0) -} - -func TestMultiEncode(t *testing.T) { - inter := []interface{}{ - []interface{}{ - "1", "2", "3", - }, - []string{ - "string", - "string2", - "\x86A0J1234567890A\x00B20A0\x82F395843F657986", - "\x86A0J1234567890A\x00B20A0\x8cF395843F657986I335612448F524099H16716881A0H13114947G2039362G1507139H16719697G1048387E65360", - }, - "test", - } - - bytes := Encode(inter) - - Decode(bytes, 0) -} - -func BenchmarkEncodeDecode(b *testing.B) { - for i := 0; i < b.N; i++ { - bytes := Encode([]string{"dog", "god", "cat"}) - Decode(bytes, 0) - } -} -- cgit v1.2.3 From e280a2a7e3d51f0fb3b35e580332d9b51a7e50c1 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 16:50:59 +0100 Subject: Server testing blocks --- ethereum.go | 6 ++++-- server.go | 43 +++++++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/ethereum.go b/ethereum.go index dd5b44308..f38de1872 100644 --- a/ethereum.go +++ b/ethereum.go @@ -65,7 +65,7 @@ func main() { go func() { for { res := dagger.Search(ethutil.Big("0"), ethutil.BigPow(2, 36)) - server.Broadcast("block", Encode(res.String())) + server.Broadcast("block", ethutil.Encode(res.String())) } }() } @@ -74,8 +74,10 @@ func main() { err = server.ConnectToPeer("localhost:12345") if err != nil { - log.Println(err) + log.Println("Error starting server", err) + server.Stop() + return } diff --git a/server.go b/server.go index 5373e2418..98d9746e8 100644 --- a/server.go +++ b/server.go @@ -4,46 +4,56 @@ import ( "container/list" "github.com/ethereum/ethdb-go" "github.com/ethereum/ethutil-go" + "github.com/ethereum/ethwire-go" "log" "net" - _ "time" + "time" ) type Server struct { // Channel for shutting down the server shutdownChan chan bool // DB interface - db *ethdb.LDBDatabase + //db *ethdb.LDBDatabase + db *ethdb.MemDatabase // Block manager for processing new blocks and managing the block chain blockManager *BlockManager // Peers (NYI) peers *list.List + // Nonce + Nonce uint64 } func NewServer() (*Server, error) { - db, err := ethdb.NewLDBDatabase() + //db, err := ethdb.NewLDBDatabase() + db, err := ethdb.NewMemDatabase() if err != nil { return nil, err } ethutil.SetConfig(db) + nonce, _ := ethutil.RandomUint64() server := &Server{ shutdownChan: make(chan bool), blockManager: NewBlockManager(), db: db, peers: list.New(), + Nonce: nonce, } return server, nil } func (s *Server) AddPeer(conn net.Conn) { - peer := NewPeer(conn, s) - s.peers.PushBack(peer) - peer.Start() + peer := NewPeer(conn, s, true) + + if peer != nil { + s.peers.PushBack(peer) + peer.Start() - log.Println("Peer connected ::", conn.RemoteAddr()) + log.Println("Peer connected ::", conn.RemoteAddr()) + } } func (s *Server) ConnectToPeer(addr string) error { @@ -53,7 +63,7 @@ func (s *Server) ConnectToPeer(addr string) error { return err } - peer := NewPeer(conn, s) + peer := NewPeer(conn, s, false) s.peers.PushBack(peer) peer.Start() @@ -65,7 +75,7 @@ func (s *Server) ConnectToPeer(addr string) error { func (s *Server) Broadcast(msgType string, data []byte) { for e := s.peers.Front(); e != nil; e = e.Next() { if peer, ok := e.Value.(*Peer); ok { - peer.QueueMessage(msgType, data) + peer.QueueMessage(ethwire.NewMessage(msgType, 0, data)) } } } @@ -83,6 +93,7 @@ func (s *Server) Start() { conn, err := ln.Accept() if err != nil { log.Println(err) + continue } @@ -91,13 +102,13 @@ func (s *Server) Start() { }() // TMP - //go func() { - // for { - // s.Broadcast("block", Encode("blockdata")) - // - // time.Sleep(100 * time.Millisecond) - // } - // }() + go func() { + for { + s.Broadcast("block", s.blockManager.bc.GenesisBlock().MarshalRlp()) + + time.Sleep(1000 * time.Millisecond) + } + }() } func (s *Server) Stop() { -- cgit v1.2.3 From f78bd4d5d0a6198c2c0e709440d9aa370e840617 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 17:19:14 +0100 Subject: Format --- block_manager.go | 8 +++----- peer.go | 11 +++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/block_manager.go b/block_manager.go index 0c7410022..b6c2cace7 100644 --- a/block_manager.go +++ b/block_manager.go @@ -1,9 +1,9 @@ package main import ( + "errors" "fmt" "github.com/ethereum/ethutil-go" - "errors" "log" "math/big" ) @@ -24,7 +24,6 @@ func NewBlockChain() *BlockChain { bc.TD = new(big.Int) bc.TD.SetBytes(ethutil.Config.Db.LastKnownTD()) - // TODO get last block from the database //bc.LastBlock = bc.genesisBlock @@ -136,7 +135,7 @@ func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { // Check if we have the parent hash, if it isn't known we discard it // Reasons might be catching up or simply an invalid block if bm.bc.LastBlock != nil && block.PrevHash == "" && - !bm.bc.HasBlock(block.PrevHash) { + !bm.bc.HasBlock(block.PrevHash) { return errors.New("Block's parent unknown") } @@ -154,7 +153,7 @@ func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { // Verify the nonce of the block. Return an error if it's not valid if bm.bc.LastBlock != nil && block.PrevHash == "" && - !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { + !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { return errors.New("Block's nonce is invalid") } @@ -175,7 +174,6 @@ func (bm *BlockManager) AccumelateRewards(block *ethutil.Block) error { // TODO Reward each uncle - return nil } diff --git a/peer.go b/peer.go index 2dda1f94f..0875c6e45 100644 --- a/peer.go +++ b/peer.go @@ -1,8 +1,8 @@ package main import ( - "github.com/ethereum/ethwire-go" "github.com/ethereum/ethutil-go" + "github.com/ethereum/ethwire-go" "log" "net" ) @@ -24,15 +24,15 @@ func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { return &Peer{ outputQueue: make(chan *ethwire.InOutMsg, 1), // Buffered chan of 1 is enough quit: make(chan bool), - server: server, - conn: conn, - inbound: inbound, + server: server, + conn: conn, + inbound: inbound, } } // Outputs any RLP encoded data to the peer func (p *Peer) QueueMessage(msg *ethwire.InOutMsg) { - p.outputQueue <- msg//ethwire.InOutMsg{MsgType: msgType, Nonce: ethutil.RandomUint64(), Data: data} + p.outputQueue <- msg //ethwire.InOutMsg{MsgType: msgType, Nonce: ethutil.RandomUint64(), Data: data} } // Outbound message handler. Outbound messages are handled here @@ -145,4 +145,3 @@ func (p *Peer) handleVersionAck(msg *ethwire.InOutMsg) { } } } - -- cgit v1.2.3 From 39bb2c94c066c36cc8e245e737bbc5a106583f92 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 22:14:19 +0100 Subject: Atomic syncs on connection states --- peer.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++------------ server.go | 36 ++++++++++++------------ 2 files changed, 96 insertions(+), 35 deletions(-) diff --git a/peer.go b/peer.go index 0875c6e45..e3a4f74cb 100644 --- a/peer.go +++ b/peer.go @@ -5,6 +5,8 @@ import ( "github.com/ethereum/ethwire-go" "log" "net" + "sync/atomic" + "time" ) type Peer struct { @@ -16,8 +18,12 @@ type Peer struct { outputQueue chan *ethwire.InOutMsg // Quit channel quit chan bool - - inbound bool // Determines whether it's an inbound or outbound peer + // Determines whether it's an inbound or outbound peer + inbound bool + // Flag for checking the peer's connectivity state + connected int32 + disconnect int32 + lastSend time.Time } func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { @@ -27,12 +33,57 @@ func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { server: server, conn: conn, inbound: inbound, + disconnect: 0, + connected: 1, } } +func NewOutboundPeer(addr string, server *Server) *Peer { + p := &Peer{ + outputQueue: make(chan *ethwire.InOutMsg, 1), // Buffered chan of 1 is enough + quit: make(chan bool), + server: server, + inbound: false, + connected: 0, + disconnect: 1, + } + + // Set up the connection in another goroutine so we don't block the main thread + go func() { + conn, err := net.Dial("tcp", addr) + if err != nil { + p.Stop() + } + p.conn = conn + + // Atomically set the connection state + atomic.StoreInt32(&p.connected, 1) + atomic.StoreInt32(&p.disconnect, 0) + + log.Println("Connected to peer ::", conn.RemoteAddr()) + }() + + return p +} + // Outputs any RLP encoded data to the peer func (p *Peer) QueueMessage(msg *ethwire.InOutMsg) { - p.outputQueue <- msg //ethwire.InOutMsg{MsgType: msgType, Nonce: ethutil.RandomUint64(), Data: data} + p.outputQueue <- msg +} + +func (p *Peer) writeMessage(msg *ethwire.InOutMsg) { + // Ignore the write if we're not connected + if atomic.LoadInt32(&p.connected) != 1 { + return + } + + err := ethwire.WriteMessage(p.conn, msg) + if err != nil { + log.Println("Can't send message:", err) + // Stop the client if there was an error writing to it + p.Stop() + return + } } // Outbound message handler. Outbound messages are handled here @@ -42,28 +93,32 @@ out: select { // Main message queue. All outbound messages are processed through here case msg := <-p.outputQueue: - // TODO Message checking and handle accordingly - err := ethwire.WriteMessage(p.conn, msg) - if err != nil { - log.Println(err) - - // Stop the client if there was an error writing to it - p.Stop() - } + p.writeMessage(msg) + p.lastSend = time.Now() // Break out of the for loop if a quit message is posted case <-p.quit: break out } } + +clean: + // This loop is for draining the output queue and anybody waiting for us + for { + select { + case <- p.outputQueue: + // TODO + default: + break clean + } + } } // Inbound handler. Inbound messages are received here and passed to the appropriate methods func (p *Peer) HandleInbound() { - defer p.Stop() out: - for { + for atomic.LoadInt32(&p.disconnect) == 0 { // Wait for a message from the peer msg, err := ethwire.ReadMessage(p.conn) if err != nil { @@ -90,8 +145,7 @@ out: } } - // Notify the out handler we're quiting - p.quit <- true + p.Stop() } func (p *Peer) Start() { @@ -111,9 +165,16 @@ func (p *Peer) Start() { } func (p *Peer) Stop() { - p.conn.Close() + if atomic.AddInt32(&p.disconnect, 1) != 1 { + return + } + + close(p.quit) + if atomic.LoadInt32(&p.connected) != 0 { + p.conn.Close() + } - p.quit <- true + log.Println("Peer shutdown") } func (p *Peer) pushVersionAck() error { diff --git a/server.go b/server.go index 98d9746e8..b01ecb08c 100644 --- a/server.go +++ b/server.go @@ -10,6 +10,16 @@ import ( "time" ) +func eachPeer(peers *list.List, callback func(*Peer)) { + // Loop thru the peers and close them (if we had them) + for e := peers.Front(); e != nil; e = e.Next() { + if peer, ok := e.Value.(*Peer); ok { + callback(peer) + } + } +} + + type Server struct { // Channel for shutting down the server shutdownChan chan bool @@ -57,27 +67,20 @@ func (s *Server) AddPeer(conn net.Conn) { } func (s *Server) ConnectToPeer(addr string) error { - conn, err := net.Dial("tcp", addr) - - if err != nil { - return err - } + peer := NewOutboundPeer(addr, s) - peer := NewPeer(conn, s, false) s.peers.PushBack(peer) + peer.Start() - log.Println("Connected to peer ::", conn.RemoteAddr()) return nil } func (s *Server) Broadcast(msgType string, data []byte) { - for e := s.peers.Front(); e != nil; e = e.Next() { - if peer, ok := e.Value.(*Peer); ok { - peer.QueueMessage(ethwire.NewMessage(msgType, 0, data)) - } - } + eachPeer(s.peers, func(p *Peer) { + p.QueueMessage(ethwire.NewMessage(msgType, 0, data)) + }) } // Start the server @@ -115,12 +118,9 @@ func (s *Server) Stop() { // Close the database defer s.db.Close() - // Loop thru the peers and close them (if we had them) - for e := s.peers.Front(); e != nil; e = e.Next() { - if peer, ok := e.Value.(*Peer); ok { - peer.Stop() - } - } + eachPeer(s.peers, func(p *Peer) { + p.Stop() + }) s.shutdownChan <- true } -- cgit v1.2.3 From 52fb3b412cde2c5afb0e3364a1da23f3c1d7b171 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 23:16:33 +0100 Subject: Increased buffer size --- peer.go | 23 ++++++++++++++++++++--- server.go | 5 ++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/peer.go b/peer.go index e3a4f74cb..e6f752022 100644 --- a/peer.go +++ b/peer.go @@ -9,6 +9,11 @@ import ( "time" ) +const ( + // The size of the output buffer for writing messages + outputBufferSize = 50 +) + type Peer struct { // Server interface server *Server @@ -24,11 +29,12 @@ type Peer struct { connected int32 disconnect int32 lastSend time.Time + versionKnown bool } func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { return &Peer{ - outputQueue: make(chan *ethwire.InOutMsg, 1), // Buffered chan of 1 is enough + outputQueue: make(chan *ethwire.InOutMsg, outputBufferSize), quit: make(chan bool), server: server, conn: conn, @@ -40,7 +46,7 @@ func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { func NewOutboundPeer(addr string, server *Server) *Peer { p := &Peer{ - outputQueue: make(chan *ethwire.InOutMsg, 1), // Buffered chan of 1 is enough + outputQueue: make(chan *ethwire.InOutMsg, outputBufferSize), quit: make(chan bool), server: server, inbound: false, @@ -61,6 +67,8 @@ func NewOutboundPeer(addr string, server *Server) *Peer { atomic.StoreInt32(&p.disconnect, 0) log.Println("Connected to peer ::", conn.RemoteAddr()) + + p.Start() }() return p @@ -77,6 +85,14 @@ func (p *Peer) writeMessage(msg *ethwire.InOutMsg) { return } + if !p.versionKnown { + switch msg.MsgType { + case "verack": // Ok + default: // Anything but ack is allowed + return + } + } + err := ethwire.WriteMessage(p.conn, msg) if err != nil { log.Println("Can't send message:", err) @@ -191,10 +207,11 @@ func (p *Peer) handleVersionAck(msg *ethwire.InOutMsg) { log.Println("Peer connected to self, disconnecting") p.Stop() + return } - log.Println("mnonce", msg.Nonce, "snonce", p.server.Nonce) + p.versionKnown = true // If this is an inbound connection send an ack back if p.inbound { diff --git a/server.go b/server.go index b01ecb08c..bc398dd92 100644 --- a/server.go +++ b/server.go @@ -71,9 +71,6 @@ func (s *Server) ConnectToPeer(addr string) error { s.peers.PushBack(peer) - peer.Start() - - return nil } @@ -106,6 +103,8 @@ func (s *Server) Start() { // TMP go func() { + //time.Sleep(500 * time.Millisecond) + for { s.Broadcast("block", s.blockManager.bc.GenesisBlock().MarshalRlp()) -- cgit v1.2.3 From 7ade1778fba0fd1f6e0bccc7647cd8fb3185528d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 12 Jan 2014 23:46:03 +0100 Subject: Peer reaping and fake network --- ethereum.go | 8 -------- peer.go | 4 ++++ server.go | 46 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/ethereum.go b/ethereum.go index f38de1872..055c1a5ad 100644 --- a/ethereum.go +++ b/ethereum.go @@ -72,14 +72,6 @@ func main() { server.Start() - err = server.ConnectToPeer("localhost:12345") - if err != nil { - log.Println("Error starting server", err) - - server.Stop() - - return - } // Wait for shutdown server.WaitForShutdown() diff --git a/peer.go b/peer.go index e6f752022..16bb22286 100644 --- a/peer.go +++ b/peer.go @@ -28,7 +28,11 @@ type Peer struct { // Flag for checking the peer's connectivity state connected int32 disconnect int32 + // Last known message send lastSend time.Time + // Indicated whether a verack has been send or not + // This flag is used by writeMessage to check if messages are allowed + // to be send or not. If no version is known all messages are ignored. versionKnown bool } diff --git a/server.go b/server.go index bc398dd92..e34bc6f20 100644 --- a/server.go +++ b/server.go @@ -8,13 +8,14 @@ import ( "log" "net" "time" + "sync/atomic" ) -func eachPeer(peers *list.List, callback func(*Peer)) { +func eachPeer(peers *list.List, callback func(*Peer, *list.Element)) { // Loop thru the peers and close them (if we had them) for e := peers.Front(); e != nil; e = e.Next() { if peer, ok := e.Value.(*Peer); ok { - callback(peer) + callback(peer, e) } } } @@ -75,19 +76,54 @@ func (s *Server) ConnectToPeer(addr string) error { } func (s *Server) Broadcast(msgType string, data []byte) { - eachPeer(s.peers, func(p *Peer) { + eachPeer(s.peers, func(p *Peer, e *list.Element) { p.QueueMessage(ethwire.NewMessage(msgType, 0, data)) }) } +const ( + processReapingTimeout = 10 // TODO increase +) + +func (s *Server) ReapDeadPeers() { + for { + eachPeer(s.peers, func(p *Peer, e *list.Element) { + if atomic.LoadInt32(&p.disconnect) == 1 { + log.Println("Dead peer found .. reaping") + + s.peers.Remove(e) + } + }) + + time.Sleep(processReapingTimeout * time.Second) + } +} + // Start the server func (s *Server) Start() { // For now this function just blocks the main thread ln, err := net.Listen("tcp", ":12345") if err != nil { - log.Fatal(err) + // This is mainly for testing to create a "network" + if Debug { + log.Println("Connection listening disabled. Acting as client") + + err = s.ConnectToPeer("localhost:12345") + if err != nil { + log.Println("Error starting server", err) + + s.Stop() + } + + return + } else { + log.Fatal(err) + } } + // Start the reaping processes + go s.ReapDeadPeers() + go func() { for { conn, err := ln.Accept() @@ -117,7 +153,7 @@ func (s *Server) Stop() { // Close the database defer s.db.Close() - eachPeer(s.peers, func(p *Peer) { + eachPeer(s.peers, func(p *Peer, e *list.Element) { p.Stop() }) -- cgit v1.2.3 From 578b63e2b8a32122fb45f9f053503e51d9a5c535 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 13 Jan 2014 01:22:33 +0100 Subject: Some miner reports --- block_manager.go | 1 + dagger.go | 7 +++++-- ethereum.go | 4 ++-- peer.go | 3 +++ server.go | 4 ++-- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/block_manager.go b/block_manager.go index b6c2cace7..7e16cdb3a 100644 --- a/block_manager.go +++ b/block_manager.go @@ -154,6 +154,7 @@ func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { // Verify the nonce of the block. Return an error if it's not valid if bm.bc.LastBlock != nil && block.PrevHash == "" && !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { + return errors.New("Block's nonce is invalid") } diff --git a/dagger.go b/dagger.go index 70b0b4692..0cc63ea94 100644 --- a/dagger.go +++ b/dagger.go @@ -7,6 +7,7 @@ import ( "math/big" "math/rand" "time" + "log" ) type Dagger struct { @@ -22,7 +23,9 @@ func (dag *Dagger) Find(obj *big.Int, resChan chan int64) { for i := 0; i < 1000; i++ { rnd := r.Int63() - if dag.Eval(big.NewInt(rnd)).Cmp(obj) < 0 { + res := dag.Eval(big.NewInt(rnd)) + log.Printf("rnd %v\nres %v\nobj %v\n", rnd, res, obj) + if res.Cmp(obj) < 0 { // Post back result on the channel resChan <- rnd // Notify other threads we've found a valid nonce @@ -119,7 +122,7 @@ func Sum(sha hash.Hash) []byte { func (dag *Dagger) Eval(N *big.Int) *big.Int { pow := ethutil.BigPow(2, 26) - dag.xn = N.Div(N, pow) + dag.xn = pow.Div(N, pow) sha := sha3.NewKeccak256() sha.Reset() diff --git a/ethereum.go b/ethereum.go index 055c1a5ad..7e13f197a 100644 --- a/ethereum.go +++ b/ethereum.go @@ -64,8 +64,8 @@ func main() { go func() { for { - res := dagger.Search(ethutil.Big("0"), ethutil.BigPow(2, 36)) - server.Broadcast("block", ethutil.Encode(res.String())) + res := dagger.Search(ethutil.Big("01001"), ethutil.BigPow(2, 26)) + server.Broadcast("blockmine", ethutil.Encode(res.String())) } }() } diff --git a/peer.go b/peer.go index 16bb22286..8f68a9bec 100644 --- a/peer.go +++ b/peer.go @@ -162,6 +162,9 @@ out: if err != nil { log.Println(err) } + case "blockmine": + d, _ := ethutil.Decode(msg.Data, 0) + log.Printf("block mined %s\n", d) } } diff --git a/server.go b/server.go index e34bc6f20..f658750a9 100644 --- a/server.go +++ b/server.go @@ -138,15 +138,15 @@ func (s *Server) Start() { }() // TMP + /* go func() { - //time.Sleep(500 * time.Millisecond) - for { s.Broadcast("block", s.blockManager.bc.GenesisBlock().MarshalRlp()) time.Sleep(1000 * time.Millisecond) } }() + */ } func (s *Server) Stop() { -- cgit v1.2.3 From 44900e363cbc81b640bc16bcc7e6a50c22352be4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 14 Jan 2014 21:48:16 +0100 Subject: Updated to temporary trie --- dagger.go | 5 +++-- dev_console.go | 36 ++++++++++++++++++++++++++++++------ ethereum.go | 3 +-- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/dagger.go b/dagger.go index 0cc63ea94..966bfa461 100644 --- a/dagger.go +++ b/dagger.go @@ -116,8 +116,8 @@ func (dag *Dagger) Node(L uint64, i uint64) *big.Int { } func Sum(sha hash.Hash) []byte { - in := make([]byte, 32) - return sha.Sum(in) + //in := make([]byte, 32) + return sha.Sum(nil) } func (dag *Dagger) Eval(N *big.Int) *big.Int { @@ -146,3 +146,4 @@ func (dag *Dagger) Eval(N *big.Int) *big.Int { return ret.SetBytes(Sum(sha)) } + diff --git a/dev_console.go b/dev_console.go index 923c483c2..228cfc47e 100644 --- a/dev_console.go +++ b/dev_console.go @@ -37,6 +37,12 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "dag" && argumentLength != 2: err = true expArgCount = 2 + case action == "decode" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "encode" && argumentLength != 1: + err = true + expArgCount = 1 } if err { @@ -46,6 +52,15 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { } } +func (i *Console) PrintRoot() { + root := ethutil.Conv(i.trie.RootT) + if len(root.AsBytes()) != 0 { + fmt.Println(hex.EncodeToString(root.AsBytes())) + } else { + fmt.Println(i.trie.RootT) + } +} + func (i *Console) ParseInput(input string) bool { scanner := bufio.NewScanner(strings.NewReader(input)) scanner.Split(bufio.ScanWords) @@ -70,21 +85,26 @@ func (i *Console) ParseInput(input string) bool { } else { switch tokens[0] { case "update": - i.trie.Update(tokens[1], tokens[2]) + i.trie.UpdateT(tokens[1], tokens[2]) - fmt.Println(hex.EncodeToString([]byte(i.trie.Root))) + i.PrintRoot() case "get": - fmt.Println(i.trie.Get(tokens[1])) + fmt.Println(i.trie.GetT(tokens[1])) case "root": - fmt.Println(hex.EncodeToString([]byte(i.trie.Root))) + i.PrintRoot() case "rawroot": - fmt.Println(i.trie.Root) + fmt.Println(i.trie.RootT) case "print": i.db.Print() case "dag": fmt.Println(DaggerVerify(ethutil.Big(tokens[1]), // hash ethutil.BigPow(2, 36), // diff ethutil.Big(tokens[2]))) // nonce + case "decode": + d, _ := ethutil.Decode([]byte(tokens[1]), 0) + fmt.Printf("%q\n", d) + case "encode": + fmt.Printf("%q\n", ethutil.Encode(tokens[1])) case "exit", "quit", "q": return false case "help": @@ -95,7 +115,11 @@ func (i *Console) ParseInput(input string) bool { "root - Prints the hex encoded merkle root\n" + "rawroot - Prints the raw merkle root\n" + "\033[1m= Dagger =\033[0m\n" + - "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n") + "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n" + + "\033[1m= Enroding =\033[0m\n" + + "decode STR\n" + + "encode STR\n") + default: fmt.Println("Unknown command:", tokens[0]) } diff --git a/ethereum.go b/ethereum.go index 7e13f197a..83f656fe2 100644 --- a/ethereum.go +++ b/ethereum.go @@ -64,7 +64,7 @@ func main() { go func() { for { - res := dagger.Search(ethutil.Big("01001"), ethutil.BigPow(2, 26)) + res := dagger.Search(ethutil.Big("01001"), ethutil.BigPow(2, 36)) server.Broadcast("blockmine", ethutil.Encode(res.String())) } }() @@ -72,7 +72,6 @@ func main() { server.Start() - // Wait for shutdown server.WaitForShutdown() } -- cgit v1.2.3 From be48f2eb91b8726cbafe9865360e64cbfa35c3de Mon Sep 17 00:00:00 2001 From: Stephan Tual Date: Wed, 15 Jan 2014 17:40:35 +0000 Subject: Fixed typo (credit to comma 8) --- dev_console.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_console.go b/dev_console.go index 228cfc47e..5340a5f46 100644 --- a/dev_console.go +++ b/dev_console.go @@ -116,7 +116,7 @@ func (i *Console) ParseInput(input string) bool { "rawroot - Prints the raw merkle root\n" + "\033[1m= Dagger =\033[0m\n" + "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n" + - "\033[1m= Enroding =\033[0m\n" + + "\033[1m= Encoding =\033[0m\n" + "decode STR\n" + "encode STR\n") -- cgit v1.2.3 From 4ea93eba501cf996395e4c0829e21cab77267cf9 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 15 Jan 2014 23:32:30 +0100 Subject: Updated tests --- dagger_test.go | 3 ++- test_runner_test.go | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dagger_test.go b/dagger_test.go index 58cdd6afd..616577a39 100644 --- a/dagger_test.go +++ b/dagger_test.go @@ -1,13 +1,14 @@ package main import ( + "github.com/ethereum/ethutil-go" "math/big" "testing" ) func BenchmarkDaggerSearch(b *testing.B) { hash := big.NewInt(0) - diff := BigPow(2, 36) + diff := ethutil.BigPow(2, 36) o := big.NewInt(0) // nonce doesn't matter. We're only testing against speed, not validity // Reset timer so the big generation isn't included in the benchmark diff --git a/test_runner_test.go b/test_runner_test.go index 5abe20002..5abf0c8df 100644 --- a/test_runner_test.go +++ b/test_runner_test.go @@ -3,6 +3,8 @@ package main import ( "encoding/hex" _ "fmt" + "github.com/ethereum/ethdb-go" + "github.com/ethereum/ethutil-go" "testing" ) @@ -15,8 +17,8 @@ var testsource = `{"Inputs":{ }` func TestTestRunner(t *testing.T) { - db, _ := NewMemDatabase() - trie := NewTrie(db, "") + db, _ := ethdb.NewMemDatabase() + trie := ethutil.NewTrie(db, "") runner := NewTestRunner(t) runner.RunFromString(testsource, func(source *TestSource) { @@ -24,7 +26,7 @@ func TestTestRunner(t *testing.T) { trie.Update(key, value) } - if hex.EncodeToString([]byte(trie.root)) != source.Expectation { + if hex.EncodeToString([]byte(trie.Root)) != source.Expectation { t.Error("trie root did not match") } }) -- cgit v1.2.3 From 64058e2663453fc3c5adde8d7e1e3627bcfe4919 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 11:00:56 +0100 Subject: Updated readme and coding standards --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23fdda2e5..ba122dc05 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,10 @@ Install Command line options ==================== +``` -c launch the developer console -m start mining fake blocks and broadcast fake messages to the net +``` Contribution ============ @@ -45,8 +47,8 @@ Style](http://golang.org/doc/effective_go.html#formatting). Unless structs fields are supposed to be directly accesible, provide Getters and hide the fields through Go's exporting facility. -Don't "overcomment", meaning that your and my mom doesn't have to read -the source code. +When you comment put meaningfull comments. Describe in detail what you +want to achieve. *wrong* @@ -57,6 +59,15 @@ if x > y { } ``` +Everyone reading the source probably know what you wanted to achieve +with above code. Those are **not** meaningful comments. + +While the project isn't 100% tested I want you to write tests non the +less. I haven't got time to evaluate everyone's code in detail so I +expect you to write tests for me so I don't have to test your code +manually. (If you want to contribute by just writing tests that's fine +too!) + ### Copy 69bce990a619e747b4f57483724b0e8a1732bb3b44ccf70b0dd6abd272af94550fc9d8b21232d33ebf30d38a148612f68e936094b4daeb9ea7174088a439070401 0255c78815d4f056f84c96de438ed9e38c69c0f8af24f5032248be5a79fe9071c3 -- cgit v1.2.3 From d8056f7ea525cbf37714613e772c0a4af97a1969 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 11:01:59 +0100 Subject: Updated readme and coding standards --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ba122dc05..b7df2afae 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ Ethereum Ethereum Go (c) [0255c7881](https://github.com/ethereum/go-ethereum#copy) +A fair warning; Ethereum is not yet to be used in production. There's no +test-net and you aren't mining read blocks (just one which is the genesis block). + Ethereum Go is split up in several sub packages. Please refer to each individual package for more information. -- cgit v1.2.3 From 79e88690b26634077912a6a4b0199c2bd85666e4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 11:02:19 +0100 Subject: Removed capitalization of the json source --- test_runner_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test_runner_test.go b/test_runner_test.go index 5abf0c8df..0a0b0d69c 100644 --- a/test_runner_test.go +++ b/test_runner_test.go @@ -8,12 +8,14 @@ import ( "testing" ) -var testsource = `{"Inputs":{ - "doe": "reindeer", - "dog": "puppy", - "dogglesworth": "cat" - }, - "Expectation":"e378927bfc1bd4f01a2e8d9f59bd18db8a208bb493ac0b00f93ce51d4d2af76c" +var testsource = ` +{ + "inputs":{ + "doe": "reindeer", + "dog": "puppy", + "dogglesworth": "cat" + }, + "expectation":"e378927bfc1bd4f01a2e8d9f59bd18db8a208bb493ac0b00f93ce51d4d2af76c" }` func TestTestRunner(t *testing.T) { -- cgit v1.2.3 From a80a3eeb238443d4b49deda360015ab315bd14a4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 11:02:41 +0100 Subject: Removed capitalization of the json source --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7df2afae..05e0156b9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Ethereum Ethereum Go (c) [0255c7881](https://github.com/ethereum/go-ethereum#copy) A fair warning; Ethereum is not yet to be used in production. There's no -test-net and you aren't mining read blocks (just one which is the genesis block). +test-net and you aren't mining real blocks (just one which is the genesis block). Ethereum Go is split up in several sub packages. Please refer to each -- cgit v1.2.3 From 83396cd836f6d963573295b9304be87bf45f5ad6 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 15:20:41 +0100 Subject: Moved stack to its own file. Moved contract processing to block manager --- block_manager.go | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- stack.go | 164 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 366 insertions(+), 5 deletions(-) create mode 100644 stack.go diff --git a/block_manager.go b/block_manager.go index 7e16cdb3a..7b2d01f29 100644 --- a/block_manager.go +++ b/block_manager.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/ethutil-go" "log" "math/big" + "strconv" ) type BlockChain struct { @@ -40,16 +41,17 @@ func (bc *BlockChain) GenesisBlock() *ethutil.Block { } type BlockManager struct { - // Ethereum virtual machine for processing contracts - vm *Vm // The block chain :) bc *BlockChain + + // Stack for processing contracts + stack *Stack } func NewBlockManager() *BlockManager { bm := &BlockManager{ - vm: NewVm(), - bc: NewBlockChain(), + bc: NewBlockChain(), + stack: NewStack(), } return bm @@ -189,7 +191,7 @@ func (bm *BlockManager) ProcessContract(tx *ethutil.Transaction, block *ethutil. }() // Process contract - bm.vm.ProcContract(tx, block, func(opType OpType) bool { + bm.ProcContract(tx, block, func(opType OpType) bool { // TODO turn on once big ints are in place //if !block.PayFee(tx.Hash(), StepFee.Uint64()) { // return false @@ -201,3 +203,198 @@ func (bm *BlockManager) ProcessContract(tx *ethutil.Transaction, block *ethutil. // Broadcast we're done lockChan <- true } + +func (bm *BlockManager) ProcContract(tx *ethutil.Transaction, + block *ethutil.Block, cb TxCallback) { + // Instruction pointer + pc := 0 + + contract := block.GetContract(tx.Hash()) + if contract == nil { + fmt.Println("Contract not found") + return + } + + Pow256 := ethutil.BigPow(2, 256) + + //fmt.Printf("# op arg\n") +out: + for { + // The base big int for all calculations. Use this for any results. + base := new(big.Int) + // XXX Should Instr return big int slice instead of string slice? + // Get the next instruction from the contract + //op, _, _ := Instr(contract.state.Get(string(Encode(uint32(pc))))) + nb := ethutil.NumberToBytes(uint64(pc), 32) + o, _, _ := ethutil.Instr(contract.State().Get(string(nb))) + op := OpCode(o) + + if !cb(0) { + break + } + + if Debug { + //fmt.Printf("%-3d %-4s\n", pc, op.String()) + } + + switch op { + case oADD: + x, y := bm.stack.Popn() + // (x + y) % 2 ** 256 + base.Add(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + bm.stack.Push(base.String()) + case oSUB: + x, y := bm.stack.Popn() + // (x - y) % 2 ** 256 + base.Sub(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + bm.stack.Push(base.String()) + case oMUL: + x, y := bm.stack.Popn() + // (x * y) % 2 ** 256 + base.Mul(x, y) + base.Mod(base, Pow256) + // Pop result back on the stack + bm.stack.Push(base.String()) + case oDIV: + x, y := bm.stack.Popn() + // floor(x / y) + base.Div(x, y) + // Pop result back on the stack + bm.stack.Push(base.String()) + case oSDIV: + x, y := bm.stack.Popn() + // n > 2**255 + if x.Cmp(Pow256) > 0 { + x.Sub(Pow256, x) + } + if y.Cmp(Pow256) > 0 { + y.Sub(Pow256, y) + } + z := new(big.Int) + z.Div(x, y) + if z.Cmp(Pow256) > 0 { + z.Sub(Pow256, z) + } + // Push result on to the stack + bm.stack.Push(z.String()) + case oMOD: + x, y := bm.stack.Popn() + base.Mod(x, y) + bm.stack.Push(base.String()) + case oSMOD: + x, y := bm.stack.Popn() + // n > 2**255 + if x.Cmp(Pow256) > 0 { + x.Sub(Pow256, x) + } + if y.Cmp(Pow256) > 0 { + y.Sub(Pow256, y) + } + z := new(big.Int) + z.Mod(x, y) + if z.Cmp(Pow256) > 0 { + z.Sub(Pow256, z) + } + // Push result on to the stack + bm.stack.Push(z.String()) + case oEXP: + x, y := bm.stack.Popn() + base.Exp(x, y, Pow256) + + bm.stack.Push(base.String()) + case oNEG: + base.Sub(Pow256, ethutil.Big(bm.stack.Pop())) + bm.stack.Push(base.String()) + case oLT: + x, y := bm.stack.Popn() + // x < y + if x.Cmp(y) < 0 { + bm.stack.Push("1") + } else { + bm.stack.Push("0") + } + case oLE: + x, y := bm.stack.Popn() + // x <= y + if x.Cmp(y) < 1 { + bm.stack.Push("1") + } else { + bm.stack.Push("0") + } + case oGT: + x, y := bm.stack.Popn() + // x > y + if x.Cmp(y) > 0 { + bm.stack.Push("1") + } else { + bm.stack.Push("0") + } + case oGE: + x, y := bm.stack.Popn() + // x >= y + if x.Cmp(y) > -1 { + bm.stack.Push("1") + } else { + bm.stack.Push("0") + } + case oNOT: + x, y := bm.stack.Popn() + // x != y + if x.Cmp(y) != 0 { + bm.stack.Push("1") + } else { + bm.stack.Push("0") + } + + // Please note that the following code contains some + // ugly string casting. This will have to change to big + // ints. TODO :) + case oMYADDRESS: + bm.stack.Push(string(tx.Hash())) + case oTXSENDER: + bm.stack.Push(string(tx.Sender())) + case oTXVALUE: + bm.stack.Push(tx.Value.String()) + case oTXDATAN: + bm.stack.Push(big.NewInt(int64(len(tx.Data))).String()) + case oTXDATA: + v := ethutil.Big(bm.stack.Pop()) + // v >= len(data) + if v.Cmp(big.NewInt(int64(len(tx.Data)))) >= 0 { + //I know this will change. It makes no + //sense. Read comment above + bm.stack.Push(ethutil.Big("0").String()) + } else { + bm.stack.Push(ethutil.Big(tx.Data[v.Uint64()]).String()) + } + case oBLK_PREVHASH: + bm.stack.Push(string(block.PrevHash)) + case oBLK_COINBASE: + bm.stack.Push(block.Coinbase) + case oBLK_TIMESTAMP: + bm.stack.Push(big.NewInt(block.Time).String()) + case oBLK_NUMBER: + + case oPUSH: + // Get the next entry and pushes the value on the stack + pc++ + bm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(pc), 32)))) + case oPOP: + // Pop current value of the stack + bm.stack.Pop() + case oLOAD: + // Load instruction X on the stack + i, _ := strconv.Atoi(bm.stack.Pop()) + bm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32)))) + case oSTOP: + break out + } + pc++ + } + + bm.stack.Print() +} diff --git a/stack.go b/stack.go new file mode 100644 index 000000000..2b2f075cf --- /dev/null +++ b/stack.go @@ -0,0 +1,164 @@ +package main + +import ( + "fmt" + "github.com/ethereum/ethutil-go" + "math/big" +) + +type OpCode byte + +// Op codes +const ( + oSTOP OpCode = 0x00 + oADD OpCode = 0x01 + oMUL OpCode = 0x02 + oSUB OpCode = 0x03 + oDIV OpCode = 0x04 + oSDIV OpCode = 0x05 + oMOD OpCode = 0x06 + oSMOD OpCode = 0x07 + oEXP OpCode = 0x08 + oNEG OpCode = 0x09 + oLT OpCode = 0x0a + oLE OpCode = 0x0b + oGT OpCode = 0x0c + oGE OpCode = 0x0d + oEQ OpCode = 0x0e + oNOT OpCode = 0x0f + oMYADDRESS OpCode = 0x10 + oTXSENDER OpCode = 0x11 + oTXVALUE OpCode = 0x12 + oTXFEE OpCode = 0x13 + oTXDATAN OpCode = 0x14 + oTXDATA OpCode = 0x15 + oBLK_PREVHASH OpCode = 0x16 + oBLK_COINBASE OpCode = 0x17 + oBLK_TIMESTAMP OpCode = 0x18 + oBLK_NUMBER OpCode = 0x19 + oBLK_DIFFICULTY OpCode = 0x1a + oSHA256 OpCode = 0x20 + oRIPEMD160 OpCode = 0x21 + oECMUL OpCode = 0x22 + oECADD OpCode = 0x23 + oECSIGN OpCode = 0x24 + oECRECOVER OpCode = 0x25 + oECVALID OpCode = 0x26 + oPUSH OpCode = 0x30 + oPOP OpCode = 0x31 + oDUP OpCode = 0x32 + oDUPN OpCode = 0x33 + oSWAP OpCode = 0x34 + oSWAPN OpCode = 0x35 + oLOAD OpCode = 0x36 + oSTORE OpCode = 0x37 + oJMP OpCode = 0x40 + oJMPI OpCode = 0x41 + oIND OpCode = 0x42 + oEXTRO OpCode = 0x50 + oBALANCE OpCode = 0x51 + oMKTX OpCode = 0x60 + oSUICIDE OpCode = 0xff +) + +// Since the opcodes aren't all in order we can't use a regular slice +var opCodeToString = map[OpCode]string{ + oSTOP: "STOP", + oADD: "ADD", + oMUL: "MUL", + oSUB: "SUB", + oDIV: "DIV", + oSDIV: "SDIV", + oMOD: "MOD", + oSMOD: "SMOD", + oEXP: "EXP", + oNEG: "NEG", + oLT: "LT", + oLE: "LE", + oGT: "GT", + oGE: "GE", + oEQ: "EQ", + oNOT: "NOT", + oMYADDRESS: "MYADDRESS", + oTXSENDER: "TXSENDER", + oTXVALUE: "TXVALUE", + oTXFEE: "TXFEE", + oTXDATAN: "TXDATAN", + oTXDATA: "TXDATA", + oBLK_PREVHASH: "BLK_PREVHASH", + oBLK_COINBASE: "BLK_COINBASE", + oBLK_TIMESTAMP: "BLK_TIMESTAMP", + oBLK_NUMBER: "BLK_NUMBER", + oBLK_DIFFICULTY: "BLK_DIFFIFULTY", + oSHA256: "SHA256", + oRIPEMD160: "RIPEMD160", + oECMUL: "ECMUL", + oECADD: "ECADD", + oECSIGN: "ECSIGN", + oECRECOVER: "ECRECOVER", + oECVALID: "ECVALID", + oPUSH: "PUSH", + oPOP: "POP", + oDUP: "DUP", + oDUPN: "DUPN", + oSWAP: "SWAP", + oSWAPN: "SWAPN", + oLOAD: "LOAD", + oSTORE: "STORE", + oJMP: "JMP", + oJMPI: "JMPI", + oIND: "IND", + oEXTRO: "EXTRO", + oBALANCE: "BALANCE", + oMKTX: "MKTX", + oSUICIDE: "SUICIDE", +} + +func (o OpCode) String() string { + return opCodeToString[o] +} + +type OpType int + +const ( + tNorm = iota + tData + tExtro + tCrypto +) + +type TxCallback func(opType OpType) bool + +// Simple push/pop stack mechanism +type Stack struct { + data []string +} + +func NewStack() *Stack { + return &Stack{} +} + +func (st *Stack) Pop() string { + s := len(st.data) + + str := st.data[s-1] + st.data = st.data[:s-1] + + return str +} + +func (st *Stack) Popn() (*big.Int, *big.Int) { + s := len(st.data) + + strs := st.data[s-2:] + st.data = st.data[:s-2] + + return ethutil.Big(strs[0]), ethutil.Big(strs[1]) +} + +func (st *Stack) Push(d string) { + st.data = append(st.data, d) +} +func (st *Stack) Print() { + fmt.Println(st.data) +} -- cgit v1.2.3 From 33004d704e4a77368e5b2b415cfee3f05a3b469f Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 18:12:31 +0100 Subject: Moved vm test --- block_manager_test.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 block_manager_test.go diff --git a/block_manager_test.go b/block_manager_test.go new file mode 100644 index 000000000..ca5e4140e --- /dev/null +++ b/block_manager_test.go @@ -0,0 +1,77 @@ +package main + +/* +import ( + _"fmt" + "testing" +) + + +/* +func TestVm(t *testing.T) { + InitFees() + + db, _ := NewMemDatabase() + Db = db + + ctrct := NewTransaction("", 200000000, []string{ + "PUSH", "1a2f2e", + "PUSH", "hallo", + "POP", // POP hallo + "PUSH", "3", + "LOAD", // Load hallo back on the stack + + "PUSH", "1", + "PUSH", "2", + "ADD", + + "PUSH", "2", + "PUSH", "1", + "SUB", + + "PUSH", "100000000000000000000000", + "PUSH", "10000000000000", + "SDIV", + + "PUSH", "105", + "PUSH", "200", + "MOD", + + "PUSH", "100000000000000000000000", + "PUSH", "10000000000000", + "SMOD", + + "PUSH", "5", + "PUSH", "10", + "LT", + + "PUSH", "5", + "PUSH", "5", + "LE", + + "PUSH", "50", + "PUSH", "5", + "GT", + + "PUSH", "5", + "PUSH", "5", + "GE", + + "PUSH", "10", + "PUSH", "10", + "NOT", + + "MYADDRESS", + "TXSENDER", + + "STOP", + }) + tx := NewTransaction("1e8a42ea8cce13", 100, []string{}) + + block := CreateBlock("", 0, "", "c014ba53", 0, 0, "", []*Transaction{ctrct, tx}) + db.Put(block.Hash(), block.MarshalRlp()) + + bm := NewBlockManager() + bm.ProcessBlock( block ) +} +*/ -- cgit v1.2.3 From 8c4eca2490fc19a488ffb5fe1160a2ae695d018f Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 18:12:55 +0100 Subject: Moved the vm code the block manager and added more opcodes --- block_manager.go | 93 +++++++++++++++--- vm.go | 284 ------------------------------------------------------- vm_test.go | 76 --------------- 3 files changed, 78 insertions(+), 375 deletions(-) delete mode 100644 vm.go delete mode 100644 vm_test.go diff --git a/block_manager.go b/block_manager.go index 7b2d01f29..4ddc3952b 100644 --- a/block_manager.go +++ b/block_manager.go @@ -6,14 +6,14 @@ import ( "github.com/ethereum/ethutil-go" "log" "math/big" - "strconv" ) type BlockChain struct { + // Last block LastBlock *ethutil.Block - + // The famous, the fabulous Mister GENESIIIIIIS (block) genesisBlock *ethutil.Block - + // Last known total difficulty TD *big.Int } @@ -22,11 +22,10 @@ func NewBlockChain() *BlockChain { bc.genesisBlock = ethutil.NewBlock(ethutil.Encode(ethutil.Genesis)) // Set the last know difficulty (might be 0x0 as initial value, Genesis) - bc.TD = new(big.Int) - bc.TD.SetBytes(ethutil.Config.Db.LastKnownTD()) + bc.TD = ethutil.BigD(ethutil.Config.Db.LastKnownTD()) // TODO get last block from the database - //bc.LastBlock = bc.genesisBlock + bc.LastBlock = bc.genesisBlock return bc } @@ -46,6 +45,9 @@ type BlockManager struct { // Stack for processing contracts stack *Stack + + // Last known block number + LastBlockNumber *big.Int } func NewBlockManager() *BlockManager { @@ -54,6 +56,10 @@ func NewBlockManager() *BlockManager { stack: NewStack(), } + // Set the last known block number based on the blockchains last + // block + bm.LastBlockNumber = bm.BlockInfo(bm.bc.LastBlock).Number + return bm } @@ -99,6 +105,20 @@ func (bm *BlockManager) ProcessBlock(block *ethutil.Block) error { return nil } +func (bm *BlockManager) writeBlockInfo(block *ethutil.Block) { + bi := ethutil.BlockInfo{Number: bm.LastBlockNumber.Add(bm.LastBlockNumber, big.NewInt(1))} + + ethutil.Config.Db.Put(append(block.Hash(), []byte("Info")...), bi.MarshalRlp()) +} + +func (bm *BlockManager) BlockInfo(block *ethutil.Block) ethutil.BlockInfo { + bi := ethutil.BlockInfo{} + data, _ := ethutil.Config.Db.Get(append(block.Hash(), []byte("Info")...)) + bi.UnmarshalRlp(data) + + return bi +} + func (bm *BlockManager) CalculateTD(block *ethutil.Block) bool { uncleDiff := new(big.Int) for _, uncle := range block.Uncles { @@ -204,10 +224,11 @@ func (bm *BlockManager) ProcessContract(tx *ethutil.Transaction, block *ethutil. lockChan <- true } -func (bm *BlockManager) ProcContract(tx *ethutil.Transaction, - block *ethutil.Block, cb TxCallback) { +// Contract evaluation is done here. +func (bm *BlockManager) ProcContract(tx *ethutil.Transaction, block *ethutil.Block, cb TxCallback) { // Instruction pointer pc := 0 + blockInfo := bm.BlockInfo(block) contract := block.GetContract(tx.Hash()) if contract == nil { @@ -238,6 +259,8 @@ out: } switch op { + case oSTOP: + break out case oADD: x, y := bm.stack.Popn() // (x + y) % 2 ** 256 @@ -378,7 +401,34 @@ out: case oBLK_TIMESTAMP: bm.stack.Push(big.NewInt(block.Time).String()) case oBLK_NUMBER: - + bm.stack.Push(blockInfo.Number.String()) + case oBLK_DIFFICULTY: + bm.stack.Push(block.Difficulty.String()) + case oBASEFEE: + // e = 10^21 + e := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(21), big.NewInt(0)) + d := new(big.Rat) + d.SetInt(block.Difficulty) + c := new(big.Rat) + c.SetFloat64(0.5) + // d = diff / 0.5 + d.Quo(d, c) + // base = floor(d) + base.Div(d.Num(), d.Denom()) + + x := new(big.Int) + x.Div(e, base) + + // x = floor(10^21 / floor(diff^0.5)) + bm.stack.Push(x.String()) + case oSHA256: + case oRIPEMD160: + case oECMUL: + case oECADD: + case oECSIGN: + case oECRECOVER: + case oECVALID: + case oSHA3: case oPUSH: // Get the next entry and pushes the value on the stack pc++ @@ -386,12 +436,25 @@ out: case oPOP: // Pop current value of the stack bm.stack.Pop() - case oLOAD: - // Load instruction X on the stack - i, _ := strconv.Atoi(bm.stack.Pop()) - bm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32)))) - case oSTOP: - break out + case oDUP: + case oSWAP: + case oMLOAD: + case oMSTORE: + case oSLOAD: + case oSSTORE: + case oJMP: + case oJMPI: + case oIND: + case oEXTRO: + case oBALANCE: + case oMKTX: + case oSUICIDE: + /* + case oLOAD: + // Load instruction X on the stack + i, _ := strconv.Atoi(bm.stack.Pop()) + bm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32)))) + */ } pc++ } diff --git a/vm.go b/vm.go deleted file mode 100644 index 96a3dfa05..000000000 --- a/vm.go +++ /dev/null @@ -1,284 +0,0 @@ -package main - -import ( - "fmt" - "github.com/ethereum/ethutil-go" - "math/big" - "strconv" -) - -// Op codes -const ( - oSTOP int = 0x00 - oADD int = 0x01 - oMUL int = 0x02 - oSUB int = 0x03 - oDIV int = 0x04 - oSDIV int = 0x05 - oMOD int = 0x06 - oSMOD int = 0x07 - oEXP int = 0x08 - oNEG int = 0x09 - oLT int = 0x0a - oLE int = 0x0b - oGT int = 0x0c - oGE int = 0x0d - oEQ int = 0x0e - oNOT int = 0x0f - oMYADDRESS int = 0x10 - oTXSENDER int = 0x11 - oTXVALUE int = 0x12 - oTXFEE int = 0x13 - oTXDATAN int = 0x14 - oTXDATA int = 0x15 - oBLK_PREVHASH int = 0x16 - oBLK_COINBASE int = 0x17 - oBLK_TIMESTAMP int = 0x18 - oBLK_NUMBER int = 0x19 - oBLK_DIFFICULTY int = 0x1a - oSHA256 int = 0x20 - oRIPEMD160 int = 0x21 - oECMUL int = 0x22 - oECADD int = 0x23 - oECSIGN int = 0x24 - oECRECOVER int = 0x25 - oECVALID int = 0x26 - oPUSH int = 0x30 - oPOP int = 0x31 - oDUP int = 0x32 - oDUPN int = 0x33 - oSWAP int = 0x34 - oSWAPN int = 0x35 - oLOAD int = 0x36 - oSTORE int = 0x37 - oJMP int = 0x40 - oJMPI int = 0x41 - oIND int = 0x42 - oEXTRO int = 0x50 - oBALANCE int = 0x51 - oMKTX int = 0x60 - oSUICIDE int = 0xff -) - -type OpType int - -const ( - tNorm = iota - tData - tExtro - tCrypto -) - -type TxCallback func(opType OpType) bool - -// Simple push/pop stack mechanism -type Stack struct { - data []string -} - -func NewStack() *Stack { - return &Stack{} -} -func (st *Stack) Pop() string { - s := len(st.data) - - str := st.data[s-1] - st.data = st.data[:s-1] - - return str -} - -func (st *Stack) Popn() (*big.Int, *big.Int) { - s := len(st.data) - - strs := st.data[s-2:] - st.data = st.data[:s-2] - - return ethutil.Big(strs[0]), ethutil.Big(strs[1]) -} - -func (st *Stack) Push(d string) { - st.data = append(st.data, d) -} -func (st *Stack) Print() { - fmt.Println(st.data) -} - -type Vm struct { - // Stack - stack *Stack -} - -func NewVm() *Vm { - return &Vm{ - stack: NewStack(), - } -} - -func (vm *Vm) ProcContract(tx *ethutil.Transaction, - block *ethutil.Block, cb TxCallback) { - // Instruction pointer - pc := 0 - - contract := block.GetContract(tx.Hash()) - if contract == nil { - fmt.Println("Contract not found") - return - } - - Pow256 := ethutil.BigPow(2, 256) - - //fmt.Printf("# op arg\n") -out: - for { - // The base big int for all calculations. Use this for any results. - base := new(big.Int) - // XXX Should Instr return big int slice instead of string slice? - // Get the next instruction from the contract - //op, _, _ := Instr(contract.state.Get(string(Encode(uint32(pc))))) - nb := ethutil.NumberToBytes(uint64(pc), 32) - op, _, _ := ethutil.Instr(contract.State().Get(string(nb))) - - if !cb(0) { - break - } - - if Debug { - //fmt.Printf("%-3d %-4d\n", pc, op) - } - - switch op { - case oADD: - x, y := vm.stack.Popn() - // (x + y) % 2 ** 256 - base.Add(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - vm.stack.Push(base.String()) - case oSUB: - x, y := vm.stack.Popn() - // (x - y) % 2 ** 256 - base.Sub(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - vm.stack.Push(base.String()) - case oMUL: - x, y := vm.stack.Popn() - // (x * y) % 2 ** 256 - base.Mul(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - vm.stack.Push(base.String()) - case oDIV: - x, y := vm.stack.Popn() - // floor(x / y) - base.Div(x, y) - // Pop result back on the stack - vm.stack.Push(base.String()) - case oSDIV: - x, y := vm.stack.Popn() - // n > 2**255 - if x.Cmp(Pow256) > 0 { - x.Sub(Pow256, x) - } - if y.Cmp(Pow256) > 0 { - y.Sub(Pow256, y) - } - z := new(big.Int) - z.Div(x, y) - if z.Cmp(Pow256) > 0 { - z.Sub(Pow256, z) - } - // Push result on to the stack - vm.stack.Push(z.String()) - case oMOD: - x, y := vm.stack.Popn() - base.Mod(x, y) - vm.stack.Push(base.String()) - case oSMOD: - x, y := vm.stack.Popn() - // n > 2**255 - if x.Cmp(Pow256) > 0 { - x.Sub(Pow256, x) - } - if y.Cmp(Pow256) > 0 { - y.Sub(Pow256, y) - } - z := new(big.Int) - z.Mod(x, y) - if z.Cmp(Pow256) > 0 { - z.Sub(Pow256, z) - } - // Push result on to the stack - vm.stack.Push(z.String()) - case oEXP: - x, y := vm.stack.Popn() - base.Exp(x, y, Pow256) - - vm.stack.Push(base.String()) - case oNEG: - base.Sub(Pow256, ethutil.Big(vm.stack.Pop())) - vm.stack.Push(base.String()) - case oLT: - x, y := vm.stack.Popn() - // x < y - if x.Cmp(y) < 0 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oLE: - x, y := vm.stack.Popn() - // x <= y - if x.Cmp(y) < 1 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oGT: - x, y := vm.stack.Popn() - // x > y - if x.Cmp(y) > 0 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oGE: - x, y := vm.stack.Popn() - // x >= y - if x.Cmp(y) > -1 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oNOT: - x, y := vm.stack.Popn() - // x != y - if x.Cmp(y) != 0 { - vm.stack.Push("1") - } else { - vm.stack.Push("0") - } - case oMYADDRESS: - vm.stack.Push(string(tx.Hash())) - case oTXSENDER: - vm.stack.Push(string(tx.Sender())) - case oPUSH: - // Get the next entry and pushes the value on the stack - pc++ - vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(pc), 32)))) - case oPOP: - // Pop current value of the stack - vm.stack.Pop() - case oLOAD: - // Load instruction X on the stack - i, _ := strconv.Atoi(vm.stack.Pop()) - vm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32)))) - case oSTOP: - break out - } - pc++ - } - - vm.stack.Print() -} diff --git a/vm_test.go b/vm_test.go deleted file mode 100644 index cb70220e1..000000000 --- a/vm_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -/* -import ( - _"fmt" - "testing" -) - - -func TestVm(t *testing.T) { - InitFees() - - db, _ := NewMemDatabase() - Db = db - - ctrct := NewTransaction("", 200000000, []string{ - "PUSH", "1a2f2e", - "PUSH", "hallo", - "POP", // POP hallo - "PUSH", "3", - "LOAD", // Load hallo back on the stack - - "PUSH", "1", - "PUSH", "2", - "ADD", - - "PUSH", "2", - "PUSH", "1", - "SUB", - - "PUSH", "100000000000000000000000", - "PUSH", "10000000000000", - "SDIV", - - "PUSH", "105", - "PUSH", "200", - "MOD", - - "PUSH", "100000000000000000000000", - "PUSH", "10000000000000", - "SMOD", - - "PUSH", "5", - "PUSH", "10", - "LT", - - "PUSH", "5", - "PUSH", "5", - "LE", - - "PUSH", "50", - "PUSH", "5", - "GT", - - "PUSH", "5", - "PUSH", "5", - "GE", - - "PUSH", "10", - "PUSH", "10", - "NOT", - - "MYADDRESS", - "TXSENDER", - - "STOP", - }) - tx := NewTransaction("1e8a42ea8cce13", 100, []string{}) - - block := CreateBlock("", 0, "", "c014ba53", 0, 0, "", []*Transaction{ctrct, tx}) - db.Put(block.Hash(), block.MarshalRlp()) - - bm := NewBlockManager() - bm.ProcessBlock( block ) -} -*/ -- cgit v1.2.3 From fd7e79f4e3604435d87b21349c8dd31052ef8a78 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 18:13:17 +0100 Subject: Added stack and opcodes to a separate file --- stack.go | 114 +++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/stack.go b/stack.go index 2b2f075cf..cee98555b 100644 --- a/stack.go +++ b/stack.go @@ -6,59 +6,61 @@ import ( "math/big" ) -type OpCode byte +type OpCode int // Op codes const ( - oSTOP OpCode = 0x00 - oADD OpCode = 0x01 - oMUL OpCode = 0x02 - oSUB OpCode = 0x03 - oDIV OpCode = 0x04 - oSDIV OpCode = 0x05 - oMOD OpCode = 0x06 - oSMOD OpCode = 0x07 - oEXP OpCode = 0x08 - oNEG OpCode = 0x09 - oLT OpCode = 0x0a - oLE OpCode = 0x0b - oGT OpCode = 0x0c - oGE OpCode = 0x0d - oEQ OpCode = 0x0e - oNOT OpCode = 0x0f - oMYADDRESS OpCode = 0x10 - oTXSENDER OpCode = 0x11 - oTXVALUE OpCode = 0x12 - oTXFEE OpCode = 0x13 - oTXDATAN OpCode = 0x14 - oTXDATA OpCode = 0x15 - oBLK_PREVHASH OpCode = 0x16 - oBLK_COINBASE OpCode = 0x17 - oBLK_TIMESTAMP OpCode = 0x18 - oBLK_NUMBER OpCode = 0x19 - oBLK_DIFFICULTY OpCode = 0x1a - oSHA256 OpCode = 0x20 - oRIPEMD160 OpCode = 0x21 - oECMUL OpCode = 0x22 - oECADD OpCode = 0x23 - oECSIGN OpCode = 0x24 - oECRECOVER OpCode = 0x25 - oECVALID OpCode = 0x26 - oPUSH OpCode = 0x30 - oPOP OpCode = 0x31 - oDUP OpCode = 0x32 - oDUPN OpCode = 0x33 - oSWAP OpCode = 0x34 - oSWAPN OpCode = 0x35 - oLOAD OpCode = 0x36 - oSTORE OpCode = 0x37 - oJMP OpCode = 0x40 - oJMPI OpCode = 0x41 - oIND OpCode = 0x42 - oEXTRO OpCode = 0x50 - oBALANCE OpCode = 0x51 - oMKTX OpCode = 0x60 - oSUICIDE OpCode = 0xff + oSTOP OpCode = iota + oADD + oMUL + oSUB + oDIV + oSDIV + oMOD + oSMOD + oEXP + oNEG + oLT + oLE + oGT + oGE + oEQ + oNOT + oMYADDRESS + oTXSENDER + oTXVALUE + oTXFEE + oTXDATAN + oTXDATA + oBLK_PREVHASH + oBLK_COINBASE + oBLK_TIMESTAMP + oBLK_NUMBER + oBLK_DIFFICULTY + oBASEFEE + oSHA256 OpCode = 32 + oRIPEMD160 OpCode = 33 + oECMUL OpCode = 34 + oECADD OpCode = 35 + oECSIGN OpCode = 36 + oECRECOVER OpCode = 37 + oECVALID OpCode = 38 + oSHA3 OpCode = 39 + oPUSH OpCode = 48 + oPOP OpCode = 49 + oDUP OpCode = 50 + oSWAP OpCode = 51 + oMLOAD OpCode = 52 + oMSTORE OpCode = 53 + oSLOAD OpCode = 54 + oSSTORE OpCode = 55 + oJMP OpCode = 56 + oJMPI OpCode = 57 + oIND OpCode = 58 + oEXTRO OpCode = 59 + oBALANCE OpCode = 60 + oMKTX OpCode = 61 + oSUICIDE OpCode = 62 ) // Since the opcodes aren't all in order we can't use a regular slice @@ -89,7 +91,8 @@ var opCodeToString = map[OpCode]string{ oBLK_COINBASE: "BLK_COINBASE", oBLK_TIMESTAMP: "BLK_TIMESTAMP", oBLK_NUMBER: "BLK_NUMBER", - oBLK_DIFFICULTY: "BLK_DIFFIFULTY", + oBLK_DIFFICULTY: "BLK_DIFFICULTY", + oBASEFEE: "BASEFEE", oSHA256: "SHA256", oRIPEMD160: "RIPEMD160", oECMUL: "ECMUL", @@ -97,14 +100,15 @@ var opCodeToString = map[OpCode]string{ oECSIGN: "ECSIGN", oECRECOVER: "ECRECOVER", oECVALID: "ECVALID", + oSHA3: "SHA3", oPUSH: "PUSH", oPOP: "POP", oDUP: "DUP", - oDUPN: "DUPN", oSWAP: "SWAP", - oSWAPN: "SWAPN", - oLOAD: "LOAD", - oSTORE: "STORE", + oMLOAD: "MLOAD", + oMSTORE: "MSTORE", + oSLOAD: "SLOAD", + oSSTORE: "SSTORE", oJMP: "JMP", oJMPI: "JMPI", oIND: "IND", -- cgit v1.2.3 From 815313c759acaf7cf2e295a61fa58d5ccdb0d126 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 16 Jan 2014 21:32:46 +0100 Subject: Added more opcodes --- block_manager.go | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/block_manager.go b/block_manager.go index 4ddc3952b..ce852612a 100644 --- a/block_manager.go +++ b/block_manager.go @@ -1,10 +1,13 @@ package main import ( + "bytes" "errors" "fmt" "github.com/ethereum/ethutil-go" + "github.com/obscuren/secp256-go" "log" + "math" "math/big" ) @@ -421,9 +424,40 @@ out: // x = floor(10^21 / floor(diff^0.5)) bm.stack.Push(x.String()) - case oSHA256: - case oRIPEMD160: + case oSHA256, oRIPEMD160: + // This is probably save + // ceil(pop / 32) + length := int(math.Ceil(float64(ethutil.Big(bm.stack.Pop()).Uint64()) / 32.0)) + // New buffer which will contain the concatenated popped items + data := new(bytes.Buffer) + for i := 0; i < length; i++ { + // Encode the number to bytes and have it 32bytes long + num := ethutil.NumberToBytes(ethutil.Big(bm.stack.Pop()).Bytes(), 256) + data.WriteString(string(num)) + } + + if op == oSHA256 { + bm.stack.Push(base.SetBytes(ethutil.Sha256Bin(data.Bytes())).String()) + } else { + bm.stack.Push(base.SetBytes(ethutil.Ripemd160(data.Bytes())).String()) + } case oECMUL: + y := bm.stack.Pop() + x := bm.stack.Pop() + n := bm.stack.Pop() + + if ethutil.Big(x).Cmp(ethutil.Big(y)) + data := new(bytes.Buffer) + data.WriteString(x) + data.WriteString(y) + if secp256.VerifyPubkeyValidity(data.Bytes()) == 1 { + // TODO + } else { + // Invalid, push infinity + bm.stack.Push("0") + bm.stack.Push("0") + } + case oECADD: case oECSIGN: case oECRECOVER: -- cgit v1.2.3 From df7967c5bbcdab3e37b198098b8fb9534979e42f Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 17 Jan 2014 16:57:06 +0100 Subject: ECmul tmp --- block_manager.go | 33 ++++++++++++++++++++++----------- block_manager_test.go | 2 +- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/block_manager.go b/block_manager.go index ce852612a..0831111e3 100644 --- a/block_manager.go +++ b/block_manager.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "github.com/ethereum/ethutil-go" - "github.com/obscuren/secp256-go" + "github.com/obscuren/secp256k1-go" "log" "math" "math/big" @@ -46,17 +46,20 @@ type BlockManager struct { // The block chain :) bc *BlockChain - // Stack for processing contracts - stack *Stack - // Last known block number LastBlockNumber *big.Int + + // Stack for processing contracts + stack *Stack + // non-persistent key/value memory storage + mem map[string]string } func NewBlockManager() *BlockManager { bm := &BlockManager{ bc: NewBlockChain(), stack: NewStack(), + mem: make(map[string]string), } // Set the last known block number based on the blockchains last @@ -100,24 +103,27 @@ func (bm *BlockManager) ProcessBlock(block *ethutil.Block) error { <-lockChan } + // Calculate the new total difficulty and sync back to the db if bm.CalculateTD(block) { - ethutil.Config.Db.Put(block.Hash(), block.MarshalRlp()) + ethutil.Config.Db.Put(block.Hash(), block.RlpEncode()) bm.bc.LastBlock = block } return nil } +// Unexported method for writing extra non-essential block info to the db func (bm *BlockManager) writeBlockInfo(block *ethutil.Block) { bi := ethutil.BlockInfo{Number: bm.LastBlockNumber.Add(bm.LastBlockNumber, big.NewInt(1))} - ethutil.Config.Db.Put(append(block.Hash(), []byte("Info")...), bi.MarshalRlp()) + // For now we use the block hash with the words "info" appended as key + ethutil.Config.Db.Put(append(block.Hash(), []byte("Info")...), bi.RlpEncode()) } func (bm *BlockManager) BlockInfo(block *ethutil.Block) ethutil.BlockInfo { bi := ethutil.BlockInfo{} data, _ := ethutil.Config.Db.Get(append(block.Hash(), []byte("Info")...)) - bi.UnmarshalRlp(data) + bi.RlpDecode(data) return bi } @@ -196,7 +202,7 @@ func (bm *BlockManager) AccumelateRewards(block *ethutil.Block) error { // Reward amount of ether to the coinbase address ether.AddFee(ethutil.CalculateBlockReward(block, len(block.Uncles))) - block.State().Update(block.Coinbase, string(ether.MarshalRlp())) + block.State().Update(block.Coinbase, string(ether.RlpEncode())) // TODO Reward each uncle @@ -444,19 +450,24 @@ out: case oECMUL: y := bm.stack.Pop() x := bm.stack.Pop() - n := bm.stack.Pop() + //n := bm.stack.Pop() - if ethutil.Big(x).Cmp(ethutil.Big(y)) + //if ethutil.Big(x).Cmp(ethutil.Big(y)) { data := new(bytes.Buffer) data.WriteString(x) data.WriteString(y) - if secp256.VerifyPubkeyValidity(data.Bytes()) == 1 { + if secp256k1.VerifyPubkeyValidity(data.Bytes()) == 1 { // TODO } else { // Invalid, push infinity bm.stack.Push("0") bm.stack.Push("0") } + //} else { + // // Invalid, push infinity + // bm.stack.Push("0") + // bm.stack.Push("0") + //} case oECADD: case oECSIGN: diff --git a/block_manager_test.go b/block_manager_test.go index ca5e4140e..3dcf572fd 100644 --- a/block_manager_test.go +++ b/block_manager_test.go @@ -69,7 +69,7 @@ func TestVm(t *testing.T) { tx := NewTransaction("1e8a42ea8cce13", 100, []string{}) block := CreateBlock("", 0, "", "c014ba53", 0, 0, "", []*Transaction{ctrct, tx}) - db.Put(block.Hash(), block.MarshalRlp()) + db.Put(block.Hash(), block.RlpEncode()) bm := NewBlockManager() bm.ProcessBlock( block ) -- cgit v1.2.3 From aed060a4ce59a5116ad774c2d8c0fbf857109287 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 17 Jan 2014 16:57:18 +0100 Subject: Updated the wire protocol --- peer.go | 61 ++++++++++++++++++++++++++++++++++++++++--------------------- server.go | 23 +++++++++++------------ 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/peer.go b/peer.go index 8f68a9bec..d876205e2 100644 --- a/peer.go +++ b/peer.go @@ -11,7 +11,7 @@ import ( const ( // The size of the output buffer for writing messages - outputBufferSize = 50 + outputBufferSize = 50 ) type Peer struct { @@ -26,7 +26,7 @@ type Peer struct { // Determines whether it's an inbound or outbound peer inbound bool // Flag for checking the peer's connectivity state - connected int32 + connected int32 disconnect int32 // Last known message send lastSend time.Time @@ -90,8 +90,8 @@ func (p *Peer) writeMessage(msg *ethwire.InOutMsg) { } if !p.versionKnown { - switch msg.MsgType { - case "verack": // Ok + switch msg.Type { + case ethwire.MsgHandshakeTy: // Ok default: // Anything but ack is allowed return } @@ -108,6 +108,8 @@ func (p *Peer) writeMessage(msg *ethwire.InOutMsg) { // Outbound message handler. Outbound messages are handled here func (p *Peer) HandleOutbound() { + // The ping timer. Makes sure that every 2 minutes a ping is send to the peer + tickleTimer := time.NewTimer(2 * time.Minute) out: for { select { @@ -116,6 +118,10 @@ out: p.writeMessage(msg) p.lastSend = time.Now() + + case <-tickleTimer.C: + p.writeMessage(ðwire.InOutMsg{Type: ethwire.MsgPingTy}) + // Break out of the for loop if a quit message is posted case <-p.quit: break out @@ -126,7 +132,7 @@ clean: // This loop is for draining the output queue and anybody waiting for us for { select { - case <- p.outputQueue: + case <-p.outputQueue: // TODO default: break clean @@ -148,23 +154,32 @@ out: } if Debug { - log.Printf("Received %s\n", msg.MsgType) + log.Printf("Received %s\n", msg.Type.String()) } // TODO Hash data and check if for existence (= ignore) - switch msg.MsgType { - case "verack": + switch msg.Type { + case ethwire.MsgHandshakeTy: // Version message - p.handleVersionAck(msg) - case "block": - err := p.server.blockManager.ProcessBlock(ethutil.NewBlock(msg.Data)) + p.handleHandshake(msg) + case ethwire.MsgBlockTy: + err := p.server.blockManager.ProcessBlock(ethutil.NewBlock(ethutil.Encode(msg.Data))) if err != nil { log.Println(err) } - case "blockmine": - d, _ := ethutil.Decode(msg.Data, 0) - log.Printf("block mined %s\n", d) + case ethwire.MsgTxTy: + case ethwire.MsgInvTy: + case ethwire.MsgGetPeersTy: + case ethwire.MsgPeersTy: + case ethwire.MsgPingTy: + case ethwire.MsgPongTy: + + /* + case "blockmine": + d, _ := ethutil.Decode(msg.Data, 0) + log.Printf("block mined %s\n", d) + */ } } @@ -173,7 +188,7 @@ out: func (p *Peer) Start() { if !p.inbound { - err := p.pushVersionAck() + err := p.pushHandshake() if err != nil { log.Printf("Peer can't send outbound version ack", err) @@ -200,17 +215,21 @@ func (p *Peer) Stop() { log.Println("Peer shutdown") } -func (p *Peer) pushVersionAck() error { - msg := ethwire.NewMessage("verack", p.server.Nonce, []byte("01")) +func (p *Peer) pushHandshake() error { + msg := ethwire.NewMessage(ethwire.MsgHandshakeTy, ethutil.Encode([]interface{}{ + 1, 0, p.server.Nonce, + })) p.QueueMessage(msg) return nil } -func (p *Peer) handleVersionAck(msg *ethwire.InOutMsg) { - // Detect self connect - if msg.Nonce == p.server.Nonce { +func (p *Peer) handleHandshake(msg *ethwire.InOutMsg) { + c := ethutil.Conv(msg.Data) + // [PROTOCOL_VERSION, NETWORK_ID, CLIENT_ID] + if c.Get(2).AsUint() == p.server.Nonce { + //if msg.Nonce == p.server.Nonce { log.Println("Peer connected to self, disconnecting") p.Stop() @@ -222,7 +241,7 @@ func (p *Peer) handleVersionAck(msg *ethwire.InOutMsg) { // If this is an inbound connection send an ack back if p.inbound { - err := p.pushVersionAck() + err := p.pushHandshake() if err != nil { log.Println("Peer can't send ack back") diff --git a/server.go b/server.go index f658750a9..f17cc32a0 100644 --- a/server.go +++ b/server.go @@ -7,8 +7,8 @@ import ( "github.com/ethereum/ethwire-go" "log" "net" - "time" "sync/atomic" + "time" ) func eachPeer(peers *list.List, callback func(*Peer, *list.Element)) { @@ -20,7 +20,6 @@ func eachPeer(peers *list.List, callback func(*Peer, *list.Element)) { } } - type Server struct { // Channel for shutting down the server shutdownChan chan bool @@ -75,14 +74,14 @@ func (s *Server) ConnectToPeer(addr string) error { return nil } -func (s *Server) Broadcast(msgType string, data []byte) { +func (s *Server) Broadcast(msgType ethwire.MsgType, data []byte) { eachPeer(s.peers, func(p *Peer, e *list.Element) { - p.QueueMessage(ethwire.NewMessage(msgType, 0, data)) + p.QueueMessage(ethwire.NewMessage(msgType, data)) }) } const ( - processReapingTimeout = 10 // TODO increase + processReapingTimeout = 10 // TODO increase ) func (s *Server) ReapDeadPeers() { @@ -139,13 +138,13 @@ func (s *Server) Start() { // TMP /* - go func() { - for { - s.Broadcast("block", s.blockManager.bc.GenesisBlock().MarshalRlp()) + go func() { + for { + s.Broadcast("block", s.blockManager.bc.GenesisBlock().RlpEncode()) - time.Sleep(1000 * time.Millisecond) - } - }() + time.Sleep(1000 * time.Millisecond) + } + }() */ } @@ -154,7 +153,7 @@ func (s *Server) Stop() { defer s.db.Close() eachPeer(s.peers, func(p *Peer, e *list.Element) { - p.Stop() + p.Stop() }) s.shutdownChan <- true -- cgit v1.2.3 From ddf82c333702e22d3cd7e5c693ad0603089a57a4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 17 Jan 2014 16:57:42 +0100 Subject: Removed dagger broadcasting to the net --- ethereum.go | 3 ++- testing.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ethereum.go b/ethereum.go index 83f656fe2..d74cb4ff2 100644 --- a/ethereum.go +++ b/ethereum.go @@ -65,7 +65,8 @@ func main() { go func() { for { res := dagger.Search(ethutil.Big("01001"), ethutil.BigPow(2, 36)) - server.Broadcast("blockmine", ethutil.Encode(res.String())) + log.Println("Res dagger", res) + //server.Broadcast("blockmine", ethutil.Encode(res.String())) } }() } diff --git a/testing.go b/testing.go index 5e2aec02b..849089a5d 100644 --- a/testing.go +++ b/testing.go @@ -16,11 +16,11 @@ func Testing() { bm := NewBlockManager() tx := NewTransaction("\x00", 20, []string{"PUSH"}) - txData := tx.MarshalRlp() + txData := tx.RlpEncode() //fmt.Printf("%q\n", txData) copyTx := &Transaction{} - copyTx.UnmarshalRlp(txData) + copyTx.RlpDecode(txData) //fmt.Println(tx) //fmt.Println(copyTx) -- cgit v1.2.3 From 87434a09418e6cc2dd4b92d7e35afc6f155994bc Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 17 Jan 2014 17:14:59 +0100 Subject: Ping pong message --- peer.go | 8 +++++++- server.go | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/peer.go b/peer.go index d876205e2..76a810063 100644 --- a/peer.go +++ b/peer.go @@ -34,6 +34,9 @@ type Peer struct { // This flag is used by writeMessage to check if messages are allowed // to be send or not. If no version is known all messages are ignored. versionKnown bool + + // Last received pong message + lastPong int64 } func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { @@ -109,7 +112,7 @@ func (p *Peer) writeMessage(msg *ethwire.InOutMsg) { // Outbound message handler. Outbound messages are handled here func (p *Peer) HandleOutbound() { // The ping timer. Makes sure that every 2 minutes a ping is send to the peer - tickleTimer := time.NewTimer(2 * time.Minute) + tickleTimer := time.NewTicker(2 * time.Minute) out: for { select { @@ -173,7 +176,10 @@ out: case ethwire.MsgGetPeersTy: case ethwire.MsgPeersTy: case ethwire.MsgPingTy: + // Respond back with pong + p.writeMessage(ðwire.InOutMsg{Type: ethwire.MsgPongTy}) case ethwire.MsgPongTy: + p.lastPong = time.Now().Unix() /* case "blockmine": diff --git a/server.go b/server.go index f17cc32a0..9907f3b24 100644 --- a/server.go +++ b/server.go @@ -81,13 +81,13 @@ func (s *Server) Broadcast(msgType ethwire.MsgType, data []byte) { } const ( - processReapingTimeout = 10 // TODO increase + processReapingTimeout = 1 // TODO increase ) func (s *Server) ReapDeadPeers() { for { eachPeer(s.peers, func(p *Peer, e *list.Element) { - if atomic.LoadInt32(&p.disconnect) == 1 { + if atomic.LoadInt32(&p.disconnect) == 1 || (p.inbound && (time.Now().Unix()-p.lastPong) > int64(5*time.Minute)) { log.Println("Dead peer found .. reaping") s.peers.Remove(e) -- cgit v1.2.3 From 5e5f386108ea4b75167bad96a2bf7cd801306045 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 17 Jan 2014 17:51:40 +0100 Subject: Renamed InOutMsg to msg --- peer.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/peer.go b/peer.go index 76a810063..8e6a5d540 100644 --- a/peer.go +++ b/peer.go @@ -20,7 +20,7 @@ type Peer struct { // Net connection conn net.Conn // Output queue which is used to communicate and handle messages - outputQueue chan *ethwire.InOutMsg + outputQueue chan *ethwire.Msg // Quit channel quit chan bool // Determines whether it's an inbound or outbound peer @@ -41,7 +41,7 @@ type Peer struct { func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { return &Peer{ - outputQueue: make(chan *ethwire.InOutMsg, outputBufferSize), + outputQueue: make(chan *ethwire.Msg, outputBufferSize), quit: make(chan bool), server: server, conn: conn, @@ -53,7 +53,7 @@ func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { func NewOutboundPeer(addr string, server *Server) *Peer { p := &Peer{ - outputQueue: make(chan *ethwire.InOutMsg, outputBufferSize), + outputQueue: make(chan *ethwire.Msg, outputBufferSize), quit: make(chan bool), server: server, inbound: false, @@ -82,11 +82,11 @@ func NewOutboundPeer(addr string, server *Server) *Peer { } // Outputs any RLP encoded data to the peer -func (p *Peer) QueueMessage(msg *ethwire.InOutMsg) { +func (p *Peer) QueueMessage(msg *ethwire.Msg) { p.outputQueue <- msg } -func (p *Peer) writeMessage(msg *ethwire.InOutMsg) { +func (p *Peer) writeMessage(msg *ethwire.Msg) { // Ignore the write if we're not connected if atomic.LoadInt32(&p.connected) != 1 { return @@ -123,7 +123,7 @@ out: p.lastSend = time.Now() case <-tickleTimer.C: - p.writeMessage(ðwire.InOutMsg{Type: ethwire.MsgPingTy}) + p.writeMessage(ðwire.Msg{Type: ethwire.MsgPingTy}) // Break out of the for loop if a quit message is posted case <-p.quit: @@ -177,7 +177,7 @@ out: case ethwire.MsgPeersTy: case ethwire.MsgPingTy: // Respond back with pong - p.writeMessage(ðwire.InOutMsg{Type: ethwire.MsgPongTy}) + p.writeMessage(ðwire.Msg{Type: ethwire.MsgPongTy}) case ethwire.MsgPongTy: p.lastPong = time.Now().Unix() @@ -231,7 +231,7 @@ func (p *Peer) pushHandshake() error { return nil } -func (p *Peer) handleHandshake(msg *ethwire.InOutMsg) { +func (p *Peer) handleHandshake(msg *ethwire.Msg) { c := ethutil.Conv(msg.Data) // [PROTOCOL_VERSION, NETWORK_ID, CLIENT_ID] if c.Get(2).AsUint() == p.server.Nonce { -- cgit v1.2.3 From ee61cfcfa776a1626fe3de7138942c1d796afdca Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 18 Jan 2014 00:19:29 +0100 Subject: Added get peers and peers msg. --- peer.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- server.go | 43 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/peer.go b/peer.go index 8e6a5d540..158541028 100644 --- a/peer.go +++ b/peer.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/ethwire-go" "log" "net" + "strconv" "sync/atomic" "time" ) @@ -37,6 +38,9 @@ type Peer struct { // Last received pong message lastPong int64 + // Indicates whether a MsgGetPeersTy was requested of the peer + // this to prevent receiving false peers. + requestedPeerList bool } func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { @@ -160,8 +164,6 @@ out: log.Printf("Received %s\n", msg.Type.String()) } - // TODO Hash data and check if for existence (= ignore) - switch msg.Type { case ethwire.MsgHandshakeTy: // Version message @@ -172,20 +174,34 @@ out: log.Println(err) } case ethwire.MsgTxTy: + //p.server.blockManager.AddToTransactionPool(ethutil.NewTransactionFromData(ethutil.Encode(msg.Data))) case ethwire.MsgInvTy: case ethwire.MsgGetPeersTy: + p.requestedPeerList = true + // Peer asked for list of connected peers + p.pushPeers() case ethwire.MsgPeersTy: + // Received a list of peers (probably because MsgGetPeersTy was send) + // Only act on message if we actually requested for a peers list + if p.requestedPeerList { + data := ethutil.Conv(msg.Data) + // Create new list of possible peers for the server to process + peers := make([]string, data.Length()) + // Parse each possible peer + for i := 0; i < data.Length(); i++ { + peers[i] = data.Get(i).AsString() + strconv.Itoa(int(data.Get(i).AsUint())) + } + + // Connect to the list of peers + p.server.ProcessPeerList(peers) + // Mark unrequested again + p.requestedPeerList = false + } case ethwire.MsgPingTy: // Respond back with pong - p.writeMessage(ðwire.Msg{Type: ethwire.MsgPongTy}) + p.QueueMessage(ðwire.Msg{Type: ethwire.MsgPongTy}) case ethwire.MsgPongTy: p.lastPong = time.Now().Unix() - - /* - case "blockmine": - d, _ := ethutil.Decode(msg.Data, 0) - log.Printf("block mined %s\n", d) - */ } } @@ -231,6 +247,20 @@ func (p *Peer) pushHandshake() error { return nil } +// Pushes the list of outbound peers to the client when requested +func (p *Peer) pushPeers() { + outPeers := make([]interface{}, len(p.server.OutboundPeers())) + // Serialise each peer + for i, peer := range p.server.OutboundPeers() { + outPeers[i] = peer.RlpEncode() + } + + // Send message to the peer with the known list of connected clients + msg := ethwire.NewMessage(ethwire.MsgPeersTy, ethutil.Encode(outPeers)) + + p.QueueMessage(msg) +} + func (p *Peer) handleHandshake(msg *ethwire.Msg) { c := ethutil.Conv(msg.Data) // [PROTOCOL_VERSION, NETWORK_ID, CLIENT_ID] @@ -255,3 +285,19 @@ func (p *Peer) handleHandshake(msg *ethwire.Msg) { } } } + +func (p *Peer) RlpEncode() []byte { + host, prt, err := net.SplitHostPort(p.conn.RemoteAddr().String()) + if err != nil { + return nil + } + + i, err := strconv.Atoi(prt) + if err != nil { + return nil + } + + port := ethutil.NumberToBytes(uint16(i), 16) + + return ethutil.Encode([]interface{}{host, port}) +} diff --git a/server.go b/server.go index 9907f3b24..7a29d1bd9 100644 --- a/server.go +++ b/server.go @@ -20,6 +20,10 @@ func eachPeer(peers *list.List, callback func(*Peer, *list.Element)) { } } +const ( + processReapingTimeout = 60 // TODO increase +) + type Server struct { // Channel for shutting down the server shutdownChan chan bool @@ -66,6 +70,13 @@ func (s *Server) AddPeer(conn net.Conn) { } } +func (s *Server) ProcessPeerList(addrs []string) { + for _, addr := range addrs { + // TODO Probably requires some sanity checks + s.ConnectToPeer(addr) + } +} + func (s *Server) ConnectToPeer(addr string) error { peer := NewOutboundPeer(addr, s) @@ -74,16 +85,40 @@ func (s *Server) ConnectToPeer(addr string) error { return nil } +func (s *Server) OutboundPeers() []*Peer { + // Create a new peer slice with at least the length of the total peers + outboundPeers := make([]*Peer, s.peers.Len()) + length := 0 + eachPeer(s.peers, func(p *Peer, e *list.Element) { + if !p.inbound { + outboundPeers[length] = p + length++ + } + }) + + return outboundPeers[:length] +} + +func (s *Server) InboundPeers() []*Peer { + // Create a new peer slice with at least the length of the total peers + inboundPeers := make([]*Peer, s.peers.Len()) + length := 0 + eachPeer(s.peers, func(p *Peer, e *list.Element) { + if p.inbound { + inboundPeers[length] = p + length++ + } + }) + + return inboundPeers[:length] +} + func (s *Server) Broadcast(msgType ethwire.MsgType, data []byte) { eachPeer(s.peers, func(p *Peer, e *list.Element) { p.QueueMessage(ethwire.NewMessage(msgType, data)) }) } -const ( - processReapingTimeout = 1 // TODO increase -) - func (s *Server) ReapDeadPeers() { for { eachPeer(s.peers, func(p *Peer, e *list.Element) { -- cgit v1.2.3 From 489576b6f04585b33c8aedd8aa6e5a8d54e1a960 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 19 Jan 2014 00:35:32 +0100 Subject: More opcodes --- block_manager.go | 133 +++++++++++++++++++++++++++++++++---------------------- stack.go | 11 +++-- 2 files changed, 85 insertions(+), 59 deletions(-) diff --git a/block_manager.go b/block_manager.go index 0831111e3..44b5461dc 100644 --- a/block_manager.go +++ b/block_manager.go @@ -9,6 +9,7 @@ import ( "log" "math" "math/big" + "strconv" ) type BlockChain struct { @@ -52,14 +53,14 @@ type BlockManager struct { // Stack for processing contracts stack *Stack // non-persistent key/value memory storage - mem map[string]string + mem map[string]*big.Int } func NewBlockManager() *BlockManager { bm := &BlockManager{ bc: NewBlockChain(), stack: NewStack(), - mem: make(map[string]string), + mem: make(map[string]*big.Int), } // Set the last known block number based on the blockchains last @@ -276,27 +277,27 @@ out: base.Add(x, y) base.Mod(base, Pow256) // Pop result back on the stack - bm.stack.Push(base.String()) + bm.stack.Push(base) case oSUB: x, y := bm.stack.Popn() // (x - y) % 2 ** 256 base.Sub(x, y) base.Mod(base, Pow256) // Pop result back on the stack - bm.stack.Push(base.String()) + bm.stack.Push(base) case oMUL: x, y := bm.stack.Popn() // (x * y) % 2 ** 256 base.Mul(x, y) base.Mod(base, Pow256) // Pop result back on the stack - bm.stack.Push(base.String()) + bm.stack.Push(base) case oDIV: x, y := bm.stack.Popn() // floor(x / y) base.Div(x, y) // Pop result back on the stack - bm.stack.Push(base.String()) + bm.stack.Push(base) case oSDIV: x, y := bm.stack.Popn() // n > 2**255 @@ -312,11 +313,11 @@ out: z.Sub(Pow256, z) } // Push result on to the stack - bm.stack.Push(z.String()) + bm.stack.Push(z) case oMOD: x, y := bm.stack.Popn() base.Mod(x, y) - bm.stack.Push(base.String()) + bm.stack.Push(base) case oSMOD: x, y := bm.stack.Popn() // n > 2**255 @@ -332,87 +333,85 @@ out: z.Sub(Pow256, z) } // Push result on to the stack - bm.stack.Push(z.String()) + bm.stack.Push(z) case oEXP: x, y := bm.stack.Popn() base.Exp(x, y, Pow256) - bm.stack.Push(base.String()) + bm.stack.Push(base) case oNEG: - base.Sub(Pow256, ethutil.Big(bm.stack.Pop())) - bm.stack.Push(base.String()) + base.Sub(Pow256, bm.stack.Pop()) + bm.stack.Push(base) case oLT: x, y := bm.stack.Popn() // x < y if x.Cmp(y) < 0 { - bm.stack.Push("1") + bm.stack.Push(ethutil.BigTrue) } else { - bm.stack.Push("0") + bm.stack.Push(ethutil.BigFalse) } case oLE: x, y := bm.stack.Popn() // x <= y if x.Cmp(y) < 1 { - bm.stack.Push("1") + bm.stack.Push(ethutil.BigTrue) } else { - bm.stack.Push("0") + bm.stack.Push(ethutil.BigFalse) } case oGT: x, y := bm.stack.Popn() // x > y if x.Cmp(y) > 0 { - bm.stack.Push("1") + bm.stack.Push(ethutil.BigTrue) } else { - bm.stack.Push("0") + bm.stack.Push(ethutil.BigFalse) } case oGE: x, y := bm.stack.Popn() // x >= y if x.Cmp(y) > -1 { - bm.stack.Push("1") + bm.stack.Push(ethutil.BigTrue) } else { - bm.stack.Push("0") + bm.stack.Push(ethutil.BigFalse) } case oNOT: x, y := bm.stack.Popn() // x != y if x.Cmp(y) != 0 { - bm.stack.Push("1") + bm.stack.Push(ethutil.BigTrue) } else { - bm.stack.Push("0") + bm.stack.Push(ethutil.BigFalse) } // Please note that the following code contains some // ugly string casting. This will have to change to big // ints. TODO :) case oMYADDRESS: - bm.stack.Push(string(tx.Hash())) + bm.stack.Push(ethutil.BigD(tx.Hash())) case oTXSENDER: - bm.stack.Push(string(tx.Sender())) + bm.stack.Push(ethutil.BigD(tx.Sender())) case oTXVALUE: - bm.stack.Push(tx.Value.String()) + bm.stack.Push(tx.Value) case oTXDATAN: - bm.stack.Push(big.NewInt(int64(len(tx.Data))).String()) + bm.stack.Push(big.NewInt(int64(len(tx.Data)))) case oTXDATA: - v := ethutil.Big(bm.stack.Pop()) + v := bm.stack.Pop() // v >= len(data) if v.Cmp(big.NewInt(int64(len(tx.Data)))) >= 0 { - //I know this will change. It makes no - //sense. Read comment above - bm.stack.Push(ethutil.Big("0").String()) + bm.stack.Push(ethutil.Big("0")) } else { - bm.stack.Push(ethutil.Big(tx.Data[v.Uint64()]).String()) + bm.stack.Push(ethutil.Big(tx.Data[v.Uint64()])) } case oBLK_PREVHASH: - bm.stack.Push(string(block.PrevHash)) + bm.stack.Push(ethutil.Big(block.PrevHash)) case oBLK_COINBASE: - bm.stack.Push(block.Coinbase) + bm.stack.Push(ethutil.Big(block.Coinbase)) case oBLK_TIMESTAMP: - bm.stack.Push(big.NewInt(block.Time).String()) + bm.stack.Push(big.NewInt(block.Time)) case oBLK_NUMBER: - bm.stack.Push(blockInfo.Number.String()) + bm.stack.Push(blockInfo.Number) case oBLK_DIFFICULTY: - bm.stack.Push(block.Difficulty.String()) + bm.stack.Push(block.Difficulty) case oBASEFEE: // e = 10^21 e := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(21), big.NewInt(0)) @@ -429,23 +428,23 @@ out: x.Div(e, base) // x = floor(10^21 / floor(diff^0.5)) - bm.stack.Push(x.String()) + bm.stack.Push(x) case oSHA256, oRIPEMD160: // This is probably save // ceil(pop / 32) - length := int(math.Ceil(float64(ethutil.Big(bm.stack.Pop()).Uint64()) / 32.0)) + length := int(math.Ceil(float64(bm.stack.Pop().Uint64()) / 32.0)) // New buffer which will contain the concatenated popped items data := new(bytes.Buffer) for i := 0; i < length; i++ { // Encode the number to bytes and have it 32bytes long - num := ethutil.NumberToBytes(ethutil.Big(bm.stack.Pop()).Bytes(), 256) + num := ethutil.NumberToBytes(bm.stack.Pop().Bytes(), 256) data.WriteString(string(num)) } if op == oSHA256 { - bm.stack.Push(base.SetBytes(ethutil.Sha256Bin(data.Bytes())).String()) + bm.stack.Push(base.SetBytes(ethutil.Sha256Bin(data.Bytes()))) } else { - bm.stack.Push(base.SetBytes(ethutil.Ripemd160(data.Bytes())).String()) + bm.stack.Push(base.SetBytes(ethutil.Ripemd160(data.Bytes()))) } case oECMUL: y := bm.stack.Pop() @@ -454,14 +453,14 @@ out: //if ethutil.Big(x).Cmp(ethutil.Big(y)) { data := new(bytes.Buffer) - data.WriteString(x) - data.WriteString(y) + data.WriteString(x.String()) + data.WriteString(y.String()) if secp256k1.VerifyPubkeyValidity(data.Bytes()) == 1 { // TODO } else { // Invalid, push infinity - bm.stack.Push("0") - bm.stack.Push("0") + bm.stack.Push(ethutil.Big("0")) + bm.stack.Push(ethutil.Big("0")) } //} else { // // Invalid, push infinity @@ -475,31 +474,59 @@ out: case oECVALID: case oSHA3: case oPUSH: - // Get the next entry and pushes the value on the stack pc++ - bm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(pc), 32)))) + bm.stack.Push(bm.mem[strconv.Itoa(pc)]) case oPOP: // Pop current value of the stack bm.stack.Pop() case oDUP: + // Dup top stack + x := bm.stack.Pop() + bm.stack.Push(x) + bm.stack.Push(x) case oSWAP: + // Swap two top most values + x, y := bm.stack.Popn() + bm.stack.Push(y) + bm.stack.Push(x) case oMLOAD: + x := bm.stack.Pop() + bm.stack.Push(bm.mem[x.String()]) case oMSTORE: + x, y := bm.stack.Popn() + bm.mem[x.String()] = y case oSLOAD: + // Load the value in storage and push it on the stack + x := bm.stack.Pop() + // decode the object as a big integer + decoder := ethutil.NewRlpDecoder([]byte(contract.State().Get(x.String()))) + if !decoder.IsNil() { + bm.stack.Push(decoder.AsBigInt()) + } else { + bm.stack.Push(ethutil.BigFalse) + } case oSSTORE: + // Store Y at index X + x, y := bm.stack.Popn() + contract.State().Update(x.String(), string(ethutil.Encode(y))) case oJMP: + x := int(bm.stack.Pop().Uint64()) + // Set pc to x - 1 (minus one so the incrementing at the end won't effect it) + pc = x + pc-- case oJMPI: + x := bm.stack.Pop() + // Set pc to x if it's non zero + if x.Cmp(ethutil.BigFalse) != 0 { + pc = int(x.Uint64()) + pc-- + } case oIND: + bm.stack.Push(big.NewInt(int64(pc))) case oEXTRO: case oBALANCE: case oMKTX: case oSUICIDE: - /* - case oLOAD: - // Load instruction X on the stack - i, _ := strconv.Atoi(bm.stack.Pop()) - bm.stack.Push(contract.State().Get(string(ethutil.NumberToBytes(uint64(i), 32)))) - */ } pc++ } diff --git a/stack.go b/stack.go index cee98555b..9d595d85b 100644 --- a/stack.go +++ b/stack.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "github.com/ethereum/ethutil-go" "math/big" ) @@ -135,14 +134,14 @@ type TxCallback func(opType OpType) bool // Simple push/pop stack mechanism type Stack struct { - data []string + data []*big.Int } func NewStack() *Stack { return &Stack{} } -func (st *Stack) Pop() string { +func (st *Stack) Pop() *big.Int { s := len(st.data) str := st.data[s-1] @@ -154,13 +153,13 @@ func (st *Stack) Pop() string { func (st *Stack) Popn() (*big.Int, *big.Int) { s := len(st.data) - strs := st.data[s-2:] + ints := st.data[s-2:] st.data = st.data[:s-2] - return ethutil.Big(strs[0]), ethutil.Big(strs[1]) + return ints[0], ints[1] } -func (st *Stack) Push(d string) { +func (st *Stack) Push(d *big.Int) { st.data = append(st.data, d) } func (st *Stack) Print() { -- cgit v1.2.3 From b608a80ef154ac7847c3f6dbb8727f8d83aca735 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 19 Jan 2014 01:02:22 +0100 Subject: More opcodes --- block_manager.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/block_manager.go b/block_manager.go index 44b5461dc..9322d0d3a 100644 --- a/block_manager.go +++ b/block_manager.go @@ -524,7 +524,16 @@ out: case oIND: bm.stack.Push(big.NewInt(int64(pc))) case oEXTRO: + memAddr := bm.stack.Pop() + contractAddr := bm.stack.Pop().Bytes() + + // Push the contract's memory on to the stack + bm.stack.Push(getContractMemory(block, contractAddr, memAddr)) case oBALANCE: + // Pushes the balance of the popped value on to the stack + d := block.State().Get(bm.stack.Pop().String()) + ether := ethutil.NewEtherFromData([]byte(d)) + bm.stack.Push(ether.Amount) case oMKTX: case oSUICIDE: } @@ -533,3 +542,20 @@ out: bm.stack.Print() } + +// Returns an address from the specified contract's address +func getContractMemory(block *ethutil.Block, contractAddr []byte, memAddr *big.Int) *big.Int { + contract := block.GetContract(contractAddr) + if contract == nil { + log.Panicf("invalid contract addr %x", contractAddr) + } + val := contract.State().Get(memAddr.String()) + + // decode the object as a big integer + decoder := ethutil.NewRlpDecoder([]byte(val)) + if decoder.IsNil() { + return ethutil.BigFalse + } + + return decoder.AsBigInt() +} -- cgit v1.2.3 From 09df961abb69bb1c49a53e7d61e9c76a1ff0edcd Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 20 Jan 2014 23:26:32 +0100 Subject: Transaction processing. Implemented sending and receiving wei --- block_manager.go | 50 +++++++++++++++++++----- block_manager_test.go | 104 ++++++++++++++++++++++++-------------------------- peer.go | 2 +- server.go | 6 ++- 4 files changed, 96 insertions(+), 66 deletions(-) diff --git a/block_manager.go b/block_manager.go index 9322d0d3a..87af9f293 100644 --- a/block_manager.go +++ b/block_manager.go @@ -10,6 +10,7 @@ import ( "math" "math/big" "strconv" + "time" ) type BlockChain struct { @@ -44,6 +45,7 @@ func (bc *BlockChain) GenesisBlock() *ethutil.Block { } type BlockManager struct { + server *Server // The block chain :) bc *BlockChain @@ -56,11 +58,12 @@ type BlockManager struct { mem map[string]*big.Int } -func NewBlockManager() *BlockManager { +func NewBlockManager(s *Server) *BlockManager { bm := &BlockManager{ - bc: NewBlockChain(), - stack: NewStack(), - mem: make(map[string]*big.Int), + server: s, + bc: NewBlockChain(), + stack: NewStack(), + mem: make(map[string]*big.Int), } // Set the last known block number based on the blockchains last @@ -161,13 +164,16 @@ func (bm *BlockManager) CalculateTD(block *ethutil.Block) bool { // an uncle or anything that isn't on the current block chain. // Validation validates easy over difficult (dagger takes longer time = difficult) func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { + // Genesis block + if bm.bc.LastBlock == nil && block.PrevHash == "" { + return nil + } // TODO // 2. Check if the difficulty is correct // Check if we have the parent hash, if it isn't known we discard it // Reasons might be catching up or simply an invalid block - if bm.bc.LastBlock != nil && block.PrevHash == "" && - !bm.bc.HasBlock(block.PrevHash) { + if !bm.bc.HasBlock(block.PrevHash) { return errors.New("Block's parent unknown") } @@ -183,9 +189,18 @@ func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { } } + diff := block.Time - bm.bc.LastBlock.Time + if diff < 0 { + return fmt.Errorf("Block timestamp less then prev block %v", diff) + } + + // New blocks must be within the 15 minute range of the last block. + if diff > int64(15*time.Minute) { + return errors.New("Block is too far in the future of last block (> 15 minutes)") + } + // Verify the nonce of the block. Return an error if it's not valid - if bm.bc.LastBlock != nil && block.PrevHash == "" && - !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { + if !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { return errors.New("Block's nonce is invalid") } @@ -429,7 +444,7 @@ out: // x = floor(10^21 / floor(diff^0.5)) bm.stack.Push(x) - case oSHA256, oRIPEMD160: + case oSHA256, oSHA3, oRIPEMD160: // This is probably save // ceil(pop / 32) length := int(math.Ceil(float64(bm.stack.Pop().Uint64()) / 32.0)) @@ -443,6 +458,8 @@ out: if op == oSHA256 { bm.stack.Push(base.SetBytes(ethutil.Sha256Bin(data.Bytes()))) + } else if op == oSHA3 { + bm.stack.Push(base.SetBytes(ethutil.Sha3Bin(data.Bytes()))) } else { bm.stack.Push(base.SetBytes(ethutil.Ripemd160(data.Bytes()))) } @@ -472,7 +489,6 @@ out: case oECSIGN: case oECRECOVER: case oECVALID: - case oSHA3: case oPUSH: pc++ bm.stack.Push(bm.mem[strconv.Itoa(pc)]) @@ -535,7 +551,21 @@ out: ether := ethutil.NewEtherFromData([]byte(d)) bm.stack.Push(ether.Amount) case oMKTX: + value, addr := bm.stack.Popn() + from, length := bm.stack.Popn() + + j := 0 + dataItems := make([]string, int(length.Uint64())) + for i := from.Uint64(); i < length.Uint64(); i++ { + dataItems[j] = string(bm.mem[strconv.Itoa(int(i))].Bytes()) + j++ + } + // TODO sign it? + tx := ethutil.NewTransaction(string(addr.Bytes()), value, dataItems) + // Add the transaction to the tx pool + bm.server.txPool.QueueTransaction(tx) case oSUICIDE: + //addr := bm.stack.Pop() } pc++ } diff --git a/block_manager_test.go b/block_manager_test.go index 3dcf572fd..5f200f3e7 100644 --- a/block_manager_test.go +++ b/block_manager_test.go @@ -1,77 +1,73 @@ package main -/* import ( - _"fmt" - "testing" + _ "fmt" + "testing" ) - -/* func TestVm(t *testing.T) { - InitFees() + InitFees() - db, _ := NewMemDatabase() - Db = db + db, _ := NewMemDatabase() + Db = db - ctrct := NewTransaction("", 200000000, []string{ - "PUSH", "1a2f2e", - "PUSH", "hallo", - "POP", // POP hallo - "PUSH", "3", - "LOAD", // Load hallo back on the stack + ctrct := NewTransaction("", 200000000, []string{ + "PUSH", "1a2f2e", + "PUSH", "hallo", + "POP", // POP hallo + "PUSH", "3", + "LOAD", // Load hallo back on the stack - "PUSH", "1", - "PUSH", "2", - "ADD", + "PUSH", "1", + "PUSH", "2", + "ADD", - "PUSH", "2", - "PUSH", "1", - "SUB", + "PUSH", "2", + "PUSH", "1", + "SUB", - "PUSH", "100000000000000000000000", - "PUSH", "10000000000000", - "SDIV", + "PUSH", "100000000000000000000000", + "PUSH", "10000000000000", + "SDIV", - "PUSH", "105", - "PUSH", "200", - "MOD", + "PUSH", "105", + "PUSH", "200", + "MOD", - "PUSH", "100000000000000000000000", - "PUSH", "10000000000000", - "SMOD", + "PUSH", "100000000000000000000000", + "PUSH", "10000000000000", + "SMOD", - "PUSH", "5", - "PUSH", "10", - "LT", + "PUSH", "5", + "PUSH", "10", + "LT", - "PUSH", "5", - "PUSH", "5", - "LE", + "PUSH", "5", + "PUSH", "5", + "LE", - "PUSH", "50", - "PUSH", "5", - "GT", + "PUSH", "50", + "PUSH", "5", + "GT", - "PUSH", "5", - "PUSH", "5", - "GE", + "PUSH", "5", + "PUSH", "5", + "GE", - "PUSH", "10", - "PUSH", "10", - "NOT", + "PUSH", "10", + "PUSH", "10", + "NOT", - "MYADDRESS", - "TXSENDER", + "MYADDRESS", + "TXSENDER", - "STOP", - }) - tx := NewTransaction("1e8a42ea8cce13", 100, []string{}) + "STOP", + }) + tx := NewTransaction("1e8a42ea8cce13", 100, []string{}) - block := CreateBlock("", 0, "", "c014ba53", 0, 0, "", []*Transaction{ctrct, tx}) - db.Put(block.Hash(), block.RlpEncode()) + block := CreateBlock("", 0, "", "c014ba53", 0, 0, "", []*Transaction{ctrct, tx}) + db.Put(block.Hash(), block.RlpEncode()) - bm := NewBlockManager() - bm.ProcessBlock( block ) + bm := NewBlockManager() + bm.ProcessBlock(block) } -*/ diff --git a/peer.go b/peer.go index 158541028..0f3422826 100644 --- a/peer.go +++ b/peer.go @@ -174,7 +174,7 @@ out: log.Println(err) } case ethwire.MsgTxTy: - //p.server.blockManager.AddToTransactionPool(ethutil.NewTransactionFromData(ethutil.Encode(msg.Data))) + p.server.txPool.QueueTransaction(ethutil.NewTransactionFromData(ethutil.Encode(msg.Data))) case ethwire.MsgInvTy: case ethwire.MsgGetPeersTy: p.requestedPeerList = true diff --git a/server.go b/server.go index 7a29d1bd9..3a35a43a2 100644 --- a/server.go +++ b/server.go @@ -32,6 +32,9 @@ type Server struct { db *ethdb.MemDatabase // Block manager for processing new blocks and managing the block chain blockManager *BlockManager + // The transaction pool. Transaction can be pushed on this pool + // for later including in the blocks + txPool *TxPool // Peers (NYI) peers *list.List // Nonce @@ -50,11 +53,12 @@ func NewServer() (*Server, error) { nonce, _ := ethutil.RandomUint64() server := &Server{ shutdownChan: make(chan bool), - blockManager: NewBlockManager(), db: db, peers: list.New(), Nonce: nonce, } + server.txPool = NewTxPool(server) + server.blockManager = NewBlockManager(server) return server, nil } -- cgit v1.2.3 From e47230f82da93ef0110faa76211b9b6f13b1060b Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 20 Jan 2014 23:26:53 +0100 Subject: Transaction processing. Implemented sending and receiving wei --- transaction_pool.go | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 transaction_pool.go diff --git a/transaction_pool.go b/transaction_pool.go new file mode 100644 index 000000000..f645afd06 --- /dev/null +++ b/transaction_pool.go @@ -0,0 +1,161 @@ +package main + +import ( + "container/list" + "errors" + "github.com/ethereum/ethutil-go" + "log" + "math/big" + "sync" +) + +const ( + txPoolQueueSize = 50 +) + +func FindTx(pool *list.List, finder func(*ethutil.Transaction, *list.Element) bool) *ethutil.Transaction { + for e := pool.Front(); e != nil; e = e.Next() { + if tx, ok := e.Value.(*ethutil.Transaction); ok { + if finder(tx, e) { + return tx + } + } + } + + return nil +} + +// The tx pool a thread safe transaction pool handler. In order to +// guarantee a non blocking pool we use a queue channel which can be +// independently read without needing access to the actual pool. If the +// pool is being drained or synced for whatever reason the transactions +// will simple queue up and handled when the mutex is freed. +type TxPool struct { + server *Server + // The mutex for accessing the Tx pool. + mutex sync.Mutex + // Queueing channel for reading and writing incoming + // transactions to + queueChan chan *ethutil.Transaction + // Quiting channel + quit chan bool + + pool *list.List +} + +func NewTxPool(s *Server) *TxPool { + return &TxPool{ + server: s, + mutex: sync.Mutex{}, + pool: list.New(), + queueChan: make(chan *ethutil.Transaction, txPoolQueueSize), + quit: make(chan bool), + } +} + +// Blocking function. Don't use directly. Use QueueTransaction instead +func (pool *TxPool) addTransaction(tx *ethutil.Transaction) { + pool.mutex.Lock() + defer pool.mutex.Unlock() + + pool.pool.PushBack(tx) +} + +// Process transaction validates the Tx and processes funds from the +// sender to the recipient. +func (pool *TxPool) processTransaction(tx *ethutil.Transaction) error { + // Get the last block so we can retrieve the sender and receiver from + // the merkle trie + block := pool.server.blockManager.bc.LastBlock + // Something has gone horribly wrong if this happens + if block == nil { + return errors.New("No last block on the block chain") + } + + var sender, receiver *ethutil.Ether + + // Get the sender + data := block.State().Get(string(tx.Sender())) + // If it doesn't exist create a new account. Of course trying to send funds + // from this account will fail since it will hold 0 Wei + if data == "" { + sender = ethutil.NewEther(big.NewInt(0)) + } else { + sender = ethutil.NewEtherFromData([]byte(data)) + } + // Defer the update. Whatever happens it should be persisted + defer block.State().Update(string(tx.Sender()), string(sender.RlpEncode())) + + // Make sure there's enough in the sender's account. Having insufficient + // funds won't invalidate this transaction but simple ignores it. + if sender.Amount.Cmp(tx.Value) < 0 { + return errors.New("Insufficient amount in sender's account") + } + + // Subtract the amount from the senders account + sender.Amount.Sub(sender.Amount, tx.Value) + // Increment the nonce making each tx valid only once to prevent replay + // attacks + sender.Nonce += 1 + + // Get the receiver + data = block.State().Get(tx.Recipient) + // If the receiver doesn't exist yet, create a new account to which the + // funds will be send. + if data == "" { + receiver = ethutil.NewEther(big.NewInt(0)) + } else { + receiver = ethutil.NewEtherFromData([]byte(data)) + } + // Defer the update + defer block.State().Update(tx.Recipient, string(receiver.RlpEncode())) + + // Add the amount to receivers account which should conclude this transaction + receiver.Amount.Add(receiver.Amount, tx.Value) + + return nil +} + +func (pool *TxPool) queueHandler() { +out: + for { + select { + case tx := <-pool.queueChan: + // Process the transaction + err := pool.processTransaction(tx) + if err != nil { + log.Println("Error processing Tx", err) + } else { + // Call blocking version. At this point it + // doesn't matter since this is a goroutine + pool.addTransaction(tx) + } + case <-pool.quit: + break out + } + } +} + +func (pool *TxPool) QueueTransaction(tx *ethutil.Transaction) { + pool.queueChan <- tx +} + +func (pool *TxPool) Flush() { + pool.mutex.Lock() + + defer pool.mutex.Unlock() + + pool.mutex.Unlock() +} + +func (pool *TxPool) Start() { + go pool.queueHandler() +} + +func (pool *TxPool) Stop() { + log.Println("[TXP] Stopping...") + + close(pool.quit) + + pool.Flush() +} -- cgit v1.2.3 From 3616080db46931202003157bacf10748008bebc0 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 21 Jan 2014 23:27:08 +0100 Subject: Added synchronisation of transactions across remote pools --- dev_console.go | 16 ++++++++++---- ethereum.go | 63 ++++++++++++++++++++++++++++++++++------------------- peer.go | 6 ++--- server.go | 34 ++++++++++++++++------------- transaction_pool.go | 26 +++++++++++++++++----- 5 files changed, 96 insertions(+), 49 deletions(-) diff --git a/dev_console.go b/dev_console.go index 5340a5f46..d14f019e5 100644 --- a/dev_console.go +++ b/dev_console.go @@ -12,15 +12,16 @@ import ( ) type Console struct { - db *ethdb.MemDatabase - trie *ethutil.Trie + db *ethdb.MemDatabase + trie *ethutil.Trie + server *Server } -func NewConsole() *Console { +func NewConsole(s *Server) *Console { db, _ := ethdb.NewMemDatabase() trie := ethutil.NewTrie(db, "") - return &Console{db: db, trie: trie} + return &Console{db: db, trie: trie, server: s} } func (i *Console) ValidateInput(action string, argumentLength int) error { @@ -43,6 +44,9 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "encode" && argumentLength != 1: err = true expArgCount = 1 + case action == "tx" && argumentLength != 2: + err = true + expArgCount = 2 } if err { @@ -105,6 +109,10 @@ func (i *Console) ParseInput(input string) bool { fmt.Printf("%q\n", d) case "encode": fmt.Printf("%q\n", ethutil.Encode(tokens[1])) + case "tx": + tx := ethutil.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) + + i.server.txPool.QueueTransaction(tx) case "exit", "quit", "q": return false case "help": diff --git a/ethereum.go b/ethereum.go index d74cb4ff2..b7f059a02 100644 --- a/ethereum.go +++ b/ethereum.go @@ -7,6 +7,7 @@ import ( "log" "os" "os/signal" + "path" "runtime" ) @@ -44,36 +45,54 @@ func main() { Init() + ethutil.ReadConfig() + + server, err := NewServer() + + if err != nil { + log.Println(err) + return + } + if StartConsole { - console := NewConsole() - console.Start() - } else { - log.Println("Starting Ethereum") - server, err := NewServer() + err := os.Mkdir(ethutil.Config.ExecPath, os.ModePerm) + // Error is OK if the error is ErrExist + if err != nil && !os.IsExist(err) { + log.Panic("Unable to create EXECPATH. Exiting") + } + // TODO The logger will eventually be a non blocking logger. Logging is a expensive task + // Log to file only + file, err := os.OpenFile(path.Join(ethutil.Config.ExecPath, "debug.log"), os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { - log.Println(err) - return + log.Panic("Unable to set proper logger", err) } - RegisterInterupts(server) + ethutil.Config.Log = log.New(file, "", 0) - if StartMining { - log.Println("Mining started") - dagger := &Dagger{} + console := NewConsole(server) + go console.Start() + } - go func() { - for { - res := dagger.Search(ethutil.Big("01001"), ethutil.BigPow(2, 36)) - log.Println("Res dagger", res) - //server.Broadcast("blockmine", ethutil.Encode(res.String())) - } - }() - } + log.Println("Starting Ethereum") + + RegisterInterupts(server) - server.Start() + if StartMining { + log.Println("Mining started") + dagger := &Dagger{} - // Wait for shutdown - server.WaitForShutdown() + go func() { + for { + res := dagger.Search(ethutil.Big("01001"), ethutil.BigPow(2, 36)) + log.Println("Res dagger", res) + //server.Broadcast("blockmine", ethutil.Encode(res.String())) + } + }() } + + server.Start() + + // Wait for shutdown + server.WaitForShutdown() } diff --git a/peer.go b/peer.go index 0f3422826..207f9e59f 100644 --- a/peer.go +++ b/peer.go @@ -62,7 +62,7 @@ func NewOutboundPeer(addr string, server *Server) *Peer { server: server, inbound: false, connected: 0, - disconnect: 1, + disconnect: 0, } // Set up the connection in another goroutine so we don't block the main thread @@ -169,12 +169,12 @@ out: // Version message p.handleHandshake(msg) case ethwire.MsgBlockTy: - err := p.server.blockManager.ProcessBlock(ethutil.NewBlock(ethutil.Encode(msg.Data))) + err := p.server.blockManager.ProcessBlock(ethutil.NewBlock(msg.Data)) if err != nil { log.Println(err) } case ethwire.MsgTxTy: - p.server.txPool.QueueTransaction(ethutil.NewTransactionFromData(ethutil.Encode(msg.Data))) + p.server.txPool.QueueTransaction(ethutil.NewTransactionFromData(msg.Data)) case ethwire.MsgInvTy: case ethwire.MsgGetPeersTy: p.requestedPeerList = true diff --git a/server.go b/server.go index 3a35a43a2..2927f023a 100644 --- a/server.go +++ b/server.go @@ -48,7 +48,7 @@ func NewServer() (*Server, error) { return nil, err } - ethutil.SetConfig(db) + ethutil.Config.Db = db nonce, _ := ethutil.RandomUint64() server := &Server{ @@ -152,28 +152,30 @@ func (s *Server) Start() { s.Stop() } - - return } else { log.Fatal(err) } + } else { + // Starting accepting connections + go func() { + for { + conn, err := ln.Accept() + if err != nil { + log.Println(err) + + continue + } + + go s.AddPeer(conn) + } + }() } // Start the reaping processes go s.ReapDeadPeers() - go func() { - for { - conn, err := ln.Accept() - if err != nil { - log.Println(err) - - continue - } - - go s.AddPeer(conn) - } - }() + // Start the tx pool + s.txPool.Start() // TMP /* @@ -196,6 +198,8 @@ func (s *Server) Stop() { }) s.shutdownChan <- true + + s.txPool.Stop() } // This function will wait for a shutdown and resumes main thread execution diff --git a/transaction_pool.go b/transaction_pool.go index f645afd06..b302931de 100644 --- a/transaction_pool.go +++ b/transaction_pool.go @@ -1,9 +1,11 @@ package main import ( + "bytes" "container/list" "errors" "github.com/ethereum/ethutil-go" + "github.com/ethereum/ethwire-go" "log" "math/big" "sync" @@ -56,9 +58,11 @@ func NewTxPool(s *Server) *TxPool { // Blocking function. Don't use directly. Use QueueTransaction instead func (pool *TxPool) addTransaction(tx *ethutil.Transaction) { pool.mutex.Lock() - defer pool.mutex.Unlock() - pool.pool.PushBack(tx) + pool.mutex.Unlock() + + // Broadcast the transaction to the rest of the peers + pool.server.Broadcast(ethwire.MsgTxTy, tx.RlpEncode()) } // Process transaction validates the Tx and processes funds from the @@ -89,7 +93,12 @@ func (pool *TxPool) processTransaction(tx *ethutil.Transaction) error { // Make sure there's enough in the sender's account. Having insufficient // funds won't invalidate this transaction but simple ignores it. if sender.Amount.Cmp(tx.Value) < 0 { - return errors.New("Insufficient amount in sender's account") + if Debug { + log.Println("Insufficient amount in sender's account. Adding 1 ETH for debug") + sender.Amount = ethutil.BigPow(10, 18) + } else { + return errors.New("Insufficient amount in sender's account") + } } // Subtract the amount from the senders account @@ -121,6 +130,15 @@ out: for { select { case tx := <-pool.queueChan: + hash := tx.Hash() + foundTx := FindTx(pool.pool, func(tx *ethutil.Transaction, e *list.Element) bool { + return bytes.Compare(tx.Hash(), hash) == 0 + }) + + if foundTx != nil { + break + } + // Process the transaction err := pool.processTransaction(tx) if err != nil { @@ -144,8 +162,6 @@ func (pool *TxPool) Flush() { pool.mutex.Lock() defer pool.mutex.Unlock() - - pool.mutex.Unlock() } func (pool *TxPool) Start() { -- cgit v1.2.3 From 477e8a7a73820ece05bbb990607a1919d3788960 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 23 Jan 2014 20:16:52 +0100 Subject: Rearrange packages --- block_manager.go | 591 -------------------------------------------------- block_manager_test.go | 73 ------- dagger.go | 149 ------------- dagger_test.go | 18 -- dev_console.go | 16 +- ethereum.go | 26 +-- peer.go | 303 -------------------------- server.go | 208 ------------------ stack.go | 167 -------------- transaction_pool.go | 177 --------------- 10 files changed, 22 insertions(+), 1706 deletions(-) delete mode 100644 block_manager.go delete mode 100644 block_manager_test.go delete mode 100644 dagger.go delete mode 100644 dagger_test.go delete mode 100644 peer.go delete mode 100644 server.go delete mode 100644 stack.go delete mode 100644 transaction_pool.go diff --git a/block_manager.go b/block_manager.go deleted file mode 100644 index 87af9f293..000000000 --- a/block_manager.go +++ /dev/null @@ -1,591 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "fmt" - "github.com/ethereum/ethutil-go" - "github.com/obscuren/secp256k1-go" - "log" - "math" - "math/big" - "strconv" - "time" -) - -type BlockChain struct { - // Last block - LastBlock *ethutil.Block - // The famous, the fabulous Mister GENESIIIIIIS (block) - genesisBlock *ethutil.Block - // Last known total difficulty - TD *big.Int -} - -func NewBlockChain() *BlockChain { - bc := &BlockChain{} - bc.genesisBlock = ethutil.NewBlock(ethutil.Encode(ethutil.Genesis)) - - // Set the last know difficulty (might be 0x0 as initial value, Genesis) - bc.TD = ethutil.BigD(ethutil.Config.Db.LastKnownTD()) - - // TODO get last block from the database - bc.LastBlock = bc.genesisBlock - - return bc -} - -func (bc *BlockChain) HasBlock(hash string) bool { - data, _ := ethutil.Config.Db.Get([]byte(hash)) - return len(data) != 0 -} - -func (bc *BlockChain) GenesisBlock() *ethutil.Block { - return bc.genesisBlock -} - -type BlockManager struct { - server *Server - // The block chain :) - bc *BlockChain - - // Last known block number - LastBlockNumber *big.Int - - // Stack for processing contracts - stack *Stack - // non-persistent key/value memory storage - mem map[string]*big.Int -} - -func NewBlockManager(s *Server) *BlockManager { - bm := &BlockManager{ - server: s, - bc: NewBlockChain(), - stack: NewStack(), - mem: make(map[string]*big.Int), - } - - // Set the last known block number based on the blockchains last - // block - bm.LastBlockNumber = bm.BlockInfo(bm.bc.LastBlock).Number - - return bm -} - -// Process a block. -func (bm *BlockManager) ProcessBlock(block *ethutil.Block) error { - // Block validation - if err := bm.ValidateBlock(block); err != nil { - return err - } - - // I'm not sure, but I don't know if there should be thrown - // any errors at this time. - if err := bm.AccumelateRewards(block); err != nil { - return err - } - - // Get the tx count. Used to create enough channels to 'join' the go routines - txCount := len(block.Transactions()) - // Locking channel. When it has been fully buffered this method will return - lockChan := make(chan bool, txCount) - - // Process each transaction/contract - for _, tx := range block.Transactions() { - // If there's no recipient, it's a contract - if tx.IsContract() { - go bm.ProcessContract(tx, block, lockChan) - } else { - // "finish" tx which isn't a contract - lockChan <- true - } - } - - // Wait for all Tx to finish processing - for i := 0; i < txCount; i++ { - <-lockChan - } - - // Calculate the new total difficulty and sync back to the db - if bm.CalculateTD(block) { - ethutil.Config.Db.Put(block.Hash(), block.RlpEncode()) - bm.bc.LastBlock = block - } - - return nil -} - -// Unexported method for writing extra non-essential block info to the db -func (bm *BlockManager) writeBlockInfo(block *ethutil.Block) { - bi := ethutil.BlockInfo{Number: bm.LastBlockNumber.Add(bm.LastBlockNumber, big.NewInt(1))} - - // For now we use the block hash with the words "info" appended as key - ethutil.Config.Db.Put(append(block.Hash(), []byte("Info")...), bi.RlpEncode()) -} - -func (bm *BlockManager) BlockInfo(block *ethutil.Block) ethutil.BlockInfo { - bi := ethutil.BlockInfo{} - data, _ := ethutil.Config.Db.Get(append(block.Hash(), []byte("Info")...)) - bi.RlpDecode(data) - - return bi -} - -func (bm *BlockManager) CalculateTD(block *ethutil.Block) bool { - uncleDiff := new(big.Int) - for _, uncle := range block.Uncles { - uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty) - } - - // TD(genesis_block) = 0 and TD(B) = TD(B.parent) + sum(u.difficulty for u in B.uncles) + B.difficulty - td := new(big.Int) - td = td.Add(bm.bc.TD, uncleDiff) - td = td.Add(td, block.Difficulty) - - // The new TD will only be accepted if the new difficulty is - // is greater than the previous. - if td.Cmp(bm.bc.TD) > 0 { - bm.bc.LastBlock = block - // Set the new total difficulty back to the block chain - bm.bc.TD = td - - if Debug { - log.Println("TD(block) =", td) - } - - return true - } - - return false -} - -// Validates the current block. Returns an error if the block was invalid, -// an uncle or anything that isn't on the current block chain. -// Validation validates easy over difficult (dagger takes longer time = difficult) -func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error { - // Genesis block - if bm.bc.LastBlock == nil && block.PrevHash == "" { - return nil - } - // TODO - // 2. Check if the difficulty is correct - - // Check if we have the parent hash, if it isn't known we discard it - // Reasons might be catching up or simply an invalid block - if !bm.bc.HasBlock(block.PrevHash) { - return errors.New("Block's parent unknown") - } - - // Check each uncle's previous hash. In order for it to be valid - // is if it has the same block hash as the current - for _, uncle := range block.Uncles { - if uncle.PrevHash != block.PrevHash { - if Debug { - log.Printf("Uncle prvhash mismatch %x %x\n", block.PrevHash, uncle.PrevHash) - } - - return errors.New("Mismatching Prvhash from uncle") - } - } - - diff := block.Time - bm.bc.LastBlock.Time - if diff < 0 { - return fmt.Errorf("Block timestamp less then prev block %v", diff) - } - - // New blocks must be within the 15 minute range of the last block. - if diff > int64(15*time.Minute) { - return errors.New("Block is too far in the future of last block (> 15 minutes)") - } - - // Verify the nonce of the block. Return an error if it's not valid - if !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) { - - return errors.New("Block's nonce is invalid") - } - - log.Println("Block validation PASSED") - - return nil -} - -func (bm *BlockManager) AccumelateRewards(block *ethutil.Block) error { - // Get the coinbase rlp data - d := block.State().Get(block.Coinbase) - - ether := ethutil.NewEtherFromData([]byte(d)) - - // Reward amount of ether to the coinbase address - ether.AddFee(ethutil.CalculateBlockReward(block, len(block.Uncles))) - block.State().Update(block.Coinbase, string(ether.RlpEncode())) - - // TODO Reward each uncle - - return nil -} - -func (bm *BlockManager) ProcessContract(tx *ethutil.Transaction, block *ethutil.Block, lockChan chan bool) { - // Recovering function in case the VM had any errors - defer func() { - if r := recover(); r != nil { - fmt.Println("Recovered from VM execution with err =", r) - // Let the channel know where done even though it failed (so the execution may resume normally) - lockChan <- true - } - }() - - // Process contract - bm.ProcContract(tx, block, func(opType OpType) bool { - // TODO turn on once big ints are in place - //if !block.PayFee(tx.Hash(), StepFee.Uint64()) { - // return false - //} - - return true // Continue - }) - - // Broadcast we're done - lockChan <- true -} - -// Contract evaluation is done here. -func (bm *BlockManager) ProcContract(tx *ethutil.Transaction, block *ethutil.Block, cb TxCallback) { - // Instruction pointer - pc := 0 - blockInfo := bm.BlockInfo(block) - - contract := block.GetContract(tx.Hash()) - if contract == nil { - fmt.Println("Contract not found") - return - } - - Pow256 := ethutil.BigPow(2, 256) - - //fmt.Printf("# op arg\n") -out: - for { - // The base big int for all calculations. Use this for any results. - base := new(big.Int) - // XXX Should Instr return big int slice instead of string slice? - // Get the next instruction from the contract - //op, _, _ := Instr(contract.state.Get(string(Encode(uint32(pc))))) - nb := ethutil.NumberToBytes(uint64(pc), 32) - o, _, _ := ethutil.Instr(contract.State().Get(string(nb))) - op := OpCode(o) - - if !cb(0) { - break - } - - if Debug { - //fmt.Printf("%-3d %-4s\n", pc, op.String()) - } - - switch op { - case oSTOP: - break out - case oADD: - x, y := bm.stack.Popn() - // (x + y) % 2 ** 256 - base.Add(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - bm.stack.Push(base) - case oSUB: - x, y := bm.stack.Popn() - // (x - y) % 2 ** 256 - base.Sub(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - bm.stack.Push(base) - case oMUL: - x, y := bm.stack.Popn() - // (x * y) % 2 ** 256 - base.Mul(x, y) - base.Mod(base, Pow256) - // Pop result back on the stack - bm.stack.Push(base) - case oDIV: - x, y := bm.stack.Popn() - // floor(x / y) - base.Div(x, y) - // Pop result back on the stack - bm.stack.Push(base) - case oSDIV: - x, y := bm.stack.Popn() - // n > 2**255 - if x.Cmp(Pow256) > 0 { - x.Sub(Pow256, x) - } - if y.Cmp(Pow256) > 0 { - y.Sub(Pow256, y) - } - z := new(big.Int) - z.Div(x, y) - if z.Cmp(Pow256) > 0 { - z.Sub(Pow256, z) - } - // Push result on to the stack - bm.stack.Push(z) - case oMOD: - x, y := bm.stack.Popn() - base.Mod(x, y) - bm.stack.Push(base) - case oSMOD: - x, y := bm.stack.Popn() - // n > 2**255 - if x.Cmp(Pow256) > 0 { - x.Sub(Pow256, x) - } - if y.Cmp(Pow256) > 0 { - y.Sub(Pow256, y) - } - z := new(big.Int) - z.Mod(x, y) - if z.Cmp(Pow256) > 0 { - z.Sub(Pow256, z) - } - // Push result on to the stack - bm.stack.Push(z) - case oEXP: - x, y := bm.stack.Popn() - base.Exp(x, y, Pow256) - - bm.stack.Push(base) - case oNEG: - base.Sub(Pow256, bm.stack.Pop()) - bm.stack.Push(base) - case oLT: - x, y := bm.stack.Popn() - // x < y - if x.Cmp(y) < 0 { - bm.stack.Push(ethutil.BigTrue) - } else { - bm.stack.Push(ethutil.BigFalse) - } - case oLE: - x, y := bm.stack.Popn() - // x <= y - if x.Cmp(y) < 1 { - bm.stack.Push(ethutil.BigTrue) - } else { - bm.stack.Push(ethutil.BigFalse) - } - case oGT: - x, y := bm.stack.Popn() - // x > y - if x.Cmp(y) > 0 { - bm.stack.Push(ethutil.BigTrue) - } else { - bm.stack.Push(ethutil.BigFalse) - } - case oGE: - x, y := bm.stack.Popn() - // x >= y - if x.Cmp(y) > -1 { - bm.stack.Push(ethutil.BigTrue) - } else { - bm.stack.Push(ethutil.BigFalse) - } - case oNOT: - x, y := bm.stack.Popn() - // x != y - if x.Cmp(y) != 0 { - bm.stack.Push(ethutil.BigTrue) - } else { - bm.stack.Push(ethutil.BigFalse) - } - - // Please note that the following code contains some - // ugly string casting. This will have to change to big - // ints. TODO :) - case oMYADDRESS: - bm.stack.Push(ethutil.BigD(tx.Hash())) - case oTXSENDER: - bm.stack.Push(ethutil.BigD(tx.Sender())) - case oTXVALUE: - bm.stack.Push(tx.Value) - case oTXDATAN: - bm.stack.Push(big.NewInt(int64(len(tx.Data)))) - case oTXDATA: - v := bm.stack.Pop() - // v >= len(data) - if v.Cmp(big.NewInt(int64(len(tx.Data)))) >= 0 { - bm.stack.Push(ethutil.Big("0")) - } else { - bm.stack.Push(ethutil.Big(tx.Data[v.Uint64()])) - } - case oBLK_PREVHASH: - bm.stack.Push(ethutil.Big(block.PrevHash)) - case oBLK_COINBASE: - bm.stack.Push(ethutil.Big(block.Coinbase)) - case oBLK_TIMESTAMP: - bm.stack.Push(big.NewInt(block.Time)) - case oBLK_NUMBER: - bm.stack.Push(blockInfo.Number) - case oBLK_DIFFICULTY: - bm.stack.Push(block.Difficulty) - case oBASEFEE: - // e = 10^21 - e := big.NewInt(0).Exp(big.NewInt(10), big.NewInt(21), big.NewInt(0)) - d := new(big.Rat) - d.SetInt(block.Difficulty) - c := new(big.Rat) - c.SetFloat64(0.5) - // d = diff / 0.5 - d.Quo(d, c) - // base = floor(d) - base.Div(d.Num(), d.Denom()) - - x := new(big.Int) - x.Div(e, base) - - // x = floor(10^21 / floor(diff^0.5)) - bm.stack.Push(x) - case oSHA256, oSHA3, oRIPEMD160: - // This is probably save - // ceil(pop / 32) - length := int(math.Ceil(float64(bm.stack.Pop().Uint64()) / 32.0)) - // New buffer which will contain the concatenated popped items - data := new(bytes.Buffer) - for i := 0; i < length; i++ { - // Encode the number to bytes and have it 32bytes long - num := ethutil.NumberToBytes(bm.stack.Pop().Bytes(), 256) - data.WriteString(string(num)) - } - - if op == oSHA256 { - bm.stack.Push(base.SetBytes(ethutil.Sha256Bin(data.Bytes()))) - } else if op == oSHA3 { - bm.stack.Push(base.SetBytes(ethutil.Sha3Bin(data.Bytes()))) - } else { - bm.stack.Push(base.SetBytes(ethutil.Ripemd160(data.Bytes()))) - } - case oECMUL: - y := bm.stack.Pop() - x := bm.stack.Pop() - //n := bm.stack.Pop() - - //if ethutil.Big(x).Cmp(ethutil.Big(y)) { - data := new(bytes.Buffer) - data.WriteString(x.String()) - data.WriteString(y.String()) - if secp256k1.VerifyPubkeyValidity(data.Bytes()) == 1 { - // TODO - } else { - // Invalid, push infinity - bm.stack.Push(ethutil.Big("0")) - bm.stack.Push(ethutil.Big("0")) - } - //} else { - // // Invalid, push infinity - // bm.stack.Push("0") - // bm.stack.Push("0") - //} - - case oECADD: - case oECSIGN: - case oECRECOVER: - case oECVALID: - case oPUSH: - pc++ - bm.stack.Push(bm.mem[strconv.Itoa(pc)]) - case oPOP: - // Pop current value of the stack - bm.stack.Pop() - case oDUP: - // Dup top stack - x := bm.stack.Pop() - bm.stack.Push(x) - bm.stack.Push(x) - case oSWAP: - // Swap two top most values - x, y := bm.stack.Popn() - bm.stack.Push(y) - bm.stack.Push(x) - case oMLOAD: - x := bm.stack.Pop() - bm.stack.Push(bm.mem[x.String()]) - case oMSTORE: - x, y := bm.stack.Popn() - bm.mem[x.String()] = y - case oSLOAD: - // Load the value in storage and push it on the stack - x := bm.stack.Pop() - // decode the object as a big integer - decoder := ethutil.NewRlpDecoder([]byte(contract.State().Get(x.String()))) - if !decoder.IsNil() { - bm.stack.Push(decoder.AsBigInt()) - } else { - bm.stack.Push(ethutil.BigFalse) - } - case oSSTORE: - // Store Y at index X - x, y := bm.stack.Popn() - contract.State().Update(x.String(), string(ethutil.Encode(y))) - case oJMP: - x := int(bm.stack.Pop().Uint64()) - // Set pc to x - 1 (minus one so the incrementing at the end won't effect it) - pc = x - pc-- - case oJMPI: - x := bm.stack.Pop() - // Set pc to x if it's non zero - if x.Cmp(ethutil.BigFalse) != 0 { - pc = int(x.Uint64()) - pc-- - } - case oIND: - bm.stack.Push(big.NewInt(int64(pc))) - case oEXTRO: - memAddr := bm.stack.Pop() - contractAddr := bm.stack.Pop().Bytes() - - // Push the contract's memory on to the stack - bm.stack.Push(getContractMemory(block, contractAddr, memAddr)) - case oBALANCE: - // Pushes the balance of the popped value on to the stack - d := block.State().Get(bm.stack.Pop().String()) - ether := ethutil.NewEtherFromData([]byte(d)) - bm.stack.Push(ether.Amount) - case oMKTX: - value, addr := bm.stack.Popn() - from, length := bm.stack.Popn() - - j := 0 - dataItems := make([]string, int(length.Uint64())) - for i := from.Uint64(); i < length.Uint64(); i++ { - dataItems[j] = string(bm.mem[strconv.Itoa(int(i))].Bytes()) - j++ - } - // TODO sign it? - tx := ethutil.NewTransaction(string(addr.Bytes()), value, dataItems) - // Add the transaction to the tx pool - bm.server.txPool.QueueTransaction(tx) - case oSUICIDE: - //addr := bm.stack.Pop() - } - pc++ - } - - bm.stack.Print() -} - -// Returns an address from the specified contract's address -func getContractMemory(block *ethutil.Block, contractAddr []byte, memAddr *big.Int) *big.Int { - contract := block.GetContract(contractAddr) - if contract == nil { - log.Panicf("invalid contract addr %x", contractAddr) - } - val := contract.State().Get(memAddr.String()) - - // decode the object as a big integer - decoder := ethutil.NewRlpDecoder([]byte(val)) - if decoder.IsNil() { - return ethutil.BigFalse - } - - return decoder.AsBigInt() -} diff --git a/block_manager_test.go b/block_manager_test.go deleted file mode 100644 index 5f200f3e7..000000000 --- a/block_manager_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - _ "fmt" - "testing" -) - -func TestVm(t *testing.T) { - InitFees() - - db, _ := NewMemDatabase() - Db = db - - ctrct := NewTransaction("", 200000000, []string{ - "PUSH", "1a2f2e", - "PUSH", "hallo", - "POP", // POP hallo - "PUSH", "3", - "LOAD", // Load hallo back on the stack - - "PUSH", "1", - "PUSH", "2", - "ADD", - - "PUSH", "2", - "PUSH", "1", - "SUB", - - "PUSH", "100000000000000000000000", - "PUSH", "10000000000000", - "SDIV", - - "PUSH", "105", - "PUSH", "200", - "MOD", - - "PUSH", "100000000000000000000000", - "PUSH", "10000000000000", - "SMOD", - - "PUSH", "5", - "PUSH", "10", - "LT", - - "PUSH", "5", - "PUSH", "5", - "LE", - - "PUSH", "50", - "PUSH", "5", - "GT", - - "PUSH", "5", - "PUSH", "5", - "GE", - - "PUSH", "10", - "PUSH", "10", - "NOT", - - "MYADDRESS", - "TXSENDER", - - "STOP", - }) - tx := NewTransaction("1e8a42ea8cce13", 100, []string{}) - - block := CreateBlock("", 0, "", "c014ba53", 0, 0, "", []*Transaction{ctrct, tx}) - db.Put(block.Hash(), block.RlpEncode()) - - bm := NewBlockManager() - bm.ProcessBlock(block) -} diff --git a/dagger.go b/dagger.go deleted file mode 100644 index 966bfa461..000000000 --- a/dagger.go +++ /dev/null @@ -1,149 +0,0 @@ -package main - -import ( - "github.com/ethereum/ethutil-go" - "github.com/obscuren/sha3" - "hash" - "math/big" - "math/rand" - "time" - "log" -) - -type Dagger struct { - hash *big.Int - xn *big.Int -} - -var Found bool - -func (dag *Dagger) Find(obj *big.Int, resChan chan int64) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - - for i := 0; i < 1000; i++ { - rnd := r.Int63() - - res := dag.Eval(big.NewInt(rnd)) - log.Printf("rnd %v\nres %v\nobj %v\n", rnd, res, obj) - if res.Cmp(obj) < 0 { - // Post back result on the channel - resChan <- rnd - // Notify other threads we've found a valid nonce - Found = true - } - - // Break out if found - if Found { - break - } - } - - resChan <- 0 -} - -func (dag *Dagger) Search(hash, diff *big.Int) *big.Int { - // TODO fix multi threading. Somehow it results in the wrong nonce - amountOfRoutines := 1 - - dag.hash = hash - - obj := ethutil.BigPow(2, 256) - obj = obj.Div(obj, diff) - - Found = false - resChan := make(chan int64, 3) - var res int64 - - for k := 0; k < amountOfRoutines; k++ { - go dag.Find(obj, resChan) - } - - // Wait for each go routine to finish - for k := 0; k < amountOfRoutines; k++ { - // Get the result from the channel. 0 = quit - if r := <-resChan; r != 0 { - res = r - } - } - - return big.NewInt(res) -} - -func DaggerVerify(hash, diff, nonce *big.Int) bool { - dagger := &Dagger{} - dagger.hash = hash - - obj := ethutil.BigPow(2, 256) - obj = obj.Div(obj, diff) - - return dagger.Eval(nonce).Cmp(obj) < 0 -} - -func (dag *Dagger) Node(L uint64, i uint64) *big.Int { - if L == i { - return dag.hash - } - - var m *big.Int - if L == 9 { - m = big.NewInt(16) - } else { - m = big.NewInt(3) - } - - sha := sha3.NewKeccak256() - sha.Reset() - d := sha3.NewKeccak256() - b := new(big.Int) - ret := new(big.Int) - - for k := 0; k < int(m.Uint64()); k++ { - d.Reset() - d.Write(dag.hash.Bytes()) - d.Write(dag.xn.Bytes()) - d.Write(big.NewInt(int64(L)).Bytes()) - d.Write(big.NewInt(int64(i)).Bytes()) - d.Write(big.NewInt(int64(k)).Bytes()) - - b.SetBytes(Sum(d)) - pk := b.Uint64() & ((1 << ((L - 1) * 3)) - 1) - sha.Write(dag.Node(L-1, pk).Bytes()) - } - - ret.SetBytes(Sum(sha)) - - return ret -} - -func Sum(sha hash.Hash) []byte { - //in := make([]byte, 32) - return sha.Sum(nil) -} - -func (dag *Dagger) Eval(N *big.Int) *big.Int { - pow := ethutil.BigPow(2, 26) - dag.xn = pow.Div(N, pow) - - sha := sha3.NewKeccak256() - sha.Reset() - ret := new(big.Int) - - for k := 0; k < 4; k++ { - d := sha3.NewKeccak256() - b := new(big.Int) - - d.Reset() - d.Write(dag.hash.Bytes()) - d.Write(dag.xn.Bytes()) - d.Write(N.Bytes()) - d.Write(big.NewInt(int64(k)).Bytes()) - - b.SetBytes(Sum(d)) - pk := (b.Uint64() & 0x1ffffff) - - sha.Write(dag.Node(9, pk).Bytes()) - } - - return ret.SetBytes(Sum(sha)) -} - diff --git a/dagger_test.go b/dagger_test.go deleted file mode 100644 index 616577a39..000000000 --- a/dagger_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "github.com/ethereum/ethutil-go" - "math/big" - "testing" -) - -func BenchmarkDaggerSearch(b *testing.B) { - hash := big.NewInt(0) - diff := ethutil.BigPow(2, 36) - o := big.NewInt(0) // nonce doesn't matter. We're only testing against speed, not validity - - // Reset timer so the big generation isn't included in the benchmark - b.ResetTimer() - // Validate - DaggerVerify(hash, diff, o) -} diff --git a/dev_console.go b/dev_console.go index d14f019e5..b3e0d73f9 100644 --- a/dev_console.go +++ b/dev_console.go @@ -5,6 +5,8 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/ethchain-go" "github.com/ethereum/ethdb-go" "github.com/ethereum/ethutil-go" "os" @@ -12,16 +14,16 @@ import ( ) type Console struct { - db *ethdb.MemDatabase - trie *ethutil.Trie - server *Server + db *ethdb.MemDatabase + trie *ethutil.Trie + ethereum *eth.Ethereum } -func NewConsole(s *Server) *Console { +func NewConsole(s *eth.Ethereum) *Console { db, _ := ethdb.NewMemDatabase() trie := ethutil.NewTrie(db, "") - return &Console{db: db, trie: trie, server: s} + return &Console{db: db, trie: trie, ethereum: s} } func (i *Console) ValidateInput(action string, argumentLength int) error { @@ -101,7 +103,7 @@ func (i *Console) ParseInput(input string) bool { case "print": i.db.Print() case "dag": - fmt.Println(DaggerVerify(ethutil.Big(tokens[1]), // hash + fmt.Println(ethchain.DaggerVerify(ethutil.Big(tokens[1]), // hash ethutil.BigPow(2, 36), // diff ethutil.Big(tokens[2]))) // nonce case "decode": @@ -112,7 +114,7 @@ func (i *Console) ParseInput(input string) bool { case "tx": tx := ethutil.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) - i.server.txPool.QueueTransaction(tx) + i.ethereum.TxPool.QueueTransaction(tx) case "exit", "quit", "q": return false case "help": diff --git a/ethereum.go b/ethereum.go index b7f059a02..7988f8418 100644 --- a/ethereum.go +++ b/ethereum.go @@ -3,6 +3,8 @@ package main import ( "flag" "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/ethchain-go" "github.com/ethereum/ethutil-go" "log" "os" @@ -23,8 +25,8 @@ func Init() { flag.Parse() } -// Register interrupt handlers so we can stop the server -func RegisterInterupts(s *Server) { +// Register interrupt handlers so we can stop the ethereum +func RegisterInterupts(s *eth.Ethereum) { // Buffered chan of one is enough c := make(chan os.Signal, 1) // Notify about interrupts for now @@ -40,15 +42,13 @@ func RegisterInterupts(s *Server) { func main() { runtime.GOMAXPROCS(runtime.NumCPU()) - - ethutil.InitFees() - Init() + ethutil.InitFees() ethutil.ReadConfig() - server, err := NewServer() - + // Instantiated a eth stack + ethereum, err := eth.New() if err != nil { log.Println(err) return @@ -70,29 +70,29 @@ func main() { ethutil.Config.Log = log.New(file, "", 0) - console := NewConsole(server) + console := NewConsole(ethereum) go console.Start() } log.Println("Starting Ethereum") - RegisterInterupts(server) + RegisterInterupts(ethereum) if StartMining { log.Println("Mining started") - dagger := &Dagger{} + dagger := ðchain.Dagger{} go func() { for { res := dagger.Search(ethutil.Big("01001"), ethutil.BigPow(2, 36)) log.Println("Res dagger", res) - //server.Broadcast("blockmine", ethutil.Encode(res.String())) + //ethereum.Broadcast("blockmine", ethutil.Encode(res.String())) } }() } - server.Start() + ethereum.Start() // Wait for shutdown - server.WaitForShutdown() + ethereum.WaitForShutdown() } diff --git a/peer.go b/peer.go deleted file mode 100644 index 207f9e59f..000000000 --- a/peer.go +++ /dev/null @@ -1,303 +0,0 @@ -package main - -import ( - "github.com/ethereum/ethutil-go" - "github.com/ethereum/ethwire-go" - "log" - "net" - "strconv" - "sync/atomic" - "time" -) - -const ( - // The size of the output buffer for writing messages - outputBufferSize = 50 -) - -type Peer struct { - // Server interface - server *Server - // Net connection - conn net.Conn - // Output queue which is used to communicate and handle messages - outputQueue chan *ethwire.Msg - // Quit channel - quit chan bool - // Determines whether it's an inbound or outbound peer - inbound bool - // Flag for checking the peer's connectivity state - connected int32 - disconnect int32 - // Last known message send - lastSend time.Time - // Indicated whether a verack has been send or not - // This flag is used by writeMessage to check if messages are allowed - // to be send or not. If no version is known all messages are ignored. - versionKnown bool - - // Last received pong message - lastPong int64 - // Indicates whether a MsgGetPeersTy was requested of the peer - // this to prevent receiving false peers. - requestedPeerList bool -} - -func NewPeer(conn net.Conn, server *Server, inbound bool) *Peer { - return &Peer{ - outputQueue: make(chan *ethwire.Msg, outputBufferSize), - quit: make(chan bool), - server: server, - conn: conn, - inbound: inbound, - disconnect: 0, - connected: 1, - } -} - -func NewOutboundPeer(addr string, server *Server) *Peer { - p := &Peer{ - outputQueue: make(chan *ethwire.Msg, outputBufferSize), - quit: make(chan bool), - server: server, - inbound: false, - connected: 0, - disconnect: 0, - } - - // Set up the connection in another goroutine so we don't block the main thread - go func() { - conn, err := net.Dial("tcp", addr) - if err != nil { - p.Stop() - } - p.conn = conn - - // Atomically set the connection state - atomic.StoreInt32(&p.connected, 1) - atomic.StoreInt32(&p.disconnect, 0) - - log.Println("Connected to peer ::", conn.RemoteAddr()) - - p.Start() - }() - - return p -} - -// Outputs any RLP encoded data to the peer -func (p *Peer) QueueMessage(msg *ethwire.Msg) { - p.outputQueue <- msg -} - -func (p *Peer) writeMessage(msg *ethwire.Msg) { - // Ignore the write if we're not connected - if atomic.LoadInt32(&p.connected) != 1 { - return - } - - if !p.versionKnown { - switch msg.Type { - case ethwire.MsgHandshakeTy: // Ok - default: // Anything but ack is allowed - return - } - } - - err := ethwire.WriteMessage(p.conn, msg) - if err != nil { - log.Println("Can't send message:", err) - // Stop the client if there was an error writing to it - p.Stop() - return - } -} - -// Outbound message handler. Outbound messages are handled here -func (p *Peer) HandleOutbound() { - // The ping timer. Makes sure that every 2 minutes a ping is send to the peer - tickleTimer := time.NewTicker(2 * time.Minute) -out: - for { - select { - // Main message queue. All outbound messages are processed through here - case msg := <-p.outputQueue: - p.writeMessage(msg) - - p.lastSend = time.Now() - - case <-tickleTimer.C: - p.writeMessage(ðwire.Msg{Type: ethwire.MsgPingTy}) - - // Break out of the for loop if a quit message is posted - case <-p.quit: - break out - } - } - -clean: - // This loop is for draining the output queue and anybody waiting for us - for { - select { - case <-p.outputQueue: - // TODO - default: - break clean - } - } -} - -// Inbound handler. Inbound messages are received here and passed to the appropriate methods -func (p *Peer) HandleInbound() { - -out: - for atomic.LoadInt32(&p.disconnect) == 0 { - // Wait for a message from the peer - msg, err := ethwire.ReadMessage(p.conn) - if err != nil { - log.Println(err) - - break out - } - - if Debug { - log.Printf("Received %s\n", msg.Type.String()) - } - - switch msg.Type { - case ethwire.MsgHandshakeTy: - // Version message - p.handleHandshake(msg) - case ethwire.MsgBlockTy: - err := p.server.blockManager.ProcessBlock(ethutil.NewBlock(msg.Data)) - if err != nil { - log.Println(err) - } - case ethwire.MsgTxTy: - p.server.txPool.QueueTransaction(ethutil.NewTransactionFromData(msg.Data)) - case ethwire.MsgInvTy: - case ethwire.MsgGetPeersTy: - p.requestedPeerList = true - // Peer asked for list of connected peers - p.pushPeers() - case ethwire.MsgPeersTy: - // Received a list of peers (probably because MsgGetPeersTy was send) - // Only act on message if we actually requested for a peers list - if p.requestedPeerList { - data := ethutil.Conv(msg.Data) - // Create new list of possible peers for the server to process - peers := make([]string, data.Length()) - // Parse each possible peer - for i := 0; i < data.Length(); i++ { - peers[i] = data.Get(i).AsString() + strconv.Itoa(int(data.Get(i).AsUint())) - } - - // Connect to the list of peers - p.server.ProcessPeerList(peers) - // Mark unrequested again - p.requestedPeerList = false - } - case ethwire.MsgPingTy: - // Respond back with pong - p.QueueMessage(ðwire.Msg{Type: ethwire.MsgPongTy}) - case ethwire.MsgPongTy: - p.lastPong = time.Now().Unix() - } - } - - p.Stop() -} - -func (p *Peer) Start() { - if !p.inbound { - err := p.pushHandshake() - if err != nil { - log.Printf("Peer can't send outbound version ack", err) - - p.Stop() - } - } - - // Run the outbound handler in a new goroutine - go p.HandleOutbound() - // Run the inbound handler in a new goroutine - go p.HandleInbound() -} - -func (p *Peer) Stop() { - if atomic.AddInt32(&p.disconnect, 1) != 1 { - return - } - - close(p.quit) - if atomic.LoadInt32(&p.connected) != 0 { - p.conn.Close() - } - - log.Println("Peer shutdown") -} - -func (p *Peer) pushHandshake() error { - msg := ethwire.NewMessage(ethwire.MsgHandshakeTy, ethutil.Encode([]interface{}{ - 1, 0, p.server.Nonce, - })) - - p.QueueMessage(msg) - - return nil -} - -// Pushes the list of outbound peers to the client when requested -func (p *Peer) pushPeers() { - outPeers := make([]interface{}, len(p.server.OutboundPeers())) - // Serialise each peer - for i, peer := range p.server.OutboundPeers() { - outPeers[i] = peer.RlpEncode() - } - - // Send message to the peer with the known list of connected clients - msg := ethwire.NewMessage(ethwire.MsgPeersTy, ethutil.Encode(outPeers)) - - p.QueueMessage(msg) -} - -func (p *Peer) handleHandshake(msg *ethwire.Msg) { - c := ethutil.Conv(msg.Data) - // [PROTOCOL_VERSION, NETWORK_ID, CLIENT_ID] - if c.Get(2).AsUint() == p.server.Nonce { - //if msg.Nonce == p.server.Nonce { - log.Println("Peer connected to self, disconnecting") - - p.Stop() - - return - } - - p.versionKnown = true - - // If this is an inbound connection send an ack back - if p.inbound { - err := p.pushHandshake() - if err != nil { - log.Println("Peer can't send ack back") - - p.Stop() - } - } -} - -func (p *Peer) RlpEncode() []byte { - host, prt, err := net.SplitHostPort(p.conn.RemoteAddr().String()) - if err != nil { - return nil - } - - i, err := strconv.Atoi(prt) - if err != nil { - return nil - } - - port := ethutil.NumberToBytes(uint16(i), 16) - - return ethutil.Encode([]interface{}{host, port}) -} diff --git a/server.go b/server.go deleted file mode 100644 index 2927f023a..000000000 --- a/server.go +++ /dev/null @@ -1,208 +0,0 @@ -package main - -import ( - "container/list" - "github.com/ethereum/ethdb-go" - "github.com/ethereum/ethutil-go" - "github.com/ethereum/ethwire-go" - "log" - "net" - "sync/atomic" - "time" -) - -func eachPeer(peers *list.List, callback func(*Peer, *list.Element)) { - // Loop thru the peers and close them (if we had them) - for e := peers.Front(); e != nil; e = e.Next() { - if peer, ok := e.Value.(*Peer); ok { - callback(peer, e) - } - } -} - -const ( - processReapingTimeout = 60 // TODO increase -) - -type Server struct { - // Channel for shutting down the server - shutdownChan chan bool - // DB interface - //db *ethdb.LDBDatabase - db *ethdb.MemDatabase - // Block manager for processing new blocks and managing the block chain - blockManager *BlockManager - // The transaction pool. Transaction can be pushed on this pool - // for later including in the blocks - txPool *TxPool - // Peers (NYI) - peers *list.List - // Nonce - Nonce uint64 -} - -func NewServer() (*Server, error) { - //db, err := ethdb.NewLDBDatabase() - db, err := ethdb.NewMemDatabase() - if err != nil { - return nil, err - } - - ethutil.Config.Db = db - - nonce, _ := ethutil.RandomUint64() - server := &Server{ - shutdownChan: make(chan bool), - db: db, - peers: list.New(), - Nonce: nonce, - } - server.txPool = NewTxPool(server) - server.blockManager = NewBlockManager(server) - - return server, nil -} - -func (s *Server) AddPeer(conn net.Conn) { - peer := NewPeer(conn, s, true) - - if peer != nil { - s.peers.PushBack(peer) - peer.Start() - - log.Println("Peer connected ::", conn.RemoteAddr()) - } -} - -func (s *Server) ProcessPeerList(addrs []string) { - for _, addr := range addrs { - // TODO Probably requires some sanity checks - s.ConnectToPeer(addr) - } -} - -func (s *Server) ConnectToPeer(addr string) error { - peer := NewOutboundPeer(addr, s) - - s.peers.PushBack(peer) - - return nil -} - -func (s *Server) OutboundPeers() []*Peer { - // Create a new peer slice with at least the length of the total peers - outboundPeers := make([]*Peer, s.peers.Len()) - length := 0 - eachPeer(s.peers, func(p *Peer, e *list.Element) { - if !p.inbound { - outboundPeers[length] = p - length++ - } - }) - - return outboundPeers[:length] -} - -func (s *Server) InboundPeers() []*Peer { - // Create a new peer slice with at least the length of the total peers - inboundPeers := make([]*Peer, s.peers.Len()) - length := 0 - eachPeer(s.peers, func(p *Peer, e *list.Element) { - if p.inbound { - inboundPeers[length] = p - length++ - } - }) - - return inboundPeers[:length] -} - -func (s *Server) Broadcast(msgType ethwire.MsgType, data []byte) { - eachPeer(s.peers, func(p *Peer, e *list.Element) { - p.QueueMessage(ethwire.NewMessage(msgType, data)) - }) -} - -func (s *Server) ReapDeadPeers() { - for { - eachPeer(s.peers, func(p *Peer, e *list.Element) { - if atomic.LoadInt32(&p.disconnect) == 1 || (p.inbound && (time.Now().Unix()-p.lastPong) > int64(5*time.Minute)) { - log.Println("Dead peer found .. reaping") - - s.peers.Remove(e) - } - }) - - time.Sleep(processReapingTimeout * time.Second) - } -} - -// Start the server -func (s *Server) Start() { - // For now this function just blocks the main thread - ln, err := net.Listen("tcp", ":12345") - if err != nil { - // This is mainly for testing to create a "network" - if Debug { - log.Println("Connection listening disabled. Acting as client") - - err = s.ConnectToPeer("localhost:12345") - if err != nil { - log.Println("Error starting server", err) - - s.Stop() - } - } else { - log.Fatal(err) - } - } else { - // Starting accepting connections - go func() { - for { - conn, err := ln.Accept() - if err != nil { - log.Println(err) - - continue - } - - go s.AddPeer(conn) - } - }() - } - - // Start the reaping processes - go s.ReapDeadPeers() - - // Start the tx pool - s.txPool.Start() - - // TMP - /* - go func() { - for { - s.Broadcast("block", s.blockManager.bc.GenesisBlock().RlpEncode()) - - time.Sleep(1000 * time.Millisecond) - } - }() - */ -} - -func (s *Server) Stop() { - // Close the database - defer s.db.Close() - - eachPeer(s.peers, func(p *Peer, e *list.Element) { - p.Stop() - }) - - s.shutdownChan <- true - - s.txPool.Stop() -} - -// This function will wait for a shutdown and resumes main thread execution -func (s *Server) WaitForShutdown() { - <-s.shutdownChan -} diff --git a/stack.go b/stack.go deleted file mode 100644 index 9d595d85b..000000000 --- a/stack.go +++ /dev/null @@ -1,167 +0,0 @@ -package main - -import ( - "fmt" - "math/big" -) - -type OpCode int - -// Op codes -const ( - oSTOP OpCode = iota - oADD - oMUL - oSUB - oDIV - oSDIV - oMOD - oSMOD - oEXP - oNEG - oLT - oLE - oGT - oGE - oEQ - oNOT - oMYADDRESS - oTXSENDER - oTXVALUE - oTXFEE - oTXDATAN - oTXDATA - oBLK_PREVHASH - oBLK_COINBASE - oBLK_TIMESTAMP - oBLK_NUMBER - oBLK_DIFFICULTY - oBASEFEE - oSHA256 OpCode = 32 - oRIPEMD160 OpCode = 33 - oECMUL OpCode = 34 - oECADD OpCode = 35 - oECSIGN OpCode = 36 - oECRECOVER OpCode = 37 - oECVALID OpCode = 38 - oSHA3 OpCode = 39 - oPUSH OpCode = 48 - oPOP OpCode = 49 - oDUP OpCode = 50 - oSWAP OpCode = 51 - oMLOAD OpCode = 52 - oMSTORE OpCode = 53 - oSLOAD OpCode = 54 - oSSTORE OpCode = 55 - oJMP OpCode = 56 - oJMPI OpCode = 57 - oIND OpCode = 58 - oEXTRO OpCode = 59 - oBALANCE OpCode = 60 - oMKTX OpCode = 61 - oSUICIDE OpCode = 62 -) - -// Since the opcodes aren't all in order we can't use a regular slice -var opCodeToString = map[OpCode]string{ - oSTOP: "STOP", - oADD: "ADD", - oMUL: "MUL", - oSUB: "SUB", - oDIV: "DIV", - oSDIV: "SDIV", - oMOD: "MOD", - oSMOD: "SMOD", - oEXP: "EXP", - oNEG: "NEG", - oLT: "LT", - oLE: "LE", - oGT: "GT", - oGE: "GE", - oEQ: "EQ", - oNOT: "NOT", - oMYADDRESS: "MYADDRESS", - oTXSENDER: "TXSENDER", - oTXVALUE: "TXVALUE", - oTXFEE: "TXFEE", - oTXDATAN: "TXDATAN", - oTXDATA: "TXDATA", - oBLK_PREVHASH: "BLK_PREVHASH", - oBLK_COINBASE: "BLK_COINBASE", - oBLK_TIMESTAMP: "BLK_TIMESTAMP", - oBLK_NUMBER: "BLK_NUMBER", - oBLK_DIFFICULTY: "BLK_DIFFICULTY", - oBASEFEE: "BASEFEE", - oSHA256: "SHA256", - oRIPEMD160: "RIPEMD160", - oECMUL: "ECMUL", - oECADD: "ECADD", - oECSIGN: "ECSIGN", - oECRECOVER: "ECRECOVER", - oECVALID: "ECVALID", - oSHA3: "SHA3", - oPUSH: "PUSH", - oPOP: "POP", - oDUP: "DUP", - oSWAP: "SWAP", - oMLOAD: "MLOAD", - oMSTORE: "MSTORE", - oSLOAD: "SLOAD", - oSSTORE: "SSTORE", - oJMP: "JMP", - oJMPI: "JMPI", - oIND: "IND", - oEXTRO: "EXTRO", - oBALANCE: "BALANCE", - oMKTX: "MKTX", - oSUICIDE: "SUICIDE", -} - -func (o OpCode) String() string { - return opCodeToString[o] -} - -type OpType int - -const ( - tNorm = iota - tData - tExtro - tCrypto -) - -type TxCallback func(opType OpType) bool - -// Simple push/pop stack mechanism -type Stack struct { - data []*big.Int -} - -func NewStack() *Stack { - return &Stack{} -} - -func (st *Stack) Pop() *big.Int { - s := len(st.data) - - str := st.data[s-1] - st.data = st.data[:s-1] - - return str -} - -func (st *Stack) Popn() (*big.Int, *big.Int) { - s := len(st.data) - - ints := st.data[s-2:] - st.data = st.data[:s-2] - - return ints[0], ints[1] -} - -func (st *Stack) Push(d *big.Int) { - st.data = append(st.data, d) -} -func (st *Stack) Print() { - fmt.Println(st.data) -} diff --git a/transaction_pool.go b/transaction_pool.go deleted file mode 100644 index b302931de..000000000 --- a/transaction_pool.go +++ /dev/null @@ -1,177 +0,0 @@ -package main - -import ( - "bytes" - "container/list" - "errors" - "github.com/ethereum/ethutil-go" - "github.com/ethereum/ethwire-go" - "log" - "math/big" - "sync" -) - -const ( - txPoolQueueSize = 50 -) - -func FindTx(pool *list.List, finder func(*ethutil.Transaction, *list.Element) bool) *ethutil.Transaction { - for e := pool.Front(); e != nil; e = e.Next() { - if tx, ok := e.Value.(*ethutil.Transaction); ok { - if finder(tx, e) { - return tx - } - } - } - - return nil -} - -// The tx pool a thread safe transaction pool handler. In order to -// guarantee a non blocking pool we use a queue channel which can be -// independently read without needing access to the actual pool. If the -// pool is being drained or synced for whatever reason the transactions -// will simple queue up and handled when the mutex is freed. -type TxPool struct { - server *Server - // The mutex for accessing the Tx pool. - mutex sync.Mutex - // Queueing channel for reading and writing incoming - // transactions to - queueChan chan *ethutil.Transaction - // Quiting channel - quit chan bool - - pool *list.List -} - -func NewTxPool(s *Server) *TxPool { - return &TxPool{ - server: s, - mutex: sync.Mutex{}, - pool: list.New(), - queueChan: make(chan *ethutil.Transaction, txPoolQueueSize), - quit: make(chan bool), - } -} - -// Blocking function. Don't use directly. Use QueueTransaction instead -func (pool *TxPool) addTransaction(tx *ethutil.Transaction) { - pool.mutex.Lock() - pool.pool.PushBack(tx) - pool.mutex.Unlock() - - // Broadcast the transaction to the rest of the peers - pool.server.Broadcast(ethwire.MsgTxTy, tx.RlpEncode()) -} - -// Process transaction validates the Tx and processes funds from the -// sender to the recipient. -func (pool *TxPool) processTransaction(tx *ethutil.Transaction) error { - // Get the last block so we can retrieve the sender and receiver from - // the merkle trie - block := pool.server.blockManager.bc.LastBlock - // Something has gone horribly wrong if this happens - if block == nil { - return errors.New("No last block on the block chain") - } - - var sender, receiver *ethutil.Ether - - // Get the sender - data := block.State().Get(string(tx.Sender())) - // If it doesn't exist create a new account. Of course trying to send funds - // from this account will fail since it will hold 0 Wei - if data == "" { - sender = ethutil.NewEther(big.NewInt(0)) - } else { - sender = ethutil.NewEtherFromData([]byte(data)) - } - // Defer the update. Whatever happens it should be persisted - defer block.State().Update(string(tx.Sender()), string(sender.RlpEncode())) - - // Make sure there's enough in the sender's account. Having insufficient - // funds won't invalidate this transaction but simple ignores it. - if sender.Amount.Cmp(tx.Value) < 0 { - if Debug { - log.Println("Insufficient amount in sender's account. Adding 1 ETH for debug") - sender.Amount = ethutil.BigPow(10, 18) - } else { - return errors.New("Insufficient amount in sender's account") - } - } - - // Subtract the amount from the senders account - sender.Amount.Sub(sender.Amount, tx.Value) - // Increment the nonce making each tx valid only once to prevent replay - // attacks - sender.Nonce += 1 - - // Get the receiver - data = block.State().Get(tx.Recipient) - // If the receiver doesn't exist yet, create a new account to which the - // funds will be send. - if data == "" { - receiver = ethutil.NewEther(big.NewInt(0)) - } else { - receiver = ethutil.NewEtherFromData([]byte(data)) - } - // Defer the update - defer block.State().Update(tx.Recipient, string(receiver.RlpEncode())) - - // Add the amount to receivers account which should conclude this transaction - receiver.Amount.Add(receiver.Amount, tx.Value) - - return nil -} - -func (pool *TxPool) queueHandler() { -out: - for { - select { - case tx := <-pool.queueChan: - hash := tx.Hash() - foundTx := FindTx(pool.pool, func(tx *ethutil.Transaction, e *list.Element) bool { - return bytes.Compare(tx.Hash(), hash) == 0 - }) - - if foundTx != nil { - break - } - - // Process the transaction - err := pool.processTransaction(tx) - if err != nil { - log.Println("Error processing Tx", err) - } else { - // Call blocking version. At this point it - // doesn't matter since this is a goroutine - pool.addTransaction(tx) - } - case <-pool.quit: - break out - } - } -} - -func (pool *TxPool) QueueTransaction(tx *ethutil.Transaction) { - pool.queueChan <- tx -} - -func (pool *TxPool) Flush() { - pool.mutex.Lock() - - defer pool.mutex.Unlock() -} - -func (pool *TxPool) Start() { - go pool.queueHandler() -} - -func (pool *TxPool) Stop() { - log.Println("[TXP] Stopping...") - - close(pool.quit) - - pool.Flush() -} -- cgit v1.2.3 From 501db83dfd4061c75822c622512cee31935ab0a6 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 23 Jan 2014 21:14:44 +0100 Subject: Updated readme --- README.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 05e0156b9..dadafcbc6 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,29 @@ A fair warning; Ethereum is not yet to be used in production. There's no test-net and you aren't mining real blocks (just one which is the genesis block). -Ethereum Go is split up in several sub packages. Please refer to each +Ethereum Go is split up in several sub packages Please refer to each individual package for more information. - 1. [ethutil](https://github.com/ethereum/ethutil-go) - 2. [ethdb](https://github.com/ethereum/ethdb-go) + 1. [eth](https://github.com/ethereum/eth-go) + 2. [ethchain](https://github.com/ethereum/ethchain-go) 3. [ethwire](https://github.com/ethereum/ethwire-go) + 4. [ethdb](https://github.com/ethereum/ethdb-go) + 5. [ethutil](https://github.com/ethereum/ethutil-go) + +The [eth](https://github.com/ethereum/eth-go) is the top-level package +of the Ethereum protocol. It functions as the Ethereum bootstrapping and +peer communication layer. The [ethchain](https://github.com/ethereum/ethchain-go) +contains the Ethereum blockchain, block manager, transaction and +transaction handlers. The [ethwire](https://github.com/ethereum/ethwire-go) contains +the Ethereum [wire protocol](http://wiki.ethereum.org/index.php/Wire_Protocol) which can be used +to hook in to the Ethereum network. [ethutil](https://github.com/ethereum/ethutil-go) contains +utility functions which are not Ethereum specific. The utility package +contains the [patricia trie](http://wiki.ethereum.org/index.php/Patricia_Tree), +[RLP Encoding](http://wiki.ethereum.org/index.php/RLP) and hex encoding +helpers. The [ethdb](https://github.com/ethereum/ethdb-go) package +contains the LevelDB interface and memory DB interface. + +This executable is the front-end (currently nothing but a dev console) for +the Ethereum Go implementation. Deps ==== -- cgit v1.2.3 From 7bf4f2a908235c75e795d0ca3796c76f9fc99454 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 23 Jan 2014 22:33:51 +0100 Subject: tx is now part of the chain package --- dev_console.go | 2 +- ethereum.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev_console.go b/dev_console.go index b3e0d73f9..b515b3e5c 100644 --- a/dev_console.go +++ b/dev_console.go @@ -112,7 +112,7 @@ func (i *Console) ParseInput(input string) bool { case "encode": fmt.Printf("%q\n", ethutil.Encode(tokens[1])) case "tx": - tx := ethutil.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) + tx := ethchain.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) i.ethereum.TxPool.QueueTransaction(tx) case "exit", "quit", "q": diff --git a/ethereum.go b/ethereum.go index 7988f8418..e7775f143 100644 --- a/ethereum.go +++ b/ethereum.go @@ -44,7 +44,7 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) Init() - ethutil.InitFees() + ethchain.InitFees() ethutil.ReadConfig() // Instantiated a eth stack -- cgit v1.2.3 From c636f8e3e66a24a13d2cc6b2e89280f362e0f91b Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 24 Jan 2014 17:51:35 +0100 Subject: Broadcast block --- dev_console.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/dev_console.go b/dev_console.go index b515b3e5c..252860fc8 100644 --- a/dev_console.go +++ b/dev_console.go @@ -9,6 +9,8 @@ import ( "github.com/ethereum/ethchain-go" "github.com/ethereum/ethdb-go" "github.com/ethereum/ethutil-go" + "github.com/ethereum/ethwire-go" + "math/big" "os" "strings" ) @@ -49,6 +51,9 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "tx" && argumentLength != 2: err = true expArgCount = 2 + case action == "getaddr" && argumentLength != 1: + err = true + expArgCount = 1 } if err { @@ -109,8 +114,35 @@ func (i *Console) ParseInput(input string) bool { case "decode": d, _ := ethutil.Decode([]byte(tokens[1]), 0) fmt.Printf("%q\n", d) + case "getaddr": + encoded, _ := hex.DecodeString(tokens[1]) + d := i.ethereum.BlockManager.BlockChain().LastBlock.State().Get(string(encoded)) + if d != "" { + decoder := ethutil.NewRlpDecoder([]byte(d)) + fmt.Println(decoder) + } else { + fmt.Println("getaddr: address unknown") + } case "encode": fmt.Printf("%q\n", ethutil.Encode(tokens[1])) + case "newblk": + block := ethchain.CreateBlock( + i.ethereum.BlockManager.BlockChain().LastBlock.State().Root, + i.ethereum.BlockManager.LastBlockHash, + "123", + big.NewInt(1), + big.NewInt(1), + "", + i.ethereum.TxPool.Flush(), + ) + i.ethereum.Broadcast(ethwire.MsgBlockTy, block.RlpData()) + //fmt.Println(ethutil.NewRlpValue(block.RlpData()).Get(0)) + //err := i.ethereum.BlockManager.ProcessBlock(block) + //if err != nil { + // fmt.Println(err) + //} else { + + // } case "tx": tx := ethchain.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) -- cgit v1.2.3 From 97882a65bbe87beed8f939591f13ee01f7af6fa7 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 24 Jan 2014 20:16:48 +0100 Subject: Dev test mining --- dev_console.go | 54 ++++++++++++++++++++++++++++++++++-------------------- ethereum.go | 28 +++++++++++++++++++++------- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/dev_console.go b/dev_console.go index 252860fc8..f2aa4e08d 100644 --- a/dev_console.go +++ b/dev_console.go @@ -9,8 +9,8 @@ import ( "github.com/ethereum/ethchain-go" "github.com/ethereum/ethdb-go" "github.com/ethereum/ethutil-go" - "github.com/ethereum/ethwire-go" - "math/big" + _ "github.com/ethereum/ethwire-go" + _ "math/big" "os" "strings" ) @@ -48,6 +48,9 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "encode" && argumentLength != 1: err = true expArgCount = 1 + case action == "gettx" && argumentLength != 1: + err = true + expArgCount = 1 case action == "tx" && argumentLength != 2: err = true expArgCount = 2 @@ -125,28 +128,39 @@ func (i *Console) ParseInput(input string) bool { } case "encode": fmt.Printf("%q\n", ethutil.Encode(tokens[1])) - case "newblk": - block := ethchain.CreateBlock( - i.ethereum.BlockManager.BlockChain().LastBlock.State().Root, - i.ethereum.BlockManager.LastBlockHash, - "123", - big.NewInt(1), - big.NewInt(1), - "", - i.ethereum.TxPool.Flush(), - ) - i.ethereum.Broadcast(ethwire.MsgBlockTy, block.RlpData()) - //fmt.Println(ethutil.NewRlpValue(block.RlpData()).Get(0)) - //err := i.ethereum.BlockManager.ProcessBlock(block) - //if err != nil { - // fmt.Println(err) - //} else { - - // } + /* + case "newblk": + block := ethchain.CreateBlock( + i.ethereum.BlockManager.BlockChain().LastBlock.State().Root, + i.ethereum.BlockManager.LastBlockHash, + "123", + big.NewInt(1), + big.NewInt(1), + "", + i.ethereum.TxPool.Flush(), + ) + err := i.ethereum.BlockManager.ProcessBlock(block) + if err != nil { + fmt.Println(err) + } else { + i.ethereum.Broadcast(ethwire.MsgBlockTy, block.RlpData()) + } + //fmt.Println(ethutil.NewRlpValue(block.RlpData()).Get(0)) + */ case "tx": tx := ethchain.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) + fmt.Printf("tx: %x\n", tx.Hash()) i.ethereum.TxPool.QueueTransaction(tx) + case "gettx": + addr, _ := hex.DecodeString(tokens[1]) + data, _ := ethutil.Config.Db.Get(addr) + if len(data) != 0 { + decoder := ethutil.NewRlpDecoder(data) + fmt.Println(decoder) + } else { + fmt.Println("gettx: tx not found") + } case "exit", "quit", "q": return false case "help": diff --git a/ethereum.go b/ethereum.go index e7775f143..9d81880f2 100644 --- a/ethereum.go +++ b/ethereum.go @@ -6,11 +6,14 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/ethchain-go" "github.com/ethereum/ethutil-go" + "github.com/ethereum/ethwire-go" "log" + "math/big" "os" "os/signal" "path" "runtime" + "time" ) const Debug = true @@ -78,21 +81,32 @@ func main() { RegisterInterupts(ethereum) + ethereum.Start() + if StartMining { - log.Println("Mining started") - dagger := ðchain.Dagger{} + log.Println("Dev Test Mining started") + // Fake block mining. It broadcasts a new block every 5 seconds go func() { for { - res := dagger.Search(ethutil.Big("01001"), ethutil.BigPow(2, 36)) - log.Println("Res dagger", res) - //ethereum.Broadcast("blockmine", ethutil.Encode(res.String())) + time.Sleep(5 * time.Second) + + block := ethchain.CreateBlock( + ethereum.BlockManager.BlockChain().LastBlock.State().Root, + ethereum.BlockManager.LastBlockHash, + "123", + big.NewInt(1), + big.NewInt(1), + "", + ethereum.TxPool.Flush()) + + ethereum.BlockManager.ProcessBlockWithState(block, block.State()) + ethereum.Broadcast(ethwire.MsgBlockTy, block.RlpData()) + log.Println("\n", block.String()) } }() } - ethereum.Start() - // Wait for shutdown ethereum.WaitForShutdown() } -- cgit v1.2.3 From e32b1a1d975e1bd8dfc3211b400872ba42bf1498 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 25 Jan 2014 01:25:36 +0100 Subject: Fake block mining --- ethereum.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ethereum.go b/ethereum.go index 9d81880f2..2d79659b0 100644 --- a/ethereum.go +++ b/ethereum.go @@ -6,7 +6,7 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/ethchain-go" "github.com/ethereum/ethutil-go" - "github.com/ethereum/ethwire-go" + _ "github.com/ethereum/ethwire-go" "log" "math/big" "os" @@ -84,12 +84,15 @@ func main() { ethereum.Start() if StartMining { - log.Println("Dev Test Mining started") + blockTime := time.Duration(15) + log.Printf("Dev Test Mining started. Blocks found each %d seconds\n", blockTime) // Fake block mining. It broadcasts a new block every 5 seconds go func() { for { - time.Sleep(5 * time.Second) + txs := ethereum.TxPool.Flush() + + time.Sleep(blockTime * time.Second) block := ethchain.CreateBlock( ethereum.BlockManager.BlockChain().LastBlock.State().Root, @@ -98,11 +101,13 @@ func main() { big.NewInt(1), big.NewInt(1), "", - ethereum.TxPool.Flush()) - - ethereum.BlockManager.ProcessBlockWithState(block, block.State()) - ethereum.Broadcast(ethwire.MsgBlockTy, block.RlpData()) - log.Println("\n", block.String()) + txs) + err := ethereum.BlockManager.ProcessBlockWithState(block, block.State()) + if err != nil { + log.Println(err) + } else { + log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) + } } }() } -- cgit v1.2.3 From 42e8930b37ddd89d17a069912a91df13f41df244 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 25 Jan 2014 15:19:29 +0100 Subject: Dev contracts --- dev_console.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dev_console.go b/dev_console.go index f2aa4e08d..1817ca9a8 100644 --- a/dev_console.go +++ b/dev_console.go @@ -57,6 +57,9 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "getaddr" && argumentLength != 1: err = true expArgCount = 1 + case action == "contract" && argumentLength != 1: + err = true + expArgCount = 1 } if err { @@ -149,7 +152,7 @@ func (i *Console) ParseInput(input string) bool { */ case "tx": tx := ethchain.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) - fmt.Printf("tx: %x\n", tx.Hash()) + fmt.Printf("%x\n", tx.Hash()) i.ethereum.TxPool.QueueTransaction(tx) case "gettx": @@ -161,6 +164,11 @@ func (i *Console) ParseInput(input string) bool { } else { fmt.Println("gettx: tx not found") } + case "contract": + contract := ethchain.NewTransaction("", ethutil.Big(tokens[1]), []string{"PUSH", "1234"}) + fmt.Printf("%x\n", contract.Hash()) + + i.ethereum.TxPool.QueueTransaction(contract) case "exit", "quit", "q": return false case "help": -- cgit v1.2.3 From d753eb77ec1e2298a8b386f0ddae5e34d450b805 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 25 Jan 2014 15:57:35 +0100 Subject: Removed the last block of the block chain Added CurrentBlock to block manager --- README.md | 2 +- dev_console.go | 2 +- ethereum.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dadafcbc6..fe3ec304d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Ethereum [![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) -Ethereum Go (c) [0255c7881](https://github.com/ethereum/go-ethereum#copy) +Ethereum Go developer client (c) [0255c7881](https://github.com/ethereum/go-ethereum#copy) A fair warning; Ethereum is not yet to be used in production. There's no test-net and you aren't mining real blocks (just one which is the genesis block). diff --git a/dev_console.go b/dev_console.go index 1817ca9a8..6beafc447 100644 --- a/dev_console.go +++ b/dev_console.go @@ -122,7 +122,7 @@ func (i *Console) ParseInput(input string) bool { fmt.Printf("%q\n", d) case "getaddr": encoded, _ := hex.DecodeString(tokens[1]) - d := i.ethereum.BlockManager.BlockChain().LastBlock.State().Get(string(encoded)) + d := i.ethereum.BlockManager.CurrentBlock.State().Get(string(encoded)) if d != "" { decoder := ethutil.NewRlpDecoder([]byte(d)) fmt.Println(decoder) diff --git a/ethereum.go b/ethereum.go index 2d79659b0..e434f3c8e 100644 --- a/ethereum.go +++ b/ethereum.go @@ -95,7 +95,7 @@ func main() { time.Sleep(blockTime * time.Second) block := ethchain.CreateBlock( - ethereum.BlockManager.BlockChain().LastBlock.State().Root, + ethereum.BlockManager.CurrentBlock.State().Root, ethereum.BlockManager.LastBlockHash, "123", big.NewInt(1), -- cgit v1.2.3 From c6062a0f73c01bd069394d81a506c64a4272b124 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 25 Jan 2014 16:45:51 +0100 Subject: Added MIT license #5 --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..803f2ef4e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Geff Obscura + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. -- cgit v1.2.3 From 8b9dcdcdf04eb13e39101532a90b4a552754cb7f Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 27 Jan 2014 15:34:10 +0100 Subject: Debugging handles --- dev_console.go | 12 +++++++++++- ethereum.go | 7 ++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/dev_console.go b/dev_console.go index 6beafc447..3395124dc 100644 --- a/dev_console.go +++ b/dev_console.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/ethchain-go" "github.com/ethereum/ethdb-go" "github.com/ethereum/ethutil-go" - _ "github.com/ethereum/ethwire-go" + "github.com/ethereum/ethwire-go" _ "math/big" "os" "strings" @@ -60,6 +60,12 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "contract" && argumentLength != 1: err = true expArgCount = 1 + case action == "say" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "addp" && argumentLength != 1: + err = true + expArgCount = 1 } if err { @@ -129,6 +135,10 @@ func (i *Console) ParseInput(input string) bool { } else { fmt.Println("getaddr: address unknown") } + case "say": + i.ethereum.Broadcast(ethwire.MsgTalkTy, tokens[1]) + case "addp": + i.ethereum.ConnectToPeer(tokens[1]) case "encode": fmt.Printf("%q\n", ethutil.Encode(tokens[1])) /* diff --git a/ethereum.go b/ethereum.go index e434f3c8e..d58033cdc 100644 --- a/ethereum.go +++ b/ethereum.go @@ -50,6 +50,8 @@ func main() { ethchain.InitFees() ethutil.ReadConfig() + log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) + // Instantiated a eth stack ethereum, err := eth.New() if err != nil { @@ -77,8 +79,6 @@ func main() { go console.Start() } - log.Println("Starting Ethereum") - RegisterInterupts(ethereum) ethereum.Start() @@ -90,10 +90,11 @@ func main() { // Fake block mining. It broadcasts a new block every 5 seconds go func() { for { - txs := ethereum.TxPool.Flush() time.Sleep(blockTime * time.Second) + txs := ethereum.TxPool.Flush() + block := ethchain.CreateBlock( ethereum.BlockManager.CurrentBlock.State().Root, ethereum.BlockManager.LastBlockHash, -- cgit v1.2.3 From 2619604549ee632289fbde2a3065fa8dcfe65a23 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 27 Jan 2014 22:15:06 +0100 Subject: Added help --- dev_console.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dev_console.go b/dev_console.go index 3395124dc..91d911ec2 100644 --- a/dev_console.go +++ b/dev_console.go @@ -192,7 +192,11 @@ func (i *Console) ParseInput(input string) bool { "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n" + "\033[1m= Encoding =\033[0m\n" + "decode STR\n" + - "encode STR\n") + "encode STR\n" + + "\033[1m= Other =\033[0m\n" + + "addp HOST:PORT\n" + + "tx TO AMOUNT\n" + + "contract AMOUNT\n") default: fmt.Println("Unknown command:", tokens[0]) -- cgit v1.2.3 From 6ab368cf7426195a0d44b14ad6d82c020c024395 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 28 Jan 2014 15:35:19 +0100 Subject: Changed block chain specific methods --- dev_console.go | 2 +- ethereum.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev_console.go b/dev_console.go index 91d911ec2..74ba343cd 100644 --- a/dev_console.go +++ b/dev_console.go @@ -128,7 +128,7 @@ func (i *Console) ParseInput(input string) bool { fmt.Printf("%q\n", d) case "getaddr": encoded, _ := hex.DecodeString(tokens[1]) - d := i.ethereum.BlockManager.CurrentBlock.State().Get(string(encoded)) + d := i.ethereum.BlockManager.BlockChain().CurrentBlock.State().Get(string(encoded)) if d != "" { decoder := ethutil.NewRlpDecoder([]byte(d)) fmt.Println(decoder) diff --git a/ethereum.go b/ethereum.go index d58033cdc..8608ca47a 100644 --- a/ethereum.go +++ b/ethereum.go @@ -84,7 +84,7 @@ func main() { ethereum.Start() if StartMining { - blockTime := time.Duration(15) + blockTime := time.Duration(2) log.Printf("Dev Test Mining started. Blocks found each %d seconds\n", blockTime) // Fake block mining. It broadcasts a new block every 5 seconds @@ -96,8 +96,8 @@ func main() { txs := ethereum.TxPool.Flush() block := ethchain.CreateBlock( - ethereum.BlockManager.CurrentBlock.State().Root, - ethereum.BlockManager.LastBlockHash, + ethereum.BlockManager.BlockChain().CurrentBlock.State().Root, + ethereum.BlockManager.BlockChain().LastBlockHash, "123", big.NewInt(1), big.NewInt(1), -- cgit v1.2.3 From ab43c001f7642eaacb8d3bbcde5f0016ce73a85d Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Wed, 29 Jan 2014 14:08:40 -0800 Subject: typo? --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe3ec304d..3693fb6b0 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Ethereum Go makes use of a modified `secp256k1-go` and therefor GMP. Install ======= -```go get -u -t https://github.com/ethereum/go-ethereum``` +```go get -u -t github.com/ethereum/go-ethereum``` Command line options -- cgit v1.2.3 From 27a03d3eea5451ddb84dbb1ff33ee916259635d0 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 30 Jan 2014 00:47:09 +0100 Subject: Updated to the new Trie --- dev_console.go | 10 +++++----- ethereum.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dev_console.go b/dev_console.go index 74ba343cd..106c372f2 100644 --- a/dev_console.go +++ b/dev_console.go @@ -76,11 +76,11 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { } func (i *Console) PrintRoot() { - root := ethutil.Conv(i.trie.RootT) + root := ethutil.Conv(i.trie.Root) if len(root.AsBytes()) != 0 { fmt.Println(hex.EncodeToString(root.AsBytes())) } else { - fmt.Println(i.trie.RootT) + fmt.Println(i.trie.Root) } } @@ -108,15 +108,15 @@ func (i *Console) ParseInput(input string) bool { } else { switch tokens[0] { case "update": - i.trie.UpdateT(tokens[1], tokens[2]) + i.trie.Update(tokens[1], tokens[2]) i.PrintRoot() case "get": - fmt.Println(i.trie.GetT(tokens[1])) + fmt.Println(i.trie.Get(tokens[1])) case "root": i.PrintRoot() case "rawroot": - fmt.Println(i.trie.RootT) + fmt.Println(i.trie.Root) case "print": i.db.Print() case "dag": diff --git a/ethereum.go b/ethereum.go index 8608ca47a..2ffb6c929 100644 --- a/ethereum.go +++ b/ethereum.go @@ -84,7 +84,7 @@ func main() { ethereum.Start() if StartMining { - blockTime := time.Duration(2) + blockTime := time.Duration(10) log.Printf("Dev Test Mining started. Blocks found each %d seconds\n", blockTime) // Fake block mining. It broadcasts a new block every 5 seconds @@ -107,7 +107,7 @@ func main() { if err != nil { log.Println(err) } else { - log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) + //log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) } } }() -- cgit v1.2.3 From e28632b997b4097fb6f899067ead02b90d9b887b Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 30 Jan 2014 23:50:15 +0100 Subject: Mine? --- dev_console.go | 4 ++-- ethereum.go | 46 +++++++++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/dev_console.go b/dev_console.go index 106c372f2..39176eeba 100644 --- a/dev_console.go +++ b/dev_console.go @@ -124,8 +124,8 @@ func (i *Console) ParseInput(input string) bool { ethutil.BigPow(2, 36), // diff ethutil.Big(tokens[2]))) // nonce case "decode": - d, _ := ethutil.Decode([]byte(tokens[1]), 0) - fmt.Printf("%q\n", d) + value := ethutil.NewRlpDecoder([]byte(tokens[1])) + fmt.Println(value) case "getaddr": encoded, _ := hex.DecodeString(tokens[1]) d := i.ethereum.BlockManager.BlockChain().CurrentBlock.State().Get(string(encoded)) diff --git a/ethereum.go b/ethereum.go index 2ffb6c929..810c30f49 100644 --- a/ethereum.go +++ b/ethereum.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/ethutil-go" _ "github.com/ethereum/ethwire-go" "log" - "math/big" "os" "os/signal" "path" @@ -89,26 +88,35 @@ func main() { // Fake block mining. It broadcasts a new block every 5 seconds go func() { - for { - - time.Sleep(blockTime * time.Second) + pow := ðchain.EasyPow{} + for { txs := ethereum.TxPool.Flush() - - block := ethchain.CreateBlock( - ethereum.BlockManager.BlockChain().CurrentBlock.State().Root, - ethereum.BlockManager.BlockChain().LastBlockHash, - "123", - big.NewInt(1), - big.NewInt(1), - "", - txs) - err := ethereum.BlockManager.ProcessBlockWithState(block, block.State()) - if err != nil { - log.Println(err) - } else { - //log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) - } + block := ethereum.BlockManager.BlockChain().NewBlock("82c3b0b72cf62f1a9ce97c64da8072efa28225d8", txs) + + nonce := pow.Search(block) + block.Nonce = nonce + + log.Println("nonce found:", nonce) + /* + time.Sleep(blockTime * time.Second) + + + block := ethchain.CreateBlock( + ethereum.BlockManager.BlockChain().CurrentBlock.State().Root, + ethereum.BlockManager.BlockChain().LastBlockHash, + "123", + big.NewInt(1), + big.NewInt(1), + "", + txs) + err := ethereum.BlockManager.ProcessBlockWithState(block, block.State()) + if err != nil { + log.Println(err) + } else { + //log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) + } + */ } }() } -- cgit v1.2.3 From a0704364ce7e4e65e805083a54362a53182cec7a Mon Sep 17 00:00:00 2001 From: Joey Zhou Date: Fri, 31 Jan 2014 01:59:30 -0800 Subject: fix build failure --- test_runner_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test_runner_test.go b/test_runner_test.go index 0a0b0d69c..5c8461ad8 100644 --- a/test_runner_test.go +++ b/test_runner_test.go @@ -27,8 +27,7 @@ func TestTestRunner(t *testing.T) { for key, value := range source.Inputs { trie.Update(key, value) } - - if hex.EncodeToString([]byte(trie.Root)) != source.Expectation { + if hex.EncodeToString(trie.Root.([]byte)) != source.Expectation { t.Error("trie root did not match") } }) -- cgit v1.2.3 From 6a7cd0c676f56ab1bf2205ee29f6bb24a2449f29 Mon Sep 17 00:00:00 2001 From: Ramesh Nair Date: Sat, 1 Feb 2014 11:10:18 +0800 Subject: Update README --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe3ec304d..ac3235e93 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,22 @@ Deps Ethereum Go makes use of a modified `secp256k1-go` and therefor GMP. -Install +Ubuntu 12+ +* `apt-get install libgmp3-dev` + +OS X 10.9+: +* `brew install gmp4` +* Symlink the headers and libs if necessary: + * `sudo ln -s /usr/local/opt/gmp4/include/gmp.h /usr/local/include/gmp.h` + * `sudo ln -s /usr/local/opt/gmp4/lib/libgmp.a /usr/local/lib/libgmp.a` + * `sudo ln -s /usr/local/opt/gmp4/lib/libgmpxx.a /usr/local/lib/libgmpxx.a` + +Build ======= -```go get -u -t https://github.com/ethereum/go-ethereum``` +* `go get -u -t github.com/ethereum/go-ethereum` +* `cd $GOPATH/src/github.com/ethereum/go-etherum` +* `go build` Command line options -- cgit v1.2.3 From 9cdf8f2cba0469429f3d3f2aeacdbb4844928a0a Mon Sep 17 00:00:00 2001 From: Ramesh Nair Date: Sat, 1 Feb 2014 11:20:29 +0800 Subject: Updated build instructions --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ac3235e93..10527931e 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,10 @@ Deps Ethereum Go makes use of a modified `secp256k1-go` and therefor GMP. Ubuntu 12+ -* `apt-get install libgmp3-dev` +* `apt-get install gmp-dev` OS X 10.9+: -* `brew install gmp4` -* Symlink the headers and libs if necessary: - * `sudo ln -s /usr/local/opt/gmp4/include/gmp.h /usr/local/include/gmp.h` - * `sudo ln -s /usr/local/opt/gmp4/lib/libgmp.a /usr/local/lib/libgmp.a` - * `sudo ln -s /usr/local/opt/gmp4/lib/libgmpxx.a /usr/local/lib/libgmpxx.a` +* `brew install gmp` Build ======= -- cgit v1.2.3 From 20671758084b5f3889659231c6e4c9ef59d1b740 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 1 Feb 2014 21:51:50 +0100 Subject: RLP Updates --- dev_console.go | 27 +++++---------------------- ethereum.go | 19 ++++++++++++++++--- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/dev_console.go b/dev_console.go index 39176eeba..4153e33bf 100644 --- a/dev_console.go +++ b/dev_console.go @@ -124,13 +124,13 @@ func (i *Console) ParseInput(input string) bool { ethutil.BigPow(2, 36), // diff ethutil.Big(tokens[2]))) // nonce case "decode": - value := ethutil.NewRlpDecoder([]byte(tokens[1])) + value := ethutil.NewRlpValueFromBytes([]byte(tokens[1])) fmt.Println(value) case "getaddr": encoded, _ := hex.DecodeString(tokens[1]) d := i.ethereum.BlockManager.BlockChain().CurrentBlock.State().Get(string(encoded)) if d != "" { - decoder := ethutil.NewRlpDecoder([]byte(d)) + decoder := ethutil.NewRlpValueFromBytes([]byte(d)) fmt.Println(decoder) } else { fmt.Println("getaddr: address unknown") @@ -139,27 +139,10 @@ func (i *Console) ParseInput(input string) bool { i.ethereum.Broadcast(ethwire.MsgTalkTy, tokens[1]) case "addp": i.ethereum.ConnectToPeer(tokens[1]) + case "pcount": + fmt.Println("peers:", i.ethereum.Peers().Len()) case "encode": fmt.Printf("%q\n", ethutil.Encode(tokens[1])) - /* - case "newblk": - block := ethchain.CreateBlock( - i.ethereum.BlockManager.BlockChain().LastBlock.State().Root, - i.ethereum.BlockManager.LastBlockHash, - "123", - big.NewInt(1), - big.NewInt(1), - "", - i.ethereum.TxPool.Flush(), - ) - err := i.ethereum.BlockManager.ProcessBlock(block) - if err != nil { - fmt.Println(err) - } else { - i.ethereum.Broadcast(ethwire.MsgBlockTy, block.RlpData()) - } - //fmt.Println(ethutil.NewRlpValue(block.RlpData()).Get(0)) - */ case "tx": tx := ethchain.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) fmt.Printf("%x\n", tx.Hash()) @@ -169,7 +152,7 @@ func (i *Console) ParseInput(input string) bool { addr, _ := hex.DecodeString(tokens[1]) data, _ := ethutil.Config.Db.Get(addr) if len(data) != 0 { - decoder := ethutil.NewRlpDecoder(data) + decoder := ethutil.NewRlpValueFromBytes(data) fmt.Println(decoder) } else { fmt.Println("gettx: tx not found") diff --git a/ethereum.go b/ethereum.go index 810c30f49..7f5570110 100644 --- a/ethereum.go +++ b/ethereum.go @@ -1,6 +1,7 @@ package main import ( + "encoding/hex" "flag" "fmt" "github.com/ethereum/eth-go" @@ -46,6 +47,8 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) Init() + //fmt.Printf("%x\n", ethutil.Encode([]interface{}{ethutil.BigPow(2, 36).Bytes()})) + ethchain.InitFees() ethutil.ReadConfig() @@ -89,17 +92,27 @@ func main() { // Fake block mining. It broadcasts a new block every 5 seconds go func() { pow := ðchain.EasyPow{} + addr, _ := hex.DecodeString("82c3b0b72cf62f1a9ce97c64da8072efa28225d8") for { + time.Sleep(blockTime * time.Second) + txs := ethereum.TxPool.Flush() - block := ethereum.BlockManager.BlockChain().NewBlock("82c3b0b72cf62f1a9ce97c64da8072efa28225d8", txs) + block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) nonce := pow.Search(block) block.Nonce = nonce - log.Println("nonce found:", nonce) + err := ethereum.BlockManager.ProcessBlockWithState(block, block.State()) + if err != nil { + log.Println(err) + } else { + //log.Println("nonce found:", nonce) + log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) + } + //os.Exit(1) + /* - time.Sleep(blockTime * time.Second) block := ethchain.CreateBlock( -- cgit v1.2.3 From f56a595954fc6641f0b7300a82850b7923d357c5 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 2 Feb 2014 01:40:03 +0100 Subject: Reverted back to old messages --- dev_console.go | 2 +- ethereum.go | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/dev_console.go b/dev_console.go index 4153e33bf..76f8ec72e 100644 --- a/dev_console.go +++ b/dev_console.go @@ -136,7 +136,7 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("getaddr: address unknown") } case "say": - i.ethereum.Broadcast(ethwire.MsgTalkTy, tokens[1]) + i.ethereum.Broadcast(ethwire.MsgTalkTy, []interface{}{tokens[1]}) case "addp": i.ethereum.ConnectToPeer(tokens[1]) case "pcount": diff --git a/ethereum.go b/ethereum.go index 7f5570110..8ad4ae524 100644 --- a/ethereum.go +++ b/ethereum.go @@ -11,7 +11,6 @@ import ( "log" "os" "os/signal" - "path" "runtime" "time" ) @@ -65,18 +64,9 @@ func main() { err := os.Mkdir(ethutil.Config.ExecPath, os.ModePerm) // Error is OK if the error is ErrExist if err != nil && !os.IsExist(err) { - log.Panic("Unable to create EXECPATH. Exiting") + log.Panic("Unable to create EXECPATH:", err) } - // TODO The logger will eventually be a non blocking logger. Logging is a expensive task - // Log to file only - file, err := os.OpenFile(path.Join(ethutil.Config.ExecPath, "debug.log"), os.O_RDWR|os.O_CREATE, os.ModePerm) - if err != nil { - log.Panic("Unable to set proper logger", err) - } - - ethutil.Config.Log = log.New(file, "", 0) - console := NewConsole(ethereum) go console.Start() } -- cgit v1.2.3 From f07b53da2db90aa84dbb5044de15d072e19fb849 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 2 Feb 2014 16:15:24 +0100 Subject: upnp test --- ethereum.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ethereum.go b/ethereum.go index 8ad4ae524..b0e242819 100644 --- a/ethereum.go +++ b/ethereum.go @@ -46,15 +46,13 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) Init() - //fmt.Printf("%x\n", ethutil.Encode([]interface{}{ethutil.BigPow(2, 36).Bytes()})) - ethchain.InitFees() ethutil.ReadConfig() log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) // Instantiated a eth stack - ethereum, err := eth.New() + ethereum, err := eth.New(eth.CapDefault) if err != nil { log.Println(err) return -- cgit v1.2.3 From cce842314fa61fcd2a4b205b04313d9728ea2a57 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 2 Feb 2014 19:44:59 +0100 Subject: UPnP support --- ethereum.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ethereum.go b/ethereum.go index b0e242819..6d6d25bd4 100644 --- a/ethereum.go +++ b/ethereum.go @@ -19,10 +19,14 @@ const Debug = true var StartConsole bool var StartMining bool +var UseUPnP bool +var OutboundPort string func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") + flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") + flag.StringVar(&OutboundPort, "port", "30303", "listening port") flag.Parse() } @@ -52,7 +56,7 @@ func main() { log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) // Instantiated a eth stack - ethereum, err := eth.New(eth.CapDefault) + ethereum, err := eth.New(eth.CapDefault, UseUPnP) if err != nil { log.Println(err) return -- cgit v1.2.3 From c3ee0e92b8f782aa51eae384d05d62f0ea9b753d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 2 Feb 2014 20:53:49 +0100 Subject: Recipient as bytes --- dev_console.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dev_console.go b/dev_console.go index 76f8ec72e..f93300aad 100644 --- a/dev_console.go +++ b/dev_console.go @@ -144,7 +144,8 @@ func (i *Console) ParseInput(input string) bool { case "encode": fmt.Printf("%q\n", ethutil.Encode(tokens[1])) case "tx": - tx := ethchain.NewTransaction(tokens[1], ethutil.Big(tokens[2]), []string{""}) + recipient, _ := hex.DecodeString(tokens[1]) + tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) fmt.Printf("%x\n", tx.Hash()) i.ethereum.TxPool.QueueTransaction(tx) @@ -158,7 +159,7 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("gettx: tx not found") } case "contract": - contract := ethchain.NewTransaction("", ethutil.Big(tokens[1]), []string{"PUSH", "1234"}) + contract := ethchain.NewTransaction([]byte{}, ethutil.Big(tokens[1]), []string{"PUSH", "1234"}) fmt.Printf("%x\n", contract.Hash()) i.ethereum.TxPool.QueueTransaction(contract) -- cgit v1.2.3 From 7f8f008253c9928b556df238984f3046304be86e Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 2 Feb 2014 23:52:06 +0100 Subject: Changed mining --- ethereum.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ethereum.go b/ethereum.go index 6d6d25bd4..74c545edc 100644 --- a/ethereum.go +++ b/ethereum.go @@ -7,12 +7,10 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/ethchain-go" "github.com/ethereum/ethutil-go" - _ "github.com/ethereum/ethwire-go" "log" "os" "os/signal" "runtime" - "time" ) const Debug = true @@ -78,8 +76,7 @@ func main() { ethereum.Start() if StartMining { - blockTime := time.Duration(10) - log.Printf("Dev Test Mining started. Blocks found each %d seconds\n", blockTime) + log.Printf("Dev Test Mining started...\n") // Fake block mining. It broadcasts a new block every 5 seconds go func() { @@ -87,13 +84,12 @@ func main() { addr, _ := hex.DecodeString("82c3b0b72cf62f1a9ce97c64da8072efa28225d8") for { - time.Sleep(blockTime * time.Second) + //time.Sleep(blockTime * time.Second) txs := ethereum.TxPool.Flush() block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) - nonce := pow.Search(block) - block.Nonce = nonce + block.Nonce = pow.Search(block) err := ethereum.BlockManager.ProcessBlockWithState(block, block.State()) if err != nil { -- cgit v1.2.3 From 00ec15c82eafc493c3349aea24c4fef315e5692e Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Feb 2014 00:09:02 +0100 Subject: Fixed mining --- ethereum.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum.go b/ethereum.go index 74c545edc..8a241f4cc 100644 --- a/ethereum.go +++ b/ethereum.go @@ -91,7 +91,7 @@ func main() { block.Nonce = pow.Search(block) - err := ethereum.BlockManager.ProcessBlockWithState(block, block.State()) + err := ethereum.BlockManager.ProcessBlock(block) if err != nil { log.Println(err) } else { -- cgit v1.2.3 From ec883db3b1172f75761c066d4b022858d1f9df5d Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Feb 2014 01:09:45 +0100 Subject: TApply transactions --- ethereum.go | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/ethereum.go b/ethereum.go index 8a241f4cc..bae473854 100644 --- a/ethereum.go +++ b/ethereum.go @@ -84,40 +84,20 @@ func main() { addr, _ := hex.DecodeString("82c3b0b72cf62f1a9ce97c64da8072efa28225d8") for { - //time.Sleep(blockTime * time.Second) - txs := ethereum.TxPool.Flush() + // Create a new block which we're going to mine block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) - + // Apply all transactions to the block + ethereum.BlockManager.ApplyTransactions(block) + // Search the nonce block.Nonce = pow.Search(block) - + // Process the block and verify err := ethereum.BlockManager.ProcessBlock(block) if err != nil { log.Println(err) } else { - //log.Println("nonce found:", nonce) log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) } - //os.Exit(1) - - /* - - - block := ethchain.CreateBlock( - ethereum.BlockManager.BlockChain().CurrentBlock.State().Root, - ethereum.BlockManager.BlockChain().LastBlockHash, - "123", - big.NewInt(1), - big.NewInt(1), - "", - txs) - err := ethereum.BlockManager.ProcessBlockWithState(block, block.State()) - if err != nil { - log.Println(err) - } else { - //log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) - } - */ } }() } -- cgit v1.2.3 From 5e36e3ccc7fad6d0b40c7f95be4f02e0cafdb2e2 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Feb 2014 02:01:31 +0100 Subject: Process transactions --- dev_console.go | 12 ++++++++---- ethereum.go | 12 ++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dev_console.go b/dev_console.go index f93300aad..e93b39c3a 100644 --- a/dev_console.go +++ b/dev_console.go @@ -144,11 +144,15 @@ func (i *Console) ParseInput(input string) bool { case "encode": fmt.Printf("%q\n", ethutil.Encode(tokens[1])) case "tx": - recipient, _ := hex.DecodeString(tokens[1]) - tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) - fmt.Printf("%x\n", tx.Hash()) + recipient, err := hex.DecodeString(tokens[1]) + if err != nil { + fmt.Println("recipient err:", err) + } else { + tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) + fmt.Printf("%x\n", tx.Hash()) + i.ethereum.TxPool.QueueTransaction(tx) + } - i.ethereum.TxPool.QueueTransaction(tx) case "gettx": addr, _ := hex.DecodeString(tokens[1]) data, _ := ethutil.Config.Db.Get(addr) diff --git a/ethereum.go b/ethereum.go index bae473854..680e52325 100644 --- a/ethereum.go +++ b/ethereum.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/ethchain-go" "github.com/ethereum/ethutil-go" + "github.com/ethereum/ethwire-go" "log" "os" "os/signal" @@ -88,16 +89,11 @@ func main() { // Create a new block which we're going to mine block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) // Apply all transactions to the block - ethereum.BlockManager.ApplyTransactions(block) + ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) // Search the nonce block.Nonce = pow.Search(block) - // Process the block and verify - err := ethereum.BlockManager.ProcessBlock(block) - if err != nil { - log.Println(err) - } else { - log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) - } + ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.RlpValue().Value}) + log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) } }() } -- cgit v1.2.3 From 7730949221a95b89282713901bf4db25673cd95c Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Feb 2014 14:26:52 +0100 Subject: Block processing during mining --- ethereum.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ethereum.go b/ethereum.go index 680e52325..5cbf86665 100644 --- a/ethereum.go +++ b/ethereum.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/ethchain-go" "github.com/ethereum/ethutil-go" - "github.com/ethereum/ethwire-go" + _ "github.com/ethereum/ethwire-go" "log" "os" "os/signal" @@ -92,7 +92,8 @@ func main() { ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) // Search the nonce block.Nonce = pow.Search(block) - ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.RlpValue().Value}) + ethereum.BlockManager.ProcessBlock(block) + //ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.RlpValue().Value}) log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) } }() -- cgit v1.2.3 From dbe42ce44e7f931b712fc461bea6f98f870331c5 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 6 Feb 2014 13:28:30 +0100 Subject: Mining using a different hash now --- README.md | 4 +--- ethereum.go | 10 +++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 10527931e..157601b39 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,7 @@ OS X 10.9+: Build ======= -* `go get -u -t github.com/ethereum/go-ethereum` -* `cd $GOPATH/src/github.com/ethereum/go-etherum` -* `go build` +`go get -u -t github.com/ethereum/go-ethereum` Command line options diff --git a/ethereum.go b/ethereum.go index 5cbf86665..8ba99a388 100644 --- a/ethereum.go +++ b/ethereum.go @@ -92,9 +92,13 @@ func main() { ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) // Search the nonce block.Nonce = pow.Search(block) - ethereum.BlockManager.ProcessBlock(block) - //ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.RlpValue().Value}) - log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) + err := ethereum.BlockManager.ProcessBlock(block) + if err != nil { + log.Println(err) + } else { + //ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.RlpValue().Value}) + log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) + } } }() } -- cgit v1.2.3 From 9f4a81a02e6d84a192c3cdcb42866563456f5267 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 6 Feb 2014 23:47:04 +0100 Subject: Removed test --- dev_console.go | 1 + test_runner_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/dev_console.go b/dev_console.go index e93b39c3a..8ef7341b2 100644 --- a/dev_console.go +++ b/dev_console.go @@ -149,6 +149,7 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("recipient err:", err) } else { tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) + tx.Sign([]byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) fmt.Printf("%x\n", tx.Hash()) i.ethereum.TxPool.QueueTransaction(tx) } diff --git a/test_runner_test.go b/test_runner_test.go index 5c8461ad8..a5672eb7a 100644 --- a/test_runner_test.go +++ b/test_runner_test.go @@ -1,5 +1,6 @@ package main +/* import ( "encoding/hex" _ "fmt" @@ -32,3 +33,4 @@ func TestTestRunner(t *testing.T) { } }) } +*/ -- cgit v1.2.3 From 827f341c97e9243b0c04a2fbde363b949bc3215d Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 7 Feb 2014 22:25:59 +0100 Subject: Genesis logging --- ethereum.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ethereum.go b/ethereum.go index 8ba99a388..659134ffd 100644 --- a/ethereum.go +++ b/ethereum.go @@ -20,10 +20,12 @@ var StartConsole bool var StartMining bool var UseUPnP bool var OutboundPort string +var ShowGenesis bool func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") + flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.StringVar(&OutboundPort, "port", "30303", "listening port") @@ -52,8 +54,6 @@ func main() { ethchain.InitFees() ethutil.ReadConfig() - log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) - // Instantiated a eth stack ethereum, err := eth.New(eth.CapDefault, UseUPnP) if err != nil { @@ -61,6 +61,13 @@ func main() { return } + if ShowGenesis { + fmt.Println(ethereum.BlockManager.BlockChain().Genesis()) + os.Exit(0) + } + + log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) + if StartConsole { err := os.Mkdir(ethutil.Config.ExecPath, os.ModePerm) // Error is OK if the error is ErrExist -- cgit v1.2.3 From f1ba1df16565614fdf04392463191f9c276270d4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 8 Feb 2014 22:07:17 +0100 Subject: Added key address and key generation --- README.md | 5 +---- config.go | 26 ++++++++++++++++++++++++ dev_console.go | 3 ++- ethereum.go | 62 ++++++++++++++++++++++++++++++++++++++-------------------- 4 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 config.go diff --git a/README.md b/README.md index 157601b39..a9b047c6b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Ethereum [![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) -Ethereum Go developer client (c) [0255c7881](https://github.com/ethereum/go-ethereum#copy) +Ethereum Go developer client (c) Jeffrey Wilcke A fair warning; Ethereum is not yet to be used in production. There's no test-net and you aren't mining real blocks (just one which is the genesis block). @@ -95,6 +95,3 @@ expect you to write tests for me so I don't have to test your code manually. (If you want to contribute by just writing tests that's fine too!) -### Copy - -69bce990a619e747b4f57483724b0e8a1732bb3b44ccf70b0dd6abd272af94550fc9d8b21232d33ebf30d38a148612f68e936094b4daeb9ea7174088a439070401 0255c78815d4f056f84c96de438ed9e38c69c0f8af24f5032248be5a79fe9071c3 diff --git a/config.go b/config.go new file mode 100644 index 000000000..6b5b9b62f --- /dev/null +++ b/config.go @@ -0,0 +1,26 @@ +package main + +import ( + "flag" +) + +var StartConsole bool +var StartMining bool +var UseUPnP bool +var OutboundPort string +var ShowGenesis bool +var AddPeer string +var MaxPeer int +var GenAddr bool + +func Init() { + flag.BoolVar(&StartConsole, "c", false, "debug and testing console") + flag.BoolVar(&StartMining, "m", false, "start dagger mining") + flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") + flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") + flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") + flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") + + flag.Parse() +} diff --git a/dev_console.go b/dev_console.go index 8ef7341b2..d2f3befb2 100644 --- a/dev_console.go +++ b/dev_console.go @@ -149,7 +149,8 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("recipient err:", err) } else { tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) - tx.Sign([]byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + privKey, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + tx.Sign(privKey) fmt.Printf("%x\n", tx.Hash()) i.ethereum.TxPool.QueueTransaction(tx) } diff --git a/ethereum.go b/ethereum.go index 659134ffd..674f7d88c 100644 --- a/ethereum.go +++ b/ethereum.go @@ -2,12 +2,11 @@ package main import ( "encoding/hex" - "flag" "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/ethchain-go" "github.com/ethereum/ethutil-go" - _ "github.com/ethereum/ethwire-go" + "github.com/obscuren/secp256k1-go" "log" "os" "os/signal" @@ -16,22 +15,6 @@ import ( const Debug = true -var StartConsole bool -var StartMining bool -var UseUPnP bool -var OutboundPort string -var ShowGenesis bool - -func Init() { - flag.BoolVar(&StartConsole, "c", false, "debug and testing console") - flag.BoolVar(&StartMining, "m", false, "start dagger mining") - flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") - flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") - flag.StringVar(&OutboundPort, "port", "30303", "listening port") - - flag.Parse() -} - // Register interrupt handlers so we can stop the ethereum func RegisterInterupts(s *eth.Ethereum) { // Buffered chan of one is enough @@ -47,6 +30,17 @@ func RegisterInterupts(s *eth.Ethereum) { }() } +func CreateKeyPair(force bool) { + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + if len(data) == 0 || force { + log.Println("Generating new address and keypair") + + pub, prv := secp256k1.GenerateKeyPair() + + ethutil.Config.Db.Put([]byte("KeyRing"), prv) + } +} + func main() { runtime.GOMAXPROCS(runtime.NumCPU()) Init() @@ -57,10 +51,31 @@ func main() { // Instantiated a eth stack ethereum, err := eth.New(eth.CapDefault, UseUPnP) if err != nil { - log.Println(err) + log.Println("eth start err:", err) return } + if GenAddr { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Println("Yes or no?", r) + } + } + + if r == "y" { + CreateKeyPair(true) + } + os.Exit(0) + } else { + CreateKeyPair(false) + } + if ShowGenesis { fmt.Println(ethereum.BlockManager.BlockChain().Genesis()) os.Exit(0) @@ -68,6 +83,9 @@ func main() { log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) + // Set the max peers + ethereum.MaxPeers = MaxPeer + if StartConsole { err := os.Mkdir(ethutil.Config.ExecPath, os.ModePerm) // Error is OK if the error is ErrExist @@ -97,14 +115,16 @@ func main() { block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) // Apply all transactions to the block ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) + + ethereum.BlockManager.AccumelateRewards(block, block) + // Search the nonce block.Nonce = pow.Search(block) err := ethereum.BlockManager.ProcessBlock(block) if err != nil { log.Println(err) } else { - //ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.RlpValue().Value}) - log.Println("\n+++++++ MINED BLK +++++++\n", block.String()) + log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockManager.BlockChain().CurrentBlock) } } }() -- cgit v1.2.3 From d7205b7affeacf2977a896a24f1ba88c933c8dce Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 8 Feb 2014 22:16:11 +0100 Subject: Updated readme --- README.md | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a9b047c6b..1cf3e1b04 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,8 @@ Ethereum Ethereum Go developer client (c) Jeffrey Wilcke -A fair warning; Ethereum is not yet to be used in production. There's no -test-net and you aren't mining real blocks (just one which is the genesis block). - +Ethereum is currently in its testing phase. The current state is "Proof +of Concept 1". For build instructions see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge). Ethereum Go is split up in several sub packages Please refer to each individual package for more information. @@ -33,31 +32,35 @@ contains the LevelDB interface and memory DB interface. This executable is the front-end (currently nothing but a dev console) for the Ethereum Go implementation. -Deps -==== - -Ethereum Go makes use of a modified `secp256k1-go` and therefor GMP. - -Ubuntu 12+ -* `apt-get install gmp-dev` - -OS X 10.9+: -* `brew install gmp` - Build ======= -`go get -u -t github.com/ethereum/go-ethereum` +For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge) Command line options ==================== ``` --c launch the developer console --m start mining fake blocks and broadcast fake messages to the net +-c Launch the developer console +-m Start mining blocks +-genaddr Generates a new address and private key (destructive action) +-p Port on which the server will accept incomming connections (= 30303) +-upnp Enable UPnP (= false) +-x Desired amount of peers (= 5) +-h This help ``` +Developer console commands +========================== + +``` +addp : Connect to the given host +tx Send Wei to the specified +``` + +See the "help" command for *developer* options. + Contribution ============ -- cgit v1.2.3 From 542bc2fce4c6d65fd5346b64f2aa0a80f23d3480 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 8 Feb 2014 22:57:40 +0100 Subject: Woops --- ethereum.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ethereum.go b/ethereum.go index 674f7d88c..902b9ae9c 100644 --- a/ethereum.go +++ b/ethereum.go @@ -37,6 +37,8 @@ func CreateKeyPair(force bool) { pub, prv := secp256k1.GenerateKeyPair() + log.Printf("Your new address is %x\n", pub[12:]) + ethutil.Config.Db.Put([]byte("KeyRing"), prv) } } -- cgit v1.2.3 From 67572417c6522647d7d14c1042c427b5edd9973e Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 8 Feb 2014 23:21:09 +0100 Subject: Use the generated key --- ethereum.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum.go b/ethereum.go index 902b9ae9c..8bbb797d3 100644 --- a/ethereum.go +++ b/ethereum.go @@ -109,7 +109,7 @@ func main() { // Fake block mining. It broadcasts a new block every 5 seconds go func() { pow := ðchain.EasyPow{} - addr, _ := hex.DecodeString("82c3b0b72cf62f1a9ce97c64da8072efa28225d8") + addr, _ := ethutil.Config.Db.Get([]byte("KeyRing")) for { txs := ethereum.TxPool.Flush() -- cgit v1.2.3 From c2bb5e39e10e781404034b3018d753699247edd1 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 8 Feb 2014 23:21:29 +0100 Subject: Unused package --- ethereum.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ethereum.go b/ethereum.go index 8bbb797d3..716507d53 100644 --- a/ethereum.go +++ b/ethereum.go @@ -1,7 +1,6 @@ package main import ( - "encoding/hex" "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/ethchain-go" -- cgit v1.2.3 From 7b7242b9ea85ccdcc29dc9b515eb2d2776e2b50d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 8 Feb 2014 23:25:48 +0100 Subject: Proper keys --- ethereum.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ethereum.go b/ethereum.go index 716507d53..bf379184f 100644 --- a/ethereum.go +++ b/ethereum.go @@ -38,7 +38,7 @@ func CreateKeyPair(force bool) { log.Printf("Your new address is %x\n", pub[12:]) - ethutil.Config.Db.Put([]byte("KeyRing"), prv) + ethutil.Config.Db.Put([]byte("KeyRing"), ethutil.Encode([]interface{}{prv, pub})) } } @@ -108,7 +108,9 @@ func main() { // Fake block mining. It broadcasts a new block every 5 seconds go func() { pow := ðchain.EasyPow{} - addr, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(1).Bytes() for { txs := ethereum.TxPool.Flush() -- cgit v1.2.3 From 9ac81c5b2b8304fe8e1efed7298998f36267612b Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 8 Feb 2014 23:26:43 +0100 Subject: Proper keys --- dev_console.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dev_console.go b/dev_console.go index d2f3befb2..64a40a1c4 100644 --- a/dev_console.go +++ b/dev_console.go @@ -149,8 +149,9 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("recipient err:", err) } else { tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) - privKey, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - tx.Sign(privKey) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + tx.Sign(keyRing.Get(0).Bytes()) fmt.Printf("%x\n", tx.Hash()) i.ethereum.TxPool.QueueTransaction(tx) } -- cgit v1.2.3 From 37a89e577c25afe0e0388f154eba1fb97a972971 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 8 Feb 2014 23:50:08 +0100 Subject: Added address --- ethereum.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum.go b/ethereum.go index bf379184f..4584ff85c 100644 --- a/ethereum.go +++ b/ethereum.go @@ -36,9 +36,9 @@ func CreateKeyPair(force bool) { pub, prv := secp256k1.GenerateKeyPair() - log.Printf("Your new address is %x\n", pub[12:]) + log.Printf("Your new address is %x\n", ethutil.Sha3Bin(pub)[12:]) - ethutil.Config.Db.Put([]byte("KeyRing"), ethutil.Encode([]interface{}{prv, pub})) + ethutil.Config.Db.Put([]byte("KeyRing"), ethutil.Encode([]interface{}{prv, ethutil.Sha3Bin(pub)[12:]})) } } -- cgit v1.2.3 From 8320fd998ee5d140b7bc99d071567f4c4c97901f Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 9 Feb 2014 23:35:02 +0100 Subject: Added pub key to keyring --- ethereum.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ethereum.go b/ethereum.go index 4584ff85c..df9eb2387 100644 --- a/ethereum.go +++ b/ethereum.go @@ -35,10 +35,13 @@ func CreateKeyPair(force bool) { log.Println("Generating new address and keypair") pub, prv := secp256k1.GenerateKeyPair() + addr := ethutil.Sha3Bin(pub)[12:] - log.Printf("Your new address is %x\n", ethutil.Sha3Bin(pub)[12:]) + log.Printf("Your new address is %x\n", addr) + log.Printf("Your new pubkey is %x (%d)\n", pub, len(pub)) - ethutil.Config.Db.Put([]byte("KeyRing"), ethutil.Encode([]interface{}{prv, ethutil.Sha3Bin(pub)[12:]})) + keyRing := ethutil.NewValue([]interface{}{prv, addr, pub}) + ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) } } @@ -103,7 +106,7 @@ func main() { ethereum.Start() if StartMining { - log.Printf("Dev Test Mining started...\n") + log.Printf("Miner started\n") // Fake block mining. It broadcasts a new block every 5 seconds go func() { -- cgit v1.2.3 From 3ecb2ef29cb5a9a0d2be8e9719875692a305bf3b Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 10 Feb 2014 11:37:11 +0100 Subject: removed pub key log --- ethereum.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ethereum.go b/ethereum.go index df9eb2387..78a22614f 100644 --- a/ethereum.go +++ b/ethereum.go @@ -38,9 +38,8 @@ func CreateKeyPair(force bool) { addr := ethutil.Sha3Bin(pub)[12:] log.Printf("Your new address is %x\n", addr) - log.Printf("Your new pubkey is %x (%d)\n", pub, len(pub)) - keyRing := ethutil.NewValue([]interface{}{prv, addr, pub}) + keyRing := ethutil.NewValue([]interface{}{prv, addr, pub[1:]}) ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) } } -- cgit v1.2.3 From d831064f6597220f5013000048cdb0d2285d82a8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 10 Feb 2014 20:22:52 +0100 Subject: Skip the first byte in generating addresses --- ethereum.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum.go b/ethereum.go index 78a22614f..dfb2a955c 100644 --- a/ethereum.go +++ b/ethereum.go @@ -35,7 +35,7 @@ func CreateKeyPair(force bool) { log.Println("Generating new address and keypair") pub, prv := secp256k1.GenerateKeyPair() - addr := ethutil.Sha3Bin(pub)[12:] + addr := ethutil.Sha3Bin(pub[1:])[12:] log.Printf("Your new address is %x\n", addr) @@ -67,7 +67,7 @@ func main() { if r == "n" || r == "y" { break } else { - fmt.Println("Yes or no?", r) + fmt.Printf("Yes or no?", r) } } -- cgit v1.2.3 From 980987ae8f2783549125d7f503f6f948ebbef665 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 10 Feb 2014 20:24:36 +0100 Subject: Added block retrieval --- dev_console.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dev_console.go b/dev_console.go index 64a40a1c4..d3635986b 100644 --- a/dev_console.go +++ b/dev_console.go @@ -66,6 +66,9 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "addp" && argumentLength != 1: err = true expArgCount = 1 + case action == "block" && argumentLength != 1: + err = true + expArgCount = 1 } if err { @@ -128,13 +131,12 @@ func (i *Console) ParseInput(input string) bool { fmt.Println(value) case "getaddr": encoded, _ := hex.DecodeString(tokens[1]) - d := i.ethereum.BlockManager.BlockChain().CurrentBlock.State().Get(string(encoded)) - if d != "" { - decoder := ethutil.NewRlpValueFromBytes([]byte(d)) - fmt.Println(decoder) - } else { - fmt.Println("getaddr: address unknown") - } + addr := i.ethereum.BlockManager.BlockChain().CurrentBlock.GetAddr(encoded) + fmt.Println("addr:", addr) + case "block": + encoded, _ := hex.DecodeString(tokens[1]) + block := i.ethereum.BlockManager.BlockChain().GetBlock(encoded) + fmt.Println(block) case "say": i.ethereum.Broadcast(ethwire.MsgTalkTy, []interface{}{tokens[1]}) case "addp": @@ -179,6 +181,8 @@ func (i *Console) ParseInput(input string) bool { "get KEY - Retrieves the given key\n" + "root - Prints the hex encoded merkle root\n" + "rawroot - Prints the raw merkle root\n" + + "block HASH - Prints the block\n" + + "getaddr ADDR - Prints the account associated with the address\n" + "\033[1m= Dagger =\033[0m\n" + "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n" + "\033[1m= Encoding =\033[0m\n" + -- cgit v1.2.3 From 954f8979386d75c3038d65cea94f481f5c15aba2 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 11 Feb 2014 18:46:10 +0100 Subject: Use seed --- config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config.go b/config.go index 6b5b9b62f..d13bb863b 100644 --- a/config.go +++ b/config.go @@ -12,12 +12,14 @@ var ShowGenesis bool var AddPeer string var MaxPeer int var GenAddr bool +var UseSeed bool func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") + flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") flag.StringVar(&OutboundPort, "p", "30303", "listening port") flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") -- cgit v1.2.3 From 68fbfe70dadd1e63baff6ecf5b59e4c9ba0eb2ca Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 12 Feb 2014 16:34:35 +0100 Subject: Default to .ethereum --- ethereum.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ethereum.go b/ethereum.go index dfb2a955c..51a2783ad 100644 --- a/ethereum.go +++ b/ethereum.go @@ -49,7 +49,8 @@ func main() { Init() ethchain.InitFees() - ethutil.ReadConfig() + ethutil.ReadConfig(".ethereum") + ethutil.Config.Seed = UseSeed // Instantiated a eth stack ethereum, err := eth.New(eth.CapDefault, UseUPnP) -- cgit v1.2.3 From fe59a2b26deb11705c524dc52dc78ce1e00eb713 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 15 Feb 2014 00:05:04 +0100 Subject: Updated to the great merge package --- README.md | 2 +- dev_console.go | 8 ++++---- ethereum.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1cf3e1b04..4deaf4579 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Ethereum Ethereum Go developer client (c) Jeffrey Wilcke Ethereum is currently in its testing phase. The current state is "Proof -of Concept 1". For build instructions see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge). +of Concept 2". For build instructions see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge). Ethereum Go is split up in several sub packages Please refer to each individual package for more information. diff --git a/dev_console.go b/dev_console.go index d3635986b..ff2539b03 100644 --- a/dev_console.go +++ b/dev_console.go @@ -6,10 +6,10 @@ import ( "errors" "fmt" "github.com/ethereum/eth-go" - "github.com/ethereum/ethchain-go" - "github.com/ethereum/ethdb-go" - "github.com/ethereum/ethutil-go" - "github.com/ethereum/ethwire-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethdb" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/ethwire" _ "math/big" "os" "strings" diff --git a/ethereum.go b/ethereum.go index 51a2783ad..9907de740 100644 --- a/ethereum.go +++ b/ethereum.go @@ -3,8 +3,8 @@ package main import ( "fmt" "github.com/ethereum/eth-go" - "github.com/ethereum/ethchain-go" - "github.com/ethereum/ethutil-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" "github.com/obscuren/secp256k1-go" "log" "os" -- cgit v1.2.3 From 3a03d091eb337ac2d5fdec50b49bf9ac5cda86f5 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 15 Feb 2014 01:34:35 +0100 Subject: Removed RlpValue in favour of Value --- dev_console.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dev_console.go b/dev_console.go index ff2539b03..f2283e341 100644 --- a/dev_console.go +++ b/dev_console.go @@ -79,9 +79,9 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { } func (i *Console) PrintRoot() { - root := ethutil.Conv(i.trie.Root) - if len(root.AsBytes()) != 0 { - fmt.Println(hex.EncodeToString(root.AsBytes())) + root := ethutil.NewValue(i.trie.Root) + if len(root.Bytes()) != 0 { + fmt.Println(hex.EncodeToString(root.Bytes())) } else { fmt.Println(i.trie.Root) } @@ -127,7 +127,7 @@ func (i *Console) ParseInput(input string) bool { ethutil.BigPow(2, 36), // diff ethutil.Big(tokens[2]))) // nonce case "decode": - value := ethutil.NewRlpValueFromBytes([]byte(tokens[1])) + value := ethutil.NewValueFromBytes([]byte(tokens[1])) fmt.Println(value) case "getaddr": encoded, _ := hex.DecodeString(tokens[1]) @@ -162,7 +162,7 @@ func (i *Console) ParseInput(input string) bool { addr, _ := hex.DecodeString(tokens[1]) data, _ := ethutil.Config.Db.Get(addr) if len(data) != 0 { - decoder := ethutil.NewRlpValueFromBytes(data) + decoder := ethutil.NewValueFromBytes(data) fmt.Println(decoder) } else { fmt.Println("gettx: tx not found") -- cgit v1.2.3 From 3fd5715872152fdd07c724dbbd8b1e06b062826b Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 15 Feb 2014 11:49:29 +0100 Subject: Added git flow explanation --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4deaf4579..4fa8d3c5a 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,12 @@ Contribution If you'd like to contribute to Ethereum Go please fork, fix, commit and send a pull request. Commits who do not comply with the coding standards -are ignored. +are ignored. If you send pull requests make absolute sure that you +commit on the `develop` branch and that you do not merge to master. +Commits that are directly based on master are simply ignored. + +To make life easier try [git flow](http://nvie.com/posts/a-successful-git-branching-model/) it sets +this all up and streamlines your work flow. Coding standards ================ -- cgit v1.2.3 From 1ba7ffe9f8eb468c11c20c933a85022a3b2dabe1 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 15 Feb 2014 12:10:13 +0100 Subject: Added text for keys --- ethereum.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ethereum.go b/ethereum.go index 9907de740..372d434af 100644 --- a/ethereum.go +++ b/ethereum.go @@ -32,12 +32,22 @@ func RegisterInterupts(s *eth.Ethereum) { func CreateKeyPair(force bool) { data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) if len(data) == 0 || force { - log.Println("Generating new address and keypair") - pub, prv := secp256k1.GenerateKeyPair() addr := ethutil.Sha3Bin(pub[1:])[12:] - log.Printf("Your new address is %x\n", addr) + fmt.Printf(` +Generating new address and keypair. +Please keep your keys somewhere save. +Currently Ethereum(G) does not support +exporting keys. + +++++++++++++++++ KeyRing +++++++++++++++++++ +addr: %x +prvk: %x +pubk: %x +++++++++++++++++++++++++++++++++++++++++++++ + +`, addr, prv, pub) keyRing := ethutil.NewValue([]interface{}{prv, addr, pub[1:]}) ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) -- cgit v1.2.3 From 6db8b5d06a41ef573ec43394a11fd0e668372860 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 15 Feb 2014 13:27:23 +0100 Subject: Added link to dev package --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4fa8d3c5a..d0a08f7b8 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,9 @@ contains the LevelDB interface and memory DB interface. This executable is the front-end (currently nothing but a dev console) for the Ethereum Go implementation. +If you'd like to start developing your own tools please check out the +[development](https://github.com/ethereum/eth-go) package. + Build ======= -- cgit v1.2.3 From 8c8554f5584dcc0efb4526c1d5f4d50bf2ed3b01 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 18 Feb 2014 01:34:33 +0100 Subject: Added license name and updated block output from the dev console --- LICENSE | 2 +- dev_console.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 803f2ef4e..b77f7909a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Geff Obscura +Copyright (c) 2013 Jeffrey Wilcke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/dev_console.go b/dev_console.go index f2283e341..6670d2a44 100644 --- a/dev_console.go +++ b/dev_console.go @@ -136,7 +136,8 @@ func (i *Console) ParseInput(input string) bool { case "block": encoded, _ := hex.DecodeString(tokens[1]) block := i.ethereum.BlockManager.BlockChain().GetBlock(encoded) - fmt.Println(block) + info := block.BlockInfo() + fmt.Printf("++++++++++ #%d ++++++++++\n%v\n", info.Number, block) case "say": i.ethereum.Broadcast(ethwire.MsgTalkTy, []interface{}{tokens[1]}) case "addp": -- cgit v1.2.3 From ab7dc924042b4cdb36ec7f2b26ab14c40d34ec9d Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 18 Feb 2014 12:09:26 +0100 Subject: Added import/exporting of private keys --- config.go | 4 ++++ ethereum.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index d13bb863b..573f5ded2 100644 --- a/config.go +++ b/config.go @@ -13,6 +13,8 @@ var AddPeer string var MaxPeer int var GenAddr bool var UseSeed bool +var ImportKey string +var ExportKey bool func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") @@ -21,7 +23,9 @@ func Init() { flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") + flag.BoolVar(&ExportKey, "export", false, "export private key") flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") flag.Parse() diff --git a/ethereum.go b/ethereum.go index 372d434af..36700a6d4 100644 --- a/ethereum.go +++ b/ethereum.go @@ -38,8 +38,6 @@ func CreateKeyPair(force bool) { fmt.Printf(` Generating new address and keypair. Please keep your keys somewhere save. -Currently Ethereum(G) does not support -exporting keys. ++++++++++++++++ KeyRing +++++++++++++++++++ addr: %x @@ -54,6 +52,29 @@ pubk: %x } } +func ImportPrivateKey(prvKey string) { + key := ethutil.FromHex(prvKey) + msg := []byte("tmp") + // Couldn't think of a better way to get the pub key + sig, _ := secp256k1.Sign(msg, key) + pub, _ := secp256k1.RecoverPubkey(msg, sig) + addr := ethutil.Sha3Bin(pub[1:])[12:] + + fmt.Printf(` +Importing private key + +++++++++++++++++ KeyRing +++++++++++++++++++ +addr: %x +prvk: %x +pubk: %x +++++++++++++++++++++++++++++++++++++++++++++ + +`, addr, key, pub) + + keyRing := ethutil.NewValue([]interface{}{key, addr, pub[1:]}) + ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) +} + func main() { runtime.GOMAXPROCS(runtime.NumCPU()) Init() @@ -87,7 +108,31 @@ func main() { } os.Exit(0) } else { - CreateKeyPair(false) + if len(ImportKey) > 0 { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + + if r == "y" { + ImportPrivateKey(ImportKey) + } + } else { + CreateKeyPair(false) + } + } + + if ExportKey { + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + fmt.Printf("%x\n", keyRing.Get(0).Bytes()) + os.Exit(0) } if ShowGenesis { -- cgit v1.2.3 From 6736c03711b3ef35285392c12a79bbf6e4cdf914 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 18 Feb 2014 12:09:36 +0100 Subject: Added editor for contracts --- dev_console.go | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/dev_console.go b/dev_console.go index 6670d2a44..2e6b385df 100644 --- a/dev_console.go +++ b/dev_console.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "bytes" "encoding/hex" "errors" "fmt" @@ -78,6 +79,32 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { } } +func (i *Console) Editor() []string { + var buff bytes.Buffer + for { + reader := bufio.NewReader(os.Stdin) + str, _, err := reader.ReadLine() + if len(str) > 0 { + buff.Write(str) + buff.WriteString("\n") + } + + if err != nil && err.Error() == "EOF" { + break + } + } + + scanner := bufio.NewScanner(strings.NewReader(buff.String())) + scanner.Split(bufio.ScanLines) + + var lines []string + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + return lines +} + func (i *Console) PrintRoot() { root := ethutil.NewValue(i.trie.Root) if len(root.Bytes()) != 0 { @@ -169,10 +196,17 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("gettx: tx not found") } case "contract": - contract := ethchain.NewTransaction([]byte{}, ethutil.Big(tokens[1]), []string{"PUSH", "1234"}) - fmt.Printf("%x\n", contract.Hash()) + fmt.Println("Contract editor (Ctrl-D = done)") + code := i.Editor() + + contract := ethchain.NewTransaction([]byte{}, ethutil.Big(tokens[1]), code) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + contract.Sign(keyRing.Get(0).Bytes()) i.ethereum.TxPool.QueueTransaction(contract) + + fmt.Printf("%x\n", contract.Hash()) case "exit", "quit", "q": return false case "help": -- cgit v1.2.3 From d7ecc92c4134e3987b2b370bb53b0cd560fc0f7b Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 00:47:07 +0100 Subject: Fixed broken links. Fixes #18 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d0a08f7b8..e0d19b712 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ of Concept 2". For build instructions see the [Wiki](https://github.com/ethereum Ethereum Go is split up in several sub packages Please refer to each individual package for more information. 1. [eth](https://github.com/ethereum/eth-go) - 2. [ethchain](https://github.com/ethereum/ethchain-go) - 3. [ethwire](https://github.com/ethereum/ethwire-go) - 4. [ethdb](https://github.com/ethereum/ethdb-go) - 5. [ethutil](https://github.com/ethereum/ethutil-go) + 2. [ethchain](https://github.com/ethereum/eth-go/ethchain) + 3. [ethwire](https://github.com/ethereum/eth-go/ethwire) + 4. [ethdb](https://github.com/ethereum/eth-go/ethdb) + 5. [ethutil](https://github.com/ethereum/eth-go/ethutil) The [eth](https://github.com/ethereum/eth-go) is the top-level package of the Ethereum protocol. It functions as the Ethereum bootstrapping and -- cgit v1.2.3 From 05c353eca0c4e01457412dd643529200816ab159 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 12:37:40 +0100 Subject: Added a basic UI --- config.go | 2 ++ ethereum.go | 86 +++++++++++++++++++++++++++++++++++-------------------------- ui/gui.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ wallet.qml | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 ui/gui.go create mode 100644 wallet.qml diff --git a/config.go b/config.go index 573f5ded2..c25c8f2a3 100644 --- a/config.go +++ b/config.go @@ -15,11 +15,13 @@ var GenAddr bool var UseSeed bool var ImportKey string var ExportKey bool +var UseGui bool func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") + flag.BoolVar(&UseGui, "gui", false, "use the gui") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") diff --git a/ethereum.go b/ethereum.go index 36700a6d4..5eda09a44 100644 --- a/ethereum.go +++ b/ethereum.go @@ -5,6 +5,8 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/ui" + "github.com/niemeyer/qml" "github.com/obscuren/secp256k1-go" "log" "os" @@ -76,9 +78,16 @@ pubk: %x } func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) Init() + // Qt has to be initialized in the main thread or it will throw errors + // It has to be called BEFORE setting the maximum procs. + if UseGui { + qml.Init(nil) + } + + runtime.GOMAXPROCS(runtime.NumCPU()) + ethchain.InitFees() ethutil.ReadConfig(".ethereum") ethutil.Config.Seed = UseSeed @@ -156,41 +165,46 @@ func main() { go console.Start() } - RegisterInterupts(ethereum) - - ethereum.Start() - - if StartMining { - log.Printf("Miner started\n") - - // Fake block mining. It broadcasts a new block every 5 seconds - go func() { - pow := ðchain.EasyPow{} - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(1).Bytes() - - for { - txs := ethereum.TxPool.Flush() - // Create a new block which we're going to mine - block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) - // Apply all transactions to the block - ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) - - ethereum.BlockManager.AccumelateRewards(block, block) - - // Search the nonce - block.Nonce = pow.Search(block) - err := ethereum.BlockManager.ProcessBlock(block) - if err != nil { - log.Println(err) - } else { - log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockManager.BlockChain().CurrentBlock) + if UseGui { + gui := ethui.New(ethereum) + gui.Start() + //ethereum.Stop() + } else { + RegisterInterupts(ethereum) + ethereum.Start() + + if StartMining { + log.Printf("Miner started\n") + + // Fake block mining. It broadcasts a new block every 5 seconds + go func() { + pow := ðchain.EasyPow{} + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(1).Bytes() + + for { + txs := ethereum.TxPool.Flush() + // Create a new block which we're going to mine + block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) + // Apply all transactions to the block + ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) + + ethereum.BlockManager.AccumelateRewards(block, block) + + // Search the nonce + block.Nonce = pow.Search(block) + err := ethereum.BlockManager.ProcessBlock(block) + if err != nil { + log.Println(err) + } else { + log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockManager.BlockChain().CurrentBlock) + } } - } - }() - } + }() + } - // Wait for shutdown - ethereum.WaitForShutdown() + // Wait for shutdown + ethereum.WaitForShutdown() + } } diff --git a/ui/gui.go b/ui/gui.go new file mode 100644 index 000000000..b2a8dad73 --- /dev/null +++ b/ui/gui.go @@ -0,0 +1,83 @@ +package ethui + +import ( + "bufio" + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/niemeyer/qml" + "strings" +) + +type Gui struct { + win *qml.Window + engine *qml.Engine + component *qml.Common + eth *eth.Ethereum +} + +func New(ethereum *eth.Ethereum) *Gui { + return &Gui{eth: ethereum} +} + +type Block struct { + Number int + Hash string +} + +func NewBlockFromBlock(block *ethchain.Block) *Block { + info := block.BlockInfo() + hash := hex.EncodeToString(block.Hash()) + + return &Block{Number: int(info.Number), Hash: hash} +} + +func (ui *Gui) Start() { + qml.RegisterTypes("GoExtensions", 1, 0, []qml.TypeSpec{{ + Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, + }}) + + ethutil.Config.Log.Infoln("[GUI] Starting GUI") + ui.engine = qml.NewEngine() + component, err := ui.engine.LoadFile("wallet.qml") + if err != nil { + panic(err) + } + + ui.win = component.CreateWindow(nil) + root := ui.win.Root() + + context := ui.engine.Context() + context.SetVar("tester", &Tester{root: root}) + + ui.eth.BlockManager.SecondaryBlockProcessor = ui + ui.eth.Start() + + ui.win.Show() + ui.win.Wait() +} + +func (ui *Gui) ProcessBlock(block *ethchain.Block) { + ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) +} + +type Tester struct { + root qml.Object +} + +func (t *Tester) Compile(area qml.Object) { + fmt.Println(area) + ethutil.Config.Log.Infoln("[TESTER] Compiling") + + code := area.String("text") + + scanner := bufio.NewScanner(strings.NewReader(code)) + scanner.Split(bufio.ScanLines) + + var lines []string + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } +} diff --git a/wallet.qml b/wallet.qml new file mode 100644 index 000000000..2bf4e4576 --- /dev/null +++ b/wallet.qml @@ -0,0 +1,81 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import GoExtensions 1.0 + +ApplicationWindow { + id: root + + width: 800 + height: 600 + minimumHeight: 300 + + title: "Ethereal" + + toolBar: ToolBar { + id: mainToolbar + + RowLayout { + width: parent.width + Button { + text: "Send" + onClicked: tester.compile(codeView) + } + + TextField { + width: 200 + placeholderText: "Amount" + } + + TextField { + width: 300 + placeholderText: "Receiver Address (or empty for contract)" + Layout.fillWidth: true + } + + } + } + + SplitView { + id: splitView + height: 200 + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + TextArea { + id: codeView + width: parent.width /2 + } + + TextArea { + readOnly: true + } + } + + property var blockModel: ListModel { + id: blockModel + } + + TableView { + width: parent.width + height: 100 + anchors.bottom: parent.bottom + anchors.top: splitView.bottom + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + + model: blockModel + } + + + statusBar: StatusBar { + RowLayout { + Label { text: "0.0.1" } + } + } + + function addBlock(block) { + blockModel.append({number: block.number, hash: block.hash}) + } +} -- cgit v1.2.3 From aaac0c9998ee78d796c1dbab118f721f886ce426 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 13:06:17 +0100 Subject: Initial block chain fetching of existing blocks --- ui/gui.go | 12 +++++++++++- wallet.qml | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index b2a8dad73..aae1320fc 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -53,12 +53,22 @@ func (ui *Gui) Start() { context.SetVar("tester", &Tester{root: root}) ui.eth.BlockManager.SecondaryBlockProcessor = ui - ui.eth.Start() + + go ui.setInitialBlockChain() ui.win.Show() ui.win.Wait() } +func (ui *Gui) setInitialBlockChain() { + chain := ui.eth.BlockManager.BlockChain().GetChain(ui.eth.BlockManager.BlockChain().CurrentBlock.Hash(), 10) + for _, block := range chain { + ui.ProcessBlock(block) + } + + ui.eth.Start() +} + func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } diff --git a/wallet.qml b/wallet.qml index 2bf4e4576..cbce7ebcc 100644 --- a/wallet.qml +++ b/wallet.qml @@ -76,6 +76,6 @@ ApplicationWindow { } function addBlock(block) { - blockModel.append({number: block.number, hash: block.hash}) + blockModel.insert(0, {number: block.number, hash: block.hash}) } } -- cgit v1.2.3 From 95a48cea18eccd4ea2cb298027dbd01bd21f43e8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 13:23:35 +0100 Subject: Peer amount update --- network.png | Bin 0 -> 2900 bytes ui/gui.go | 9 +++++++++ wallet.qml | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 network.png diff --git a/network.png b/network.png new file mode 100644 index 000000000..0a9ffe2ec Binary files /dev/null and b/network.png differ diff --git a/ui/gui.go b/ui/gui.go index aae1320fc..e223fe262 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" "strings" + "time" ) type Gui struct { @@ -55,6 +56,7 @@ func (ui *Gui) Start() { ui.eth.BlockManager.SecondaryBlockProcessor = ui go ui.setInitialBlockChain() + go ui.updatePeers() ui.win.Show() ui.win.Wait() @@ -73,6 +75,13 @@ func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } +func (ui *Gui) updatePeers() { + for { + ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) + time.Sleep(1 * time.Second) + } +} + type Tester struct { root qml.Object } diff --git a/wallet.qml b/wallet.qml index cbce7ebcc..b19e7f32b 100644 --- a/wallet.qml +++ b/wallet.qml @@ -71,11 +71,30 @@ ApplicationWindow { statusBar: StatusBar { RowLayout { + anchors.fill: parent Label { text: "0.0.1" } + Label { + anchors.right: peerImage.left + anchors.rightMargin: 5 + id: peerLabel + font.pixelSize: 8 + text: "0 / 0" + } + + Image { + id: peerImage + anchors.right: parent.right + width: 10; height: 10 + source: "network.png" + } } } function addBlock(block) { blockModel.insert(0, {number: block.number, hash: block.hash}) } + + function setPeers(text) { + peerLabel.text = text + } } -- cgit v1.2.3 From 3e8b27c9dc78ffeeefae987e67730fae17707df4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Feb 2014 17:29:59 +0100 Subject: WIP library, sample app --- test_app.qml | 35 +++++++++++++++++++++++++++++++++++ ui/gui.go | 28 +++++++++++++++++++++++++--- ui/library.go | 42 ++++++++++++++++++++++++++++++++++++++++++ wallet.qml | 59 ++++++++++++++++++++++++++++++++++++++--------------------- 4 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 test_app.qml create mode 100644 ui/library.go diff --git a/test_app.qml b/test_app.qml new file mode 100644 index 000000000..c7593bf12 --- /dev/null +++ b/test_app.qml @@ -0,0 +1,35 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import GoExtensions 1.0 + +ApplicationWindow { + minimumWidth: 500 + maximumWidth: 500 + maximumHeight: 100 + minimumHeight: 100 + + title: "Ethereum Dice" + + TextField { + id: textField + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Amount" + } + Label { + id: txHash + anchors.bottom: textField.top + anchors.bottomMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + } + Button { + anchors.top: textField.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 5 + text: "Place bet" + onClicked: { + txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", parseInt(textField.text)) + } + } +} diff --git a/ui/gui.go b/ui/gui.go index e223fe262..8f063843c 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -17,10 +17,15 @@ type Gui struct { engine *qml.Engine component *qml.Common eth *eth.Ethereum + + // The Ethereum library + lib *EthLib } func New(ethereum *eth.Ethereum) *Gui { - return &Gui{eth: ethereum} + lib := &EthLib{blockManager: ethereum.BlockManager, blockChain: ethereum.BlockManager.BlockChain(), txPool: ethereum.TxPool} + + return &Gui{eth: ethereum, lib: lib} } type Block struct { @@ -48,10 +53,10 @@ func (ui *Gui) Start() { } ui.win = component.CreateWindow(nil) - root := ui.win.Root() context := ui.engine.Context() - context.SetVar("tester", &Tester{root: root}) + context.SetVar("eth", ui.lib) + context.SetVar("ui", &UiLib{engine: ui.engine}) ui.eth.BlockManager.SecondaryBlockProcessor = ui @@ -82,6 +87,23 @@ func (ui *Gui) updatePeers() { } } +type UiLib struct { + engine *qml.Engine +} + +func (ui *UiLib) Open(path string) { + component, err := ui.engine.LoadFile(path[7:]) + if err != nil { + ethutil.Config.Log.Debugln(err) + } + win := component.CreateWindow(nil) + + go func() { + win.Show() + win.Wait() + }() +} + type Tester struct { root qml.Object } diff --git a/ui/library.go b/ui/library.go new file mode 100644 index 000000000..36952e198 --- /dev/null +++ b/ui/library.go @@ -0,0 +1,42 @@ +package ethui + +import ( + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "math/big" +) + +type EthLib struct { + blockManager *ethchain.BlockManager + blockChain *ethchain.BlockChain + txPool *ethchain.TxPool +} + +func (lib *EthLib) CreateTx(receiver string, amount uint64) string { + hash, err := hex.DecodeString(receiver) + if err != nil { + return err.Error() + } + + tx := ethchain.NewTransaction(hash, big.NewInt(int64(amount)), []string{""}) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + tx.Sign(keyRing.Get(0).Bytes()) + + lib.txPool.QueueTransaction(tx) + + return ethutil.Hex(tx.Hash()) +} + +func (lib *EthLib) GetBlock(hexHash string) *Block { + hash, err := hex.DecodeString(hexHash) + if err != nil { + return nil + } + + block := lib.blockChain.GetBlock(hash) + fmt.Println(block) + return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} +} diff --git a/wallet.qml b/wallet.qml index b19e7f32b..f9bd8ec76 100644 --- a/wallet.qml +++ b/wallet.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; import GoExtensions 1.0 ApplicationWindow { @@ -12,6 +13,7 @@ ApplicationWindow { title: "Ethereal" + toolBar: ToolBar { id: mainToolbar @@ -19,7 +21,7 @@ ApplicationWindow { width: parent.width Button { text: "Send" - onClicked: tester.compile(codeView) + onClicked: console.log("SEND") } TextField { @@ -66,35 +68,50 @@ ApplicationWindow { TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } model: blockModel + + onDoubleClicked: { + console.log(eth.getBlock(blockModel.get(row).hash)) + } } + FileDialog { + id: openAppDialog + title: "Open QML Application" + onAccepted: { + ui.open(openAppDialog.fileUrl.toString()) + } + } statusBar: StatusBar { - RowLayout { - anchors.fill: parent - Label { text: "0.0.1" } - Label { - anchors.right: peerImage.left - anchors.rightMargin: 5 - id: peerLabel - font.pixelSize: 8 - text: "0 / 0" - } - - Image { - id: peerImage - anchors.right: parent.right - width: 10; height: 10 - source: "network.png" - } + RowLayout { + anchors.fill: parent + Button { + onClicked: openAppDialog.open() + text: "Import App" + } + + Label { text: "0.0.1" } + Label { + anchors.right: peerImage.left + anchors.rightMargin: 5 + id: peerLabel + font.pixelSize: 8 + text: "0 / 0" } + Image { + id: peerImage + anchors.right: parent.right + width: 10; height: 10 + source: "network.png" + } + } } function addBlock(block) { - blockModel.insert(0, {number: block.number, hash: block.hash}) + blockModel.insert(0, {number: block.number, hash: block.hash}) } function setPeers(text) { - peerLabel.text = text - } + peerLabel.text = text + } } -- cgit v1.2.3 From 2b967558cebcef9d3ef9719cbb28a5e596982a5d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 22 Feb 2014 01:52:47 +0100 Subject: Working out UI --- config.go | 2 +- ui/gui.go | 21 +++++++++++++++++++-- wallet.qml | 46 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index c25c8f2a3..79d40d706 100644 --- a/config.go +++ b/config.go @@ -21,7 +21,7 @@ func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") - flag.BoolVar(&UseGui, "gui", false, "use the gui") + flag.BoolVar(&UseGui, "gui", true, "use the gui") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") diff --git a/ui/gui.go b/ui/gui.go index 8f063843c..fad7e9591 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -56,10 +56,12 @@ func (ui *Gui) Start() { context := ui.engine.Context() context.SetVar("eth", ui.lib) - context.SetVar("ui", &UiLib{engine: ui.engine}) + context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) ui.eth.BlockManager.SecondaryBlockProcessor = ui + ethutil.Config.Log.AddLogSystem(ui) + go ui.setInitialBlockChain() go ui.updatePeers() @@ -73,13 +75,23 @@ func (ui *Gui) setInitialBlockChain() { ui.ProcessBlock(block) } - ui.eth.Start() } func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } +func (ui *Gui) Println(v ...interface{}) { + str := fmt.Sprintln(v...) + // remove last \n + ui.win.Root().Call("addLog", str[:len(str)-1]) +} + +func (ui *Gui) Printf(format string, v ...interface{}) { + str := strings.TrimRight(fmt.Sprintf(format, v...), "\n") + ui.win.Root().Call("addLog", str) +} + func (ui *Gui) updatePeers() { for { ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) @@ -89,6 +101,7 @@ func (ui *Gui) updatePeers() { type UiLib struct { engine *qml.Engine + eth *eth.Ethereum } func (ui *UiLib) Open(path string) { @@ -104,6 +117,10 @@ func (ui *UiLib) Open(path string) { }() } +func (ui *UiLib) Connect() { + ui.eth.Start() +} + type Tester struct { root qml.Object } diff --git a/wallet.qml b/wallet.qml index f9bd8ec76..e6cb32b18 100644 --- a/wallet.qml +++ b/wallet.qml @@ -2,6 +2,7 @@ import QtQuick 2.0 import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; import GoExtensions 1.0 ApplicationWindow { @@ -60,20 +61,36 @@ ApplicationWindow { } TableView { + id: blockTable width: parent.width - height: 100 - anchors.bottom: parent.bottom anchors.top: splitView.bottom + anchors.bottom: logView.top TableViewColumn{ role: "number" ; title: "#" ; width: 100 } TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } model: blockModel onDoubleClicked: { - console.log(eth.getBlock(blockModel.get(row).hash)) + popup.visible = true + popup.block = eth.getBlock(blockModel.get(row).hash) + popup.hashLabel.text = popup.block.hash } } + property var logModel: ListModel { + id: logModel + } + + TableView { + id: logView + width: parent.width + height: 150 + anchors.bottom: parent.bottom + TableViewColumn{ role: "description" ; title: "log" } + + model: logModel + } + FileDialog { id: openAppDialog title: "Open QML Application" @@ -86,6 +103,13 @@ ApplicationWindow { RowLayout { anchors.fill: parent Button { + id: connectButton + onClicked: ui.connect() + text: "Connect" + } + Button { + anchors.left: connectButton.right + anchors.leftMargin: 5 onClicked: openAppDialog.open() text: "Import App" } @@ -107,10 +131,26 @@ ApplicationWindow { } } + Window { + id: popup + visible: false + property var block + Label { + id: hashLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + function addBlock(block) { blockModel.insert(0, {number: block.number, hash: block.hash}) } + function addLog(str) { + console.log(str) + logModel.insert(0, {description: str}) + } + function setPeers(text) { peerLabel.text = text } -- cgit v1.2.3 From aa33a4b2fb9dc07468498decceb6fdb56d38f54d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 22 Feb 2014 23:19:38 +0100 Subject: Added some ui elements to make it easier to connect to nodes --- config.go | 2 ++ ethereum.go | 3 ++- test_app.qml | 4 +-- ui/gui.go | 81 +++++++++++++++++++++++++++++++++++++++++++---------------- ui/library.go | 10 +++++--- wallet.qml | 70 +++++++++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 136 insertions(+), 34 deletions(-) diff --git a/config.go b/config.go index 79d40d706..bafc3e300 100644 --- a/config.go +++ b/config.go @@ -16,6 +16,7 @@ var UseSeed bool var ImportKey string var ExportKey bool var UseGui bool +var DataDir string func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") @@ -27,6 +28,7 @@ func Init() { flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") flag.BoolVar(&ExportKey, "export", false, "export private key") flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.StringVar(&DataDir, "dir", ".ethereum", "ethereum data directory") flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") diff --git a/ethereum.go b/ethereum.go index 5eda09a44..384b22748 100644 --- a/ethereum.go +++ b/ethereum.go @@ -89,11 +89,12 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) ethchain.InitFees() - ethutil.ReadConfig(".ethereum") + ethutil.ReadConfig(DataDir) ethutil.Config.Seed = UseSeed // Instantiated a eth stack ethereum, err := eth.New(eth.CapDefault, UseUPnP) + ethereum.Port = OutboundPort if err != nil { log.Println("eth start err:", err) return diff --git a/test_app.qml b/test_app.qml index c7593bf12..aace4e881 100644 --- a/test_app.qml +++ b/test_app.qml @@ -1,7 +1,7 @@ import QtQuick 2.0 import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; -import GoExtensions 1.0 +import Ethereum 1.0 ApplicationWindow { minimumWidth: 500 @@ -29,7 +29,7 @@ ApplicationWindow { anchors.topMargin: 5 text: "Place bet" onClicked: { - txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", parseInt(textField.text)) + txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", textField.text) } } } diff --git a/ui/gui.go b/ui/gui.go index fad7e9591..51fd6d9a8 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -12,41 +12,56 @@ import ( "time" ) +// Block interface exposed to QML +type Block struct { + Number int + Hash string +} + +// Creates a new QML Block from a chain block +func NewBlockFromBlock(block *ethchain.Block) *Block { + info := block.BlockInfo() + hash := hex.EncodeToString(block.Hash()) + + return &Block{Number: int(info.Number), Hash: hash} +} + type Gui struct { - win *qml.Window + // The main application window + win *qml.Window + // QML Engine engine *qml.Engine component *qml.Common - eth *eth.Ethereum + // The ethereum interface + eth *eth.Ethereum - // The Ethereum library + // The public Ethereum library lib *EthLib } +// Create GUI, but doesn't start it func New(ethereum *eth.Ethereum) *Gui { lib := &EthLib{blockManager: ethereum.BlockManager, blockChain: ethereum.BlockManager.BlockChain(), txPool: ethereum.TxPool} - return &Gui{eth: ethereum, lib: lib} -} + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(1).Bytes() -type Block struct { - Number int - Hash string -} - -func NewBlockFromBlock(block *ethchain.Block) *Block { - info := block.BlockInfo() - hash := hex.EncodeToString(block.Hash()) + ethereum.BlockManager.WatchAddr(addr) - return &Block{Number: int(info.Number), Hash: hash} + return &Gui{eth: ethereum, lib: lib} } func (ui *Gui) Start() { - qml.RegisterTypes("GoExtensions", 1, 0, []qml.TypeSpec{{ + // Register ethereum functions + qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, }}) ethutil.Config.Log.Infoln("[GUI] Starting GUI") + // Create a new QML engine ui.engine = qml.NewEngine() + // Load the main QML interface component, err := ui.engine.LoadFile("wallet.qml") if err != nil { panic(err) @@ -55,13 +70,18 @@ func (ui *Gui) Start() { ui.win = component.CreateWindow(nil) context := ui.engine.Context() + + // Expose the eth library and the ui library to QML context.SetVar("eth", ui.lib) context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) + // Register the ui as a block processor ui.eth.BlockManager.SecondaryBlockProcessor = ui + // Add the ui as a log system so we can log directly to the UGI ethutil.Config.Log.AddLogSystem(ui) + // Loads previous blocks go ui.setInitialBlockChain() go ui.updatePeers() @@ -70,6 +90,7 @@ func (ui *Gui) Start() { } func (ui *Gui) setInitialBlockChain() { + // Load previous 10 blocks chain := ui.eth.BlockManager.BlockChain().GetChain(ui.eth.BlockManager.BlockChain().CurrentBlock.Hash(), 10) for _, block := range chain { ui.ProcessBlock(block) @@ -81,17 +102,24 @@ func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } +// Logging functions that log directly to the GUI interface func (ui *Gui) Println(v ...interface{}) { - str := fmt.Sprintln(v...) - // remove last \n - ui.win.Root().Call("addLog", str[:len(str)-1]) + str := strings.TrimRight(fmt.Sprintln(v...), "\n") + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } } func (ui *Gui) Printf(format string, v ...interface{}) { str := strings.TrimRight(fmt.Sprintf(format, v...), "\n") - ui.win.Root().Call("addLog", str) + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } } +// Simple go routine function that updates the list of peers in the GUI func (ui *Gui) updatePeers() { for { ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) @@ -99,11 +127,14 @@ func (ui *Gui) updatePeers() { } } +// UI Library that has some basic functionality exposed type UiLib struct { - engine *qml.Engine - eth *eth.Ethereum + engine *qml.Engine + eth *eth.Ethereum + connected bool } +// Opens a QML file (external application) func (ui *UiLib) Open(path string) { component, err := ui.engine.LoadFile(path[7:]) if err != nil { @@ -118,7 +149,13 @@ func (ui *UiLib) Open(path string) { } func (ui *UiLib) Connect() { - ui.eth.Start() + if !ui.connected { + ui.eth.Start() + } +} + +func (ui *UiLib) ConnectToPeer(addr string) { + ui.eth.ConnectToPeer(addr) } type Tester struct { diff --git a/ui/library.go b/ui/library.go index 36952e198..0dfb10ac7 100644 --- a/ui/library.go +++ b/ui/library.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" - "math/big" ) type EthLib struct { @@ -14,15 +13,18 @@ type EthLib struct { txPool *ethchain.TxPool } -func (lib *EthLib) CreateTx(receiver string, amount uint64) string { +func (lib *EthLib) CreateTx(receiver, a string) string { hash, err := hex.DecodeString(receiver) if err != nil { return err.Error() } - - tx := ethchain.NewTransaction(hash, big.NewInt(int64(amount)), []string{""}) data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) + + amount := ethutil.Big(a) + tx := ethchain.NewTransaction(hash, amount, []string{""}) + tx.Nonce = lib.blockManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce + tx.Sign(keyRing.Get(0).Bytes()) lib.txPool.QueueTransaction(tx) diff --git a/wallet.qml b/wallet.qml index e6cb32b18..b18614801 100644 --- a/wallet.qml +++ b/wallet.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; import QtQuick.Dialogs 1.0; import QtQuick.Window 2.1; -import GoExtensions 1.0 +import Ethereum 1.0 ApplicationWindow { id: root @@ -22,15 +22,19 @@ ApplicationWindow { width: parent.width Button { text: "Send" - onClicked: console.log("SEND") + onClicked: { + console.log(eth.createTx(txReceiver.text, txAmount.text)) + } } TextField { + id: txAmount width: 200 placeholderText: "Amount" } TextField { + id: txReceiver width: 300 placeholderText: "Receiver Address (or empty for contract)" Layout.fillWidth: true @@ -47,15 +51,43 @@ ApplicationWindow { anchors.left: parent.left TextArea { - id: codeView - width: parent.width /2 + id: codeView + width: parent.width /2 } TextArea { - readOnly: true + readOnly: true } } + MenuBar { + Menu { + title: "File" + MenuItem { + text: "Import App" + shortcut: "Ctrl+o" + onTriggered: openAppDialog.open() + } + } + + Menu { + title: "Network" + MenuItem { + text: "Add Peer" + shortcut: "Ctrl+p" + onTriggered: { + addPeerWin.visible = true + } + } + + MenuItem { + text: "Start" + onTriggered: ui.connect() + } + } + } + + property var blockModel: ListModel { id: blockModel } @@ -142,6 +174,34 @@ ApplicationWindow { } } + Window { + id: addPeerWin + visible: false + minimumWidth: 230 + maximumWidth: 230 + maximumHeight: 50 + minimumHeight: 50 + + TextField { + id: addrField + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + placeholderText: "address:port" + } + Button { + anchors.left: addrField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Add" + onClicked: { + ui.connectToPeer(addrField.text) + addrPeerWin.visible = false + } + } + } + + function addBlock(block) { blockModel.insert(0, {number: block.number, hash: block.hash}) } -- cgit v1.2.3 From 0656f465b0c0690f237e42ac1e8f306dcda6c40b Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 23 Feb 2014 01:56:04 +0100 Subject: Added transactions window --- transactions.qml | 9 +++ ui/gui.go | 19 ++++++ wallet.qml | 177 ++++++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 163 insertions(+), 42 deletions(-) create mode 100644 transactions.qml diff --git a/transactions.qml b/transactions.qml new file mode 100644 index 000000000..e9a035a85 --- /dev/null +++ b/transactions.qml @@ -0,0 +1,9 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; + +Rectangle { + id: transactionView + visible: false + Text { text: "TX VIEW" } +} diff --git a/ui/gui.go b/ui/gui.go index 51fd6d9a8..f0e281de1 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -18,6 +18,17 @@ type Block struct { Hash string } +type Tx struct { + Value, Hash, Address string +} + +func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { + hash := hex.EncodeToString(tx.Hash()) + sender := hex.EncodeToString(tx.Recipient) + + return &Tx{Hash: hash[:4], Value: tx.Value.String(), Address: sender} +} + // Creates a new QML Block from a chain block func NewBlockFromBlock(block *ethchain.Block) *Block { info := block.BlockInfo() @@ -56,6 +67,8 @@ func (ui *Gui) Start() { // Register ethereum functions qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, + }, { + Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, }}) ethutil.Config.Log.Infoln("[GUI] Starting GUI") @@ -66,6 +79,7 @@ func (ui *Gui) Start() { if err != nil { panic(err) } + ui.engine.LoadFile("transactions.qml") ui.win = component.CreateWindow(nil) @@ -77,6 +91,7 @@ func (ui *Gui) Start() { // Register the ui as a block processor ui.eth.BlockManager.SecondaryBlockProcessor = ui + ui.eth.TxPool.SecondaryProcessor = ui // Add the ui as a log system so we can log directly to the UGI ethutil.Config.Log.AddLogSystem(ui) @@ -102,6 +117,10 @@ func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } +func (ui *Gui) ProcessTransaction(tx *ethchain.Transaction) { + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) +} + // Logging functions that log directly to the GUI interface func (ui *Gui) Println(v ...interface{}) { str := strings.TrimRight(fmt.Sprintln(v...), "\n") diff --git a/wallet.qml b/wallet.qml index b18614801..6ee6ff110 100644 --- a/wallet.qml +++ b/wallet.qml @@ -3,18 +3,18 @@ import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; import QtQuick.Dialogs 1.0; import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 import Ethereum 1.0 ApplicationWindow { id: root - width: 800 + width: 900 height: 600 minimumHeight: 300 title: "Ethereal" - toolBar: ToolBar { id: mainToolbar @@ -43,22 +43,6 @@ ApplicationWindow { } } - SplitView { - id: splitView - height: 200 - anchors.top: parent.top - anchors.right: parent.right - anchors.left: parent.left - - TextArea { - id: codeView - width: parent.width /2 - } - - TextArea { - readOnly: true - } - } MenuBar { Menu { @@ -85,42 +69,133 @@ ApplicationWindow { onTriggered: ui.connect() } } + + Menu { + title: "Help" + MenuItem { + text: "About" + onTriggered: { + aboutWin.visible = true + } + } + } + } property var blockModel: ListModel { id: blockModel } + function setView(view) { + mainView.visible = false + transactionView.visible = false + view.visible = true + } - TableView { - id: blockTable - width: parent.width - anchors.top: splitView.bottom - anchors.bottom: logView.top - TableViewColumn{ role: "number" ; title: "#" ; width: 100 } - TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + SplitView { + anchors.fill: parent + + + Rectangle { + id: menu + width: 200 + anchors.bottom: parent.bottom + anchors.top: parent.top + color: "#D9DDE7" + + GridLayout { + columns: 1 + Button { + text: "Main" + onClicked: { + setView(mainView) + } + } + Button { + text: "Transactions" + onClicked: { + setView(transactionView) + } + } + } + + } - model: blockModel + property var txModel: ListModel { + id: txModel + } - onDoubleClicked: { - popup.visible = true - popup.block = eth.getBlock(blockModel.get(row).hash) - popup.hashLabel.text = popup.block.hash + Rectangle { + id: transactionView + visible: false + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + TableView { + id: txTableView + anchors.fill: parent + TableViewColumn{ role: "hash" ; title: "#" ; width: 150 } + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "address" ; title: "Address" ; } + + model: txModel + } } - } - property var logModel: ListModel { - id: logModel - } + Rectangle { + id: mainView + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.top: parent.top + SplitView { + id: splitView + height: 200 + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + TextArea { + id: codeView + width: parent.width /2 + } + + TextArea { + readOnly: true + } + } + + TableView { + id: blockTable + width: parent.width + anchors.top: splitView.bottom + anchors.bottom: logView.top + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + + model: blockModel - TableView { - id: logView - width: parent.width - height: 150 - anchors.bottom: parent.bottom - TableViewColumn{ role: "description" ; title: "log" } + onDoubleClicked: { + popup.visible = true + popup.block = eth.getBlock(blockModel.get(row).hash) + popup.hashLabel.text = popup.block.hash + } + } - model: logModel + property var logModel: ListModel { + id: logModel + } + + TableView { + id: logView + width: parent.width + height: 150 + anchors.bottom: parent.bottom + TableViewColumn{ role: "description" ; title: "log" } + + model: logModel + } + } } FileDialog { @@ -146,7 +221,6 @@ ApplicationWindow { text: "Import App" } - Label { text: "0.0.1" } Label { anchors.right: peerImage.left anchors.rightMargin: 5 @@ -201,6 +275,25 @@ ApplicationWindow { } } + Window { + id: aboutWin + visible: false + title: "About" + minimumWidth: 300 + maximumWidth: 300 + maximumHeight: 200 + minimumHeight: 200 + + Text { + font.pointSize: 18 + text: "Eth Go" + } + + } + + function addTx(tx) { + txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) + } function addBlock(block) { blockModel.insert(0, {number: block.number, hash: block.hash}) -- cgit v1.2.3 From fe9eb472887baec464cc50657affd85b13bcbeba Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 24 Feb 2014 13:51:16 +0100 Subject: Minor fixes that to reflect changes in library --- dev_console.go | 6 +++--- ethereum.go | 2 ++ ui/library.go | 28 +++++++++++++++++++++------- wallet.qml | 7 ++++--- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/dev_console.go b/dev_console.go index 2e6b385df..696493cdd 100644 --- a/dev_console.go +++ b/dev_console.go @@ -197,16 +197,16 @@ func (i *Console) ParseInput(input string) bool { } case "contract": fmt.Println("Contract editor (Ctrl-D = done)") - code := i.Editor() + code := ethchain.Compile(i.Editor()) - contract := ethchain.NewTransaction([]byte{}, ethutil.Big(tokens[1]), code) + contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) contract.Sign(keyRing.Get(0).Bytes()) i.ethereum.TxPool.QueueTransaction(contract) - fmt.Printf("%x\n", contract.Hash()) + fmt.Printf("%x\n", contract.Hash()[12:]) case "exit", "quit", "q": return false case "help": diff --git a/ethereum.go b/ethereum.go index 384b22748..0b941dce3 100644 --- a/ethereum.go +++ b/ethereum.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/ethwire" "github.com/ethereum/go-ethereum/ui" "github.com/niemeyer/qml" "github.com/obscuren/secp256k1-go" @@ -195,6 +196,7 @@ func main() { // Search the nonce block.Nonce = pow.Search(block) + ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) err := ethereum.BlockManager.ProcessBlock(block) if err != nil { log.Println(err) diff --git a/ui/library.go b/ui/library.go index 0dfb10ac7..c9273e8c5 100644 --- a/ui/library.go +++ b/ui/library.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "strings" ) type EthLib struct { @@ -13,22 +14,34 @@ type EthLib struct { txPool *ethchain.TxPool } -func (lib *EthLib) CreateTx(receiver, a string) string { - hash, err := hex.DecodeString(receiver) - if err != nil { - return err.Error() +func (lib *EthLib) CreateTx(receiver, a, data string) string { + var hash []byte + if len(receiver) == 0 { + hash = ethchain.ContractAddr + } else { + var err error + hash, err = hex.DecodeString(receiver) + if err != nil { + return err.Error() + } } - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) + + k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(k) amount := ethutil.Big(a) - tx := ethchain.NewTransaction(hash, amount, []string{""}) + code := ethchain.Compile(strings.Split(data, "\n")) + tx := ethchain.NewTransaction(hash, amount, code) tx.Nonce = lib.blockManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce tx.Sign(keyRing.Get(0).Bytes()) lib.txPool.QueueTransaction(tx) + if len(receiver) == 0 { + ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } + return ethutil.Hex(tx.Hash()) } @@ -40,5 +53,6 @@ func (lib *EthLib) GetBlock(hexHash string) *Block { block := lib.blockChain.GetBlock(hash) fmt.Println(block) + return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} } diff --git a/wallet.qml b/wallet.qml index 6ee6ff110..e7145cef3 100644 --- a/wallet.qml +++ b/wallet.qml @@ -23,7 +23,7 @@ ApplicationWindow { Button { text: "Send" onClicked: { - console.log(eth.createTx(txReceiver.text, txAmount.text)) + console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) } } @@ -300,8 +300,9 @@ ApplicationWindow { } function addLog(str) { - console.log(str) - logModel.insert(0, {description: str}) + if(str.len != 0) { + logModel.append({description: str}) + } } function setPeers(text) { -- cgit v1.2.3 From e60ff6ca41832c8124acfab6b8408516d60ac140 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 25 Feb 2014 10:54:15 +0100 Subject: Moved ui lib --- ui/ui_lib.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ui/ui_lib.go diff --git a/ui/ui_lib.go b/ui/ui_lib.go new file mode 100644 index 000000000..93712eba2 --- /dev/null +++ b/ui/ui_lib.go @@ -0,0 +1,38 @@ +package ethui + +import ( + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethutil" + "github.com/niemeyer/qml" +) + +// UI Library that has some basic functionality exposed +type UiLib struct { + engine *qml.Engine + eth *eth.Ethereum + connected bool +} + +// Opens a QML file (external application) +func (ui *UiLib) Open(path string) { + component, err := ui.engine.LoadFile(path[7:]) + if err != nil { + ethutil.Config.Log.Debugln(err) + } + win := component.CreateWindow(nil) + + go func() { + win.Show() + win.Wait() + }() +} + +func (ui *UiLib) Connect() { + if !ui.connected { + ui.eth.Start() + } +} + +func (ui *UiLib) ConnectToPeer(addr string) { + ui.eth.ConnectToPeer(addr) +} -- cgit v1.2.3 From 78b6e7ad9531461f389c5de3cef10fc665607050 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 25 Feb 2014 10:54:37 +0100 Subject: Proper Nonce --- ui/gui.go | 143 ++++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index f0e281de1..72e5b976a 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -1,15 +1,15 @@ package ethui import ( - "bufio" + "bytes" "encoding/hex" "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" "strings" - "time" ) // Block interface exposed to QML @@ -26,7 +26,7 @@ func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { hash := hex.EncodeToString(tx.Hash()) sender := hex.EncodeToString(tx.Recipient) - return &Tx{Hash: hash[:4], Value: tx.Value.String(), Address: sender} + return &Tx{Hash: hash, Value: tx.Value.String(), Address: sender} } // Creates a new QML Block from a chain block @@ -48,11 +48,19 @@ type Gui struct { // The public Ethereum library lib *EthLib + + txDb *ethdb.LDBDatabase + + addr []byte } // Create GUI, but doesn't start it func New(ethereum *eth.Ethereum) *Gui { lib := &EthLib{blockManager: ethereum.BlockManager, blockChain: ethereum.BlockManager.BlockChain(), txPool: ethereum.TxPool} + db, err := ethdb.NewLDBDatabase("tx_database") + if err != nil { + panic(err) + } data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) @@ -60,10 +68,12 @@ func New(ethereum *eth.Ethereum) *Gui { ethereum.BlockManager.WatchAddr(addr) - return &Gui{eth: ethereum, lib: lib} + return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} } func (ui *Gui) Start() { + defer ui.txDb.Close() + // Register ethereum functions qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, @@ -91,17 +101,20 @@ func (ui *Gui) Start() { // Register the ui as a block processor ui.eth.BlockManager.SecondaryBlockProcessor = ui - ui.eth.TxPool.SecondaryProcessor = ui + //ui.eth.TxPool.SecondaryProcessor = ui // Add the ui as a log system so we can log directly to the UGI ethutil.Config.Log.AddLogSystem(ui) // Loads previous blocks go ui.setInitialBlockChain() - go ui.updatePeers() + go ui.readPreviousTransactions() + go ui.update() ui.win.Show() ui.win.Wait() + + ui.eth.Stop() } func (ui *Gui) setInitialBlockChain() { @@ -113,12 +126,72 @@ func (ui *Gui) setInitialBlockChain() { } +func (ui *Gui) readPreviousTransactions() { + it := ui.txDb.Db().NewIterator(nil) + for it.Next() { + tx := ethchain.NewTransactionFromBytes(it.Value()) + + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + } + it.Release() +} + func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } func (ui *Gui) ProcessTransaction(tx *ethchain.Transaction) { + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + + // TODO replace with general subscribe model +} + +// Simple go routine function that updates the list of peers in the GUI +func (ui *Gui) update() { + txChan := make(chan ethchain.TxMsg) + ui.eth.TxPool.Subscribe(txChan) + + account := ui.eth.BlockManager.GetAddrState(ui.addr).Account + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", account.Amount)) + for { + select { + case txMsg := <-txChan: + tx := txMsg.Tx + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + // Yeah, yeah, stupid code. Refactor next week + if txMsg.Type == ethchain.TxPre { + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (- %v)", account.Amount, tx.Value)) + ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 + fmt.Println("Nonce", ui.eth.BlockManager.GetAddrState(ui.addr).Nonce) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (+ %v)", account.Amount, tx.Value)) + } + } else { + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + amount := account.Amount.Sub(account.Amount, tx.Value) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", amount)) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + amount := account.Amount.Sub(account.Amount, tx.Value) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", amount)) + } + } + } + + /* + accountAmount := ui.eth.BlockManager.GetAddrState(ui.addr).Account.Amount + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", accountAmount)) + + ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) + + time.Sleep(1 * time.Second) + */ + + } } // Logging functions that log directly to the GUI interface @@ -137,61 +210,3 @@ func (ui *Gui) Printf(format string, v ...interface{}) { ui.win.Root().Call("addLog", line) } } - -// Simple go routine function that updates the list of peers in the GUI -func (ui *Gui) updatePeers() { - for { - ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) - time.Sleep(1 * time.Second) - } -} - -// UI Library that has some basic functionality exposed -type UiLib struct { - engine *qml.Engine - eth *eth.Ethereum - connected bool -} - -// Opens a QML file (external application) -func (ui *UiLib) Open(path string) { - component, err := ui.engine.LoadFile(path[7:]) - if err != nil { - ethutil.Config.Log.Debugln(err) - } - win := component.CreateWindow(nil) - - go func() { - win.Show() - win.Wait() - }() -} - -func (ui *UiLib) Connect() { - if !ui.connected { - ui.eth.Start() - } -} - -func (ui *UiLib) ConnectToPeer(addr string) { - ui.eth.ConnectToPeer(addr) -} - -type Tester struct { - root qml.Object -} - -func (t *Tester) Compile(area qml.Object) { - fmt.Println(area) - ethutil.Config.Log.Infoln("[TESTER] Compiling") - - code := area.String("text") - - scanner := bufio.NewScanner(strings.NewReader(code)) - scanner.Split(bufio.ScanLines) - - var lines []string - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } -} -- cgit v1.2.3 From 6451a7187a5eeff6ac819ded11b6e7f0a5aa1b1b Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 25 Feb 2014 10:55:44 +0100 Subject: Minor UI change --- ui/library.go | 2 ++ wallet.qml | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ui/library.go b/ui/library.go index c9273e8c5..3bbb01314 100644 --- a/ui/library.go +++ b/ui/library.go @@ -40,6 +40,8 @@ func (lib *EthLib) CreateTx(receiver, a, data string) string { if len(receiver) == 0 { ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } else { + ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) } return ethutil.Hex(tx.Hash()) diff --git a/wallet.qml b/wallet.qml index e7145cef3..3e921b78d 100644 --- a/wallet.qml +++ b/wallet.qml @@ -221,6 +221,10 @@ ApplicationWindow { text: "Import App" } + Label { + id: walletValueLabel + } + Label { anchors.right: peerImage.left anchors.rightMargin: 5 @@ -270,7 +274,7 @@ ApplicationWindow { text: "Add" onClicked: { ui.connectToPeer(addrField.text) - addrPeerWin.visible = false + addPeerWin.visible = false } } } @@ -291,6 +295,10 @@ ApplicationWindow { } + function setWalletValue(value) { + walletValueLabel.text = value + } + function addTx(tx) { txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) } -- cgit v1.2.3 From dba1ba38220c92f96aaeeb2d4cc99c1991220dc4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 25 Feb 2014 11:24:04 +0100 Subject: Currency to string --- ui/gui.go | 14 +++++++------- wallet.qml | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index 72e5b976a..6e30b5891 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -26,7 +26,7 @@ func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { hash := hex.EncodeToString(tx.Hash()) sender := hex.EncodeToString(tx.Recipient) - return &Tx{Hash: hash, Value: tx.Value.String(), Address: sender} + return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender} } // Creates a new QML Block from a chain block @@ -154,7 +154,7 @@ func (ui *Gui) update() { ui.eth.TxPool.Subscribe(txChan) account := ui.eth.BlockManager.GetAddrState(ui.addr).Account - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", account.Amount)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) for { select { case txMsg := <-txChan: @@ -162,22 +162,22 @@ func (ui *Gui) update() { ui.txDb.Put(tx.Hash(), tx.RlpEncode()) ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - // Yeah, yeah, stupid code. Refactor next week + // TODO FOR THE LOVE OF EVERYTHING GOOD IN THIS WORLD REFACTOR ME if txMsg.Type == ethchain.TxPre { if bytes.Compare(tx.Sender(), ui.addr) == 0 { - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (- %v)", account.Amount, tx.Value)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (- %v)", ethutil.CurrencyToString(account.Amount), ethutil.CurrencyToString(tx.Value))) ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 fmt.Println("Nonce", ui.eth.BlockManager.GetAddrState(ui.addr).Nonce) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (+ %v)", account.Amount, tx.Value)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (+ %v)", ethutil.CurrencyToString(account.Amount), ethutil.CurrencyToString(tx.Value))) } } else { if bytes.Compare(tx.Sender(), ui.addr) == 0 { amount := account.Amount.Sub(account.Amount, tx.Value) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", amount)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { amount := account.Amount.Sub(account.Amount, tx.Value) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", amount)) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) } } } diff --git a/wallet.qml b/wallet.qml index 3e921b78d..04c1ffaed 100644 --- a/wallet.qml +++ b/wallet.qml @@ -215,6 +215,7 @@ ApplicationWindow { text: "Connect" } Button { + id: importAppButton anchors.left: connectButton.right anchors.leftMargin: 5 onClicked: openAppDialog.open() @@ -222,6 +223,8 @@ ApplicationWindow { } Label { + anchors.left: importAppButton.right + anchors.leftMargin: 5 id: walletValueLabel } -- cgit v1.2.3 From 8d1d72abeeee68ddcdffe02a28587a9bec1647e9 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:16:46 +0100 Subject: Improved overall UI design and added a bunch of icons --- net.png | Bin 0 -> 4669 bytes net.pxm | Bin 0 -> 95418 bytes new.png | Bin 0 -> 4776 bytes tx.png | Bin 0 -> 4070 bytes tx.pxm | Bin 0 -> 93227 bytes ui/gui.go | 51 +++++++++++--------- wallet.qml | 157 +++++++++++++++++++++++++++++++++++++------------------------ 7 files changed, 124 insertions(+), 84 deletions(-) create mode 100644 net.png create mode 100644 net.pxm create mode 100644 new.png create mode 100644 tx.png create mode 100644 tx.pxm diff --git a/net.png b/net.png new file mode 100644 index 000000000..65a20ea00 Binary files /dev/null and b/net.png differ diff --git a/net.pxm b/net.pxm new file mode 100644 index 000000000..20d45d08c Binary files /dev/null and b/net.pxm differ diff --git a/new.png b/new.png new file mode 100644 index 000000000..e80096748 Binary files /dev/null and b/new.png differ diff --git a/tx.png b/tx.png new file mode 100644 index 000000000..62204c315 Binary files /dev/null and b/tx.png differ diff --git a/tx.pxm b/tx.pxm new file mode 100644 index 000000000..881420da9 Binary files /dev/null and b/tx.pxm differ diff --git a/ui/gui.go b/ui/gui.go index 6e30b5891..556e682a9 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" + "math/big" "strings" ) @@ -62,9 +63,8 @@ func New(ethereum *eth.Ethereum) *Gui { panic(err) } - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(1).Bytes() + key := ethutil.Config.Db.GetKeys()[0] + addr := key.Address() ethereum.BlockManager.WatchAddr(addr) @@ -127,7 +127,7 @@ func (ui *Gui) setInitialBlockChain() { } func (ui *Gui) readPreviousTransactions() { - it := ui.txDb.Db().NewIterator(nil) + it := ui.txDb.Db().NewIterator(nil, nil) for it.Next() { tx := ethchain.NewTransactionFromBytes(it.Value()) @@ -140,45 +140,50 @@ func (ui *Gui) ProcessBlock(block *ethchain.Block) { ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) } -func (ui *Gui) ProcessTransaction(tx *ethchain.Transaction) { - ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - - // TODO replace with general subscribe model -} - // Simple go routine function that updates the list of peers in the GUI func (ui *Gui) update() { - txChan := make(chan ethchain.TxMsg) + txChan := make(chan ethchain.TxMsg, 1) ui.eth.TxPool.Subscribe(txChan) account := ui.eth.BlockManager.GetAddrState(ui.addr).Account + unconfirmedFunds := new(big.Int) ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) for { select { case txMsg := <-txChan: tx := txMsg.Tx - ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - // TODO FOR THE LOVE OF EVERYTHING GOOD IN THIS WORLD REFACTOR ME if txMsg.Type == ethchain.TxPre { if bytes.Compare(tx.Sender(), ui.addr) == 0 { - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (- %v)", ethutil.CurrencyToString(account.Amount), ethutil.CurrencyToString(tx.Value))) + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 - fmt.Println("Nonce", ui.eth.BlockManager.GetAddrState(ui.addr).Nonce) + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v (+ %v)", ethutil.CurrencyToString(account.Amount), ethutil.CurrencyToString(tx.Value))) + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + unconfirmedFunds.Add(unconfirmedFunds, tx.Value) + } + + pos := "+" + if unconfirmedFunds.Cmp(big.NewInt(0)) >= 0 { + pos = "-" } + val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds))) + str := fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(account.Amount), pos, val) + + ui.win.Root().Call("setWalletValue", str) } else { + amount := account.Amount if bytes.Compare(tx.Sender(), ui.addr) == 0 { - amount := account.Amount.Sub(account.Amount, tx.Value) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) + amount.Sub(account.Amount, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - amount := account.Amount.Sub(account.Amount, tx.Value) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) + amount.Add(account.Amount, tx.Value) } + + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) } } diff --git a/wallet.qml b/wallet.qml index 04c1ffaed..8c91039fc 100644 --- a/wallet.qml +++ b/wallet.qml @@ -15,35 +15,6 @@ ApplicationWindow { title: "Ethereal" - toolBar: ToolBar { - id: mainToolbar - - RowLayout { - width: parent.width - Button { - text: "Send" - onClicked: { - console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) - } - } - - TextField { - id: txAmount - width: 200 - placeholderText: "Amount" - } - - TextField { - id: txReceiver - width: 300 - placeholderText: "Receiver Address (or empty for contract)" - Layout.fillWidth: true - } - - } - } - - MenuBar { Menu { title: "File" @@ -86,35 +57,61 @@ ApplicationWindow { property var blockModel: ListModel { id: blockModel } - function setView(view) { - mainView.visible = false - transactionView.visible = false - view.visible = true - } + + function setView(view) { + networkView.visible = false + historyView.visible = false + newTxView.visible = false + view.visible = true + //root.title = "Ethereal - " = view.title + } SplitView { anchors.fill: parent - + resizing: false Rectangle { id: menu - width: 200 + Layout.minimumWidth: 80 + Layout.maximumWidth: 80 anchors.bottom: parent.bottom anchors.top: parent.top - color: "#D9DDE7" + //color: "#D9DDE7" + color: "#252525" - GridLayout { - columns: 1 - Button { - text: "Main" - onClicked: { - setView(mainView) + ColumnLayout { + y: 50 + anchors.left: parent.left + anchors.right: parent.right + height: 200 + Image { + source: "tx.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(historyView) + } } } - Button { - text: "Transactions" - onClicked: { - setView(transactionView) + Image { + source: "new.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(newTxView) + } + } + } + Image { + source: "net.png" + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(networkView) + } } } } @@ -126,8 +123,8 @@ ApplicationWindow { } Rectangle { - id: transactionView - visible: false + id: historyView + property var title: "Transactions" anchors.right: parent.right anchors.left: menu.right anchors.bottom: parent.bottom @@ -135,40 +132,73 @@ ApplicationWindow { TableView { id: txTableView anchors.fill: parent - TableViewColumn{ role: "hash" ; title: "#" ; width: 150 } TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } - TableViewColumn{ role: "address" ; title: "Address" ; } + TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } model: txModel } } Rectangle { - id: mainView + id: newTxView + property var title: "New transaction" + visible: false anchors.right: parent.right + anchors.left: menu.right anchors.bottom: parent.bottom anchors.top: parent.top - SplitView { - id: splitView - height: 200 - anchors.top: parent.top - anchors.right: parent.right + color: "#00000000" + + ColumnLayout { + width: 400 anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 5 + anchors.topMargin: 5 + TextField { + id: txAmount + width: 200 + placeholderText: "Amount" + } + + TextField { + id: txReceiver + placeholderText: "Receiver Address (or empty for contract)" + Layout.fillWidth: true + } + Label { + text: "Transaction data" + } TextArea { id: codeView + anchors.topMargin: 5 + Layout.fillWidth: true width: parent.width /2 } - TextArea { - readOnly: true + Button { + text: "Send" + onClicked: { + console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) + } } } + } + + + Rectangle { + id: networkView + property var title: "Network" + visible: false + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.top: parent.top TableView { id: blockTable width: parent.width - anchors.top: splitView.bottom + anchors.top: parent.top anchors.bottom: logView.top TableViewColumn{ role: "number" ; title: "#" ; width: 100 } TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } @@ -210,8 +240,13 @@ ApplicationWindow { RowLayout { anchors.fill: parent Button { + property var enabled: true id: connectButton - onClicked: ui.connect() + onClicked: { + if(this.enabled) { + ui.connect(this) + } + } text: "Connect" } Button { -- cgit v1.2.3 From 5e7f8cca4f83566fdfbd1eb76c8b565094958e6c Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:17:02 +0100 Subject: Exit after importing a key --- ethereum.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/ethereum.go b/ethereum.go index 0b941dce3..336cd4d00 100644 --- a/ethereum.go +++ b/ethereum.go @@ -36,7 +36,8 @@ func CreateKeyPair(force bool) { data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) if len(data) == 0 || force { pub, prv := secp256k1.GenerateKeyPair() - addr := ethutil.Sha3Bin(pub[1:])[12:] + pair := ðutil.Key{PrivateKey: prv, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) fmt.Printf(` Generating new address and keypair. @@ -48,10 +49,8 @@ prvk: %x pubk: %x ++++++++++++++++++++++++++++++++++++++++++++ -`, addr, prv, pub) +`, pair.Address(), prv, pub) - keyRing := ethutil.NewValue([]interface{}{prv, addr, pub[1:]}) - ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) } } @@ -61,7 +60,8 @@ func ImportPrivateKey(prvKey string) { // Couldn't think of a better way to get the pub key sig, _ := secp256k1.Sign(msg, key) pub, _ := secp256k1.RecoverPubkey(msg, sig) - addr := ethutil.Sha3Bin(pub[1:])[12:] + pair := ðutil.Key{PrivateKey: key, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) fmt.Printf(` Importing private key @@ -72,10 +72,7 @@ prvk: %x pubk: %x ++++++++++++++++++++++++++++++++++++++++++++ -`, addr, key, pub) - - keyRing := ethutil.NewValue([]interface{}{key, addr, pub[1:]}) - ethutil.Config.Db.Put([]byte("KeyRing"), keyRing.Encode()) +`, pair.Address(), key, pub) } func main() { @@ -95,11 +92,11 @@ func main() { // Instantiated a eth stack ethereum, err := eth.New(eth.CapDefault, UseUPnP) - ethereum.Port = OutboundPort if err != nil { log.Println("eth start err:", err) return } + ethereum.Port = OutboundPort if GenAddr { fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") @@ -133,6 +130,7 @@ func main() { if r == "y" { ImportPrivateKey(ImportKey) + os.Exit(0) } } else { CreateKeyPair(false) @@ -140,9 +138,8 @@ func main() { } if ExportKey { - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - fmt.Printf("%x\n", keyRing.Get(0).Bytes()) + key := ethutil.Config.Db.GetKeys()[0] + fmt.Printf("%x\n", key.PrivateKey) os.Exit(0) } -- cgit v1.2.3 From 075acec9e709d3cda8a791cb0829720f5a1ae142 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:17:26 +0100 Subject: Changed to new get keys method on database interface --- dev_console.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dev_console.go b/dev_console.go index 696493cdd..09e06aa22 100644 --- a/dev_console.go +++ b/dev_console.go @@ -179,13 +179,13 @@ func (i *Console) ParseInput(input string) bool { fmt.Println("recipient err:", err) } else { tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - tx.Sign(keyRing.Get(0).Bytes()) - fmt.Printf("%x\n", tx.Hash()) + + key := ethutil.Config.Db.GetKeys()[0] + tx.Sign(key.PrivateKey) i.ethereum.TxPool.QueueTransaction(tx) - } + fmt.Printf("%x\n", tx.Hash()) + } case "gettx": addr, _ := hex.DecodeString(tokens[1]) data, _ := ethutil.Config.Db.Get(addr) @@ -200,9 +200,9 @@ func (i *Console) ParseInput(input string) bool { code := ethchain.Compile(i.Editor()) contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - contract.Sign(keyRing.Get(0).Bytes()) + + key := ethutil.Config.Db.GetKeys()[0] + contract.Sign(key.PrivateKey) i.ethereum.TxPool.QueueTransaction(contract) -- cgit v1.2.3 From 3a35d45ea84e055a4574a7a1d1b4b5485bea1331 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:17:43 +0100 Subject: Updated readme --- README.md | 41 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index e0d19b712..034b35f97 100644 --- a/README.md +++ b/README.md @@ -3,44 +3,18 @@ Ethereum [![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) -Ethereum Go developer client (c) Jeffrey Wilcke - -Ethereum is currently in its testing phase. The current state is "Proof -of Concept 2". For build instructions see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge). - -Ethereum Go is split up in several sub packages Please refer to each -individual package for more information. - 1. [eth](https://github.com/ethereum/eth-go) - 2. [ethchain](https://github.com/ethereum/eth-go/ethchain) - 3. [ethwire](https://github.com/ethereum/eth-go/ethwire) - 4. [ethdb](https://github.com/ethereum/eth-go/ethdb) - 5. [ethutil](https://github.com/ethereum/eth-go/ethutil) - -The [eth](https://github.com/ethereum/eth-go) is the top-level package -of the Ethereum protocol. It functions as the Ethereum bootstrapping and -peer communication layer. The [ethchain](https://github.com/ethereum/ethchain-go) -contains the Ethereum blockchain, block manager, transaction and -transaction handlers. The [ethwire](https://github.com/ethereum/ethwire-go) contains -the Ethereum [wire protocol](http://wiki.ethereum.org/index.php/Wire_Protocol) which can be used -to hook in to the Ethereum network. [ethutil](https://github.com/ethereum/ethutil-go) contains -utility functions which are not Ethereum specific. The utility package -contains the [patricia trie](http://wiki.ethereum.org/index.php/Patricia_Tree), -[RLP Encoding](http://wiki.ethereum.org/index.php/RLP) and hex encoding -helpers. The [ethdb](https://github.com/ethereum/ethdb-go) package -contains the LevelDB interface and memory DB interface. - -This executable is the front-end (currently nothing but a dev console) for -the Ethereum Go implementation. - -If you'd like to start developing your own tools please check out the -[development](https://github.com/ethereum/eth-go) package. +Ethereum Go Client (c) Jeffrey Wilcke + +The current state is "Proof of Concept 3". For build instructions see +the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge). + +For the development Go Package please see [eth-go package](https://github.com/ethereum/eth-go). Build ======= For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge) - Command line options ==================== @@ -52,6 +26,9 @@ Command line options -upnp Enable UPnP (= false) -x Desired amount of peers (= 5) -h This help +-gui Launch with GUI (= true) +-dir Data directory used to store configs and databases (=".ethereum") +-import Import a private key (hex) ``` Developer console commands -- cgit v1.2.3 From 560a7073f457a32c9d053f1a4b20f76d949f519d Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 12:18:19 +0100 Subject: Fixed connection button spamming --- ui/ui_lib.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/ui_lib.go b/ui/ui_lib.go index 93712eba2..c956fd032 100644 --- a/ui/ui_lib.go +++ b/ui/ui_lib.go @@ -27,9 +27,11 @@ func (ui *UiLib) Open(path string) { }() } -func (ui *UiLib) Connect() { +func (ui *UiLib) Connect(button qml.Object) { if !ui.connected { ui.eth.Start() + ui.connected = true + button.Set("enabled", false) } } -- cgit v1.2.3 From f6a9aa41101b6154ce5f019a77e6c897df662466 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 28 Feb 2014 13:08:41 +0100 Subject: fixed about window --- facet.png | Bin 0 -> 27302 bytes wallet.qml | 21 +++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 facet.png diff --git a/facet.png b/facet.png new file mode 100644 index 000000000..49a266e96 Binary files /dev/null and b/facet.png differ diff --git a/wallet.qml b/wallet.qml index 8c91039fc..e86551ad6 100644 --- a/wallet.qml +++ b/wallet.qml @@ -321,14 +321,27 @@ ApplicationWindow { id: aboutWin visible: false title: "About" - minimumWidth: 300 - maximumWidth: 300 + minimumWidth: 350 + maximumWidth: 350 maximumHeight: 200 minimumHeight: 200 + Image { + id: aboutIcon + height: 150 + width: 150 + fillMode: Image.PreserveAspectFit + smooth: true + source: "facet.png" + x: 10 + y: 10 + } + Text { - font.pointSize: 18 - text: "Eth Go" + anchors.left: aboutIcon.right + anchors.leftMargin: 10 + font.pointSize: 12 + text: "

Ethereum(Go)


Development

Jeffrey Wilcke

Binary Distribution

Jarrad Hope
" } } -- cgit v1.2.3 From aa7c53b7efacb35b23668f95df4349ff380b6910 Mon Sep 17 00:00:00 2001 From: Jarrad Hope Date: Fri, 28 Feb 2014 16:41:30 +0100 Subject: Search bin directory for qml --- ui/gui.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index 556e682a9..389fe4f68 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -9,6 +9,8 @@ import ( "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" + "bitbucket.org/kardianos/osext" + "path/filepath" "math/big" "strings" ) @@ -84,12 +86,16 @@ func (ui *Gui) Start() { ethutil.Config.Log.Infoln("[GUI] Starting GUI") // Create a new QML engine ui.engine = qml.NewEngine() + + // Get Binary Directory + exedir , _ := osext.ExecutableFolder() + // Load the main QML interface - component, err := ui.engine.LoadFile("wallet.qml") + component, err := ui.engine.LoadFile(filepath.Join(exedir, "wallet.qml")) if err != nil { panic(err) } - ui.engine.LoadFile("transactions.qml") + ui.engine.LoadFile(filepath.Join(exedir, "transactions.qml")) ui.win = component.CreateWindow(nil) -- cgit v1.2.3 From 59a68b316f18aadd916aec3255af3d7e81f5b05c Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Fri, 28 Feb 2014 17:02:24 +0100 Subject: Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 034b35f97..f9f6ddbe6 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,7 @@ For the development Go Package please see [eth-go package](https://github.com/et Build ======= -For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge) - +For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go) Command line options ==================== -- cgit v1.2.3 From 30c5922aa4c5d3e0bc730f2bf05b18be661c78b2 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Fri, 28 Feb 2014 17:02:38 +0100 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9f6ddbe6..11f5f9f96 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ For the development Go Package please see [eth-go package](https://github.com/et Build ======= -For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go) +For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go)) Command line options ==================== -- cgit v1.2.3 From c6d6ca283d24f7860dfa185c95a3d7b4a90afc60 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Fri, 28 Feb 2014 17:04:45 +0100 Subject: Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 034b35f97..986c5e486 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ Ethereum Ethereum Go Client (c) Jeffrey Wilcke -The current state is "Proof of Concept 3". For build instructions see -the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Edge). +The current state is "Proof of Concept 3". For the development Go Package please see [eth-go package](https://github.com/ethereum/eth-go). -- cgit v1.2.3 From b0be8474162bd3d12669600f4072c422089bc77a Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Mar 2014 00:53:10 +0100 Subject: Updated to use the state on the blocks --- dev_console.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_console.go b/dev_console.go index 09e06aa22..5a69ffa9e 100644 --- a/dev_console.go +++ b/dev_console.go @@ -158,7 +158,7 @@ func (i *Console) ParseInput(input string) bool { fmt.Println(value) case "getaddr": encoded, _ := hex.DecodeString(tokens[1]) - addr := i.ethereum.BlockManager.BlockChain().CurrentBlock.GetAddr(encoded) + addr := i.ethereum.BlockManager.BlockChain().CurrentBlock.State().GetAccount(encoded) fmt.Println("addr:", addr) case "block": encoded, _ := hex.DecodeString(tokens[1]) -- cgit v1.2.3 From 00533003b7e5369260f63e65b88e2f08ab33350b Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Mar 2014 00:53:24 +0100 Subject: Get rid of the xpm files --- net.pxm | Bin 95418 -> 0 bytes tx.pxm | Bin 93227 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 net.pxm delete mode 100644 tx.pxm diff --git a/net.pxm b/net.pxm deleted file mode 100644 index 20d45d08c..000000000 Binary files a/net.pxm and /dev/null differ diff --git a/tx.pxm b/tx.pxm deleted file mode 100644 index 881420da9..000000000 Binary files a/tx.pxm and /dev/null differ -- cgit v1.2.3 From a482b0cc1b7ddbeae00988c7dd10391df01b0009 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Mar 2014 00:54:05 +0100 Subject: WIP Makefile --- Makefile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..4da4c551c --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +UNAME = $(shell uname) + +# Default is building +all: + go install + +install: +# Linux build +ifeq ($(UNAME),Linux) + mkdir /usr/local/ethereal + files=(wallet.qml net.png network.png new.png tx.png) + for file in "${files[@]}"; do + cp $file /usr/share/ethereal + done + cp $GOPATH/bin/go-ethereum /usr/local/bin/ethereal +endif +# OS X build +ifeq ($(UNAME),Darwin) + # Execute py script +endif -- cgit v1.2.3 From 3d2c3b0107d438dd081c57ef74036f4c5a03855f Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Mar 2014 00:54:41 +0100 Subject: Using asset path helper (includes a debug path atm) --- ui/gui.go | 9 +-- ui/ui_lib.go | 27 ++++++++ wallet.qml | 198 +++++++++++++++++++++++++++++++++++------------------------ 3 files changed, 147 insertions(+), 87 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index 389fe4f68..196d2cd76 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -9,8 +9,6 @@ import ( "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" - "bitbucket.org/kardianos/osext" - "path/filepath" "math/big" "strings" ) @@ -87,15 +85,12 @@ func (ui *Gui) Start() { // Create a new QML engine ui.engine = qml.NewEngine() - // Get Binary Directory - exedir , _ := osext.ExecutableFolder() - // Load the main QML interface - component, err := ui.engine.LoadFile(filepath.Join(exedir, "wallet.qml")) + component, err := ui.engine.LoadFile(AssetPath("wallet.qml")) if err != nil { panic(err) } - ui.engine.LoadFile(filepath.Join(exedir, "transactions.qml")) + ui.engine.LoadFile(AssetPath("transactions.qml")) ui.win = component.CreateWindow(nil) diff --git a/ui/ui_lib.go b/ui/ui_lib.go index c956fd032..2a1abee23 100644 --- a/ui/ui_lib.go +++ b/ui/ui_lib.go @@ -1,9 +1,13 @@ package ethui import ( + "bitbucket.org/kardianos/osext" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" + "path" + "path/filepath" + "runtime" ) // UI Library that has some basic functionality exposed @@ -38,3 +42,26 @@ func (ui *UiLib) Connect(button qml.Object) { func (ui *UiLib) ConnectToPeer(addr string) { ui.eth.ConnectToPeer(addr) } + +func (ui *UiLib) AssetPath(p string) string { + return AssetPath(p) +} + +func AssetPath(p string) string { + var base string + switch runtime.GOOS { + case "darwin": + // Get Binary Directory + exedir, _ := osext.ExecutableFolder() + base = filepath.Join(exedir, "../Resources") + base = "/Users/jeffrey/go/src/github.com/ethereum/go-ethereum" + case "linux": + base = "/usr/share/ethereal" + case "window": + fallthrough + default: + base = "." + } + + return path.Join(base, p) +} diff --git a/wallet.qml b/wallet.qml index e86551ad6..39bc21f39 100644 --- a/wallet.qml +++ b/wallet.qml @@ -115,116 +115,141 @@ ApplicationWindow { } } } - - } - - property var txModel: ListModel { - id: txModel } Rectangle { - id: historyView - property var title: "Transactions" + id: mainView + color: "#00000000" anchors.right: parent.right anchors.left: menu.right anchors.bottom: parent.bottom anchors.top: parent.top - TableView { - id: txTableView - anchors.fill: parent - TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } - TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } - model: txModel + property var txModel: ListModel { + id: txModel } - } - Rectangle { - id: newTxView - property var title: "New transaction" - visible: false - anchors.right: parent.right - anchors.left: menu.right - anchors.bottom: parent.bottom - anchors.top: parent.top - color: "#00000000" + Rectangle { + id: historyView + anchors.fill: parent - ColumnLayout { - width: 400 - anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: 5 - anchors.topMargin: 5 - TextField { - id: txAmount - width: 200 - placeholderText: "Amount" - } + property var title: "Transactions" + TableView { + id: txTableView + anchors.fill: parent + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } - TextField { - id: txReceiver - placeholderText: "Receiver Address (or empty for contract)" - Layout.fillWidth: true + model: txModel } + } - Label { - text: "Transaction data" - } - TextArea { - id: codeView + Rectangle { + id: newTxView + property var title: "New transaction" + visible: false + anchors.fill: parent + color: "#00000000" + + ColumnLayout { + width: 400 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 5 anchors.topMargin: 5 - Layout.fillWidth: true - width: parent.width /2 - } + TextField { + id: txAmount + width: 200 + placeholderText: "Amount" + } + + TextField { + id: txReceiver + placeholderText: "Receiver Address (or empty for contract)" + Layout.fillWidth: true + } - Button { - text: "Send" - onClicked: { - console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) + Label { + text: "Transaction data" + } + TextArea { + id: codeView + anchors.topMargin: 5 + Layout.fillWidth: true + width: parent.width /2 + } + + Button { + text: "Send" + onClicked: { + console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) + } } } } - } - Rectangle { - id: networkView - property var title: "Network" - visible: false - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.top: parent.top + Rectangle { + id: networkView + property var title: "Network" + visible: false + anchors.fill: parent + + TableView { + id: blockTable + width: parent.width + anchors.top: parent.top + anchors.bottom: logView.top + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + + model: blockModel + + /* + onDoubleClicked: { + popup.visible = true + popup.block = eth.getBlock(blockModel.get(row).hash) + popup.hashLabel.text = popup.block.hash + } + */ + } - TableView { - id: blockTable - width: parent.width - anchors.top: parent.top - anchors.bottom: logView.top - TableViewColumn{ role: "number" ; title: "#" ; width: 100 } - TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + property var logModel: ListModel { + id: logModel + } - model: blockModel + TableView { + id: logView + width: parent.width + height: 150 + anchors.bottom: parent.bottom + TableViewColumn{ role: "description" ; title: "log" } - onDoubleClicked: { - popup.visible = true - popup.block = eth.getBlock(blockModel.get(row).hash) - popup.hashLabel.text = popup.block.hash + model: logModel } } - property var logModel: ListModel { - id: logModel + /* + signal addPlugin(string name) + Component { + id: pluginWindow + Rectangle { + anchors.fill: parent + Label { + id: pluginTitle + anchors.centerIn: parent + text: "Hello world" + } + Component.onCompleted: setView(this) + } } - TableView { - id: logView - width: parent.width - height: 150 - anchors.bottom: parent.bottom - TableViewColumn{ role: "description" ; title: "log" } - - model: logModel + onAddPlugin: { + var pluginWin = pluginWindow.createObject(mainView) + console.log(pluginWin) + pluginWin.pluginTitle.text = "Test" } + */ } } @@ -249,6 +274,7 @@ ApplicationWindow { } text: "Connect" } + Button { id: importAppButton anchors.left: connectButton.right @@ -304,6 +330,10 @@ ApplicationWindow { anchors.left: parent.left anchors.leftMargin: 10 placeholderText: "address:port" + onAccepted: { + ui.connectToPeer(addrField.text) + addPeerWin.visible = false + } } Button { anchors.left: addrField.right @@ -315,6 +345,9 @@ ApplicationWindow { addPeerWin.visible = false } } + Component.onCompleted: { + addrField.focus = true + } } Window { @@ -346,6 +379,11 @@ ApplicationWindow { } + function loadPlugin(name) { + console.log("Loading plugin" + name) + mainView.addPlugin(name) + } + function setWalletValue(value) { walletValueLabel.text = value } -- cgit v1.2.3 From 833a1a4eab01392b69339762cf8fe443697138a2 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 3 Mar 2014 11:32:58 +0100 Subject: Block mining log --- ethereum.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ethereum.go b/ethereum.go index 336cd4d00..07a40b10f 100644 --- a/ethereum.go +++ b/ethereum.go @@ -186,6 +186,7 @@ func main() { txs := ethereum.TxPool.Flush() // Create a new block which we're going to mine block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) + log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") // Apply all transactions to the block ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) -- cgit v1.2.3 From 827505985652ac164d1f670797396d0f46774fb2 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 5 Mar 2014 10:44:33 +0100 Subject: Moved qml files, conform to the new server model. QML files got moved to their own directories. QML now has a ui helper which should find assets in the correct resource directory --- ethereum.go | 15 +- qml/test_app.qml | 35 +++++ qml/transactions.qml | 9 ++ qml/wallet.qml | 408 +++++++++++++++++++++++++++++++++++++++++++++++++++ test_app.qml | 35 ----- transactions.qml | 9 -- ui/gui.go | 29 ++-- wallet.qml | 408 --------------------------------------------------- 8 files changed, 474 insertions(+), 474 deletions(-) create mode 100644 qml/test_app.qml create mode 100644 qml/transactions.qml create mode 100644 qml/wallet.qml delete mode 100644 test_app.qml delete mode 100644 transactions.qml delete mode 100644 wallet.qml diff --git a/ethereum.go b/ethereum.go index 07a40b10f..36cd75e47 100644 --- a/ethereum.go +++ b/ethereum.go @@ -144,7 +144,7 @@ func main() { } if ShowGenesis { - fmt.Println(ethereum.BlockManager.BlockChain().Genesis()) + fmt.Println(ethereum.BlockChain().Genesis()) os.Exit(0) } @@ -183,23 +183,24 @@ func main() { addr := keyRing.Get(1).Bytes() for { - txs := ethereum.TxPool.Flush() + txs := ethereum.TxPool().Flush() // Create a new block which we're going to mine - block := ethereum.BlockManager.BlockChain().NewBlock(addr, txs) + block := ethereum.BlockChain().NewBlock(addr, txs) log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") // Apply all transactions to the block - ethereum.BlockManager.ApplyTransactions(block, block.Transactions()) + ethereum.StateManager().ApplyTransactions(block, block.Transactions()) - ethereum.BlockManager.AccumelateRewards(block, block) + ethereum.StateManager().AccumelateRewards(block, block) // Search the nonce block.Nonce = pow.Search(block) ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) - err := ethereum.BlockManager.ProcessBlock(block) + err := ethereum.StateManager().ProcessBlock(block) if err != nil { log.Println(err) } else { - log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockManager.BlockChain().CurrentBlock) + //log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) + log.Printf("🔨 Mined block %x\n", block.Hash()) } } }() diff --git a/qml/test_app.qml b/qml/test_app.qml new file mode 100644 index 000000000..aace4e881 --- /dev/null +++ b/qml/test_app.qml @@ -0,0 +1,35 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import Ethereum 1.0 + +ApplicationWindow { + minimumWidth: 500 + maximumWidth: 500 + maximumHeight: 100 + minimumHeight: 100 + + title: "Ethereum Dice" + + TextField { + id: textField + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Amount" + } + Label { + id: txHash + anchors.bottom: textField.top + anchors.bottomMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + } + Button { + anchors.top: textField.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 5 + text: "Place bet" + onClicked: { + txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", textField.text) + } + } +} diff --git a/qml/transactions.qml b/qml/transactions.qml new file mode 100644 index 000000000..e9a035a85 --- /dev/null +++ b/qml/transactions.qml @@ -0,0 +1,9 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; + +Rectangle { + id: transactionView + visible: false + Text { text: "TX VIEW" } +} diff --git a/qml/wallet.qml b/qml/wallet.qml new file mode 100644 index 000000000..7fc7f5447 --- /dev/null +++ b/qml/wallet.qml @@ -0,0 +1,408 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +ApplicationWindow { + id: root + + width: 900 + height: 600 + minimumHeight: 300 + + title: "Ethereal" + + MenuBar { + Menu { + title: "File" + MenuItem { + text: "Import App" + shortcut: "Ctrl+o" + onTriggered: openAppDialog.open() + } + } + + Menu { + title: "Network" + MenuItem { + text: "Add Peer" + shortcut: "Ctrl+p" + onTriggered: { + addPeerWin.visible = true + } + } + + MenuItem { + text: "Start" + onTriggered: ui.connect() + } + } + + Menu { + title: "Help" + MenuItem { + text: "About" + onTriggered: { + aboutWin.visible = true + } + } + } + + } + + + property var blockModel: ListModel { + id: blockModel + } + + function setView(view) { + networkView.visible = false + historyView.visible = false + newTxView.visible = false + view.visible = true + //root.title = "Ethereal - " = view.title + } + + SplitView { + anchors.fill: parent + resizing: false + + Rectangle { + id: menu + Layout.minimumWidth: 80 + Layout.maximumWidth: 80 + anchors.bottom: parent.bottom + anchors.top: parent.top + //color: "#D9DDE7" + color: "#252525" + + ColumnLayout { + y: 50 + anchors.left: parent.left + anchors.right: parent.right + height: 200 + Image { + source: ui.assetPath("tx.png") + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(historyView) + } + } + } + Image { + source: ui.assetPath("new.png") + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(newTxView) + } + } + } + Image { + source: ui.assetPath("net.png") + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(networkView) + } + } + } + } + } + + Rectangle { + id: mainView + color: "#00000000" + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + + property var txModel: ListModel { + id: txModel + } + + Rectangle { + id: historyView + anchors.fill: parent + + property var title: "Transactions" + TableView { + id: txTableView + anchors.fill: parent + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } + + model: txModel + } + } + + Rectangle { + id: newTxView + property var title: "New transaction" + visible: false + anchors.fill: parent + color: "#00000000" + + ColumnLayout { + width: 400 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 5 + anchors.topMargin: 5 + TextField { + id: txAmount + width: 200 + placeholderText: "Amount" + } + + TextField { + id: txReceiver + placeholderText: "Receiver Address (or empty for contract)" + Layout.fillWidth: true + } + + Label { + text: "Transaction data" + } + TextArea { + id: codeView + anchors.topMargin: 5 + Layout.fillWidth: true + width: parent.width /2 + } + + Button { + text: "Send" + onClicked: { + console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) + } + } + } + } + + + Rectangle { + id: networkView + property var title: "Network" + visible: false + anchors.fill: parent + + TableView { + id: blockTable + width: parent.width + anchors.top: parent.top + anchors.bottom: logView.top + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + + model: blockModel + + /* + onDoubleClicked: { + popup.visible = true + popup.block = eth.getBlock(blockModel.get(row).hash) + popup.hashLabel.text = popup.block.hash + } + */ + } + + property var logModel: ListModel { + id: logModel + } + + TableView { + id: logView + width: parent.width + height: 150 + anchors.bottom: parent.bottom + TableViewColumn{ role: "description" ; title: "log" } + + model: logModel + } + } + + /* + signal addPlugin(string name) + Component { + id: pluginWindow + Rectangle { + anchors.fill: parent + Label { + id: pluginTitle + anchors.centerIn: parent + text: "Hello world" + } + Component.onCompleted: setView(this) + } + } + + onAddPlugin: { + var pluginWin = pluginWindow.createObject(mainView) + console.log(pluginWin) + pluginWin.pluginTitle.text = "Test" + } + */ + } + } + + FileDialog { + id: openAppDialog + title: "Open QML Application" + onAccepted: { + ui.open(openAppDialog.fileUrl.toString()) + } + } + + statusBar: StatusBar { + RowLayout { + anchors.fill: parent + Button { + property var enabled: true + id: connectButton + onClicked: { + if(this.enabled) { + ui.connect(this) + } + } + text: "Connect" + } + + Button { + id: importAppButton + anchors.left: connectButton.right + anchors.leftMargin: 5 + onClicked: openAppDialog.open() + text: "Import App" + } + + Label { + anchors.left: importAppButton.right + anchors.leftMargin: 5 + id: walletValueLabel + } + + Label { + anchors.right: peerImage.left + anchors.rightMargin: 5 + id: peerLabel + font.pixelSize: 8 + text: "0 / 0" + } + Image { + id: peerImage + anchors.right: parent.right + width: 10; height: 10 + source: ui.assetPath("network.png") + } + } + } + + Window { + id: popup + visible: false + property var block + Label { + id: hashLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + + Window { + id: addPeerWin + visible: false + minimumWidth: 230 + maximumWidth: 230 + maximumHeight: 50 + minimumHeight: 50 + + TextField { + id: addrField + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + placeholderText: "address:port" + onAccepted: { + ui.connectToPeer(addrField.text) + addPeerWin.visible = false + } + } + Button { + anchors.left: addrField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Add" + onClicked: { + ui.connectToPeer(addrField.text) + addPeerWin.visible = false + } + } + Component.onCompleted: { + addrField.focus = true + } + } + + Window { + id: aboutWin + visible: false + title: "About" + minimumWidth: 350 + maximumWidth: 350 + maximumHeight: 200 + minimumHeight: 200 + + Image { + id: aboutIcon + height: 150 + width: 150 + fillMode: Image.PreserveAspectFit + smooth: true + source: ui.assetPath("facet.png") + x: 10 + y: 10 + } + + Text { + anchors.left: aboutIcon.right + anchors.leftMargin: 10 + font.pointSize: 12 + text: "

Ethereum(Go)


Development

Jeffrey Wilcke

Binary Distribution

Jarrad Hope
" + } + + } + + function loadPlugin(name) { + console.log("Loading plugin" + name) + mainView.addPlugin(name) + } + + function setWalletValue(value) { + walletValueLabel.text = value + } + + function addTx(tx) { + txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) + } + + function addBlock(block) { + blockModel.insert(0, {number: block.number, hash: block.hash}) + } + + function addLog(str) { + if(str.len != 0) { + logModel.append({description: str}) + } + } + + function setPeers(text) { + peerLabel.text = text + } +} diff --git a/test_app.qml b/test_app.qml deleted file mode 100644 index aace4e881..000000000 --- a/test_app.qml +++ /dev/null @@ -1,35 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0; -import QtQuick.Layouts 1.0; -import Ethereum 1.0 - -ApplicationWindow { - minimumWidth: 500 - maximumWidth: 500 - maximumHeight: 100 - minimumHeight: 100 - - title: "Ethereum Dice" - - TextField { - id: textField - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "Amount" - } - Label { - id: txHash - anchors.bottom: textField.top - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - } - Button { - anchors.top: textField.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 5 - text: "Place bet" - onClicked: { - txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", textField.text) - } - } -} diff --git a/transactions.qml b/transactions.qml deleted file mode 100644 index e9a035a85..000000000 --- a/transactions.qml +++ /dev/null @@ -1,9 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0; -import QtQuick.Layouts 1.0; - -Rectangle { - id: transactionView - visible: false - Text { text: "TX VIEW" } -} diff --git a/ui/gui.go b/ui/gui.go index 196d2cd76..fa65ea4f6 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -57,7 +57,7 @@ type Gui struct { // Create GUI, but doesn't start it func New(ethereum *eth.Ethereum) *Gui { - lib := &EthLib{blockManager: ethereum.BlockManager, blockChain: ethereum.BlockManager.BlockChain(), txPool: ethereum.TxPool} + lib := &EthLib{blockManager: ethereum.StateManager(), blockChain: ethereum.BlockChain(), txPool: ethereum.TxPool()} db, err := ethdb.NewLDBDatabase("tx_database") if err != nil { panic(err) @@ -66,7 +66,7 @@ func New(ethereum *eth.Ethereum) *Gui { key := ethutil.Config.Db.GetKeys()[0] addr := key.Address() - ethereum.BlockManager.WatchAddr(addr) + ethereum.StateManager().WatchAddr(addr) return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} } @@ -84,24 +84,23 @@ func (ui *Gui) Start() { ethutil.Config.Log.Infoln("[GUI] Starting GUI") // Create a new QML engine ui.engine = qml.NewEngine() + context := ui.engine.Context() + + // Expose the eth library and the ui library to QML + context.SetVar("eth", ui.lib) + context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) // Load the main QML interface - component, err := ui.engine.LoadFile(AssetPath("wallet.qml")) + component, err := ui.engine.LoadFile(AssetPath("qml/wallet.qml")) if err != nil { panic(err) } - ui.engine.LoadFile(AssetPath("transactions.qml")) + ui.engine.LoadFile(AssetPath("qml/transactions.qml")) ui.win = component.CreateWindow(nil) - context := ui.engine.Context() - - // Expose the eth library and the ui library to QML - context.SetVar("eth", ui.lib) - context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) - // Register the ui as a block processor - ui.eth.BlockManager.SecondaryBlockProcessor = ui + //ui.eth.BlockManager.SecondaryBlockProcessor = ui //ui.eth.TxPool.SecondaryProcessor = ui // Add the ui as a log system so we can log directly to the UGI @@ -120,7 +119,7 @@ func (ui *Gui) Start() { func (ui *Gui) setInitialBlockChain() { // Load previous 10 blocks - chain := ui.eth.BlockManager.BlockChain().GetChain(ui.eth.BlockManager.BlockChain().CurrentBlock.Hash(), 10) + chain := ui.eth.BlockChain().GetChain(ui.eth.BlockChain().CurrentBlock.Hash(), 10) for _, block := range chain { ui.ProcessBlock(block) } @@ -144,9 +143,9 @@ func (ui *Gui) ProcessBlock(block *ethchain.Block) { // Simple go routine function that updates the list of peers in the GUI func (ui *Gui) update() { txChan := make(chan ethchain.TxMsg, 1) - ui.eth.TxPool.Subscribe(txChan) + ui.eth.TxPool().Subscribe(txChan) - account := ui.eth.BlockManager.GetAddrState(ui.addr).Account + account := ui.eth.StateManager().GetAddrState(ui.addr).Account unconfirmedFunds := new(big.Int) ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) for { @@ -159,7 +158,7 @@ func (ui *Gui) update() { ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 + ui.eth.StateManager().GetAddrState(ui.addr).Nonce += 1 unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) diff --git a/wallet.qml b/wallet.qml deleted file mode 100644 index 39bc21f39..000000000 --- a/wallet.qml +++ /dev/null @@ -1,408 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0; -import QtQuick.Layouts 1.0; -import QtQuick.Dialogs 1.0; -import QtQuick.Window 2.1; -import QtQuick.Controls.Styles 1.1 -import Ethereum 1.0 - -ApplicationWindow { - id: root - - width: 900 - height: 600 - minimumHeight: 300 - - title: "Ethereal" - - MenuBar { - Menu { - title: "File" - MenuItem { - text: "Import App" - shortcut: "Ctrl+o" - onTriggered: openAppDialog.open() - } - } - - Menu { - title: "Network" - MenuItem { - text: "Add Peer" - shortcut: "Ctrl+p" - onTriggered: { - addPeerWin.visible = true - } - } - - MenuItem { - text: "Start" - onTriggered: ui.connect() - } - } - - Menu { - title: "Help" - MenuItem { - text: "About" - onTriggered: { - aboutWin.visible = true - } - } - } - - } - - - property var blockModel: ListModel { - id: blockModel - } - - function setView(view) { - networkView.visible = false - historyView.visible = false - newTxView.visible = false - view.visible = true - //root.title = "Ethereal - " = view.title - } - - SplitView { - anchors.fill: parent - resizing: false - - Rectangle { - id: menu - Layout.minimumWidth: 80 - Layout.maximumWidth: 80 - anchors.bottom: parent.bottom - anchors.top: parent.top - //color: "#D9DDE7" - color: "#252525" - - ColumnLayout { - y: 50 - anchors.left: parent.left - anchors.right: parent.right - height: 200 - Image { - source: "tx.png" - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(historyView) - } - } - } - Image { - source: "new.png" - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(newTxView) - } - } - } - Image { - source: "net.png" - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(networkView) - } - } - } - } - } - - Rectangle { - id: mainView - color: "#00000000" - anchors.right: parent.right - anchors.left: menu.right - anchors.bottom: parent.bottom - anchors.top: parent.top - - property var txModel: ListModel { - id: txModel - } - - Rectangle { - id: historyView - anchors.fill: parent - - property var title: "Transactions" - TableView { - id: txTableView - anchors.fill: parent - TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } - TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } - - model: txModel - } - } - - Rectangle { - id: newTxView - property var title: "New transaction" - visible: false - anchors.fill: parent - color: "#00000000" - - ColumnLayout { - width: 400 - anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: 5 - anchors.topMargin: 5 - TextField { - id: txAmount - width: 200 - placeholderText: "Amount" - } - - TextField { - id: txReceiver - placeholderText: "Receiver Address (or empty for contract)" - Layout.fillWidth: true - } - - Label { - text: "Transaction data" - } - TextArea { - id: codeView - anchors.topMargin: 5 - Layout.fillWidth: true - width: parent.width /2 - } - - Button { - text: "Send" - onClicked: { - console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) - } - } - } - } - - - Rectangle { - id: networkView - property var title: "Network" - visible: false - anchors.fill: parent - - TableView { - id: blockTable - width: parent.width - anchors.top: parent.top - anchors.bottom: logView.top - TableViewColumn{ role: "number" ; title: "#" ; width: 100 } - TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } - - model: blockModel - - /* - onDoubleClicked: { - popup.visible = true - popup.block = eth.getBlock(blockModel.get(row).hash) - popup.hashLabel.text = popup.block.hash - } - */ - } - - property var logModel: ListModel { - id: logModel - } - - TableView { - id: logView - width: parent.width - height: 150 - anchors.bottom: parent.bottom - TableViewColumn{ role: "description" ; title: "log" } - - model: logModel - } - } - - /* - signal addPlugin(string name) - Component { - id: pluginWindow - Rectangle { - anchors.fill: parent - Label { - id: pluginTitle - anchors.centerIn: parent - text: "Hello world" - } - Component.onCompleted: setView(this) - } - } - - onAddPlugin: { - var pluginWin = pluginWindow.createObject(mainView) - console.log(pluginWin) - pluginWin.pluginTitle.text = "Test" - } - */ - } - } - - FileDialog { - id: openAppDialog - title: "Open QML Application" - onAccepted: { - ui.open(openAppDialog.fileUrl.toString()) - } - } - - statusBar: StatusBar { - RowLayout { - anchors.fill: parent - Button { - property var enabled: true - id: connectButton - onClicked: { - if(this.enabled) { - ui.connect(this) - } - } - text: "Connect" - } - - Button { - id: importAppButton - anchors.left: connectButton.right - anchors.leftMargin: 5 - onClicked: openAppDialog.open() - text: "Import App" - } - - Label { - anchors.left: importAppButton.right - anchors.leftMargin: 5 - id: walletValueLabel - } - - Label { - anchors.right: peerImage.left - anchors.rightMargin: 5 - id: peerLabel - font.pixelSize: 8 - text: "0 / 0" - } - Image { - id: peerImage - anchors.right: parent.right - width: 10; height: 10 - source: "network.png" - } - } - } - - Window { - id: popup - visible: false - property var block - Label { - id: hashLabel - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - } - - Window { - id: addPeerWin - visible: false - minimumWidth: 230 - maximumWidth: 230 - maximumHeight: 50 - minimumHeight: 50 - - TextField { - id: addrField - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - placeholderText: "address:port" - onAccepted: { - ui.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Button { - anchors.left: addrField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Add" - onClicked: { - ui.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Component.onCompleted: { - addrField.focus = true - } - } - - Window { - id: aboutWin - visible: false - title: "About" - minimumWidth: 350 - maximumWidth: 350 - maximumHeight: 200 - minimumHeight: 200 - - Image { - id: aboutIcon - height: 150 - width: 150 - fillMode: Image.PreserveAspectFit - smooth: true - source: "facet.png" - x: 10 - y: 10 - } - - Text { - anchors.left: aboutIcon.right - anchors.leftMargin: 10 - font.pointSize: 12 - text: "

Ethereum(Go)


Development

Jeffrey Wilcke

Binary Distribution

Jarrad Hope
" - } - - } - - function loadPlugin(name) { - console.log("Loading plugin" + name) - mainView.addPlugin(name) - } - - function setWalletValue(value) { - walletValueLabel.text = value - } - - function addTx(tx) { - txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) - } - - function addBlock(block) { - blockModel.insert(0, {number: block.number, hash: block.hash}) - } - - function addLog(str) { - if(str.len != 0) { - logModel.append({description: str}) - } - } - - function setPeers(text) { - peerLabel.text = text - } -} -- cgit v1.2.3 From f7fb5b902c8b216c835c10fc4676b7b08fb90d04 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 5 Mar 2014 10:44:43 +0100 Subject: Conform to the new server model --- dev_console.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev_console.go b/dev_console.go index 5a69ffa9e..ead4b55e5 100644 --- a/dev_console.go +++ b/dev_console.go @@ -158,11 +158,11 @@ func (i *Console) ParseInput(input string) bool { fmt.Println(value) case "getaddr": encoded, _ := hex.DecodeString(tokens[1]) - addr := i.ethereum.BlockManager.BlockChain().CurrentBlock.State().GetAccount(encoded) + addr := i.ethereum.BlockChain().CurrentBlock.State().GetAccount(encoded) fmt.Println("addr:", addr) case "block": encoded, _ := hex.DecodeString(tokens[1]) - block := i.ethereum.BlockManager.BlockChain().GetBlock(encoded) + block := i.ethereum.BlockChain().GetBlock(encoded) info := block.BlockInfo() fmt.Printf("++++++++++ #%d ++++++++++\n%v\n", info.Number, block) case "say": @@ -182,7 +182,7 @@ func (i *Console) ParseInput(input string) bool { key := ethutil.Config.Db.GetKeys()[0] tx.Sign(key.PrivateKey) - i.ethereum.TxPool.QueueTransaction(tx) + i.ethereum.TxPool().QueueTransaction(tx) fmt.Printf("%x\n", tx.Hash()) } @@ -204,7 +204,7 @@ func (i *Console) ParseInput(input string) bool { key := ethutil.Config.Db.GetKeys()[0] contract.Sign(key.PrivateKey) - i.ethereum.TxPool.QueueTransaction(contract) + i.ethereum.TxPool().QueueTransaction(contract) fmt.Printf("%x\n", contract.Hash()[12:]) case "exit", "quit", "q": -- cgit v1.2.3 From fbd53f0e34b636074941f5ea7b151bf173c5ed17 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 5 Mar 2014 10:57:14 +0100 Subject: Renamed block manager to state manager --- ui/gui.go | 2 +- ui/library.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/gui.go b/ui/gui.go index fa65ea4f6..5f0b6e52d 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -57,7 +57,7 @@ type Gui struct { // Create GUI, but doesn't start it func New(ethereum *eth.Ethereum) *Gui { - lib := &EthLib{blockManager: ethereum.StateManager(), blockChain: ethereum.BlockChain(), txPool: ethereum.TxPool()} + lib := &EthLib{stateManager: ethereum.StateManager(), blockChain: ethereum.BlockChain(), txPool: ethereum.TxPool()} db, err := ethdb.NewLDBDatabase("tx_database") if err != nil { panic(err) diff --git a/ui/library.go b/ui/library.go index 3bbb01314..8dda0a89e 100644 --- a/ui/library.go +++ b/ui/library.go @@ -9,7 +9,7 @@ import ( ) type EthLib struct { - blockManager *ethchain.BlockManager + stateManager *ethchain.StateManager blockChain *ethchain.BlockChain txPool *ethchain.TxPool } @@ -32,7 +32,7 @@ func (lib *EthLib) CreateTx(receiver, a, data string) string { amount := ethutil.Big(a) code := ethchain.Compile(strings.Split(data, "\n")) tx := ethchain.NewTransaction(hash, amount, code) - tx.Nonce = lib.blockManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce + tx.Nonce = lib.stateManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce tx.Sign(keyRing.Get(0).Bytes()) -- cgit v1.2.3 From 96fcc1da323ae37489721f58d04f6ebf1c14ff91 Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 10 Mar 2014 11:53:31 +0100 Subject: Initial smart-miner stuff --- ethereum.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++------------ ui/ui_lib.go | 2 +- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/ethereum.go b/ethereum.go index 36cd75e47..ed14a7feb 100644 --- a/ethereum.go +++ b/ethereum.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" @@ -172,6 +173,16 @@ func main() { RegisterInterupts(ethereum) ethereum.Start() + minerChan := make(chan ethutil.React, 5) + ethereum.Reactor().Subscribe("newBlock", minerChan) + ethereum.Reactor().Subscribe("newTx", minerChan) + + minerChan2 := make(chan ethutil.React, 5) + ethereum.Reactor().Subscribe("newBlock", minerChan2) + ethereum.Reactor().Subscribe("newTx", minerChan2) + + ethereum.StateManager().PrepareMiningState() + if StartMining { log.Printf("Miner started\n") @@ -181,26 +192,70 @@ func main() { data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) addr := keyRing.Get(1).Bytes() + txs := ethereum.TxPool().Flush() + block := ethereum.BlockChain().NewBlock(addr, txs) for { - txs := ethereum.TxPool().Flush() - // Create a new block which we're going to mine - block := ethereum.BlockChain().NewBlock(addr, txs) - log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") - // Apply all transactions to the block - ethereum.StateManager().ApplyTransactions(block, block.Transactions()) - - ethereum.StateManager().AccumelateRewards(block, block) - - // Search the nonce - block.Nonce = pow.Search(block) - ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) - err := ethereum.StateManager().ProcessBlock(block) - if err != nil { - log.Println(err) - } else { - //log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) - log.Printf("🔨 Mined block %x\n", block.Hash()) + select { + case chanMessage := <-minerChan: + log.Println("REACTOR: Got new block") + + if block, ok := chanMessage.Resource.(*ethchain.Block); ok { + if bytes.Compare(ethereum.BlockChain().CurrentBlock.Hash(), block.Hash()) == 0 { + // TODO: Perhaps continue mining to get some uncle rewards + log.Println("New top block found resetting state") + // Reapplies the latest block to the mining state, thus resetting + ethereum.StateManager().PrepareMiningState() + block = ethereum.BlockChain().NewBlock(addr, txs) + log.Println("Block set") + } else { + if bytes.Compare(block.PrevHash, ethereum.BlockChain().CurrentBlock.PrevHash) == 0 { + log.Println("HELLO UNCLE") + // TODO: Add uncle to block + } + } + } + + if tx, ok := chanMessage.Resource.(*ethchain.Transaction); ok { + log.Println("REACTOR: Got new transaction", tx) + found := false + for _, ctx := range txs { + if found = bytes.Compare(ctx.Hash(), tx.Hash()) == 0; found { + break + } + + } + if found == false { + log.Println("We did not know about this transaction, adding") + txs = append(txs, tx) + } else { + log.Println("We already had this transaction, ignoring") + } + } + log.Println("Sending block reset") + // Start mining over + log.Println("Block reset done") + default: + // Create a new block which we're going to mine + log.Println("Mining on block. Includes", len(txs), "transactions") + + // Apply all transactions to the block + ethereum.StateManager().ApplyTransactions(block, txs) + ethereum.StateManager().AccumelateRewards(block, block) + + // Search the nonce + block.Nonce = pow.Search(block, minerChan2) + if block.Nonce != nil { + ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) + err := ethereum.StateManager().ProcessBlock(block) + if err != nil { + log.Println(err) + } else { + //log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) + log.Printf("🔨 Mined block %x\n", block.Hash()) + block = ethereum.BlockChain().NewBlock(addr, txs) + } + } } } }() diff --git a/ui/ui_lib.go b/ui/ui_lib.go index 2a1abee23..fb5957cc3 100644 --- a/ui/ui_lib.go +++ b/ui/ui_lib.go @@ -54,7 +54,7 @@ func AssetPath(p string) string { // Get Binary Directory exedir, _ := osext.ExecutableFolder() base = filepath.Join(exedir, "../Resources") - base = "/Users/jeffrey/go/src/github.com/ethereum/go-ethereum" + base = "/Users/maranhidskes/projects/go/src/github.com/ethereum/go-ethereum" case "linux": base = "/usr/share/ethereal" case "window": -- cgit v1.2.3 From 1c983ed80cc71e7451c7223bae7e30fc41e07b20 Mon Sep 17 00:00:00 2001 From: Maran Date: Tue, 11 Mar 2014 15:17:23 +0100 Subject: More mining stuff --- ethereum.go | 16 +++++++++++----- ui/library.go | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ethereum.go b/ethereum.go index ed14a7feb..4d1a74ed1 100644 --- a/ethereum.go +++ b/ethereum.go @@ -173,11 +173,11 @@ func main() { RegisterInterupts(ethereum) ethereum.Start() - minerChan := make(chan ethutil.React, 5) + minerChan := make(chan ethutil.React, 1) ethereum.Reactor().Subscribe("newBlock", minerChan) ethereum.Reactor().Subscribe("newTx", minerChan) - minerChan2 := make(chan ethutil.React, 5) + minerChan2 := make(chan ethutil.React, 1) ethereum.Reactor().Subscribe("newBlock", minerChan2) ethereum.Reactor().Subscribe("newTx", minerChan2) @@ -186,7 +186,6 @@ func main() { if StartMining { log.Printf("Miner started\n") - // Fake block mining. It broadcasts a new block every 5 seconds go func() { pow := ðchain.EasyPow{} data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) @@ -194,6 +193,7 @@ func main() { addr := keyRing.Get(1).Bytes() txs := ethereum.TxPool().Flush() block := ethereum.BlockChain().NewBlock(addr, txs) + var uncles []*ethchain.Block for { select { @@ -204,6 +204,7 @@ func main() { if bytes.Compare(ethereum.BlockChain().CurrentBlock.Hash(), block.Hash()) == 0 { // TODO: Perhaps continue mining to get some uncle rewards log.Println("New top block found resetting state") + // TODO: We probably want to skip this if it's our own block // Reapplies the latest block to the mining state, thus resetting ethereum.StateManager().PrepareMiningState() block = ethereum.BlockChain().NewBlock(addr, txs) @@ -212,6 +213,7 @@ func main() { if bytes.Compare(block.PrevHash, ethereum.BlockChain().CurrentBlock.PrevHash) == 0 { log.Println("HELLO UNCLE") // TODO: Add uncle to block + uncles = append(uncles, block) } } } @@ -227,7 +229,7 @@ func main() { } if found == false { log.Println("We did not know about this transaction, adding") - txs = append(txs, tx) + txs = ethereum.TxPool().Flush() } else { log.Println("We already had this transaction, ignoring") } @@ -239,6 +241,11 @@ func main() { // Create a new block which we're going to mine log.Println("Mining on block. Includes", len(txs), "transactions") + // Apply uncles + if len(uncles) > 0 { + block.SetUncles(uncles) + } + // Apply all transactions to the block ethereum.StateManager().ApplyTransactions(block, txs) ethereum.StateManager().AccumelateRewards(block, block) @@ -253,7 +260,6 @@ func main() { } else { //log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) log.Printf("🔨 Mined block %x\n", block.Hash()) - block = ethereum.BlockChain().NewBlock(addr, txs) } } } diff --git a/ui/library.go b/ui/library.go index 8dda0a89e..8412a8d6c 100644 --- a/ui/library.go +++ b/ui/library.go @@ -35,6 +35,7 @@ func (lib *EthLib) CreateTx(receiver, a, data string) string { tx.Nonce = lib.stateManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce tx.Sign(keyRing.Get(0).Bytes()) + ethutil.Config.Log.Infof("nonce: %x", tx.Nonce) lib.txPool.QueueTransaction(tx) -- cgit v1.2.3 From 13e18e1d8f717ffab1b59a39cf67ef169a542e74 Mon Sep 17 00:00:00 2001 From: Cayman Nava Date: Sat, 15 Mar 2014 16:46:10 -0700 Subject: working linux makefile when building develop branch, checkout of eth-go develop branch is required --- Makefile | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 4da4c551c..709c416db 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,23 @@ UNAME = $(shell uname) +FILES=qml *.png +GOPATH=$(PWD) + # Default is building all: - go install + go get -d + cp *.go $(GOPATH)/src/github.com/ethereum/go-ethereum + cp -r ui $(GOPATH)/src/github.com/ethereum/go-ethereum + go build install: # Linux build ifeq ($(UNAME),Linux) - mkdir /usr/local/ethereal - files=(wallet.qml net.png network.png new.png tx.png) - for file in "${files[@]}"; do - cp $file /usr/share/ethereal + mkdir -p /usr/share/ethereal + for file in $(FILES); do \ + cp -r $$file /usr/share/ethereal; \ done - cp $GOPATH/bin/go-ethereum /usr/local/bin/ethereal + cp go-ethereum /usr/local/bin/ethereal endif # OS X build ifeq ($(UNAME),Darwin) -- cgit v1.2.3 From 85e04476845a5a41836824b133d939faf4e1c3fa Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 16 Mar 2014 18:34:34 +0100 Subject: Fixed asset path error. Fixes #29 --- ui/ui_lib.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/ui/ui_lib.go b/ui/ui_lib.go index 2a1abee23..83e8bf2d1 100644 --- a/ui/ui_lib.go +++ b/ui/ui_lib.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" + "os" "path" "path/filepath" "runtime" @@ -49,18 +50,26 @@ func (ui *UiLib) AssetPath(p string) string { func AssetPath(p string) string { var base string - switch runtime.GOOS { - case "darwin": - // Get Binary Directory - exedir, _ := osext.ExecutableFolder() - base = filepath.Join(exedir, "../Resources") - base = "/Users/jeffrey/go/src/github.com/ethereum/go-ethereum" - case "linux": - base = "/usr/share/ethereal" - case "window": - fallthrough - default: - base = "." + + // If the current working directory is the go-ethereum dir + // assume a debug build and use the source directory as + // asset directory. + pwd, _ := os.Getwd() + if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum") { + base = pwd + } else { + switch runtime.GOOS { + case "darwin": + // Get Binary Directory + exedir, _ := osext.ExecutableFolder() + base = filepath.Join(exedir, "../Resources") + case "linux": + base = "/usr/share/ethereal" + case "window": + fallthrough + default: + base = "." + } } return path.Join(base, p) -- cgit v1.2.3 From 3002570085c6823da4b8e12015eafa4bd87177fb Mon Sep 17 00:00:00 2001 From: Maran Date: Thu, 20 Mar 2014 11:20:10 +0100 Subject: Mining rework --- ethereum.go | 91 ++++------------------------------------------------------- ui/library.go | 7 +++-- 2 files changed, 9 insertions(+), 89 deletions(-) diff --git a/ethereum.go b/ethereum.go index 4d1a74ed1..556a33851 100644 --- a/ethereum.go +++ b/ethereum.go @@ -1,12 +1,11 @@ package main import ( - "bytes" "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethminer" "github.com/ethereum/eth-go/ethutil" - "github.com/ethereum/eth-go/ethwire" "github.com/ethereum/go-ethereum/ui" "github.com/niemeyer/qml" "github.com/obscuren/secp256k1-go" @@ -173,97 +172,17 @@ func main() { RegisterInterupts(ethereum) ethereum.Start() - minerChan := make(chan ethutil.React, 1) - ethereum.Reactor().Subscribe("newBlock", minerChan) - ethereum.Reactor().Subscribe("newTx", minerChan) - - minerChan2 := make(chan ethutil.React, 1) - ethereum.Reactor().Subscribe("newBlock", minerChan2) - ethereum.Reactor().Subscribe("newTx", minerChan2) - - ethereum.StateManager().PrepareMiningState() - if StartMining { log.Printf("Miner started\n") go func() { - pow := ðchain.EasyPow{} data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) addr := keyRing.Get(1).Bytes() - txs := ethereum.TxPool().Flush() - block := ethereum.BlockChain().NewBlock(addr, txs) - var uncles []*ethchain.Block - - for { - select { - case chanMessage := <-minerChan: - log.Println("REACTOR: Got new block") - - if block, ok := chanMessage.Resource.(*ethchain.Block); ok { - if bytes.Compare(ethereum.BlockChain().CurrentBlock.Hash(), block.Hash()) == 0 { - // TODO: Perhaps continue mining to get some uncle rewards - log.Println("New top block found resetting state") - // TODO: We probably want to skip this if it's our own block - // Reapplies the latest block to the mining state, thus resetting - ethereum.StateManager().PrepareMiningState() - block = ethereum.BlockChain().NewBlock(addr, txs) - log.Println("Block set") - } else { - if bytes.Compare(block.PrevHash, ethereum.BlockChain().CurrentBlock.PrevHash) == 0 { - log.Println("HELLO UNCLE") - // TODO: Add uncle to block - uncles = append(uncles, block) - } - } - } - - if tx, ok := chanMessage.Resource.(*ethchain.Transaction); ok { - log.Println("REACTOR: Got new transaction", tx) - found := false - for _, ctx := range txs { - if found = bytes.Compare(ctx.Hash(), tx.Hash()) == 0; found { - break - } - - } - if found == false { - log.Println("We did not know about this transaction, adding") - txs = ethereum.TxPool().Flush() - } else { - log.Println("We already had this transaction, ignoring") - } - } - log.Println("Sending block reset") - // Start mining over - log.Println("Block reset done") - default: - // Create a new block which we're going to mine - log.Println("Mining on block. Includes", len(txs), "transactions") - - // Apply uncles - if len(uncles) > 0 { - block.SetUncles(uncles) - } - - // Apply all transactions to the block - ethereum.StateManager().ApplyTransactions(block, txs) - ethereum.StateManager().AccumelateRewards(block, block) - - // Search the nonce - block.Nonce = pow.Search(block, minerChan2) - if block.Nonce != nil { - ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) - err := ethereum.StateManager().ProcessBlock(block) - if err != nil { - log.Println(err) - } else { - //log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) - log.Printf("🔨 Mined block %x\n", block.Hash()) - } - } - } - } + + miner := ethminer.NewDefaultMiner(addr, ethereum) + miner.Start() + }() } diff --git a/ui/library.go b/ui/library.go index 8412a8d6c..d6ce94b75 100644 --- a/ui/library.go +++ b/ui/library.go @@ -27,14 +27,15 @@ func (lib *EthLib) CreateTx(receiver, a, data string) string { } k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(k) + keyPair := ethutil.NewKeyFromBytes(k) amount := ethutil.Big(a) code := ethchain.Compile(strings.Split(data, "\n")) tx := ethchain.NewTransaction(hash, amount, code) - tx.Nonce = lib.stateManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce + tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce + + tx.Sign(keyPair.PrivateKey) - tx.Sign(keyRing.Get(0).Bytes()) ethutil.Config.Log.Infof("nonce: %x", tx.Nonce) lib.txPool.QueueTransaction(tx) -- cgit v1.2.3 From 0db86e4485176aff7c45f0ce673174ec8407c0fc Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 21 Mar 2014 11:16:41 +0100 Subject: Updated to work with the new chain --- Makefile | 5 +++-- ethereum.go | 7 +++++-- ui/gui.go | 1 + ui/library.go | 6 +++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 4da4c551c..02d127963 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,12 @@ all: install: # Linux build ifeq ($(UNAME),Linux) - mkdir /usr/local/ethereal - files=(wallet.qml net.png network.png new.png tx.png) + mkdir -p /usr/local/ethereal + files=(net.png network.png new.png tx.png) for file in "${files[@]}"; do cp $file /usr/share/ethereal done + cp -r qml /usr/share/ethereal/qml cp $GOPATH/bin/go-ethereum /usr/local/bin/ethereal endif # OS X build diff --git a/ethereum.go b/ethereum.go index 36cd75e47..ac1de5af4 100644 --- a/ethereum.go +++ b/ethereum.go @@ -190,16 +190,19 @@ func main() { // Apply all transactions to the block ethereum.StateManager().ApplyTransactions(block, block.Transactions()) - ethereum.StateManager().AccumelateRewards(block, block) + ethereum.StateManager().Prepare(block.State(), block.State()) + ethereum.StateManager().AccumelateRewards(block) // Search the nonce block.Nonce = pow.Search(block) ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) + + ethereum.StateManager().PrepareDefault(block) err := ethereum.StateManager().ProcessBlock(block) if err != nil { log.Println(err) } else { - //log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) + log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) log.Printf("🔨 Mined block %x\n", block.Hash()) } } diff --git a/ui/gui.go b/ui/gui.go index 5f0b6e52d..c8f4bedab 100644 --- a/ui/gui.go +++ b/ui/gui.go @@ -81,6 +81,7 @@ func (ui *Gui) Start() { Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, }}) + ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.1")) ethutil.Config.Log.Infoln("[GUI] Starting GUI") // Create a new QML engine ui.engine = qml.NewEngine() diff --git a/ui/library.go b/ui/library.go index 8dda0a89e..05fffd579 100644 --- a/ui/library.go +++ b/ui/library.go @@ -27,14 +27,14 @@ func (lib *EthLib) CreateTx(receiver, a, data string) string { } k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(k) + keyPair := ethutil.NewKeyFromBytes(k) amount := ethutil.Big(a) code := ethchain.Compile(strings.Split(data, "\n")) tx := ethchain.NewTransaction(hash, amount, code) - tx.Nonce = lib.stateManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce + tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce - tx.Sign(keyRing.Get(0).Bytes()) + tx.Sign(keyPair.PrivateKey) lib.txPool.QueueTransaction(tx) -- cgit v1.2.3 From 22b4e9b6173437b28045d69e8fd0b468e526e559 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 22 Mar 2014 00:35:53 +0100 Subject: . --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd9286659..a29bfb6d3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Ethereum Ethereum Go Client (c) Jeffrey Wilcke -The current state is "Proof of Concept 3". +The current state is "Proof of Concept 3.5". For the development Go Package please see [eth-go package](https://github.com/ethereum/eth-go). -- cgit v1.2.3 From 1f2547b8a7cfe100f64428d20f4bcf95eb9ecc5c Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 22 Mar 2014 01:02:24 +0100 Subject: Major re-organisation. The Ethereum node and Gui are now separated. --- Makefile | 21 -- config.go | 36 ---- dev_console.go | 253 ---------------------- ethereal/Makefile | 21 ++ ethereal/assets/facet.png | Bin 0 -> 27302 bytes ethereal/assets/net.png | Bin 0 -> 4669 bytes ethereal/assets/network.png | Bin 0 -> 2900 bytes ethereal/assets/new.png | Bin 0 -> 4776 bytes ethereal/assets/qml/test_app.qml | 35 +++ ethereal/assets/qml/transactions.qml | 9 + ethereal/assets/qml/wallet.qml | 408 +++++++++++++++++++++++++++++++++++ ethereal/assets/tx.png | Bin 0 -> 4070 bytes ethereal/config.go | 34 +++ ethereal/ethereum.go | 110 ++++++++++ ethereal/ui/gui.go | 218 +++++++++++++++++++ ethereal/ui/library.go | 60 ++++++ ethereal/ui/ui_lib.go | 76 +++++++ ethereum.go | 215 ------------------ facet.png | Bin 27302 -> 0 bytes net.png | Bin 4669 -> 0 bytes network.png | Bin 2900 -> 0 bytes new.png | Bin 4776 -> 0 bytes node/config.go | 37 ++++ node/dev_console.go | 253 ++++++++++++++++++++++ node/ethereum.go | 158 ++++++++++++++ node/node | Bin 0 -> 8154988 bytes qml/test_app.qml | 35 --- qml/transactions.qml | 9 - qml/wallet.qml | 408 ----------------------------------- test_runner.go | 35 --- test_runner_test.go | 36 ---- testing.go | 33 --- tx.png | Bin 4070 -> 0 bytes ui/gui.go | 218 ------------------- ui/library.go | 60 ------ ui/ui_lib.go | 76 ------- utils/keys.go | 50 +++++ 37 files changed, 1469 insertions(+), 1435 deletions(-) delete mode 100644 Makefile delete mode 100644 config.go delete mode 100644 dev_console.go create mode 100644 ethereal/Makefile create mode 100644 ethereal/assets/facet.png create mode 100644 ethereal/assets/net.png create mode 100644 ethereal/assets/network.png create mode 100644 ethereal/assets/new.png create mode 100644 ethereal/assets/qml/test_app.qml create mode 100644 ethereal/assets/qml/transactions.qml create mode 100644 ethereal/assets/qml/wallet.qml create mode 100644 ethereal/assets/tx.png create mode 100644 ethereal/config.go create mode 100644 ethereal/ethereum.go create mode 100644 ethereal/ui/gui.go create mode 100644 ethereal/ui/library.go create mode 100644 ethereal/ui/ui_lib.go delete mode 100644 ethereum.go delete mode 100644 facet.png delete mode 100644 net.png delete mode 100644 network.png delete mode 100644 new.png create mode 100644 node/config.go create mode 100644 node/dev_console.go create mode 100644 node/ethereum.go create mode 100755 node/node delete mode 100644 qml/test_app.qml delete mode 100644 qml/transactions.qml delete mode 100644 qml/wallet.qml delete mode 100644 test_runner.go delete mode 100644 test_runner_test.go delete mode 100644 testing.go delete mode 100644 tx.png delete mode 100644 ui/gui.go delete mode 100644 ui/library.go delete mode 100644 ui/ui_lib.go create mode 100644 utils/keys.go diff --git a/Makefile b/Makefile deleted file mode 100644 index 02d127963..000000000 --- a/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -UNAME = $(shell uname) - -# Default is building -all: - go install - -install: -# Linux build -ifeq ($(UNAME),Linux) - mkdir -p /usr/local/ethereal - files=(net.png network.png new.png tx.png) - for file in "${files[@]}"; do - cp $file /usr/share/ethereal - done - cp -r qml /usr/share/ethereal/qml - cp $GOPATH/bin/go-ethereum /usr/local/bin/ethereal -endif -# OS X build -ifeq ($(UNAME),Darwin) - # Execute py script -endif diff --git a/config.go b/config.go deleted file mode 100644 index bafc3e300..000000000 --- a/config.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "flag" -) - -var StartConsole bool -var StartMining bool -var UseUPnP bool -var OutboundPort string -var ShowGenesis bool -var AddPeer string -var MaxPeer int -var GenAddr bool -var UseSeed bool -var ImportKey string -var ExportKey bool -var UseGui bool -var DataDir string - -func Init() { - flag.BoolVar(&StartConsole, "c", false, "debug and testing console") - flag.BoolVar(&StartMining, "m", false, "start dagger mining") - flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") - flag.BoolVar(&UseGui, "gui", true, "use the gui") - flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") - flag.BoolVar(&UseSeed, "seed", true, "seed peers") - flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") - flag.BoolVar(&ExportKey, "export", false, "export private key") - flag.StringVar(&OutboundPort, "p", "30303", "listening port") - flag.StringVar(&DataDir, "dir", ".ethereum", "ethereum data directory") - flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") - flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") - - flag.Parse() -} diff --git a/dev_console.go b/dev_console.go deleted file mode 100644 index ead4b55e5..000000000 --- a/dev_console.go +++ /dev/null @@ -1,253 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "encoding/hex" - "errors" - "fmt" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethdb" - "github.com/ethereum/eth-go/ethutil" - "github.com/ethereum/eth-go/ethwire" - _ "math/big" - "os" - "strings" -) - -type Console struct { - db *ethdb.MemDatabase - trie *ethutil.Trie - ethereum *eth.Ethereum -} - -func NewConsole(s *eth.Ethereum) *Console { - db, _ := ethdb.NewMemDatabase() - trie := ethutil.NewTrie(db, "") - - return &Console{db: db, trie: trie, ethereum: s} -} - -func (i *Console) ValidateInput(action string, argumentLength int) error { - err := false - var expArgCount int - - switch { - case action == "update" && argumentLength != 2: - err = true - expArgCount = 2 - case action == "get" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "dag" && argumentLength != 2: - err = true - expArgCount = 2 - case action == "decode" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "encode" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "gettx" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "tx" && argumentLength != 2: - err = true - expArgCount = 2 - case action == "getaddr" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "contract" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "say" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "addp" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "block" && argumentLength != 1: - err = true - expArgCount = 1 - } - - if err { - return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) - } else { - return nil - } -} - -func (i *Console) Editor() []string { - var buff bytes.Buffer - for { - reader := bufio.NewReader(os.Stdin) - str, _, err := reader.ReadLine() - if len(str) > 0 { - buff.Write(str) - buff.WriteString("\n") - } - - if err != nil && err.Error() == "EOF" { - break - } - } - - scanner := bufio.NewScanner(strings.NewReader(buff.String())) - scanner.Split(bufio.ScanLines) - - var lines []string - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - - return lines -} - -func (i *Console) PrintRoot() { - root := ethutil.NewValue(i.trie.Root) - if len(root.Bytes()) != 0 { - fmt.Println(hex.EncodeToString(root.Bytes())) - } else { - fmt.Println(i.trie.Root) - } -} - -func (i *Console) ParseInput(input string) bool { - scanner := bufio.NewScanner(strings.NewReader(input)) - scanner.Split(bufio.ScanWords) - - count := 0 - var tokens []string - for scanner.Scan() { - count++ - tokens = append(tokens, scanner.Text()) - } - if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "reading input:", err) - } - - if len(tokens) == 0 { - return true - } - - err := i.ValidateInput(tokens[0], count-1) - if err != nil { - fmt.Println(err) - } else { - switch tokens[0] { - case "update": - i.trie.Update(tokens[1], tokens[2]) - - i.PrintRoot() - case "get": - fmt.Println(i.trie.Get(tokens[1])) - case "root": - i.PrintRoot() - case "rawroot": - fmt.Println(i.trie.Root) - case "print": - i.db.Print() - case "dag": - fmt.Println(ethchain.DaggerVerify(ethutil.Big(tokens[1]), // hash - ethutil.BigPow(2, 36), // diff - ethutil.Big(tokens[2]))) // nonce - case "decode": - value := ethutil.NewValueFromBytes([]byte(tokens[1])) - fmt.Println(value) - case "getaddr": - encoded, _ := hex.DecodeString(tokens[1]) - addr := i.ethereum.BlockChain().CurrentBlock.State().GetAccount(encoded) - fmt.Println("addr:", addr) - case "block": - encoded, _ := hex.DecodeString(tokens[1]) - block := i.ethereum.BlockChain().GetBlock(encoded) - info := block.BlockInfo() - fmt.Printf("++++++++++ #%d ++++++++++\n%v\n", info.Number, block) - case "say": - i.ethereum.Broadcast(ethwire.MsgTalkTy, []interface{}{tokens[1]}) - case "addp": - i.ethereum.ConnectToPeer(tokens[1]) - case "pcount": - fmt.Println("peers:", i.ethereum.Peers().Len()) - case "encode": - fmt.Printf("%q\n", ethutil.Encode(tokens[1])) - case "tx": - recipient, err := hex.DecodeString(tokens[1]) - if err != nil { - fmt.Println("recipient err:", err) - } else { - tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) - - key := ethutil.Config.Db.GetKeys()[0] - tx.Sign(key.PrivateKey) - i.ethereum.TxPool().QueueTransaction(tx) - - fmt.Printf("%x\n", tx.Hash()) - } - case "gettx": - addr, _ := hex.DecodeString(tokens[1]) - data, _ := ethutil.Config.Db.Get(addr) - if len(data) != 0 { - decoder := ethutil.NewValueFromBytes(data) - fmt.Println(decoder) - } else { - fmt.Println("gettx: tx not found") - } - case "contract": - fmt.Println("Contract editor (Ctrl-D = done)") - code := ethchain.Compile(i.Editor()) - - contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) - - key := ethutil.Config.Db.GetKeys()[0] - contract.Sign(key.PrivateKey) - - i.ethereum.TxPool().QueueTransaction(contract) - - fmt.Printf("%x\n", contract.Hash()[12:]) - case "exit", "quit", "q": - return false - case "help": - fmt.Printf("COMMANDS:\n" + - "\033[1m= DB =\033[0m\n" + - "update KEY VALUE - Updates/Creates a new value for the given key\n" + - "get KEY - Retrieves the given key\n" + - "root - Prints the hex encoded merkle root\n" + - "rawroot - Prints the raw merkle root\n" + - "block HASH - Prints the block\n" + - "getaddr ADDR - Prints the account associated with the address\n" + - "\033[1m= Dagger =\033[0m\n" + - "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n" + - "\033[1m= Encoding =\033[0m\n" + - "decode STR\n" + - "encode STR\n" + - "\033[1m= Other =\033[0m\n" + - "addp HOST:PORT\n" + - "tx TO AMOUNT\n" + - "contract AMOUNT\n") - - default: - fmt.Println("Unknown command:", tokens[0]) - } - } - - return true -} - -func (i *Console) Start() { - fmt.Printf("Eth Console. Type (help) for help\n") - reader := bufio.NewReader(os.Stdin) - for { - fmt.Printf("eth >>> ") - str, _, err := reader.ReadLine() - if err != nil { - fmt.Println("Error reading input", err) - } else { - if !i.ParseInput(string(str)) { - return - } - } - } -} diff --git a/ethereal/Makefile b/ethereal/Makefile new file mode 100644 index 000000000..02d127963 --- /dev/null +++ b/ethereal/Makefile @@ -0,0 +1,21 @@ +UNAME = $(shell uname) + +# Default is building +all: + go install + +install: +# Linux build +ifeq ($(UNAME),Linux) + mkdir -p /usr/local/ethereal + files=(net.png network.png new.png tx.png) + for file in "${files[@]}"; do + cp $file /usr/share/ethereal + done + cp -r qml /usr/share/ethereal/qml + cp $GOPATH/bin/go-ethereum /usr/local/bin/ethereal +endif +# OS X build +ifeq ($(UNAME),Darwin) + # Execute py script +endif diff --git a/ethereal/assets/facet.png b/ethereal/assets/facet.png new file mode 100644 index 000000000..49a266e96 Binary files /dev/null and b/ethereal/assets/facet.png differ diff --git a/ethereal/assets/net.png b/ethereal/assets/net.png new file mode 100644 index 000000000..65a20ea00 Binary files /dev/null and b/ethereal/assets/net.png differ diff --git a/ethereal/assets/network.png b/ethereal/assets/network.png new file mode 100644 index 000000000..0a9ffe2ec Binary files /dev/null and b/ethereal/assets/network.png differ diff --git a/ethereal/assets/new.png b/ethereal/assets/new.png new file mode 100644 index 000000000..e80096748 Binary files /dev/null and b/ethereal/assets/new.png differ diff --git a/ethereal/assets/qml/test_app.qml b/ethereal/assets/qml/test_app.qml new file mode 100644 index 000000000..aace4e881 --- /dev/null +++ b/ethereal/assets/qml/test_app.qml @@ -0,0 +1,35 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import Ethereum 1.0 + +ApplicationWindow { + minimumWidth: 500 + maximumWidth: 500 + maximumHeight: 100 + minimumHeight: 100 + + title: "Ethereum Dice" + + TextField { + id: textField + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Amount" + } + Label { + id: txHash + anchors.bottom: textField.top + anchors.bottomMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + } + Button { + anchors.top: textField.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 5 + text: "Place bet" + onClicked: { + txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", textField.text) + } + } +} diff --git a/ethereal/assets/qml/transactions.qml b/ethereal/assets/qml/transactions.qml new file mode 100644 index 000000000..e9a035a85 --- /dev/null +++ b/ethereal/assets/qml/transactions.qml @@ -0,0 +1,9 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; + +Rectangle { + id: transactionView + visible: false + Text { text: "TX VIEW" } +} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml new file mode 100644 index 000000000..7fc7f5447 --- /dev/null +++ b/ethereal/assets/qml/wallet.qml @@ -0,0 +1,408 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +ApplicationWindow { + id: root + + width: 900 + height: 600 + minimumHeight: 300 + + title: "Ethereal" + + MenuBar { + Menu { + title: "File" + MenuItem { + text: "Import App" + shortcut: "Ctrl+o" + onTriggered: openAppDialog.open() + } + } + + Menu { + title: "Network" + MenuItem { + text: "Add Peer" + shortcut: "Ctrl+p" + onTriggered: { + addPeerWin.visible = true + } + } + + MenuItem { + text: "Start" + onTriggered: ui.connect() + } + } + + Menu { + title: "Help" + MenuItem { + text: "About" + onTriggered: { + aboutWin.visible = true + } + } + } + + } + + + property var blockModel: ListModel { + id: blockModel + } + + function setView(view) { + networkView.visible = false + historyView.visible = false + newTxView.visible = false + view.visible = true + //root.title = "Ethereal - " = view.title + } + + SplitView { + anchors.fill: parent + resizing: false + + Rectangle { + id: menu + Layout.minimumWidth: 80 + Layout.maximumWidth: 80 + anchors.bottom: parent.bottom + anchors.top: parent.top + //color: "#D9DDE7" + color: "#252525" + + ColumnLayout { + y: 50 + anchors.left: parent.left + anchors.right: parent.right + height: 200 + Image { + source: ui.assetPath("tx.png") + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(historyView) + } + } + } + Image { + source: ui.assetPath("new.png") + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(newTxView) + } + } + } + Image { + source: ui.assetPath("net.png") + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + setView(networkView) + } + } + } + } + } + + Rectangle { + id: mainView + color: "#00000000" + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + + property var txModel: ListModel { + id: txModel + } + + Rectangle { + id: historyView + anchors.fill: parent + + property var title: "Transactions" + TableView { + id: txTableView + anchors.fill: parent + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } + + model: txModel + } + } + + Rectangle { + id: newTxView + property var title: "New transaction" + visible: false + anchors.fill: parent + color: "#00000000" + + ColumnLayout { + width: 400 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 5 + anchors.topMargin: 5 + TextField { + id: txAmount + width: 200 + placeholderText: "Amount" + } + + TextField { + id: txReceiver + placeholderText: "Receiver Address (or empty for contract)" + Layout.fillWidth: true + } + + Label { + text: "Transaction data" + } + TextArea { + id: codeView + anchors.topMargin: 5 + Layout.fillWidth: true + width: parent.width /2 + } + + Button { + text: "Send" + onClicked: { + console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) + } + } + } + } + + + Rectangle { + id: networkView + property var title: "Network" + visible: false + anchors.fill: parent + + TableView { + id: blockTable + width: parent.width + anchors.top: parent.top + anchors.bottom: logView.top + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + + model: blockModel + + /* + onDoubleClicked: { + popup.visible = true + popup.block = eth.getBlock(blockModel.get(row).hash) + popup.hashLabel.text = popup.block.hash + } + */ + } + + property var logModel: ListModel { + id: logModel + } + + TableView { + id: logView + width: parent.width + height: 150 + anchors.bottom: parent.bottom + TableViewColumn{ role: "description" ; title: "log" } + + model: logModel + } + } + + /* + signal addPlugin(string name) + Component { + id: pluginWindow + Rectangle { + anchors.fill: parent + Label { + id: pluginTitle + anchors.centerIn: parent + text: "Hello world" + } + Component.onCompleted: setView(this) + } + } + + onAddPlugin: { + var pluginWin = pluginWindow.createObject(mainView) + console.log(pluginWin) + pluginWin.pluginTitle.text = "Test" + } + */ + } + } + + FileDialog { + id: openAppDialog + title: "Open QML Application" + onAccepted: { + ui.open(openAppDialog.fileUrl.toString()) + } + } + + statusBar: StatusBar { + RowLayout { + anchors.fill: parent + Button { + property var enabled: true + id: connectButton + onClicked: { + if(this.enabled) { + ui.connect(this) + } + } + text: "Connect" + } + + Button { + id: importAppButton + anchors.left: connectButton.right + anchors.leftMargin: 5 + onClicked: openAppDialog.open() + text: "Import App" + } + + Label { + anchors.left: importAppButton.right + anchors.leftMargin: 5 + id: walletValueLabel + } + + Label { + anchors.right: peerImage.left + anchors.rightMargin: 5 + id: peerLabel + font.pixelSize: 8 + text: "0 / 0" + } + Image { + id: peerImage + anchors.right: parent.right + width: 10; height: 10 + source: ui.assetPath("network.png") + } + } + } + + Window { + id: popup + visible: false + property var block + Label { + id: hashLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + + Window { + id: addPeerWin + visible: false + minimumWidth: 230 + maximumWidth: 230 + maximumHeight: 50 + minimumHeight: 50 + + TextField { + id: addrField + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + placeholderText: "address:port" + onAccepted: { + ui.connectToPeer(addrField.text) + addPeerWin.visible = false + } + } + Button { + anchors.left: addrField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Add" + onClicked: { + ui.connectToPeer(addrField.text) + addPeerWin.visible = false + } + } + Component.onCompleted: { + addrField.focus = true + } + } + + Window { + id: aboutWin + visible: false + title: "About" + minimumWidth: 350 + maximumWidth: 350 + maximumHeight: 200 + minimumHeight: 200 + + Image { + id: aboutIcon + height: 150 + width: 150 + fillMode: Image.PreserveAspectFit + smooth: true + source: ui.assetPath("facet.png") + x: 10 + y: 10 + } + + Text { + anchors.left: aboutIcon.right + anchors.leftMargin: 10 + font.pointSize: 12 + text: "

Ethereum(Go)


Development

Jeffrey Wilcke

Binary Distribution

Jarrad Hope
" + } + + } + + function loadPlugin(name) { + console.log("Loading plugin" + name) + mainView.addPlugin(name) + } + + function setWalletValue(value) { + walletValueLabel.text = value + } + + function addTx(tx) { + txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) + } + + function addBlock(block) { + blockModel.insert(0, {number: block.number, hash: block.hash}) + } + + function addLog(str) { + if(str.len != 0) { + logModel.append({description: str}) + } + } + + function setPeers(text) { + peerLabel.text = text + } +} diff --git a/ethereal/assets/tx.png b/ethereal/assets/tx.png new file mode 100644 index 000000000..62204c315 Binary files /dev/null and b/ethereal/assets/tx.png differ diff --git a/ethereal/config.go b/ethereal/config.go new file mode 100644 index 000000000..a534bb182 --- /dev/null +++ b/ethereal/config.go @@ -0,0 +1,34 @@ +package main + +import ( + "flag" +) + +var StartConsole bool +var StartMining bool +var UseUPnP bool +var OutboundPort string +var ShowGenesis bool +var AddPeer string +var MaxPeer int +var GenAddr bool +var UseSeed bool +var ImportKey string +var ExportKey bool +var DataDir string + +func Init() { + flag.BoolVar(&StartConsole, "c", false, "debug and testing console") + flag.BoolVar(&StartMining, "m", false, "start dagger mining") + flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") + flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") + flag.BoolVar(&UseSeed, "seed", true, "seed peers") + flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") + flag.BoolVar(&ExportKey, "export", false, "export private key") + flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.StringVar(&DataDir, "dir", ".ethereal", "ethereum data directory") + flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") + flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") + + flag.Parse() +} diff --git a/ethereal/ethereum.go b/ethereal/ethereum.go new file mode 100644 index 000000000..618d2b00f --- /dev/null +++ b/ethereal/ethereum.go @@ -0,0 +1,110 @@ +package main + +import ( + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/ethereal/ui" + "github.com/ethereum/go-ethereum/utils" + "github.com/niemeyer/qml" + "log" + "os" + "os/signal" + "runtime" +) + +const Debug = true + +// Register interrupt handlers so we can stop the ethereum +func RegisterInterupts(s *eth.Ethereum) { + // Buffered chan of one is enough + c := make(chan os.Signal, 1) + // Notify about interrupts for now + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + fmt.Printf("Shutting down (%v) ... \n", sig) + + s.Stop() + } + }() +} + +func main() { + Init() + + qml.Init(nil) + + runtime.GOMAXPROCS(runtime.NumCPU()) + + ethchain.InitFees() + ethutil.ReadConfig(DataDir) + ethutil.Config.Seed = UseSeed + + // Instantiated a eth stack + ethereum, err := eth.New(eth.CapDefault, UseUPnP) + if err != nil { + log.Println("eth start err:", err) + return + } + ethereum.Port = OutboundPort + + if GenAddr { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + + if r == "y" { + utils.CreateKeyPair(true) + } + os.Exit(0) + } else { + if len(ImportKey) > 0 { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + + if r == "y" { + utils.ImportPrivateKey(ImportKey) + os.Exit(0) + } + } else { + utils.CreateKeyPair(false) + } + } + + if ExportKey { + key := ethutil.Config.Db.GetKeys()[0] + fmt.Printf("%x\n", key.PrivateKey) + os.Exit(0) + } + + if ShowGenesis { + fmt.Println(ethereum.BlockChain().Genesis()) + os.Exit(0) + } + + log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) + + // Set the max peers + ethereum.MaxPeers = MaxPeer + + gui := ethui.New(ethereum) + gui.Start() +} diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go new file mode 100644 index 000000000..c8f4bedab --- /dev/null +++ b/ethereal/ui/gui.go @@ -0,0 +1,218 @@ +package ethui + +import ( + "bytes" + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethdb" + "github.com/ethereum/eth-go/ethutil" + "github.com/niemeyer/qml" + "math/big" + "strings" +) + +// Block interface exposed to QML +type Block struct { + Number int + Hash string +} + +type Tx struct { + Value, Hash, Address string +} + +func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { + hash := hex.EncodeToString(tx.Hash()) + sender := hex.EncodeToString(tx.Recipient) + + return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender} +} + +// Creates a new QML Block from a chain block +func NewBlockFromBlock(block *ethchain.Block) *Block { + info := block.BlockInfo() + hash := hex.EncodeToString(block.Hash()) + + return &Block{Number: int(info.Number), Hash: hash} +} + +type Gui struct { + // The main application window + win *qml.Window + // QML Engine + engine *qml.Engine + component *qml.Common + // The ethereum interface + eth *eth.Ethereum + + // The public Ethereum library + lib *EthLib + + txDb *ethdb.LDBDatabase + + addr []byte +} + +// Create GUI, but doesn't start it +func New(ethereum *eth.Ethereum) *Gui { + lib := &EthLib{stateManager: ethereum.StateManager(), blockChain: ethereum.BlockChain(), txPool: ethereum.TxPool()} + db, err := ethdb.NewLDBDatabase("tx_database") + if err != nil { + panic(err) + } + + key := ethutil.Config.Db.GetKeys()[0] + addr := key.Address() + + ethereum.StateManager().WatchAddr(addr) + + return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} +} + +func (ui *Gui) Start() { + defer ui.txDb.Close() + + // Register ethereum functions + qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ + Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, + }, { + Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, + }}) + + ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.1")) + ethutil.Config.Log.Infoln("[GUI] Starting GUI") + // Create a new QML engine + ui.engine = qml.NewEngine() + context := ui.engine.Context() + + // Expose the eth library and the ui library to QML + context.SetVar("eth", ui.lib) + context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) + + // Load the main QML interface + component, err := ui.engine.LoadFile(AssetPath("qml/wallet.qml")) + if err != nil { + panic(err) + } + ui.engine.LoadFile(AssetPath("qml/transactions.qml")) + + ui.win = component.CreateWindow(nil) + + // Register the ui as a block processor + //ui.eth.BlockManager.SecondaryBlockProcessor = ui + //ui.eth.TxPool.SecondaryProcessor = ui + + // Add the ui as a log system so we can log directly to the UGI + ethutil.Config.Log.AddLogSystem(ui) + + // Loads previous blocks + go ui.setInitialBlockChain() + go ui.readPreviousTransactions() + go ui.update() + + ui.win.Show() + ui.win.Wait() + + ui.eth.Stop() +} + +func (ui *Gui) setInitialBlockChain() { + // Load previous 10 blocks + chain := ui.eth.BlockChain().GetChain(ui.eth.BlockChain().CurrentBlock.Hash(), 10) + for _, block := range chain { + ui.ProcessBlock(block) + } + +} + +func (ui *Gui) readPreviousTransactions() { + it := ui.txDb.Db().NewIterator(nil, nil) + for it.Next() { + tx := ethchain.NewTransactionFromBytes(it.Value()) + + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + } + it.Release() +} + +func (ui *Gui) ProcessBlock(block *ethchain.Block) { + ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) +} + +// Simple go routine function that updates the list of peers in the GUI +func (ui *Gui) update() { + txChan := make(chan ethchain.TxMsg, 1) + ui.eth.TxPool().Subscribe(txChan) + + account := ui.eth.StateManager().GetAddrState(ui.addr).Account + unconfirmedFunds := new(big.Int) + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) + for { + select { + case txMsg := <-txChan: + tx := txMsg.Tx + + if txMsg.Type == ethchain.TxPre { + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + ui.eth.StateManager().GetAddrState(ui.addr).Nonce += 1 + unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.txDb.Put(tx.Hash(), tx.RlpEncode()) + + unconfirmedFunds.Add(unconfirmedFunds, tx.Value) + } + + pos := "+" + if unconfirmedFunds.Cmp(big.NewInt(0)) >= 0 { + pos = "-" + } + val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds))) + str := fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(account.Amount), pos, val) + + ui.win.Root().Call("setWalletValue", str) + } else { + amount := account.Amount + if bytes.Compare(tx.Sender(), ui.addr) == 0 { + amount.Sub(account.Amount, tx.Value) + } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { + amount.Add(account.Amount, tx.Value) + } + + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) + } + } + + /* + accountAmount := ui.eth.BlockManager.GetAddrState(ui.addr).Account.Amount + ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", accountAmount)) + + ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) + + time.Sleep(1 * time.Second) + */ + + } +} + +// Logging functions that log directly to the GUI interface +func (ui *Gui) Println(v ...interface{}) { + str := strings.TrimRight(fmt.Sprintln(v...), "\n") + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } +} + +func (ui *Gui) Printf(format string, v ...interface{}) { + str := strings.TrimRight(fmt.Sprintf(format, v...), "\n") + lines := strings.Split(str, "\n") + for _, line := range lines { + ui.win.Root().Call("addLog", line) + } +} diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go new file mode 100644 index 000000000..05fffd579 --- /dev/null +++ b/ethereal/ui/library.go @@ -0,0 +1,60 @@ +package ethui + +import ( + "encoding/hex" + "fmt" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "strings" +) + +type EthLib struct { + stateManager *ethchain.StateManager + blockChain *ethchain.BlockChain + txPool *ethchain.TxPool +} + +func (lib *EthLib) CreateTx(receiver, a, data string) string { + var hash []byte + if len(receiver) == 0 { + hash = ethchain.ContractAddr + } else { + var err error + hash, err = hex.DecodeString(receiver) + if err != nil { + return err.Error() + } + } + + k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyPair := ethutil.NewKeyFromBytes(k) + + amount := ethutil.Big(a) + code := ethchain.Compile(strings.Split(data, "\n")) + tx := ethchain.NewTransaction(hash, amount, code) + tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce + + tx.Sign(keyPair.PrivateKey) + + lib.txPool.QueueTransaction(tx) + + if len(receiver) == 0 { + ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } else { + ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) + } + + return ethutil.Hex(tx.Hash()) +} + +func (lib *EthLib) GetBlock(hexHash string) *Block { + hash, err := hex.DecodeString(hexHash) + if err != nil { + return nil + } + + block := lib.blockChain.GetBlock(hash) + fmt.Println(block) + + return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} +} diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go new file mode 100644 index 000000000..3997191fa --- /dev/null +++ b/ethereal/ui/ui_lib.go @@ -0,0 +1,76 @@ +package ethui + +import ( + "bitbucket.org/kardianos/osext" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethutil" + "github.com/niemeyer/qml" + "os" + "path" + "path/filepath" + "runtime" +) + +// UI Library that has some basic functionality exposed +type UiLib struct { + engine *qml.Engine + eth *eth.Ethereum + connected bool +} + +// Opens a QML file (external application) +func (ui *UiLib) Open(path string) { + component, err := ui.engine.LoadFile(path[7:]) + if err != nil { + ethutil.Config.Log.Debugln(err) + } + win := component.CreateWindow(nil) + + go func() { + win.Show() + win.Wait() + }() +} + +func (ui *UiLib) Connect(button qml.Object) { + if !ui.connected { + ui.eth.Start() + ui.connected = true + button.Set("enabled", false) + } +} + +func (ui *UiLib) ConnectToPeer(addr string) { + ui.eth.ConnectToPeer(addr) +} + +func (ui *UiLib) AssetPath(p string) string { + return AssetPath(p) +} + +func AssetPath(p string) string { + var base string + + // If the current working directory is the go-ethereum dir + // assume a debug build and use the source directory as + // asset directory. + pwd, _ := os.Getwd() + if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "ethereal") { + base = path.Join(pwd, "assets") + } else { + switch runtime.GOOS { + case "darwin": + // Get Binary Directory + exedir, _ := osext.ExecutableFolder() + base = filepath.Join(exedir, "../Resources") + case "linux": + base = "/usr/share/ethereal" + case "window": + fallthrough + default: + base = "." + } + } + + return path.Join(base, p) +} diff --git a/ethereum.go b/ethereum.go deleted file mode 100644 index ac1de5af4..000000000 --- a/ethereum.go +++ /dev/null @@ -1,215 +0,0 @@ -package main - -import ( - "fmt" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethutil" - "github.com/ethereum/eth-go/ethwire" - "github.com/ethereum/go-ethereum/ui" - "github.com/niemeyer/qml" - "github.com/obscuren/secp256k1-go" - "log" - "os" - "os/signal" - "runtime" -) - -const Debug = true - -// Register interrupt handlers so we can stop the ethereum -func RegisterInterupts(s *eth.Ethereum) { - // Buffered chan of one is enough - c := make(chan os.Signal, 1) - // Notify about interrupts for now - signal.Notify(c, os.Interrupt) - go func() { - for sig := range c { - fmt.Printf("Shutting down (%v) ... \n", sig) - - s.Stop() - } - }() -} - -func CreateKeyPair(force bool) { - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - if len(data) == 0 || force { - pub, prv := secp256k1.GenerateKeyPair() - pair := ðutil.Key{PrivateKey: prv, PublicKey: pub} - ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) - - fmt.Printf(` -Generating new address and keypair. -Please keep your keys somewhere save. - -++++++++++++++++ KeyRing +++++++++++++++++++ -addr: %x -prvk: %x -pubk: %x -++++++++++++++++++++++++++++++++++++++++++++ - -`, pair.Address(), prv, pub) - - } -} - -func ImportPrivateKey(prvKey string) { - key := ethutil.FromHex(prvKey) - msg := []byte("tmp") - // Couldn't think of a better way to get the pub key - sig, _ := secp256k1.Sign(msg, key) - pub, _ := secp256k1.RecoverPubkey(msg, sig) - pair := ðutil.Key{PrivateKey: key, PublicKey: pub} - ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) - - fmt.Printf(` -Importing private key - -++++++++++++++++ KeyRing +++++++++++++++++++ -addr: %x -prvk: %x -pubk: %x -++++++++++++++++++++++++++++++++++++++++++++ - -`, pair.Address(), key, pub) -} - -func main() { - Init() - - // Qt has to be initialized in the main thread or it will throw errors - // It has to be called BEFORE setting the maximum procs. - if UseGui { - qml.Init(nil) - } - - runtime.GOMAXPROCS(runtime.NumCPU()) - - ethchain.InitFees() - ethutil.ReadConfig(DataDir) - ethutil.Config.Seed = UseSeed - - // Instantiated a eth stack - ethereum, err := eth.New(eth.CapDefault, UseUPnP) - if err != nil { - log.Println("eth start err:", err) - return - } - ethereum.Port = OutboundPort - - if GenAddr { - fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") - - var r string - fmt.Scanln(&r) - for ; ; fmt.Scanln(&r) { - if r == "n" || r == "y" { - break - } else { - fmt.Printf("Yes or no?", r) - } - } - - if r == "y" { - CreateKeyPair(true) - } - os.Exit(0) - } else { - if len(ImportKey) > 0 { - fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") - var r string - fmt.Scanln(&r) - for ; ; fmt.Scanln(&r) { - if r == "n" || r == "y" { - break - } else { - fmt.Printf("Yes or no?", r) - } - } - - if r == "y" { - ImportPrivateKey(ImportKey) - os.Exit(0) - } - } else { - CreateKeyPair(false) - } - } - - if ExportKey { - key := ethutil.Config.Db.GetKeys()[0] - fmt.Printf("%x\n", key.PrivateKey) - os.Exit(0) - } - - if ShowGenesis { - fmt.Println(ethereum.BlockChain().Genesis()) - os.Exit(0) - } - - log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) - - // Set the max peers - ethereum.MaxPeers = MaxPeer - - if StartConsole { - err := os.Mkdir(ethutil.Config.ExecPath, os.ModePerm) - // Error is OK if the error is ErrExist - if err != nil && !os.IsExist(err) { - log.Panic("Unable to create EXECPATH:", err) - } - - console := NewConsole(ethereum) - go console.Start() - } - - if UseGui { - gui := ethui.New(ethereum) - gui.Start() - //ethereum.Stop() - } else { - RegisterInterupts(ethereum) - ethereum.Start() - - if StartMining { - log.Printf("Miner started\n") - - // Fake block mining. It broadcasts a new block every 5 seconds - go func() { - pow := ðchain.EasyPow{} - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(1).Bytes() - - for { - txs := ethereum.TxPool().Flush() - // Create a new block which we're going to mine - block := ethereum.BlockChain().NewBlock(addr, txs) - log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") - // Apply all transactions to the block - ethereum.StateManager().ApplyTransactions(block, block.Transactions()) - - ethereum.StateManager().Prepare(block.State(), block.State()) - ethereum.StateManager().AccumelateRewards(block) - - // Search the nonce - block.Nonce = pow.Search(block) - ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) - - ethereum.StateManager().PrepareDefault(block) - err := ethereum.StateManager().ProcessBlock(block) - if err != nil { - log.Println(err) - } else { - log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) - log.Printf("🔨 Mined block %x\n", block.Hash()) - } - } - }() - } - - // Wait for shutdown - ethereum.WaitForShutdown() - } -} diff --git a/facet.png b/facet.png deleted file mode 100644 index 49a266e96..000000000 Binary files a/facet.png and /dev/null differ diff --git a/net.png b/net.png deleted file mode 100644 index 65a20ea00..000000000 Binary files a/net.png and /dev/null differ diff --git a/network.png b/network.png deleted file mode 100644 index 0a9ffe2ec..000000000 Binary files a/network.png and /dev/null differ diff --git a/new.png b/new.png deleted file mode 100644 index e80096748..000000000 Binary files a/new.png and /dev/null differ diff --git a/node/config.go b/node/config.go new file mode 100644 index 000000000..e4935dfed --- /dev/null +++ b/node/config.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" +) + +var StartConsole bool +var StartMining bool +var UseUPnP bool +var OutboundPort string +var ShowGenesis bool +var AddPeer string +var MaxPeer int +var GenAddr bool +var UseSeed bool +var ImportKey string +var ExportKey bool + +//var UseGui bool +var DataDir string + +func Init() { + flag.BoolVar(&StartConsole, "c", false, "debug and testing console") + flag.BoolVar(&StartMining, "m", false, "start dagger mining") + flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") + //flag.BoolVar(&UseGui, "gui", true, "use the gui") + flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") + flag.BoolVar(&UseSeed, "seed", true, "seed peers") + flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") + flag.BoolVar(&ExportKey, "export", false, "export private key") + flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.StringVar(&DataDir, "dir", ".ethereum", "ethereum data directory") + flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") + flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") + + flag.Parse() +} diff --git a/node/dev_console.go b/node/dev_console.go new file mode 100644 index 000000000..ead4b55e5 --- /dev/null +++ b/node/dev_console.go @@ -0,0 +1,253 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/hex" + "errors" + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethdb" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/ethwire" + _ "math/big" + "os" + "strings" +) + +type Console struct { + db *ethdb.MemDatabase + trie *ethutil.Trie + ethereum *eth.Ethereum +} + +func NewConsole(s *eth.Ethereum) *Console { + db, _ := ethdb.NewMemDatabase() + trie := ethutil.NewTrie(db, "") + + return &Console{db: db, trie: trie, ethereum: s} +} + +func (i *Console) ValidateInput(action string, argumentLength int) error { + err := false + var expArgCount int + + switch { + case action == "update" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "get" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "dag" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "decode" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "encode" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "gettx" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "tx" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "getaddr" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "contract" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "say" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "addp" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "block" && argumentLength != 1: + err = true + expArgCount = 1 + } + + if err { + return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) + } else { + return nil + } +} + +func (i *Console) Editor() []string { + var buff bytes.Buffer + for { + reader := bufio.NewReader(os.Stdin) + str, _, err := reader.ReadLine() + if len(str) > 0 { + buff.Write(str) + buff.WriteString("\n") + } + + if err != nil && err.Error() == "EOF" { + break + } + } + + scanner := bufio.NewScanner(strings.NewReader(buff.String())) + scanner.Split(bufio.ScanLines) + + var lines []string + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + return lines +} + +func (i *Console) PrintRoot() { + root := ethutil.NewValue(i.trie.Root) + if len(root.Bytes()) != 0 { + fmt.Println(hex.EncodeToString(root.Bytes())) + } else { + fmt.Println(i.trie.Root) + } +} + +func (i *Console) ParseInput(input string) bool { + scanner := bufio.NewScanner(strings.NewReader(input)) + scanner.Split(bufio.ScanWords) + + count := 0 + var tokens []string + for scanner.Scan() { + count++ + tokens = append(tokens, scanner.Text()) + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading input:", err) + } + + if len(tokens) == 0 { + return true + } + + err := i.ValidateInput(tokens[0], count-1) + if err != nil { + fmt.Println(err) + } else { + switch tokens[0] { + case "update": + i.trie.Update(tokens[1], tokens[2]) + + i.PrintRoot() + case "get": + fmt.Println(i.trie.Get(tokens[1])) + case "root": + i.PrintRoot() + case "rawroot": + fmt.Println(i.trie.Root) + case "print": + i.db.Print() + case "dag": + fmt.Println(ethchain.DaggerVerify(ethutil.Big(tokens[1]), // hash + ethutil.BigPow(2, 36), // diff + ethutil.Big(tokens[2]))) // nonce + case "decode": + value := ethutil.NewValueFromBytes([]byte(tokens[1])) + fmt.Println(value) + case "getaddr": + encoded, _ := hex.DecodeString(tokens[1]) + addr := i.ethereum.BlockChain().CurrentBlock.State().GetAccount(encoded) + fmt.Println("addr:", addr) + case "block": + encoded, _ := hex.DecodeString(tokens[1]) + block := i.ethereum.BlockChain().GetBlock(encoded) + info := block.BlockInfo() + fmt.Printf("++++++++++ #%d ++++++++++\n%v\n", info.Number, block) + case "say": + i.ethereum.Broadcast(ethwire.MsgTalkTy, []interface{}{tokens[1]}) + case "addp": + i.ethereum.ConnectToPeer(tokens[1]) + case "pcount": + fmt.Println("peers:", i.ethereum.Peers().Len()) + case "encode": + fmt.Printf("%q\n", ethutil.Encode(tokens[1])) + case "tx": + recipient, err := hex.DecodeString(tokens[1]) + if err != nil { + fmt.Println("recipient err:", err) + } else { + tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) + + key := ethutil.Config.Db.GetKeys()[0] + tx.Sign(key.PrivateKey) + i.ethereum.TxPool().QueueTransaction(tx) + + fmt.Printf("%x\n", tx.Hash()) + } + case "gettx": + addr, _ := hex.DecodeString(tokens[1]) + data, _ := ethutil.Config.Db.Get(addr) + if len(data) != 0 { + decoder := ethutil.NewValueFromBytes(data) + fmt.Println(decoder) + } else { + fmt.Println("gettx: tx not found") + } + case "contract": + fmt.Println("Contract editor (Ctrl-D = done)") + code := ethchain.Compile(i.Editor()) + + contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) + + key := ethutil.Config.Db.GetKeys()[0] + contract.Sign(key.PrivateKey) + + i.ethereum.TxPool().QueueTransaction(contract) + + fmt.Printf("%x\n", contract.Hash()[12:]) + case "exit", "quit", "q": + return false + case "help": + fmt.Printf("COMMANDS:\n" + + "\033[1m= DB =\033[0m\n" + + "update KEY VALUE - Updates/Creates a new value for the given key\n" + + "get KEY - Retrieves the given key\n" + + "root - Prints the hex encoded merkle root\n" + + "rawroot - Prints the raw merkle root\n" + + "block HASH - Prints the block\n" + + "getaddr ADDR - Prints the account associated with the address\n" + + "\033[1m= Dagger =\033[0m\n" + + "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n" + + "\033[1m= Encoding =\033[0m\n" + + "decode STR\n" + + "encode STR\n" + + "\033[1m= Other =\033[0m\n" + + "addp HOST:PORT\n" + + "tx TO AMOUNT\n" + + "contract AMOUNT\n") + + default: + fmt.Println("Unknown command:", tokens[0]) + } + } + + return true +} + +func (i *Console) Start() { + fmt.Printf("Eth Console. Type (help) for help\n") + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("eth >>> ") + str, _, err := reader.ReadLine() + if err != nil { + fmt.Println("Error reading input", err) + } else { + if !i.ParseInput(string(str)) { + return + } + } + } +} diff --git a/node/ethereum.go b/node/ethereum.go new file mode 100644 index 000000000..3f5e4a8f5 --- /dev/null +++ b/node/ethereum.go @@ -0,0 +1,158 @@ +package main + +import ( + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/ethwire" + "github.com/ethereum/go-ethereum/utils" + "log" + "os" + "os/signal" + "runtime" +) + +const Debug = true + +// Register interrupt handlers so we can stop the ethereum +func RegisterInterupts(s *eth.Ethereum) { + // Buffered chan of one is enough + c := make(chan os.Signal, 1) + // Notify about interrupts for now + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + fmt.Printf("Shutting down (%v) ... \n", sig) + + s.Stop() + } + }() +} + +func main() { + Init() + + runtime.GOMAXPROCS(runtime.NumCPU()) + + ethchain.InitFees() + ethutil.ReadConfig(DataDir) + ethutil.Config.Seed = UseSeed + + // Instantiated a eth stack + ethereum, err := eth.New(eth.CapDefault, UseUPnP) + if err != nil { + log.Println("eth start err:", err) + return + } + ethereum.Port = OutboundPort + + if GenAddr { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + + if r == "y" { + utils.CreateKeyPair(true) + } + os.Exit(0) + } else { + if len(ImportKey) > 0 { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + + if r == "y" { + utils.ImportPrivateKey(ImportKey) + os.Exit(0) + } + } else { + utils.CreateKeyPair(false) + } + } + + if ExportKey { + key := ethutil.Config.Db.GetKeys()[0] + fmt.Printf("%x\n", key.PrivateKey) + os.Exit(0) + } + + if ShowGenesis { + fmt.Println(ethereum.BlockChain().Genesis()) + os.Exit(0) + } + + log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) + + // Set the max peers + ethereum.MaxPeers = MaxPeer + + if StartConsole { + err := os.Mkdir(ethutil.Config.ExecPath, os.ModePerm) + // Error is OK if the error is ErrExist + if err != nil && !os.IsExist(err) { + log.Panic("Unable to create EXECPATH:", err) + } + + console := NewConsole(ethereum) + go console.Start() + } + + RegisterInterupts(ethereum) + ethereum.Start() + + if StartMining { + log.Printf("Miner started\n") + + // Fake block mining. It broadcasts a new block every 5 seconds + go func() { + pow := ðchain.EasyPow{} + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(1).Bytes() + + for { + txs := ethereum.TxPool().Flush() + // Create a new block which we're going to mine + block := ethereum.BlockChain().NewBlock(addr, txs) + log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") + // Apply all transactions to the block + ethereum.StateManager().ApplyTransactions(block, block.Transactions()) + + ethereum.StateManager().Prepare(block.State(), block.State()) + ethereum.StateManager().AccumelateRewards(block) + + // Search the nonce + block.Nonce = pow.Search(block) + ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) + + ethereum.StateManager().PrepareDefault(block) + err := ethereum.StateManager().ProcessBlock(block) + if err != nil { + log.Println(err) + } else { + log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) + log.Printf("🔨 Mined block %x\n", block.Hash()) + } + } + }() + } + + // Wait for shutdown + ethereum.WaitForShutdown() +} diff --git a/node/node b/node/node new file mode 100755 index 000000000..48452886c Binary files /dev/null and b/node/node differ diff --git a/qml/test_app.qml b/qml/test_app.qml deleted file mode 100644 index aace4e881..000000000 --- a/qml/test_app.qml +++ /dev/null @@ -1,35 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0; -import QtQuick.Layouts 1.0; -import Ethereum 1.0 - -ApplicationWindow { - minimumWidth: 500 - maximumWidth: 500 - maximumHeight: 100 - minimumHeight: 100 - - title: "Ethereum Dice" - - TextField { - id: textField - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "Amount" - } - Label { - id: txHash - anchors.bottom: textField.top - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - } - Button { - anchors.top: textField.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 5 - text: "Place bet" - onClicked: { - txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", textField.text) - } - } -} diff --git a/qml/transactions.qml b/qml/transactions.qml deleted file mode 100644 index e9a035a85..000000000 --- a/qml/transactions.qml +++ /dev/null @@ -1,9 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0; -import QtQuick.Layouts 1.0; - -Rectangle { - id: transactionView - visible: false - Text { text: "TX VIEW" } -} diff --git a/qml/wallet.qml b/qml/wallet.qml deleted file mode 100644 index 7fc7f5447..000000000 --- a/qml/wallet.qml +++ /dev/null @@ -1,408 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0; -import QtQuick.Layouts 1.0; -import QtQuick.Dialogs 1.0; -import QtQuick.Window 2.1; -import QtQuick.Controls.Styles 1.1 -import Ethereum 1.0 - -ApplicationWindow { - id: root - - width: 900 - height: 600 - minimumHeight: 300 - - title: "Ethereal" - - MenuBar { - Menu { - title: "File" - MenuItem { - text: "Import App" - shortcut: "Ctrl+o" - onTriggered: openAppDialog.open() - } - } - - Menu { - title: "Network" - MenuItem { - text: "Add Peer" - shortcut: "Ctrl+p" - onTriggered: { - addPeerWin.visible = true - } - } - - MenuItem { - text: "Start" - onTriggered: ui.connect() - } - } - - Menu { - title: "Help" - MenuItem { - text: "About" - onTriggered: { - aboutWin.visible = true - } - } - } - - } - - - property var blockModel: ListModel { - id: blockModel - } - - function setView(view) { - networkView.visible = false - historyView.visible = false - newTxView.visible = false - view.visible = true - //root.title = "Ethereal - " = view.title - } - - SplitView { - anchors.fill: parent - resizing: false - - Rectangle { - id: menu - Layout.minimumWidth: 80 - Layout.maximumWidth: 80 - anchors.bottom: parent.bottom - anchors.top: parent.top - //color: "#D9DDE7" - color: "#252525" - - ColumnLayout { - y: 50 - anchors.left: parent.left - anchors.right: parent.right - height: 200 - Image { - source: ui.assetPath("tx.png") - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(historyView) - } - } - } - Image { - source: ui.assetPath("new.png") - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(newTxView) - } - } - } - Image { - source: ui.assetPath("net.png") - anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - setView(networkView) - } - } - } - } - } - - Rectangle { - id: mainView - color: "#00000000" - anchors.right: parent.right - anchors.left: menu.right - anchors.bottom: parent.bottom - anchors.top: parent.top - - property var txModel: ListModel { - id: txModel - } - - Rectangle { - id: historyView - anchors.fill: parent - - property var title: "Transactions" - TableView { - id: txTableView - anchors.fill: parent - TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } - TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } - - model: txModel - } - } - - Rectangle { - id: newTxView - property var title: "New transaction" - visible: false - anchors.fill: parent - color: "#00000000" - - ColumnLayout { - width: 400 - anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: 5 - anchors.topMargin: 5 - TextField { - id: txAmount - width: 200 - placeholderText: "Amount" - } - - TextField { - id: txReceiver - placeholderText: "Receiver Address (or empty for contract)" - Layout.fillWidth: true - } - - Label { - text: "Transaction data" - } - TextArea { - id: codeView - anchors.topMargin: 5 - Layout.fillWidth: true - width: parent.width /2 - } - - Button { - text: "Send" - onClicked: { - console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) - } - } - } - } - - - Rectangle { - id: networkView - property var title: "Network" - visible: false - anchors.fill: parent - - TableView { - id: blockTable - width: parent.width - anchors.top: parent.top - anchors.bottom: logView.top - TableViewColumn{ role: "number" ; title: "#" ; width: 100 } - TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } - - model: blockModel - - /* - onDoubleClicked: { - popup.visible = true - popup.block = eth.getBlock(blockModel.get(row).hash) - popup.hashLabel.text = popup.block.hash - } - */ - } - - property var logModel: ListModel { - id: logModel - } - - TableView { - id: logView - width: parent.width - height: 150 - anchors.bottom: parent.bottom - TableViewColumn{ role: "description" ; title: "log" } - - model: logModel - } - } - - /* - signal addPlugin(string name) - Component { - id: pluginWindow - Rectangle { - anchors.fill: parent - Label { - id: pluginTitle - anchors.centerIn: parent - text: "Hello world" - } - Component.onCompleted: setView(this) - } - } - - onAddPlugin: { - var pluginWin = pluginWindow.createObject(mainView) - console.log(pluginWin) - pluginWin.pluginTitle.text = "Test" - } - */ - } - } - - FileDialog { - id: openAppDialog - title: "Open QML Application" - onAccepted: { - ui.open(openAppDialog.fileUrl.toString()) - } - } - - statusBar: StatusBar { - RowLayout { - anchors.fill: parent - Button { - property var enabled: true - id: connectButton - onClicked: { - if(this.enabled) { - ui.connect(this) - } - } - text: "Connect" - } - - Button { - id: importAppButton - anchors.left: connectButton.right - anchors.leftMargin: 5 - onClicked: openAppDialog.open() - text: "Import App" - } - - Label { - anchors.left: importAppButton.right - anchors.leftMargin: 5 - id: walletValueLabel - } - - Label { - anchors.right: peerImage.left - anchors.rightMargin: 5 - id: peerLabel - font.pixelSize: 8 - text: "0 / 0" - } - Image { - id: peerImage - anchors.right: parent.right - width: 10; height: 10 - source: ui.assetPath("network.png") - } - } - } - - Window { - id: popup - visible: false - property var block - Label { - id: hashLabel - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - } - - Window { - id: addPeerWin - visible: false - minimumWidth: 230 - maximumWidth: 230 - maximumHeight: 50 - minimumHeight: 50 - - TextField { - id: addrField - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - placeholderText: "address:port" - onAccepted: { - ui.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Button { - anchors.left: addrField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Add" - onClicked: { - ui.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Component.onCompleted: { - addrField.focus = true - } - } - - Window { - id: aboutWin - visible: false - title: "About" - minimumWidth: 350 - maximumWidth: 350 - maximumHeight: 200 - minimumHeight: 200 - - Image { - id: aboutIcon - height: 150 - width: 150 - fillMode: Image.PreserveAspectFit - smooth: true - source: ui.assetPath("facet.png") - x: 10 - y: 10 - } - - Text { - anchors.left: aboutIcon.right - anchors.leftMargin: 10 - font.pointSize: 12 - text: "

Ethereum(Go)


Development

Jeffrey Wilcke

Binary Distribution

Jarrad Hope
" - } - - } - - function loadPlugin(name) { - console.log("Loading plugin" + name) - mainView.addPlugin(name) - } - - function setWalletValue(value) { - walletValueLabel.text = value - } - - function addTx(tx) { - txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) - } - - function addBlock(block) { - blockModel.insert(0, {number: block.number, hash: block.hash}) - } - - function addLog(str) { - if(str.len != 0) { - logModel.append({description: str}) - } - } - - function setPeers(text) { - peerLabel.text = text - } -} diff --git a/test_runner.go b/test_runner.go deleted file mode 100644 index e8a1698ce..000000000 --- a/test_runner.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "testing" -) - -type TestSource struct { - Inputs map[string]string - Expectation string -} - -func NewTestSource(source string) *TestSource { - s := &TestSource{} - err := json.Unmarshal([]byte(source), s) - if err != nil { - fmt.Println(err) - } - - return s -} - -type TestRunner struct { - source *TestSource -} - -func NewTestRunner(t *testing.T) *TestRunner { - return &TestRunner{} -} - -func (runner *TestRunner) RunFromString(input string, Cb func(*TestSource)) { - source := NewTestSource(input) - Cb(source) -} diff --git a/test_runner_test.go b/test_runner_test.go deleted file mode 100644 index a5672eb7a..000000000 --- a/test_runner_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -/* -import ( - "encoding/hex" - _ "fmt" - "github.com/ethereum/ethdb-go" - "github.com/ethereum/ethutil-go" - "testing" -) - -var testsource = ` -{ - "inputs":{ - "doe": "reindeer", - "dog": "puppy", - "dogglesworth": "cat" - }, - "expectation":"e378927bfc1bd4f01a2e8d9f59bd18db8a208bb493ac0b00f93ce51d4d2af76c" -}` - -func TestTestRunner(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - trie := ethutil.NewTrie(db, "") - - runner := NewTestRunner(t) - runner.RunFromString(testsource, func(source *TestSource) { - for key, value := range source.Inputs { - trie.Update(key, value) - } - if hex.EncodeToString(trie.Root.([]byte)) != source.Expectation { - t.Error("trie root did not match") - } - }) -} -*/ diff --git a/testing.go b/testing.go deleted file mode 100644 index 849089a5d..000000000 --- a/testing.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -/* - -import ( - _"fmt" -) - -// This will eventually go away -var Db *MemDatabase - -func Testing() { - db, _ := NewMemDatabase() - Db = db - - bm := NewBlockManager() - - tx := NewTransaction("\x00", 20, []string{"PUSH"}) - txData := tx.RlpEncode() - //fmt.Printf("%q\n", txData) - - copyTx := &Transaction{} - copyTx.RlpDecode(txData) - //fmt.Println(tx) - //fmt.Println(copyTx) - - tx2 := NewTransaction("\x00", 20, []string{"SET 10 6", "LD 10 10"}) - - blck := CreateTestBlock([]*Transaction{tx2, tx}) - - bm.ProcessBlock( blck ) -} -*/ diff --git a/tx.png b/tx.png deleted file mode 100644 index 62204c315..000000000 Binary files a/tx.png and /dev/null differ diff --git a/ui/gui.go b/ui/gui.go deleted file mode 100644 index c8f4bedab..000000000 --- a/ui/gui.go +++ /dev/null @@ -1,218 +0,0 @@ -package ethui - -import ( - "bytes" - "encoding/hex" - "fmt" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethdb" - "github.com/ethereum/eth-go/ethutil" - "github.com/niemeyer/qml" - "math/big" - "strings" -) - -// Block interface exposed to QML -type Block struct { - Number int - Hash string -} - -type Tx struct { - Value, Hash, Address string -} - -func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { - hash := hex.EncodeToString(tx.Hash()) - sender := hex.EncodeToString(tx.Recipient) - - return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender} -} - -// Creates a new QML Block from a chain block -func NewBlockFromBlock(block *ethchain.Block) *Block { - info := block.BlockInfo() - hash := hex.EncodeToString(block.Hash()) - - return &Block{Number: int(info.Number), Hash: hash} -} - -type Gui struct { - // The main application window - win *qml.Window - // QML Engine - engine *qml.Engine - component *qml.Common - // The ethereum interface - eth *eth.Ethereum - - // The public Ethereum library - lib *EthLib - - txDb *ethdb.LDBDatabase - - addr []byte -} - -// Create GUI, but doesn't start it -func New(ethereum *eth.Ethereum) *Gui { - lib := &EthLib{stateManager: ethereum.StateManager(), blockChain: ethereum.BlockChain(), txPool: ethereum.TxPool()} - db, err := ethdb.NewLDBDatabase("tx_database") - if err != nil { - panic(err) - } - - key := ethutil.Config.Db.GetKeys()[0] - addr := key.Address() - - ethereum.StateManager().WatchAddr(addr) - - return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} -} - -func (ui *Gui) Start() { - defer ui.txDb.Close() - - // Register ethereum functions - qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ - Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, - }, { - Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, - }}) - - ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.1")) - ethutil.Config.Log.Infoln("[GUI] Starting GUI") - // Create a new QML engine - ui.engine = qml.NewEngine() - context := ui.engine.Context() - - // Expose the eth library and the ui library to QML - context.SetVar("eth", ui.lib) - context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) - - // Load the main QML interface - component, err := ui.engine.LoadFile(AssetPath("qml/wallet.qml")) - if err != nil { - panic(err) - } - ui.engine.LoadFile(AssetPath("qml/transactions.qml")) - - ui.win = component.CreateWindow(nil) - - // Register the ui as a block processor - //ui.eth.BlockManager.SecondaryBlockProcessor = ui - //ui.eth.TxPool.SecondaryProcessor = ui - - // Add the ui as a log system so we can log directly to the UGI - ethutil.Config.Log.AddLogSystem(ui) - - // Loads previous blocks - go ui.setInitialBlockChain() - go ui.readPreviousTransactions() - go ui.update() - - ui.win.Show() - ui.win.Wait() - - ui.eth.Stop() -} - -func (ui *Gui) setInitialBlockChain() { - // Load previous 10 blocks - chain := ui.eth.BlockChain().GetChain(ui.eth.BlockChain().CurrentBlock.Hash(), 10) - for _, block := range chain { - ui.ProcessBlock(block) - } - -} - -func (ui *Gui) readPreviousTransactions() { - it := ui.txDb.Db().NewIterator(nil, nil) - for it.Next() { - tx := ethchain.NewTransactionFromBytes(it.Value()) - - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - } - it.Release() -} - -func (ui *Gui) ProcessBlock(block *ethchain.Block) { - ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) -} - -// Simple go routine function that updates the list of peers in the GUI -func (ui *Gui) update() { - txChan := make(chan ethchain.TxMsg, 1) - ui.eth.TxPool().Subscribe(txChan) - - account := ui.eth.StateManager().GetAddrState(ui.addr).Account - unconfirmedFunds := new(big.Int) - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) - for { - select { - case txMsg := <-txChan: - tx := txMsg.Tx - - if txMsg.Type == ethchain.TxPre { - if bytes.Compare(tx.Sender(), ui.addr) == 0 { - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - - ui.eth.StateManager().GetAddrState(ui.addr).Nonce += 1 - unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) - } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) - ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - - unconfirmedFunds.Add(unconfirmedFunds, tx.Value) - } - - pos := "+" - if unconfirmedFunds.Cmp(big.NewInt(0)) >= 0 { - pos = "-" - } - val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds))) - str := fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(account.Amount), pos, val) - - ui.win.Root().Call("setWalletValue", str) - } else { - amount := account.Amount - if bytes.Compare(tx.Sender(), ui.addr) == 0 { - amount.Sub(account.Amount, tx.Value) - } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - amount.Add(account.Amount, tx.Value) - } - - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) - } - } - - /* - accountAmount := ui.eth.BlockManager.GetAddrState(ui.addr).Account.Amount - ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", accountAmount)) - - ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) - - time.Sleep(1 * time.Second) - */ - - } -} - -// Logging functions that log directly to the GUI interface -func (ui *Gui) Println(v ...interface{}) { - str := strings.TrimRight(fmt.Sprintln(v...), "\n") - lines := strings.Split(str, "\n") - for _, line := range lines { - ui.win.Root().Call("addLog", line) - } -} - -func (ui *Gui) Printf(format string, v ...interface{}) { - str := strings.TrimRight(fmt.Sprintf(format, v...), "\n") - lines := strings.Split(str, "\n") - for _, line := range lines { - ui.win.Root().Call("addLog", line) - } -} diff --git a/ui/library.go b/ui/library.go deleted file mode 100644 index 05fffd579..000000000 --- a/ui/library.go +++ /dev/null @@ -1,60 +0,0 @@ -package ethui - -import ( - "encoding/hex" - "fmt" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethutil" - "strings" -) - -type EthLib struct { - stateManager *ethchain.StateManager - blockChain *ethchain.BlockChain - txPool *ethchain.TxPool -} - -func (lib *EthLib) CreateTx(receiver, a, data string) string { - var hash []byte - if len(receiver) == 0 { - hash = ethchain.ContractAddr - } else { - var err error - hash, err = hex.DecodeString(receiver) - if err != nil { - return err.Error() - } - } - - k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyPair := ethutil.NewKeyFromBytes(k) - - amount := ethutil.Big(a) - code := ethchain.Compile(strings.Split(data, "\n")) - tx := ethchain.NewTransaction(hash, amount, code) - tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce - - tx.Sign(keyPair.PrivateKey) - - lib.txPool.QueueTransaction(tx) - - if len(receiver) == 0 { - ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) - } else { - ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) - } - - return ethutil.Hex(tx.Hash()) -} - -func (lib *EthLib) GetBlock(hexHash string) *Block { - hash, err := hex.DecodeString(hexHash) - if err != nil { - return nil - } - - block := lib.blockChain.GetBlock(hash) - fmt.Println(block) - - return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} -} diff --git a/ui/ui_lib.go b/ui/ui_lib.go deleted file mode 100644 index 83e8bf2d1..000000000 --- a/ui/ui_lib.go +++ /dev/null @@ -1,76 +0,0 @@ -package ethui - -import ( - "bitbucket.org/kardianos/osext" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethutil" - "github.com/niemeyer/qml" - "os" - "path" - "path/filepath" - "runtime" -) - -// UI Library that has some basic functionality exposed -type UiLib struct { - engine *qml.Engine - eth *eth.Ethereum - connected bool -} - -// Opens a QML file (external application) -func (ui *UiLib) Open(path string) { - component, err := ui.engine.LoadFile(path[7:]) - if err != nil { - ethutil.Config.Log.Debugln(err) - } - win := component.CreateWindow(nil) - - go func() { - win.Show() - win.Wait() - }() -} - -func (ui *UiLib) Connect(button qml.Object) { - if !ui.connected { - ui.eth.Start() - ui.connected = true - button.Set("enabled", false) - } -} - -func (ui *UiLib) ConnectToPeer(addr string) { - ui.eth.ConnectToPeer(addr) -} - -func (ui *UiLib) AssetPath(p string) string { - return AssetPath(p) -} - -func AssetPath(p string) string { - var base string - - // If the current working directory is the go-ethereum dir - // assume a debug build and use the source directory as - // asset directory. - pwd, _ := os.Getwd() - if pwd == path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum") { - base = pwd - } else { - switch runtime.GOOS { - case "darwin": - // Get Binary Directory - exedir, _ := osext.ExecutableFolder() - base = filepath.Join(exedir, "../Resources") - case "linux": - base = "/usr/share/ethereal" - case "window": - fallthrough - default: - base = "." - } - } - - return path.Join(base, p) -} diff --git a/utils/keys.go b/utils/keys.go new file mode 100644 index 000000000..910c8c477 --- /dev/null +++ b/utils/keys.go @@ -0,0 +1,50 @@ +package utils + +import ( + "fmt" + "github.com/ethereum/eth-go/ethutil" + "github.com/obscuren/secp256k1-go" +) + +func CreateKeyPair(force bool) { + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + if len(data) == 0 || force { + pub, prv := secp256k1.GenerateKeyPair() + pair := ðutil.Key{PrivateKey: prv, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) + + fmt.Printf(` +Generating new address and keypair. +Please keep your keys somewhere save. + +++++++++++++++++ KeyRing +++++++++++++++++++ +addr: %x +prvk: %x +pubk: %x +++++++++++++++++++++++++++++++++++++++++++++ + +`, pair.Address(), prv, pub) + + } +} + +func ImportPrivateKey(prvKey string) { + key := ethutil.FromHex(prvKey) + msg := []byte("tmp") + // Couldn't think of a better way to get the pub key + sig, _ := secp256k1.Sign(msg, key) + pub, _ := secp256k1.RecoverPubkey(msg, sig) + pair := ðutil.Key{PrivateKey: key, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) + + fmt.Printf(` +Importing private key + +++++++++++++++++ KeyRing +++++++++++++++++++ +addr: %x +prvk: %x +pubk: %x +++++++++++++++++++++++++++++++++++++++++++++ + +`, pair.Address(), key, pub) +} -- cgit v1.2.3 From 45ec9c88e4bb5060455df4c12891f94fb5bb28c6 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 22 Mar 2014 12:03:03 +0100 Subject: Moved node to ethereum --- node/config.go | 37 -------- node/dev_console.go | 253 ---------------------------------------------------- node/ethereum.go | 158 -------------------------------- node/node | Bin 8154988 -> 0 bytes 4 files changed, 448 deletions(-) delete mode 100644 node/config.go delete mode 100644 node/dev_console.go delete mode 100644 node/ethereum.go delete mode 100755 node/node diff --git a/node/config.go b/node/config.go deleted file mode 100644 index e4935dfed..000000000 --- a/node/config.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "flag" -) - -var StartConsole bool -var StartMining bool -var UseUPnP bool -var OutboundPort string -var ShowGenesis bool -var AddPeer string -var MaxPeer int -var GenAddr bool -var UseSeed bool -var ImportKey string -var ExportKey bool - -//var UseGui bool -var DataDir string - -func Init() { - flag.BoolVar(&StartConsole, "c", false, "debug and testing console") - flag.BoolVar(&StartMining, "m", false, "start dagger mining") - flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") - //flag.BoolVar(&UseGui, "gui", true, "use the gui") - flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") - flag.BoolVar(&UseSeed, "seed", true, "seed peers") - flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") - flag.BoolVar(&ExportKey, "export", false, "export private key") - flag.StringVar(&OutboundPort, "p", "30303", "listening port") - flag.StringVar(&DataDir, "dir", ".ethereum", "ethereum data directory") - flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") - flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") - - flag.Parse() -} diff --git a/node/dev_console.go b/node/dev_console.go deleted file mode 100644 index ead4b55e5..000000000 --- a/node/dev_console.go +++ /dev/null @@ -1,253 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "encoding/hex" - "errors" - "fmt" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethdb" - "github.com/ethereum/eth-go/ethutil" - "github.com/ethereum/eth-go/ethwire" - _ "math/big" - "os" - "strings" -) - -type Console struct { - db *ethdb.MemDatabase - trie *ethutil.Trie - ethereum *eth.Ethereum -} - -func NewConsole(s *eth.Ethereum) *Console { - db, _ := ethdb.NewMemDatabase() - trie := ethutil.NewTrie(db, "") - - return &Console{db: db, trie: trie, ethereum: s} -} - -func (i *Console) ValidateInput(action string, argumentLength int) error { - err := false - var expArgCount int - - switch { - case action == "update" && argumentLength != 2: - err = true - expArgCount = 2 - case action == "get" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "dag" && argumentLength != 2: - err = true - expArgCount = 2 - case action == "decode" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "encode" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "gettx" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "tx" && argumentLength != 2: - err = true - expArgCount = 2 - case action == "getaddr" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "contract" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "say" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "addp" && argumentLength != 1: - err = true - expArgCount = 1 - case action == "block" && argumentLength != 1: - err = true - expArgCount = 1 - } - - if err { - return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) - } else { - return nil - } -} - -func (i *Console) Editor() []string { - var buff bytes.Buffer - for { - reader := bufio.NewReader(os.Stdin) - str, _, err := reader.ReadLine() - if len(str) > 0 { - buff.Write(str) - buff.WriteString("\n") - } - - if err != nil && err.Error() == "EOF" { - break - } - } - - scanner := bufio.NewScanner(strings.NewReader(buff.String())) - scanner.Split(bufio.ScanLines) - - var lines []string - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - - return lines -} - -func (i *Console) PrintRoot() { - root := ethutil.NewValue(i.trie.Root) - if len(root.Bytes()) != 0 { - fmt.Println(hex.EncodeToString(root.Bytes())) - } else { - fmt.Println(i.trie.Root) - } -} - -func (i *Console) ParseInput(input string) bool { - scanner := bufio.NewScanner(strings.NewReader(input)) - scanner.Split(bufio.ScanWords) - - count := 0 - var tokens []string - for scanner.Scan() { - count++ - tokens = append(tokens, scanner.Text()) - } - if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "reading input:", err) - } - - if len(tokens) == 0 { - return true - } - - err := i.ValidateInput(tokens[0], count-1) - if err != nil { - fmt.Println(err) - } else { - switch tokens[0] { - case "update": - i.trie.Update(tokens[1], tokens[2]) - - i.PrintRoot() - case "get": - fmt.Println(i.trie.Get(tokens[1])) - case "root": - i.PrintRoot() - case "rawroot": - fmt.Println(i.trie.Root) - case "print": - i.db.Print() - case "dag": - fmt.Println(ethchain.DaggerVerify(ethutil.Big(tokens[1]), // hash - ethutil.BigPow(2, 36), // diff - ethutil.Big(tokens[2]))) // nonce - case "decode": - value := ethutil.NewValueFromBytes([]byte(tokens[1])) - fmt.Println(value) - case "getaddr": - encoded, _ := hex.DecodeString(tokens[1]) - addr := i.ethereum.BlockChain().CurrentBlock.State().GetAccount(encoded) - fmt.Println("addr:", addr) - case "block": - encoded, _ := hex.DecodeString(tokens[1]) - block := i.ethereum.BlockChain().GetBlock(encoded) - info := block.BlockInfo() - fmt.Printf("++++++++++ #%d ++++++++++\n%v\n", info.Number, block) - case "say": - i.ethereum.Broadcast(ethwire.MsgTalkTy, []interface{}{tokens[1]}) - case "addp": - i.ethereum.ConnectToPeer(tokens[1]) - case "pcount": - fmt.Println("peers:", i.ethereum.Peers().Len()) - case "encode": - fmt.Printf("%q\n", ethutil.Encode(tokens[1])) - case "tx": - recipient, err := hex.DecodeString(tokens[1]) - if err != nil { - fmt.Println("recipient err:", err) - } else { - tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) - - key := ethutil.Config.Db.GetKeys()[0] - tx.Sign(key.PrivateKey) - i.ethereum.TxPool().QueueTransaction(tx) - - fmt.Printf("%x\n", tx.Hash()) - } - case "gettx": - addr, _ := hex.DecodeString(tokens[1]) - data, _ := ethutil.Config.Db.Get(addr) - if len(data) != 0 { - decoder := ethutil.NewValueFromBytes(data) - fmt.Println(decoder) - } else { - fmt.Println("gettx: tx not found") - } - case "contract": - fmt.Println("Contract editor (Ctrl-D = done)") - code := ethchain.Compile(i.Editor()) - - contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) - - key := ethutil.Config.Db.GetKeys()[0] - contract.Sign(key.PrivateKey) - - i.ethereum.TxPool().QueueTransaction(contract) - - fmt.Printf("%x\n", contract.Hash()[12:]) - case "exit", "quit", "q": - return false - case "help": - fmt.Printf("COMMANDS:\n" + - "\033[1m= DB =\033[0m\n" + - "update KEY VALUE - Updates/Creates a new value for the given key\n" + - "get KEY - Retrieves the given key\n" + - "root - Prints the hex encoded merkle root\n" + - "rawroot - Prints the raw merkle root\n" + - "block HASH - Prints the block\n" + - "getaddr ADDR - Prints the account associated with the address\n" + - "\033[1m= Dagger =\033[0m\n" + - "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n" + - "\033[1m= Encoding =\033[0m\n" + - "decode STR\n" + - "encode STR\n" + - "\033[1m= Other =\033[0m\n" + - "addp HOST:PORT\n" + - "tx TO AMOUNT\n" + - "contract AMOUNT\n") - - default: - fmt.Println("Unknown command:", tokens[0]) - } - } - - return true -} - -func (i *Console) Start() { - fmt.Printf("Eth Console. Type (help) for help\n") - reader := bufio.NewReader(os.Stdin) - for { - fmt.Printf("eth >>> ") - str, _, err := reader.ReadLine() - if err != nil { - fmt.Println("Error reading input", err) - } else { - if !i.ParseInput(string(str)) { - return - } - } - } -} diff --git a/node/ethereum.go b/node/ethereum.go deleted file mode 100644 index 3f5e4a8f5..000000000 --- a/node/ethereum.go +++ /dev/null @@ -1,158 +0,0 @@ -package main - -import ( - "fmt" - "github.com/ethereum/eth-go" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethutil" - "github.com/ethereum/eth-go/ethwire" - "github.com/ethereum/go-ethereum/utils" - "log" - "os" - "os/signal" - "runtime" -) - -const Debug = true - -// Register interrupt handlers so we can stop the ethereum -func RegisterInterupts(s *eth.Ethereum) { - // Buffered chan of one is enough - c := make(chan os.Signal, 1) - // Notify about interrupts for now - signal.Notify(c, os.Interrupt) - go func() { - for sig := range c { - fmt.Printf("Shutting down (%v) ... \n", sig) - - s.Stop() - } - }() -} - -func main() { - Init() - - runtime.GOMAXPROCS(runtime.NumCPU()) - - ethchain.InitFees() - ethutil.ReadConfig(DataDir) - ethutil.Config.Seed = UseSeed - - // Instantiated a eth stack - ethereum, err := eth.New(eth.CapDefault, UseUPnP) - if err != nil { - log.Println("eth start err:", err) - return - } - ethereum.Port = OutboundPort - - if GenAddr { - fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") - - var r string - fmt.Scanln(&r) - for ; ; fmt.Scanln(&r) { - if r == "n" || r == "y" { - break - } else { - fmt.Printf("Yes or no?", r) - } - } - - if r == "y" { - utils.CreateKeyPair(true) - } - os.Exit(0) - } else { - if len(ImportKey) > 0 { - fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") - var r string - fmt.Scanln(&r) - for ; ; fmt.Scanln(&r) { - if r == "n" || r == "y" { - break - } else { - fmt.Printf("Yes or no?", r) - } - } - - if r == "y" { - utils.ImportPrivateKey(ImportKey) - os.Exit(0) - } - } else { - utils.CreateKeyPair(false) - } - } - - if ExportKey { - key := ethutil.Config.Db.GetKeys()[0] - fmt.Printf("%x\n", key.PrivateKey) - os.Exit(0) - } - - if ShowGenesis { - fmt.Println(ethereum.BlockChain().Genesis()) - os.Exit(0) - } - - log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) - - // Set the max peers - ethereum.MaxPeers = MaxPeer - - if StartConsole { - err := os.Mkdir(ethutil.Config.ExecPath, os.ModePerm) - // Error is OK if the error is ErrExist - if err != nil && !os.IsExist(err) { - log.Panic("Unable to create EXECPATH:", err) - } - - console := NewConsole(ethereum) - go console.Start() - } - - RegisterInterupts(ethereum) - ethereum.Start() - - if StartMining { - log.Printf("Miner started\n") - - // Fake block mining. It broadcasts a new block every 5 seconds - go func() { - pow := ðchain.EasyPow{} - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(1).Bytes() - - for { - txs := ethereum.TxPool().Flush() - // Create a new block which we're going to mine - block := ethereum.BlockChain().NewBlock(addr, txs) - log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") - // Apply all transactions to the block - ethereum.StateManager().ApplyTransactions(block, block.Transactions()) - - ethereum.StateManager().Prepare(block.State(), block.State()) - ethereum.StateManager().AccumelateRewards(block) - - // Search the nonce - block.Nonce = pow.Search(block) - ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) - - ethereum.StateManager().PrepareDefault(block) - err := ethereum.StateManager().ProcessBlock(block) - if err != nil { - log.Println(err) - } else { - log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) - log.Printf("🔨 Mined block %x\n", block.Hash()) - } - } - }() - } - - // Wait for shutdown - ethereum.WaitForShutdown() -} diff --git a/node/node b/node/node deleted file mode 100755 index 48452886c..000000000 Binary files a/node/node and /dev/null differ -- cgit v1.2.3 From 642630db15a793cf0a0f7fbd827daee364df5423 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 22 Mar 2014 12:03:10 +0100 Subject: Moved node to ethereum --- ethereum/config.go | 37 +++++++ ethereum/dev_console.go | 253 ++++++++++++++++++++++++++++++++++++++++++++++++ ethereum/ethereum.go | 158 ++++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 ethereum/config.go create mode 100644 ethereum/dev_console.go create mode 100644 ethereum/ethereum.go diff --git a/ethereum/config.go b/ethereum/config.go new file mode 100644 index 000000000..e4935dfed --- /dev/null +++ b/ethereum/config.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" +) + +var StartConsole bool +var StartMining bool +var UseUPnP bool +var OutboundPort string +var ShowGenesis bool +var AddPeer string +var MaxPeer int +var GenAddr bool +var UseSeed bool +var ImportKey string +var ExportKey bool + +//var UseGui bool +var DataDir string + +func Init() { + flag.BoolVar(&StartConsole, "c", false, "debug and testing console") + flag.BoolVar(&StartMining, "m", false, "start dagger mining") + flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") + //flag.BoolVar(&UseGui, "gui", true, "use the gui") + flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") + flag.BoolVar(&UseSeed, "seed", true, "seed peers") + flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") + flag.BoolVar(&ExportKey, "export", false, "export private key") + flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.StringVar(&DataDir, "dir", ".ethereum", "ethereum data directory") + flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") + flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") + + flag.Parse() +} diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go new file mode 100644 index 000000000..ead4b55e5 --- /dev/null +++ b/ethereum/dev_console.go @@ -0,0 +1,253 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/hex" + "errors" + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethdb" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/ethwire" + _ "math/big" + "os" + "strings" +) + +type Console struct { + db *ethdb.MemDatabase + trie *ethutil.Trie + ethereum *eth.Ethereum +} + +func NewConsole(s *eth.Ethereum) *Console { + db, _ := ethdb.NewMemDatabase() + trie := ethutil.NewTrie(db, "") + + return &Console{db: db, trie: trie, ethereum: s} +} + +func (i *Console) ValidateInput(action string, argumentLength int) error { + err := false + var expArgCount int + + switch { + case action == "update" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "get" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "dag" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "decode" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "encode" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "gettx" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "tx" && argumentLength != 2: + err = true + expArgCount = 2 + case action == "getaddr" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "contract" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "say" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "addp" && argumentLength != 1: + err = true + expArgCount = 1 + case action == "block" && argumentLength != 1: + err = true + expArgCount = 1 + } + + if err { + return errors.New(fmt.Sprintf("'%s' requires %d args, got %d", action, expArgCount, argumentLength)) + } else { + return nil + } +} + +func (i *Console) Editor() []string { + var buff bytes.Buffer + for { + reader := bufio.NewReader(os.Stdin) + str, _, err := reader.ReadLine() + if len(str) > 0 { + buff.Write(str) + buff.WriteString("\n") + } + + if err != nil && err.Error() == "EOF" { + break + } + } + + scanner := bufio.NewScanner(strings.NewReader(buff.String())) + scanner.Split(bufio.ScanLines) + + var lines []string + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + return lines +} + +func (i *Console) PrintRoot() { + root := ethutil.NewValue(i.trie.Root) + if len(root.Bytes()) != 0 { + fmt.Println(hex.EncodeToString(root.Bytes())) + } else { + fmt.Println(i.trie.Root) + } +} + +func (i *Console) ParseInput(input string) bool { + scanner := bufio.NewScanner(strings.NewReader(input)) + scanner.Split(bufio.ScanWords) + + count := 0 + var tokens []string + for scanner.Scan() { + count++ + tokens = append(tokens, scanner.Text()) + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading input:", err) + } + + if len(tokens) == 0 { + return true + } + + err := i.ValidateInput(tokens[0], count-1) + if err != nil { + fmt.Println(err) + } else { + switch tokens[0] { + case "update": + i.trie.Update(tokens[1], tokens[2]) + + i.PrintRoot() + case "get": + fmt.Println(i.trie.Get(tokens[1])) + case "root": + i.PrintRoot() + case "rawroot": + fmt.Println(i.trie.Root) + case "print": + i.db.Print() + case "dag": + fmt.Println(ethchain.DaggerVerify(ethutil.Big(tokens[1]), // hash + ethutil.BigPow(2, 36), // diff + ethutil.Big(tokens[2]))) // nonce + case "decode": + value := ethutil.NewValueFromBytes([]byte(tokens[1])) + fmt.Println(value) + case "getaddr": + encoded, _ := hex.DecodeString(tokens[1]) + addr := i.ethereum.BlockChain().CurrentBlock.State().GetAccount(encoded) + fmt.Println("addr:", addr) + case "block": + encoded, _ := hex.DecodeString(tokens[1]) + block := i.ethereum.BlockChain().GetBlock(encoded) + info := block.BlockInfo() + fmt.Printf("++++++++++ #%d ++++++++++\n%v\n", info.Number, block) + case "say": + i.ethereum.Broadcast(ethwire.MsgTalkTy, []interface{}{tokens[1]}) + case "addp": + i.ethereum.ConnectToPeer(tokens[1]) + case "pcount": + fmt.Println("peers:", i.ethereum.Peers().Len()) + case "encode": + fmt.Printf("%q\n", ethutil.Encode(tokens[1])) + case "tx": + recipient, err := hex.DecodeString(tokens[1]) + if err != nil { + fmt.Println("recipient err:", err) + } else { + tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) + + key := ethutil.Config.Db.GetKeys()[0] + tx.Sign(key.PrivateKey) + i.ethereum.TxPool().QueueTransaction(tx) + + fmt.Printf("%x\n", tx.Hash()) + } + case "gettx": + addr, _ := hex.DecodeString(tokens[1]) + data, _ := ethutil.Config.Db.Get(addr) + if len(data) != 0 { + decoder := ethutil.NewValueFromBytes(data) + fmt.Println(decoder) + } else { + fmt.Println("gettx: tx not found") + } + case "contract": + fmt.Println("Contract editor (Ctrl-D = done)") + code := ethchain.Compile(i.Editor()) + + contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) + + key := ethutil.Config.Db.GetKeys()[0] + contract.Sign(key.PrivateKey) + + i.ethereum.TxPool().QueueTransaction(contract) + + fmt.Printf("%x\n", contract.Hash()[12:]) + case "exit", "quit", "q": + return false + case "help": + fmt.Printf("COMMANDS:\n" + + "\033[1m= DB =\033[0m\n" + + "update KEY VALUE - Updates/Creates a new value for the given key\n" + + "get KEY - Retrieves the given key\n" + + "root - Prints the hex encoded merkle root\n" + + "rawroot - Prints the raw merkle root\n" + + "block HASH - Prints the block\n" + + "getaddr ADDR - Prints the account associated with the address\n" + + "\033[1m= Dagger =\033[0m\n" + + "dag HASH NONCE - Verifies a nonce with the given hash with dagger\n" + + "\033[1m= Encoding =\033[0m\n" + + "decode STR\n" + + "encode STR\n" + + "\033[1m= Other =\033[0m\n" + + "addp HOST:PORT\n" + + "tx TO AMOUNT\n" + + "contract AMOUNT\n") + + default: + fmt.Println("Unknown command:", tokens[0]) + } + } + + return true +} + +func (i *Console) Start() { + fmt.Printf("Eth Console. Type (help) for help\n") + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("eth >>> ") + str, _, err := reader.ReadLine() + if err != nil { + fmt.Println("Error reading input", err) + } else { + if !i.ParseInput(string(str)) { + return + } + } + } +} diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go new file mode 100644 index 000000000..3f5e4a8f5 --- /dev/null +++ b/ethereum/ethereum.go @@ -0,0 +1,158 @@ +package main + +import ( + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/eth-go/ethwire" + "github.com/ethereum/go-ethereum/utils" + "log" + "os" + "os/signal" + "runtime" +) + +const Debug = true + +// Register interrupt handlers so we can stop the ethereum +func RegisterInterupts(s *eth.Ethereum) { + // Buffered chan of one is enough + c := make(chan os.Signal, 1) + // Notify about interrupts for now + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + fmt.Printf("Shutting down (%v) ... \n", sig) + + s.Stop() + } + }() +} + +func main() { + Init() + + runtime.GOMAXPROCS(runtime.NumCPU()) + + ethchain.InitFees() + ethutil.ReadConfig(DataDir) + ethutil.Config.Seed = UseSeed + + // Instantiated a eth stack + ethereum, err := eth.New(eth.CapDefault, UseUPnP) + if err != nil { + log.Println("eth start err:", err) + return + } + ethereum.Port = OutboundPort + + if GenAddr { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + + if r == "y" { + utils.CreateKeyPair(true) + } + os.Exit(0) + } else { + if len(ImportKey) > 0 { + fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + + if r == "y" { + utils.ImportPrivateKey(ImportKey) + os.Exit(0) + } + } else { + utils.CreateKeyPair(false) + } + } + + if ExportKey { + key := ethutil.Config.Db.GetKeys()[0] + fmt.Printf("%x\n", key.PrivateKey) + os.Exit(0) + } + + if ShowGenesis { + fmt.Println(ethereum.BlockChain().Genesis()) + os.Exit(0) + } + + log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) + + // Set the max peers + ethereum.MaxPeers = MaxPeer + + if StartConsole { + err := os.Mkdir(ethutil.Config.ExecPath, os.ModePerm) + // Error is OK if the error is ErrExist + if err != nil && !os.IsExist(err) { + log.Panic("Unable to create EXECPATH:", err) + } + + console := NewConsole(ethereum) + go console.Start() + } + + RegisterInterupts(ethereum) + ethereum.Start() + + if StartMining { + log.Printf("Miner started\n") + + // Fake block mining. It broadcasts a new block every 5 seconds + go func() { + pow := ðchain.EasyPow{} + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(1).Bytes() + + for { + txs := ethereum.TxPool().Flush() + // Create a new block which we're going to mine + block := ethereum.BlockChain().NewBlock(addr, txs) + log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") + // Apply all transactions to the block + ethereum.StateManager().ApplyTransactions(block, block.Transactions()) + + ethereum.StateManager().Prepare(block.State(), block.State()) + ethereum.StateManager().AccumelateRewards(block) + + // Search the nonce + block.Nonce = pow.Search(block) + ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) + + ethereum.StateManager().PrepareDefault(block) + err := ethereum.StateManager().ProcessBlock(block) + if err != nil { + log.Println(err) + } else { + log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) + log.Printf("🔨 Mined block %x\n", block.Hash()) + } + } + }() + } + + // Wait for shutdown + ethereum.WaitForShutdown() +} -- cgit v1.2.3 From a30f5730b384bf99d23f6e83b356e27a14f961d1 Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 24 Mar 2014 10:56:42 +0100 Subject: Reimplement new miner creation --- ethereum/ethereum.go | 44 +++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index 3f5e4a8f5..c82e7dcd8 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethminer" "github.com/ethereum/eth-go/ethutil" - "github.com/ethereum/eth-go/ethwire" "github.com/ethereum/go-ethereum/utils" "log" "os" @@ -121,36 +121,22 @@ func main() { // Fake block mining. It broadcasts a new block every 5 seconds go func() { - pow := ðchain.EasyPow{} - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(1).Bytes() - - for { - txs := ethereum.TxPool().Flush() - // Create a new block which we're going to mine - block := ethereum.BlockChain().NewBlock(addr, txs) - log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") - // Apply all transactions to the block - ethereum.StateManager().ApplyTransactions(block, block.Transactions()) - - ethereum.StateManager().Prepare(block.State(), block.State()) - ethereum.StateManager().AccumelateRewards(block) - - // Search the nonce - block.Nonce = pow.Search(block) - ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) - - ethereum.StateManager().PrepareDefault(block) - err := ethereum.StateManager().ProcessBlock(block) - if err != nil { - log.Println(err) - } else { - log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) - log.Printf("🔨 Mined block %x\n", block.Hash()) - } + + if StartMining { + log.Printf("Miner started\n") + + go func() { + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(1).Bytes() + + miner := ethminer.NewDefaultMiner(addr, ethereum) + miner.Start() + + }() } }() + } // Wait for shutdown -- cgit v1.2.3 From 49c710bf442940b3abc68123964b56b211b92c12 Mon Sep 17 00:00:00 2001 From: zelig Date: Thu, 27 Mar 2014 17:14:04 +0700 Subject: assetPath configurable on command line for ethereal GUI - solves the problem of non-standard installs - add AssetPath to config as string var - introduced UiLib constructor which falls back to defaultAssetPath (earlier behaviour) if no assetPath is set - defaultAssetPath now internal concern of UiLib - gui.Start(assetPath) argument passed from ethereal main() as set Init() in config.go - informative log message if wallet.qml fails to open --- ethereal/config.go | 2 ++ ethereal/ethereum.go | 4 ++-- ethereal/ui/gui.go | 11 +++++++---- ethereal/ui/ui_lib.go | 14 +++++++++++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/ethereal/config.go b/ethereal/config.go index a534bb182..ac4484d0b 100644 --- a/ethereal/config.go +++ b/ethereal/config.go @@ -16,6 +16,7 @@ var UseSeed bool var ImportKey string var ExportKey bool var DataDir string +var AssetPath string func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") @@ -29,6 +30,7 @@ func Init() { flag.StringVar(&DataDir, "dir", ".ethereal", "ethereum data directory") flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") + flag.StringVar(&AssetPath, "asset_path", "", "absolute path to GUI assets directory") flag.Parse() } diff --git a/ethereal/ethereum.go b/ethereal/ethereum.go index 618d2b00f..99f3b0b52 100644 --- a/ethereal/ethereum.go +++ b/ethereal/ethereum.go @@ -100,11 +100,11 @@ func main() { os.Exit(0) } - log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) + log.Printf("Starting Ethereum GUI v%s\n", ethutil.Config.Ver) // Set the max peers ethereum.MaxPeers = MaxPeer gui := ethui.New(ethereum) - gui.Start() + gui.Start(AssetPath) } diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index c8f4bedab..89736ac29 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -53,6 +53,7 @@ type Gui struct { txDb *ethdb.LDBDatabase addr []byte + } // Create GUI, but doesn't start it @@ -71,7 +72,7 @@ func New(ethereum *eth.Ethereum) *Gui { return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} } -func (ui *Gui) Start() { +func (ui *Gui) Start(assetPath string) { defer ui.txDb.Close() // Register ethereum functions @@ -89,14 +90,16 @@ func (ui *Gui) Start() { // Expose the eth library and the ui library to QML context.SetVar("eth", ui.lib) - context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) + uiLib := NewUiLib(ui.engine, ui.eth, assetPath) + context.SetVar("ui", uiLib) // Load the main QML interface - component, err := ui.engine.LoadFile(AssetPath("qml/wallet.qml")) + component, err := ui.engine.LoadFile(uiLib.AssetPath("qml/wallet.qml")) if err != nil { + ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") panic(err) } - ui.engine.LoadFile(AssetPath("qml/transactions.qml")) + ui.engine.LoadFile(uiLib.AssetPath("qml/transactions.qml")) ui.win = component.CreateWindow(nil) diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 3997191fa..4441a7238 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -16,6 +16,14 @@ type UiLib struct { engine *qml.Engine eth *eth.Ethereum connected bool + assetPath string +} + +func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { + if assetPath == "" { + assetPath = DefaultAssetPath() + } + return &UiLib{engine: engine, eth: eth, assetPath: assetPath} } // Opens a QML file (external application) @@ -45,10 +53,10 @@ func (ui *UiLib) ConnectToPeer(addr string) { } func (ui *UiLib) AssetPath(p string) string { - return AssetPath(p) + return path.Join(ui.assetPath, p) } -func AssetPath(p string) string { +func DefaultAssetPath() string { var base string // If the current working directory is the go-ethereum dir @@ -72,5 +80,5 @@ func AssetPath(p string) string { } } - return path.Join(base, p) + return base } -- cgit v1.2.3 From e65c4ee93e9dad629997c7839df7a8a0e7cff353 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 27 Mar 2014 15:22:20 +0100 Subject: Updated transaction constructor --- .gitignore | 1 + ethereum/dev_console.go | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index f725d58d1..f816a06a1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ *un~ .DS_Store */**/.DS_Store +./ethereum/ethereum diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go index ead4b55e5..5452b9a61 100644 --- a/ethereum/dev_console.go +++ b/ethereum/dev_console.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethwire" + "github.com/obscuren/mutan" _ "math/big" "os" "strings" @@ -58,9 +59,9 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "getaddr" && argumentLength != 1: err = true expArgCount = 1 - case action == "contract" && argumentLength != 1: + case action == "contract" && argumentLength != 2: err = true - expArgCount = 1 + expArgCount = 2 case action == "say" && argumentLength != 1: err = true expArgCount = 1 @@ -79,7 +80,7 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { } } -func (i *Console) Editor() []string { +func (i *Console) Editor() string { var buff bytes.Buffer for { reader := bufio.NewReader(os.Stdin) @@ -94,15 +95,7 @@ func (i *Console) Editor() []string { } } - scanner := bufio.NewScanner(strings.NewReader(buff.String())) - scanner.Split(bufio.ScanLines) - - var lines []string - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - - return lines + return buff.String() } func (i *Console) PrintRoot() { @@ -178,7 +171,7 @@ func (i *Console) ParseInput(input string) bool { if err != nil { fmt.Println("recipient err:", err) } else { - tx := ethchain.NewTransaction(recipient, ethutil.Big(tokens[2]), []string{""}) + tx := ethchain.NewTx(recipient, ethutil.Big(tokens[2]), []string{""}) key := ethutil.Config.Db.GetKeys()[0] tx.Sign(key.PrivateKey) @@ -197,9 +190,11 @@ func (i *Console) ParseInput(input string) bool { } case "contract": fmt.Println("Contract editor (Ctrl-D = done)") - code := ethchain.Compile(i.Editor()) + asm := mutan.NewCompiler().Compile(strings.NewReader(i.Editor())) + + code := ethutil.Assemble(asm) - contract := ethchain.NewTransaction(ethchain.ContractAddr, ethutil.Big(tokens[1]), code) + contract := ethchain.NewContractCreationTx(ethutil.Big(tokens[0]), ethutil.Big(tokens[1]), code) key := ethutil.Config.Db.GetKeys()[0] contract.Sign(key.PrivateKey) -- cgit v1.2.3 From c5215fd4fb9de7594fdb812f8f9e4c471ee8d003 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 27 Mar 2014 19:41:42 +0100 Subject: Added gas and gas price. * library's `createTx` method changed so it accepts a gas price * dev console accepts code as well as the library --- ethereal/assets/qml/wallet.qml | 29 +++++++++++++++++++------ ethereal/ui/library.go | 48 +++++++++++++++++++++++++++++++++++++++++- ethereum/dev_console.go | 6 +++++- 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 7fc7f5447..f6b31f0fd 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -158,20 +158,35 @@ ApplicationWindow { anchors.leftMargin: 5 anchors.topMargin: 5 TextField { - id: txAmount + id: txRecipient + placeholderText: "Recipient address (or empty for contract)" + Layout.fillWidth: true + } + + TextField { + id: txValue width: 200 placeholderText: "Amount" } - TextField { - id: txReceiver - placeholderText: "Receiver Address (or empty for contract)" - Layout.fillWidth: true + id: txGas + width: 200 + placeholderText: "Gas" + anchors.left: txValue + anchors.leftMargin: 5 + } + TextField { + id: txGasPrice + width: 200 + placeholderText: "Gas price" + anchors.left: txGas + anchors.leftMargin: 5 } Label { text: "Transaction data" } + TextArea { id: codeView anchors.topMargin: 5 @@ -180,9 +195,11 @@ ApplicationWindow { } Button { + id: txButton text: "Send" onClicked: { - console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) + this.enabled = false + console.log(eth.createTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text)) } } } diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 05fffd579..bd67f3c20 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/obscuren/mutan" "strings" ) @@ -14,6 +15,50 @@ type EthLib struct { txPool *ethchain.TxPool } +func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) string { + var hash []byte + var contractCreation bool + if len(recipient) == 0 { + contractCreation = true + } else { + var err error + hash, err = hex.DecodeString(recipient) + if err != nil { + return err.Error() + } + } + + keyPair := ethutil.Config.Db.GetKeys()[0] + value := ethutil.Big(valueStr) + gas := ethutil.Big(valueStr) + gasPrice := ethutil.Big(gasPriceStr) + var tx *ethchain.Transaction + // Compile and assemble the given data + if contractCreation { + asm, err := mutan.NewCompiler().Compile(strings.NewReader(data)) + if err != nil { + return err.Error() + } + + code := ethutil.Assemble(asm) + tx = ethchain.NewContractCreationTx(value, gasPrice, code) + } else { + tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, []string{}) + } + tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce + tx.Sign(keyPair.PrivateKey) + lib.txPool.QueueTransaction(tx) + + if contractCreation { + ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } else { + ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) + } + + return ethutil.Hex(tx.Hash()) +} + +/* func (lib *EthLib) CreateTx(receiver, a, data string) string { var hash []byte if len(receiver) == 0 { @@ -31,7 +76,7 @@ func (lib *EthLib) CreateTx(receiver, a, data string) string { amount := ethutil.Big(a) code := ethchain.Compile(strings.Split(data, "\n")) - tx := ethchain.NewTransaction(hash, amount, code) + tx := ethchain.NewTx(hash, amount, code) tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce tx.Sign(keyPair.PrivateKey) @@ -46,6 +91,7 @@ func (lib *EthLib) CreateTx(receiver, a, data string) string { return ethutil.Hex(tx.Hash()) } +*/ func (lib *EthLib) GetBlock(hexHash string) *Block { hash, err := hex.DecodeString(hexHash) diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go index 5452b9a61..5fdf90509 100644 --- a/ethereum/dev_console.go +++ b/ethereum/dev_console.go @@ -190,7 +190,11 @@ func (i *Console) ParseInput(input string) bool { } case "contract": fmt.Println("Contract editor (Ctrl-D = done)") - asm := mutan.NewCompiler().Compile(strings.NewReader(i.Editor())) + asm, err := mutan.NewCompiler().Compile(strings.NewReader(i.Editor())) + if err != nil { + fmt.Println(err) + break + } code := ethutil.Assemble(asm) -- cgit v1.2.3 From 3fb7ae2fa19fede545466694fbf9d86a85310bd9 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 27 Mar 2014 19:45:55 +0100 Subject: Removed CreateTx --- ethereum/dev_console.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go index 5fdf90509..ff25d694c 100644 --- a/ethereum/dev_console.go +++ b/ethereum/dev_console.go @@ -53,9 +53,9 @@ func (i *Console) ValidateInput(action string, argumentLength int) error { case action == "gettx" && argumentLength != 1: err = true expArgCount = 1 - case action == "tx" && argumentLength != 2: + case action == "tx" && argumentLength != 4: err = true - expArgCount = 2 + expArgCount = 4 case action == "getaddr" && argumentLength != 1: err = true expArgCount = 1 @@ -171,7 +171,7 @@ func (i *Console) ParseInput(input string) bool { if err != nil { fmt.Println("recipient err:", err) } else { - tx := ethchain.NewTx(recipient, ethutil.Big(tokens[2]), []string{""}) + tx := ethchain.NewTransactionMessage(recipient, ethutil.Big(tokens[2]), ethutil.Big(tokens[3]), ethutil.Big(tokens[4]), []string{""}) key := ethutil.Config.Db.GetKeys()[0] tx.Sign(key.PrivateKey) -- cgit v1.2.3 From ebbc5e7cb8b3886c8557f9f8623113f52f7f5e4a Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 30 Mar 2014 22:03:29 +0200 Subject: Updated to new mutan api --- README.md | 12 ++++++------ ethereal/ui/library.go | 2 +- ethereum/dev_console.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a29bfb6d3..04c0cf78a 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,17 @@ Build ======= For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go)) -Command line options +General command line options ==================== ``` --c Launch the developer console +-c Launch the developer console (node only) -m Start mining blocks -genaddr Generates a new address and private key (destructive action) -p Port on which the server will accept incomming connections (= 30303) -upnp Enable UPnP (= false) -x Desired amount of peers (= 5) -h This help --gui Launch with GUI (= true) -dir Data directory used to store configs and databases (=".ethereum") -import Import a private key (hex) ``` @@ -33,8 +32,9 @@ Developer console commands ========================== ``` -addp : Connect to the given host -tx Send Wei to the specified +addp : Connect to the given host +tx Send Wei to the specified +contract Creates a new contract and launches the editor ``` See the "help" command for *developer* options. @@ -57,7 +57,7 @@ Coding standards Sources should be formatted according to the [Go Formatting Style](http://golang.org/doc/effective_go.html#formatting). -Unless structs fields are supposed to be directly accesible, provide +Unless structs fields are supposed to be directly accessible, provide Getters and hide the fields through Go's exporting facility. When you comment put meaningfull comments. Describe in detail what you diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index bd67f3c20..692ec454c 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -35,7 +35,7 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin var tx *ethchain.Transaction // Compile and assemble the given data if contractCreation { - asm, err := mutan.NewCompiler().Compile(strings.NewReader(data)) + asm, err := mutan.Compile(strings.NewReader(data), false) if err != nil { return err.Error() } diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go index ff25d694c..421c3fa60 100644 --- a/ethereum/dev_console.go +++ b/ethereum/dev_console.go @@ -190,7 +190,7 @@ func (i *Console) ParseInput(input string) bool { } case "contract": fmt.Println("Contract editor (Ctrl-D = done)") - asm, err := mutan.NewCompiler().Compile(strings.NewReader(i.Editor())) + asm, err := mutan.Compile(strings.NewReader(i.Editor()), false) if err != nil { fmt.Println(err) break -- cgit v1.2.3 From 97b98b1250890977ea622af378fe864e4620e313 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 31 Mar 2014 00:22:50 +0200 Subject: Fixed an issue with sending gas to a contract --- ethereal/assets/qml/wallet.qml | 2 +- ethereal/ui/library.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index f6b31f0fd..9093e3ca8 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -198,7 +198,7 @@ ApplicationWindow { id: txButton text: "Send" onClicked: { - this.enabled = false + //this.enabled = false console.log(eth.createTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text)) } } diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 692ec454c..6f8cb6f65 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -30,7 +30,7 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin keyPair := ethutil.Config.Db.GetKeys()[0] value := ethutil.Big(valueStr) - gas := ethutil.Big(valueStr) + gas := ethutil.Big(gasStr) gasPrice := ethutil.Big(gasPriceStr) var tx *ethchain.Transaction // Compile and assemble the given data @@ -40,7 +40,7 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin return err.Error() } - code := ethutil.Assemble(asm) + code := ethutil.Assemble(asm...) tx = ethchain.NewContractCreationTx(value, gasPrice, code) } else { tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, []string{}) -- cgit v1.2.3 From e403b28eea6959c1d0ed003d955df3dee586083b Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 31 Mar 2014 01:02:00 +0200 Subject: Fixed miner --- ethereum/ethereum.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index 3f5e4a8f5..666c75117 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -131,15 +131,14 @@ func main() { // Create a new block which we're going to mine block := ethereum.BlockChain().NewBlock(addr, txs) log.Println("Mining on new block. Includes", len(block.Transactions()), "transactions") - // Apply all transactions to the block - ethereum.StateManager().ApplyTransactions(block, block.Transactions()) ethereum.StateManager().Prepare(block.State(), block.State()) + // Apply all transactions to the block + ethereum.StateManager().ApplyTransactions(block, block.Transactions()) ethereum.StateManager().AccumelateRewards(block) // Search the nonce block.Nonce = pow.Search(block) - ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) ethereum.StateManager().PrepareDefault(block) err := ethereum.StateManager().ProcessBlock(block) @@ -148,6 +147,7 @@ func main() { } else { log.Println("\n+++++++ MINED BLK +++++++\n", ethereum.BlockChain().CurrentBlock) log.Printf("🔨 Mined block %x\n", block.Hash()) + ethereum.Broadcast(ethwire.MsgBlockTy, []interface{}{block.Value().Val}) } } }() -- cgit v1.2.3 From 5660d598df3d86f892975fcf9628133529598221 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 1 Apr 2014 10:40:34 +0200 Subject: Added tx output --- ethereal/assets/qml/wallet.qml | 17 +++++++++++++++-- ethereal/ui/library.go | 8 ++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 9093e3ca8..b22e82f9a 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -199,9 +199,22 @@ ApplicationWindow { text: "Send" onClicked: { //this.enabled = false - console.log(eth.createTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text)) + var res = eth.createTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) + if(res[1]) { + txOutput.text = "Output:\n" + res[1].error() + } else { + txOutput.text = "Output:\n" + res[0] + } + txOutput.visible = true } } + TextArea { + id: txOutput + visible: false + Layout.fillWidth: true + height: 40 + anchors.bottom: parent.bottom + } } } @@ -391,7 +404,7 @@ ApplicationWindow { anchors.left: aboutIcon.right anchors.leftMargin: 10 font.pointSize: 12 - text: "

Ethereum(Go)


Development

Jeffrey Wilcke

Binary Distribution

Jarrad Hope
" + text: "

Ethereal


Development

Jeffrey Wilcke
Maran Hidskes

Binary Distribution

Jarrad Hope
" } } diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 6f8cb6f65..7a8939bf1 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -15,7 +15,7 @@ type EthLib struct { txPool *ethchain.TxPool } -func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) string { +func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { var hash []byte var contractCreation bool if len(recipient) == 0 { @@ -24,7 +24,7 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin var err error hash, err = hex.DecodeString(recipient) if err != nil { - return err.Error() + return "", err } } @@ -37,7 +37,7 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin if contractCreation { asm, err := mutan.Compile(strings.NewReader(data), false) if err != nil { - return err.Error() + return "", err } code := ethutil.Assemble(asm...) @@ -55,7 +55,7 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) } - return ethutil.Hex(tx.Hash()) + return ethutil.Hex(tx.Hash()), nil } /* -- cgit v1.2.3 From 2edf133b4671287e58c1f8cdb22f0cd342f309f2 Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 7 Apr 2014 13:59:42 +0200 Subject: Added mnemonic priv key --- utils/keys.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/keys.go b/utils/keys.go index 910c8c477..2f39c10b2 100644 --- a/utils/keys.go +++ b/utils/keys.go @@ -12,6 +12,7 @@ func CreateKeyPair(force bool) { pub, prv := secp256k1.GenerateKeyPair() pair := ðutil.Key{PrivateKey: prv, PublicKey: pub} ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) + mne := ethutil.MnemonicEncode(ethutil.Hex(prv)) fmt.Printf(` Generating new address and keypair. @@ -22,8 +23,8 @@ addr: %x prvk: %x pubk: %x ++++++++++++++++++++++++++++++++++++++++++++ - -`, pair.Address(), prv, pub) +save these words so you can restore your account later: %s +`, pair.Address(), prv, pub, mne) } } -- cgit v1.2.3 From cd799926d2d547a9121856d5c68b1f93956ab29b Mon Sep 17 00:00:00 2001 From: Alex Cabrera Date: Mon, 7 Apr 2014 15:58:36 -0400 Subject: Fixed up grammar and spelling in README --- README.md | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 04c0cf78a..7c473c780 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,17 @@ Ethereum [![Build Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum) -Ethereum Go Client (c) Jeffrey Wilcke +Ethereum Go Client © 2014 Jeffrey Wilcke. -The current state is "Proof of Concept 3.5". +Current state: Proof of Concept 3.5. -For the development Go Package please see [eth-go package](https://github.com/ethereum/eth-go). +For the development package please see the [eth-go package](https://github.com/ethereum/eth-go). Build ======= For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go)) + General command line options ==================== @@ -42,26 +43,24 @@ See the "help" command for *developer* options. Contribution ============ -If you'd like to contribute to Ethereum Go please fork, fix, commit and -send a pull request. Commits who do not comply with the coding standards -are ignored. If you send pull requests make absolute sure that you -commit on the `develop` branch and that you do not merge to master. -Commits that are directly based on master are simply ignored. +If you would like to contribute to Ethereum Go, please fork, fix, commit and +send a pull request to the main repository. Commits which do not comply with the coding standards explained below +will be ignored. If you send a pull request, make sure that you +commit to the `develop` branch and that you do not merge to `master`. +Commits that are directly based off of the `master` branch instead of the `develop` branch will be ignored. -To make life easier try [git flow](http://nvie.com/posts/a-successful-git-branching-model/) it sets -this all up and streamlines your work flow. +To make this process simpler try following the [git flow](http://nvie.com/posts/a-successful-git-branching-model/) branching model, as it sets this process up and streamlines work flow. Coding standards ================ -Sources should be formatted according to the [Go Formatting +Code should be formatted according to the [Go Formatting Style](http://golang.org/doc/effective_go.html#formatting). -Unless structs fields are supposed to be directly accessible, provide -Getters and hide the fields through Go's exporting facility. +Unless struct fields are supposed to be directly accessible, provide +getters and hide the fields through Go's exporting facility. -When you comment put meaningfull comments. Describe in detail what you -want to achieve. +Make comments in your code meaningful and only use them when necessary. Describe in detail what your code is trying to achieve. For example, this would be redundant and unnecessary commenting: *wrong* @@ -72,12 +71,7 @@ if x > y { } ``` -Everyone reading the source probably know what you wanted to achieve -with above code. Those are **not** meaningful comments. +Everyone reading the source code should know what this code snippet was meant to achieve, and so those are **not** meaningful comments. -While the project isn't 100% tested I want you to write tests non the -less. I haven't got time to evaluate everyone's code in detail so I -expect you to write tests for me so I don't have to test your code -manually. (If you want to contribute by just writing tests that's fine -too!) +While this project is constantly tested and run, code tests should be written regardless. There is not time to evaluate every person's code specifically, so it is expected of you to write tests for the code so that it does not have to be tested manually. In fact, contributing by simply writing tests is perfectly fine! -- cgit v1.2.3 From 1e94cb5286067da80c3227861a836c611f01e32b Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 9 Apr 2014 16:01:11 +0200 Subject: Nonce handling --- ethereal/ui/library.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 7a8939bf1..9a7469426 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -45,7 +45,9 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin } else { tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, []string{}) } - tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce + acc := lib.stateManager.GetAddrState(keyPair.Address()) + tx.Nonce = acc.Nonce + //acc.Nonce++ tx.Sign(keyPair.PrivateKey) lib.txPool.QueueTransaction(tx) -- cgit v1.2.3 From cc5501b12f1b4f2297344d85f4d7f8c95b36fc34 Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 9 Apr 2014 10:29:52 -0400 Subject: Importing mnemonic support --- ethereum/ethereum.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index c82e7dcd8..e1e803771 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -11,6 +11,7 @@ import ( "os" "os/signal" "runtime" + "strings" ) const Debug = true @@ -78,7 +79,17 @@ func main() { } if r == "y" { - utils.ImportPrivateKey(ImportKey) + mnemonic := strings.Split(ImportKey, " ") + if len(mnemonic) == 24 { + fmt.Println("Got mnemonic key, importing.") + key := ethutil.MnemonicDecode(mnemonic) + utils.ImportPrivateKey(key) + } else if len(mnemonic) == 1 { + fmt.Println("Got hex key, importing.") + utils.ImportPrivateKey(ImportKey) + } else { + fmt.Println("Did not recognise format, exiting.") + } os.Exit(0) } } else { -- cgit v1.2.3 From e2bf5d1270b1dc33f308ab134e7e7d3f4f64b7d4 Mon Sep 17 00:00:00 2001 From: Maran Date: Thu, 10 Apr 2014 14:53:12 -0400 Subject: Implemented key importing/generation for the GUI --- ethereal/assets/qml/first_run.qml | 155 ++++++++++++++++++++++++++++++++++++++ ethereal/assets/qml/wallet.qml | 29 +++++++ ethereal/ethereum.go | 2 - ethereal/ui/gui.go | 34 ++++++--- ethereal/ui/library.go | 28 +++++++ 5 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 ethereal/assets/qml/first_run.qml diff --git a/ethereal/assets/qml/first_run.qml b/ethereal/assets/qml/first_run.qml new file mode 100644 index 000000000..0bd3b4ce1 --- /dev/null +++ b/ethereal/assets/qml/first_run.qml @@ -0,0 +1,155 @@ +import QtQuick 2.0 +import Ethereum 1.0 + +// Which ones do we actually need? +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.1 + +ApplicationWindow { + id: wizardRoot + width: 500 + height: 400 + title: "Ethereal first run setup" + + Column { + spacing: 5 + anchors.leftMargin: 10 + anchors.left: parent.left + + Text { + visible: true + text: "

Ethereal setup

" + } + + Column { + id: restoreColumn + spacing: 5 + Text { + visible: true + font.pointSize: 14 + text: "Restore your Ethereum account" + id: restoreLabel + } + + TextField { + id: txPrivKey + width: 480 + placeholderText: "Private key or mnemonic words" + focus: true + onTextChanged: { + if(this.text.length == 64){ + detailLabel.text = "Private (hex) key detected." + actionButton.enabled = true + } + else if(this.text.split(" ").length == 24){ + detailLabel.text = "Mnemonic key detected." + actionButton.enabled = true + }else{ + detailLabel.text = "" + actionButton.enabled = false + } + } + } + Row { + spacing: 10 + Button { + id: actionButton + text: "Restore" + enabled: false + onClicked: { + var success = eth.importAndSetPrivKey(txPrivKey.text) + if(success){ + importedDetails.visible = true + restoreColumn.visible = false + newKey.visible = false + wizardRoot.height = 120 + } + } + } + Text { + id: detailLabel + font.pointSize: 12 + anchors.topMargin: 10 + } + } + } + Column { + id: importedDetails + visible: false + Text { + text: "Your account has been imported. Please close the application and restart it again to let the changes take effect." + wrapMode: Text.WordWrap + width: 460 + } + } + Column { + spacing: 5 + id: newDetailsColumn + visible: false + Text { + font.pointSize: 14 + text: "Your account details" + } + Label { + text: "Address" + } + TextField { + id: addressInput + readOnly:true + width: 480 + } + Label { + text: "Private key" + } + TextField { + id: privkeyInput + readOnly:true + width: 480 + } + Label { + text: "Mnemonic words" + } + TextField { + id: mnemonicInput + readOnly:true + width: 480 + } + Label { + text: "A new account has been created. Please take the time to write down the 24 words. You can use those to restore your account at a later date." + wrapMode: Text.WordWrap + width: 480 + } + Label { + text: "Please restart the application once you have completed the steps above." + wrapMode: Text.WordWrap + width: 480 + } + } + + } + Button { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: 10 + anchors.bottomMargin: 10 + id: newKey + text: "I don't have an account yet" + onClicked: { + var res = eth.createAndSetPrivKey() + mnemonicInput.text = res[0] + addressInput.text = res[1] + privkeyInput.text = res[2] + + // Hide restore + restoreColumn.visible = false + + // Show new details + newDetailsColumn.visible = true + newKey.visible = false + } + } +} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index b22e82f9a..58ba3e3dc 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -25,6 +25,35 @@ ApplicationWindow { } } + Menu { + title: "Test" + MenuItem { + text: "Test test" + shortcut: "Ctrl+t" + onTriggered: { + var win + function finishedLoading(){ + console.log("Trigged") + win = wizard.createObject(root) + } + console.log("Loading wizard") + + var wizard = Qt.createComponent("first_run.qml") + if(wizard.status== Component.Ready){ + console.log("Component is ready") + finishedLoading() + }else if( wizard.status == Component.Error){ + console.log("Error loading component:", wizard.errorString()) + } + else{ + wizard.statusChanged.connect(finishedLoading) + console.log("Component is NOT ready") + win = wizard.createObject(root) + } + } + } + } + Menu { title: "Network" MenuItem { diff --git a/ethereal/ethereum.go b/ethereal/ethereum.go index 99f3b0b52..9cc52039d 100644 --- a/ethereal/ethereum.go +++ b/ethereal/ethereum.go @@ -84,8 +84,6 @@ func main() { utils.ImportPrivateKey(ImportKey) os.Exit(0) } - } else { - utils.CreateKeyPair(false) } } diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 89736ac29..6184baee6 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -53,7 +53,6 @@ type Gui struct { txDb *ethdb.LDBDatabase addr []byte - } // Create GUI, but doesn't start it @@ -64,10 +63,16 @@ func New(ethereum *eth.Ethereum) *Gui { panic(err) } - key := ethutil.Config.Db.GetKeys()[0] - addr := key.Address() + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + // On first run we won't have any keys yet, so this would crash. + // Therefor we check if we are ready to actually start this process + var addr []byte + if len(data) > 0 { + key := ethutil.Config.Db.GetKeys()[0] + addr = key.Address() - ethereum.StateManager().WatchAddr(addr) + ethereum.StateManager().WatchAddr(addr) + } return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} } @@ -94,9 +99,18 @@ func (ui *Gui) Start(assetPath string) { context.SetVar("ui", uiLib) // Load the main QML interface - component, err := ui.engine.LoadFile(uiLib.AssetPath("qml/wallet.qml")) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + var err error + var component qml.Object + firstRun := len(data) == 0 + + if firstRun { + component, err = ui.engine.LoadFile(uiLib.AssetPath("qml/first_run.qml")) + } else { + component, err = ui.engine.LoadFile(uiLib.AssetPath("qml/wallet.qml")) + } if err != nil { - ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") + ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") panic(err) } ui.engine.LoadFile(uiLib.AssetPath("qml/transactions.qml")) @@ -111,9 +125,11 @@ func (ui *Gui) Start(assetPath string) { ethutil.Config.Log.AddLogSystem(ui) // Loads previous blocks - go ui.setInitialBlockChain() - go ui.readPreviousTransactions() - go ui.update() + if firstRun == false { + go ui.setInitialBlockChain() + go ui.readPreviousTransactions() + go ui.update() + } ui.win.Show() ui.win.Wait() diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 9a7469426..b8ecf3b14 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -5,7 +5,9 @@ import ( "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" "github.com/obscuren/mutan" + "github.com/obscuren/secp256k1-go" "strings" ) @@ -15,6 +17,32 @@ type EthLib struct { txPool *ethchain.TxPool } +func (lib *EthLib) ImportAndSetPrivKey(privKey string) bool { + fmt.Println(privKey) + mnemonic := strings.Split(privKey, " ") + if len(mnemonic) == 24 { + fmt.Println("Got mnemonic key, importing.") + key := ethutil.MnemonicDecode(mnemonic) + utils.ImportPrivateKey(key) + } else if len(mnemonic) == 1 { + fmt.Println("Got hex key, importing.") + utils.ImportPrivateKey(privKey) + } else { + fmt.Println("Did not recognise format, exiting.") + return false + } + return true +} + +func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { + pub, prv := secp256k1.GenerateKeyPair() + pair := ðutil.Key{PrivateKey: prv, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) + mne := ethutil.MnemonicEncode(ethutil.Hex(prv)) + mnemonicString := strings.Join(mne, " ") + return mnemonicString, fmt.Sprintf("%x", pair.Address()), fmt.Sprintf("%x", prv), fmt.Sprintf("%x", pub) +} + func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { var hash []byte var contractCreation bool -- cgit v1.2.3 From 3238894a3ba4767773849eabb7c0892554d32874 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 11 Apr 2014 12:50:31 -0400 Subject: Added wip debugger --- ethereal/assets/qml/wallet.qml | 111 +++++++++++++++++++++++++++++++++++++---- ethereal/ui/gui.go | 4 +- ethereal/ui/library.go | 37 +------------- ethereal/ui/ui_lib.go | 63 +++++++++++++++++++++++ 4 files changed, 166 insertions(+), 49 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index b22e82f9a..e759757b2 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -194,20 +194,32 @@ ApplicationWindow { width: parent.width /2 } - Button { - id: txButton - text: "Send" - onClicked: { - //this.enabled = false - var res = eth.createTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) - if(res[1]) { - txOutput.text = "Output:\n" + res[1].error() - } else { - txOutput.text = "Output:\n" + res[0] + RowLayout { + Button { + id: txButton + text: "Send" + onClicked: { + //this.enabled = false + var res = eth.createTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) + if(res[1]) { + txOutput.text = "Output:\n" + res[1].error() + } else { + txOutput.text = "Output:\n" + res[0] + } + txOutput.visible = true + } + } + + Button { + id: debugButton + text: "Debug" + onClicked: { + var res = ui.debugTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) + debugWindow.visible = true } - txOutput.visible = true } } + TextArea { id: txOutput visible: false @@ -409,6 +421,83 @@ ApplicationWindow { } + Window { + id: debugWindow + visible: false + title: "Debugger" + minimumWidth: 600 + minimumHeight: 600 + width: 800 + height: 600 + + SplitView { + anchors.fill: parent + property var asmModel: ListModel { + id: asmModel + } + TableView { + id: asmTableView + width: 200 + TableViewColumn{ role: "value" ; title: "" ; width: 100 } + model: asmModel + } + + Rectangle { + anchors.left: asmTableView.right + anchors.right: parent.right + SplitView { + orientation: Qt.Vertical + anchors.fill: parent + + TableView { + property var memModel: ListModel { + id: memModel + } + height: parent.height/2 + width: parent.width + TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50} + TableViewColumn{ role: "value" ; title: "Memory" ; width: 750} + model: memModel + } + + TableView { + property var stackModel: ListModel { + id: stackModel + } + height: parent.height/2 + width: parent.width + TableViewColumn{ role: "value" ; title: "Stack" ; width: parent.width } + model: stackModel + } + } + } + } + } + + function setAsm(asm) { + //for(var i = 0; i < asm.length; i++) { + asmModel.append({asm: asm}) + //} + } + function clearAsm() { + asmModel.clear() + } + + function setMem(mem) { + memModel.append({num: mem.num, value: mem.value}) + } + function clearMem(){ + memModel.clear() + } + + function setStack(stack) { + stackModel.append({value: stack}) + } + + function clearStack() { + stackModel.clear() + } + function loadPlugin(name) { console.log("Loading plugin" + name) mainView.addPlugin(name) diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 89736ac29..32e7edbdc 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -53,7 +53,6 @@ type Gui struct { txDb *ethdb.LDBDatabase addr []byte - } // Create GUI, but doesn't start it @@ -96,12 +95,13 @@ func (ui *Gui) Start(assetPath string) { // Load the main QML interface component, err := ui.engine.LoadFile(uiLib.AssetPath("qml/wallet.qml")) if err != nil { - ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") + ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") panic(err) } ui.engine.LoadFile(uiLib.AssetPath("qml/transactions.qml")) ui.win = component.CreateWindow(nil) + uiLib.win = ui.win // Register the ui as a block processor //ui.eth.BlockManager.SecondaryBlockProcessor = ui diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 9a7469426..13400a2a0 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -43,7 +43,7 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin code := ethutil.Assemble(asm...) tx = ethchain.NewContractCreationTx(value, gasPrice, code) } else { - tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, []string{}) + tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, nil) } acc := lib.stateManager.GetAddrState(keyPair.Address()) tx.Nonce = acc.Nonce @@ -60,41 +60,6 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin return ethutil.Hex(tx.Hash()), nil } -/* -func (lib *EthLib) CreateTx(receiver, a, data string) string { - var hash []byte - if len(receiver) == 0 { - hash = ethchain.ContractAddr - } else { - var err error - hash, err = hex.DecodeString(receiver) - if err != nil { - return err.Error() - } - } - - k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyPair := ethutil.NewKeyFromBytes(k) - - amount := ethutil.Big(a) - code := ethchain.Compile(strings.Split(data, "\n")) - tx := ethchain.NewTx(hash, amount, code) - tx.Nonce = lib.stateManager.GetAddrState(keyPair.Address()).Nonce - - tx.Sign(keyPair.PrivateKey) - - lib.txPool.QueueTransaction(tx) - - if len(receiver) == 0 { - ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) - } else { - ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) - } - - return ethutil.Hex(tx.Hash()) -} -*/ - func (lib *EthLib) GetBlock(hexHash string) *Block { hash, err := hex.DecodeString(hexHash) if err != nil { diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 5c3c98fb9..adba177d0 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -2,13 +2,18 @@ package ethui import ( "bitbucket.org/kardianos/osext" + "fmt" "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" "github.com/niemeyer/qml" + "github.com/obscuren/mutan" + "math/big" "os" "path" "path/filepath" "runtime" + "strings" ) // UI Library that has some basic functionality exposed @@ -17,6 +22,8 @@ type UiLib struct { eth *eth.Ethereum connected bool assetPath string + // The main application window + win *qml.Window } func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { @@ -81,3 +88,59 @@ func DefaultAssetPath() string { return base } + +type memAddr struct { + Num string + Value string +} + +func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { + state := ui.eth.BlockChain().CurrentBlock.State() + + asm, err := mutan.Compile(strings.NewReader(data), false) + if err != nil { + fmt.Println(err) + } + + callerScript := ethutil.Assemble(asm...) + dis := ethchain.Disassemble(callerScript) + ui.win.Root().Call("clearAsm") + for _, str := range dis { + ui.win.Root().Call("setAsm", str) + } + callerTx := ethchain.NewContractCreationTx(ethutil.Big(valueStr), ethutil.Big(gasPriceStr), callerScript) + + // Contract addr as test address + keyPair := ethutil.Config.Db.GetKeys()[0] + account := ui.eth.StateManager().GetAddrState(keyPair.Address()).Account + c := ethchain.MakeContract(callerTx, state) + callerClosure := ethchain.NewClosure(account, c, c.Script(), state, ethutil.Big(gasStr), new(big.Int)) + + block := ui.eth.BlockChain().CurrentBlock + vm := ethchain.NewVm(state, ethchain.RuntimeVars{ + Origin: account.Address(), + BlockNumber: block.BlockInfo().Number, + PrevHash: block.PrevHash, + Coinbase: block.Coinbase, + Time: block.Time, + Diff: block.Difficulty, + TxData: nil, + }) + callerClosure.Call(vm, nil, func(op ethchain.OpCode, mem *ethchain.Memory, stack *ethchain.Stack) { + ui.win.Root().Call("clearMem") + ui.win.Root().Call("clearStack") + + addr := 0 + for i := 0; i+32 <= mem.Len(); i += 32 { + ui.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("% x", mem.Data()[i:i+32])}) + addr++ + } + + for _, val := range stack.Data() { + ui.win.Root().Call("setStack", val.String()) + } + }) + state.Reset() + + return "", nil +} -- cgit v1.2.3 From cf1ae41bc0bedeb5208dc00696c538c13f2183c6 Mon Sep 17 00:00:00 2001 From: Maran Date: Fri, 11 Apr 2014 13:26:14 -0400 Subject: Improved (hopefully) the send transaction tab --- ethereal/assets/qml/wallet.qml | 269 +++++++++++++++++++++++++++++++++-------- ethereal/ui/library.go | 14 ++- 2 files changed, 226 insertions(+), 57 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 58ba3e3dc..742a1283a 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -179,70 +179,233 @@ ApplicationWindow { visible: false anchors.fill: parent color: "#00000000" - - ColumnLayout { - width: 400 - anchors.left: parent.left - anchors.top: parent.top + TabView{ + anchors.fill: parent + anchors.rightMargin: 5 anchors.leftMargin: 5 anchors.topMargin: 5 - TextField { - id: txRecipient - placeholderText: "Recipient address (or empty for contract)" - Layout.fillWidth: true - } - - TextField { - id: txValue - width: 200 - placeholderText: "Amount" + anchors.bottomMargin: 5 + id: newTransactionTab + Component.onCompleted:{ + addTab("Send ether", newTransaction) + addTab("Create contract", newContract) } - TextField { - id: txGas - width: 200 - placeholderText: "Gas" - anchors.left: txValue + } + Component { + id: newTransaction + Column { + spacing: 5 anchors.leftMargin: 5 + anchors.topMargin: 5 + anchors.top: parent.top + anchors.left: parent.left + TextField { + id: txSimpleRecipient + placeholderText: "Recipient address" + Layout.fillWidth: true + validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } + width: 530 + } + TextField { + id: txSimpleValue + placeholderText: "Amount" + anchors.rightMargin: 5 + validator: IntValidator { } + } + Button { + id: txSimpleButton + text: "Send" + onClicked: { + //this.enabled = false + var res = eth.createTx(txSimpleRecipient.text, txSimpleValue.text,"","","") + if(res[1]) { + txSimpleResult.text = "There has been an error broadcasting your transaction:" + res[1].error() + txSimpleResult.visible = true + } else { + txSimpleResult.text = "Your transaction has been broadcasted over the network.\nYour transaction id is:" + txSimpleOutput.text = res[0] + txSimpleOutput.visible = true + txSimpleResult.visible = true + txSimpleValue.visible = false + txSimpleRecipient.visible = false + txSimpleValue.text = "" + txSimpleRecipient.text = "" + txSimpleRecipient.focus = true + newSimpleTxButton.visible = true + this.visible = false + } + } + } + Text { + id: txSimpleResult + visible: false + + } + TextField { + id: txSimpleOutput + visible: false + width: 530 + } + Button { + id: newSimpleTxButton + visible: false + text: "Create an other transaction" + onClicked: { + this.visible = false + txSimpleResult.text = "" + txSimpleOutput.text = "" + txSimpleResult.visible = false + txSimpleOutput.visible = false + txSimpleValue.visible = true + txSimpleRecipient.visible = true + txSimpleButton.visible = true + } + } } - TextField { - id: txGasPrice - width: 200 - placeholderText: "Gas price" - anchors.left: txGas + } + Component { + id: newContract + Column { + id: mainContractColumn + function contractFormReady(){ + if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { + txButton.state = "READY" + }else{ + txButton.state = "NOTREADY" + } + } + states: [ + State{ + name: "ERROR" + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: codeView; visible:true} + }, + State { + name: "DONE" + PropertyChanges { target: txValue; visible:false} + PropertyChanges { target: txGas; visible:false} + PropertyChanges { target: txGasPrice; visible:false} + PropertyChanges { target: codeView; visible:false} + PropertyChanges { target: txButton; visible:false} + PropertyChanges { target: txDataLabel; visible:false} + + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: txOutput; visible:true} + PropertyChanges { target: newTxButton; visible:true} + }, + State { + name: "SETUP" + PropertyChanges { target: txValue; visible:true; text: ""} + PropertyChanges { target: txGas; visible:true; text: ""} + PropertyChanges { target: txGasPrice; visible:true; text: ""} + PropertyChanges { target: codeView; visible:true; text: ""} + PropertyChanges { target: txButton; visible:true} + PropertyChanges { target: txDataLabel; visible:true} + + PropertyChanges { target: txResult; visible:false} + PropertyChanges { target: txOutput; visible:false} + PropertyChanges { target: newTxButton; visible:false} + } + ] + width: 400 + spacing: 5 + anchors.left: parent.left + anchors.top: parent.top anchors.leftMargin: 5 - } + anchors.topMargin: 5 - Label { - text: "Transaction data" - } + TextField { + id: txValue + width: 200 + placeholderText: "Amount" + validator: IntValidator { } + onTextChanged: { + contractFormReady() + } + } + TextField { + id: txGas + width: 200 + validator: IntValidator { } + placeholderText: "Gas" + onTextChanged: { + contractFormReady() + } + } + TextField { + id: txGasPrice + width: 200 + placeholderText: "Gas price" + validator: IntValidator { } + onTextChanged: { + contractFormReady() + } + } - TextArea { - id: codeView - anchors.topMargin: 5 - Layout.fillWidth: true - width: parent.width /2 - } + Label { + id: txDataLabel + text: "Transaction data" + } - Button { - id: txButton - text: "Send" - onClicked: { - //this.enabled = false - var res = eth.createTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) - if(res[1]) { - txOutput.text = "Output:\n" + res[1].error() - } else { - txOutput.text = "Output:\n" + res[0] + TextArea { + id: codeView + anchors.topMargin: 5 + Layout.fillWidth: true + width: parent.width /2 + onTextChanged: { + contractFormReady() + } + } + + Button { + id: txButton + states: [ + State { + name: "READY" + PropertyChanges { target: txButton; enabled: true} + }, + State { + name: "NOTREADY" + PropertyChanges { target: txButton; enabled:false} + } + ] + text: "Send" + enabled: false + onClicked: { + //this.enabled = false + var res = eth.createTx("", txValue.text, txGas.text, txGasPrice.text, codeView.text) + if(res[1]) { + txResult.text = "Your contract could not be send over the network:\n" + txResult.text += res[1].error() + txResult.text += "" + mainContractColumn.state = "ERROR" + } else { + txResult.text = "Your contract has been submitted:\n" + txOutput.text = res[0] + mainContractColumn.state = "DONE" + } + } + } + Text { + id: txResult + visible: false + } + TextField { + id: txOutput + visible: false + width: 530 + } + Button { + id: newTxButton + visible: false + text: "Create an other contract" + onClicked: { + this.visible = false + txResult.text = "" + txOutput.text = "" + mainContractColumn.state = "SETUP" } - txOutput.visible = true } - } - TextArea { - id: txOutput - visible: false - Layout.fillWidth: true - height: 40 - anchors.bottom: parent.bottom } } } diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index b8ecf3b14..d4800bf1d 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -63,15 +63,21 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin var tx *ethchain.Transaction // Compile and assemble the given data if contractCreation { - asm, err := mutan.Compile(strings.NewReader(data), false) - if err != nil { - return "", err + asm, errors := mutan.Compile(strings.NewReader(data), false) + if len(errors) > 0 { + var errs string + for _, er := range errors { + if er != nil { + errs += er.Error() + } + } + return "", fmt.Errorf(errs) } code := ethutil.Assemble(asm...) tx = ethchain.NewContractCreationTx(value, gasPrice, code) } else { - tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, []string{}) + tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, nil) } acc := lib.stateManager.GetAddrState(keyPair.Address()) tx.Nonce = acc.Nonce -- cgit v1.2.3 From a9a65859134b93cfe2818fdad290530822d6a7b4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 11 Apr 2014 13:36:25 -0400 Subject: Debugger --- ethereal/ui/ui_lib.go | 56 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index adba177d0..fde2697b8 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -94,7 +94,40 @@ type memAddr struct { Value string } -func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { +type Debugger struct { + ui *UiLib + next chan bool +} + +func (d *Debugger) halting(op ethchain.OpCode, mem *ethchain.Memory, stack *ethchain.Stack) { + d.ui.win.Root().Call("clearMem") + d.ui.win.Root().Call("clearStack") + + addr := 0 + for i := 0; i+32 <= mem.Len(); i += 32 { + d.ui.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("% x", mem.Data()[i:i+32])}) + addr++ + } + + for _, val := range stack.Data() { + d.ui.win.Root().Call("setStack", val.String()) + } + +out: + for { + select { + case <-d.next: + break out + default: + } + } +} + +func (d *Debugger) Next() { + d.next <- true +} + +func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) { state := ui.eth.BlockChain().CurrentBlock.State() asm, err := mutan.Compile(strings.NewReader(data), false) @@ -126,21 +159,12 @@ func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) Diff: block.Difficulty, TxData: nil, }) - callerClosure.Call(vm, nil, func(op ethchain.OpCode, mem *ethchain.Memory, stack *ethchain.Stack) { - ui.win.Root().Call("clearMem") - ui.win.Root().Call("clearStack") - - addr := 0 - for i := 0; i+32 <= mem.Len(); i += 32 { - ui.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("% x", mem.Data()[i:i+32])}) - addr++ - } - for _, val := range stack.Data() { - ui.win.Root().Call("setStack", val.String()) - } - }) - state.Reset() + db := &Debugger{ui, make(chan bool)} + ui.engine.Context().SetVar("db", db) + go func() { + callerClosure.Call(vm, nil, db.halting) - return "", nil + state.Reset() + }() } -- cgit v1.2.3 From 5768b18a3b1cfcb1229e5f0236d75da4e9b65dcd Mon Sep 17 00:00:00 2001 From: Maran Date: Fri, 11 Apr 2014 15:18:38 -0400 Subject: Refactored simple send to use states --- ethereal/assets/qml/wallet.qml | 391 ++++++++++++++++++++++------------------- 1 file changed, 211 insertions(+), 180 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 2c8d1f241..1a14697e6 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -30,27 +30,27 @@ ApplicationWindow { MenuItem { text: "Test test" shortcut: "Ctrl+t" - onTriggered: { - var win - function finishedLoading(){ - console.log("Trigged") - win = wizard.createObject(root) - } - console.log("Loading wizard") - - var wizard = Qt.createComponent("first_run.qml") - if(wizard.status== Component.Ready){ - console.log("Component is ready") - finishedLoading() - }else if( wizard.status == Component.Error){ - console.log("Error loading component:", wizard.errorString()) - } - else{ - wizard.statusChanged.connect(finishedLoading) - console.log("Component is NOT ready") - win = wizard.createObject(root) - } - } + onTriggered: { + var win + function finishedLoading(){ + console.log("Trigged") + win = wizard.createObject(root) + } + console.log("Loading wizard") + + var wizard = Qt.createComponent("first_run.qml") + if(wizard.status== Component.Ready){ + console.log("Component is ready") + finishedLoading() + }else if( wizard.status == Component.Error){ + console.log("Error loading component:", wizard.errorString()) + } + else{ + wizard.statusChanged.connect(finishedLoading) + console.log("Component is NOT ready") + win = wizard.createObject(root) + } + } } } @@ -187,52 +187,89 @@ ApplicationWindow { anchors.bottomMargin: 5 id: newTransactionTab Component.onCompleted:{ - addTab("Send ether", newTransaction) + addTab("Simple send", newTransaction) addTab("Create contract", newContract) } } Component { id: newTransaction Column { + id: simpleSendColumn + states: [ + State{ + name: "ERROR" + }, + State { + name: "DONE" + PropertyChanges { target: txSimpleValue; visible:false} + PropertyChanges { target: txSimpleRecipient; visible:false} + PropertyChanges { target:newSimpleTxButton; visible:false} + + PropertyChanges { target: txSimpleResult; visible:true} + PropertyChanges { target: txSimpleOutput; visible:true} + PropertyChanges { target:newSimpleTxButton; visible:true} + }, + State { + name: "SETUP" + PropertyChanges { target: txSimpleValue; visible:true; text: ""} + PropertyChanges { target: txSimpleRecipient; visible:true; text: ""} + PropertyChanges { target: txSimpleButton; visible:true} + PropertyChanges { target:newSimpleTxButton; visible:false} + } + ] spacing: 5 anchors.leftMargin: 5 anchors.topMargin: 5 anchors.top: parent.top anchors.left: parent.left + + function checkFormState(){ + if(txSimpleRecipient.text.length == 40 && txSimpleValue.text.length > 0) { + txSimpleButton.state = "READY" + }else{ + txSimpleButton.state = "NOTREADY" + } + } + TextField { id: txSimpleRecipient placeholderText: "Recipient address" Layout.fillWidth: true - validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } + validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } width: 530 + onTextChanged: { checkFormState() } } TextField { id: txSimpleValue placeholderText: "Amount" anchors.rightMargin: 5 validator: IntValidator { } + onTextChanged: { checkFormState() } } Button { id: txSimpleButton + states: [ + State { + name: "READY" + PropertyChanges { target: txSimpleButton; enabled: true} + }, + State { + name: "NOTREADY" + PropertyChanges { target: txSimpleButton; enabled: false} + } + ] text: "Send" + enabled: false onClicked: { //this.enabled = false var res = eth.createTx(txSimpleRecipient.text, txSimpleValue.text,"","","") if(res[1]) { txSimpleResult.text = "There has been an error broadcasting your transaction:" + res[1].error() - txSimpleResult.visible = true } else { txSimpleResult.text = "Your transaction has been broadcasted over the network.\nYour transaction id is:" txSimpleOutput.text = res[0] - txSimpleOutput.visible = true - txSimpleResult.visible = true - txSimpleValue.visible = false - txSimpleRecipient.visible = false - txSimpleValue.text = "" - txSimpleRecipient.text = "" - txSimpleRecipient.focus = true - newSimpleTxButton.visible = true - this.visible = false + this.visible = false + simpleSendColumn.state = "DONE" } } } @@ -247,18 +284,12 @@ ApplicationWindow { width: 530 } Button { - id: newSimpleTxButton + id: newSimpleTxButton visible: false text: "Create an other transaction" onClicked: { this.visible = false - txSimpleResult.text = "" - txSimpleOutput.text = "" - txSimpleResult.visible = false - txSimpleOutput.visible = false - txSimpleValue.visible = true - txSimpleRecipient.visible = true - txSimpleButton.visible = true + simpleSendColumn.state = "SETUP" } } } @@ -266,49 +297,49 @@ ApplicationWindow { Component { id: newContract Column { - id: mainContractColumn - function contractFormReady(){ - if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { - txButton.state = "READY" - }else{ - txButton.state = "NOTREADY" - } - } - states: [ - State{ - name: "ERROR" - PropertyChanges { target: txResult; visible:true} - PropertyChanges { target: codeView; visible:true} - }, - State { - name: "DONE" - PropertyChanges { target: txValue; visible:false} - PropertyChanges { target: txGas; visible:false} - PropertyChanges { target: txGasPrice; visible:false} - PropertyChanges { target: codeView; visible:false} - PropertyChanges { target: txButton; visible:false} - PropertyChanges { target: txDataLabel; visible:false} - - PropertyChanges { target: txResult; visible:true} - PropertyChanges { target: txOutput; visible:true} - PropertyChanges { target: newTxButton; visible:true} - }, - State { - name: "SETUP" - PropertyChanges { target: txValue; visible:true; text: ""} - PropertyChanges { target: txGas; visible:true; text: ""} - PropertyChanges { target: txGasPrice; visible:true; text: ""} - PropertyChanges { target: codeView; visible:true; text: ""} - PropertyChanges { target: txButton; visible:true} - PropertyChanges { target: txDataLabel; visible:true} - - PropertyChanges { target: txResult; visible:false} - PropertyChanges { target: txOutput; visible:false} - PropertyChanges { target: newTxButton; visible:false} - } - ] + id: mainContractColumn + function contractFormReady(){ + if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { + txButton.state = "READY" + }else{ + txButton.state = "NOTREADY" + } + } + states: [ + State{ + name: "ERROR" + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: codeView; visible:true} + }, + State { + name: "DONE" + PropertyChanges { target: txValue; visible:false} + PropertyChanges { target: txGas; visible:false} + PropertyChanges { target: txGasPrice; visible:false} + PropertyChanges { target: codeView; visible:false} + PropertyChanges { target: txButton; visible:false} + PropertyChanges { target: txDataLabel; visible:false} + + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: txOutput; visible:true} + PropertyChanges { target: newTxButton; visible:true} + }, + State { + name: "SETUP" + PropertyChanges { target: txValue; visible:true; text: ""} + PropertyChanges { target: txGas; visible:true; text: ""} + PropertyChanges { target: txGasPrice; visible:true; text: ""} + PropertyChanges { target: codeView; visible:true; text: ""} + PropertyChanges { target: txButton; visible:true} + PropertyChanges { target: txDataLabel; visible:true} + + PropertyChanges { target: txResult; visible:false} + PropertyChanges { target: txOutput; visible:false} + PropertyChanges { target: newTxButton; visible:false} + } + ] width: 400 - spacing: 5 + spacing: 5 anchors.left: parent.left anchors.top: parent.top anchors.leftMargin: 5 @@ -319,31 +350,31 @@ ApplicationWindow { width: 200 placeholderText: "Amount" validator: IntValidator { } - onTextChanged: { - contractFormReady() - } + onTextChanged: { + contractFormReady() + } } TextField { id: txGas width: 200 validator: IntValidator { } placeholderText: "Gas" - onTextChanged: { - contractFormReady() - } + onTextChanged: { + contractFormReady() + } } TextField { id: txGasPrice width: 200 placeholderText: "Gas price" validator: IntValidator { } - onTextChanged: { - contractFormReady() - } + onTextChanged: { + contractFormReady() + } } Label { - id: txDataLabel + id: txDataLabel text: "Transaction data" } @@ -352,58 +383,58 @@ ApplicationWindow { anchors.topMargin: 5 Layout.fillWidth: true width: parent.width /2 - onTextChanged: { - contractFormReady() - } + onTextChanged: { + contractFormReady() + } } Button { id: txButton - states: [ - State { - name: "READY" - PropertyChanges { target: txButton; enabled: true} - }, - State { - name: "NOTREADY" - PropertyChanges { target: txButton; enabled:false} - } - ] + states: [ + State { + name: "READY" + PropertyChanges { target: txButton; enabled: true} + }, + State { + name: "NOTREADY" + PropertyChanges { target: txButton; enabled:false} + } + ] text: "Send" - enabled: false + enabled: false onClicked: { //this.enabled = false var res = eth.createTx("", txValue.text, txGas.text, txGasPrice.text, codeView.text) if(res[1]) { - txResult.text = "Your contract could not be send over the network:\n" - txResult.text += res[1].error() - txResult.text += "" - mainContractColumn.state = "ERROR" + txResult.text = "Your contract could not be send over the network:\n" + txResult.text += res[1].error() + txResult.text += "" + mainContractColumn.state = "ERROR" } else { - txResult.text = "Your contract has been submitted:\n" - txOutput.text = res[0] - mainContractColumn.state = "DONE" + txResult.text = "Your contract has been submitted:\n" + txOutput.text = res[0] + mainContractColumn.state = "DONE" } } } - Text { - id: txResult - visible: false - } + Text { + id: txResult + visible: false + } TextField { id: txOutput visible: false width: 530 } Button { - id: newTxButton + id: newTxButton visible: false text: "Create an other contract" onClicked: { this.visible = false - txResult.text = "" - txOutput.text = "" - mainContractColumn.state = "SETUP" + txResult.text = "" + txOutput.text = "" + mainContractColumn.state = "SETUP" } } @@ -461,26 +492,26 @@ ApplicationWindow { } /* - signal addPlugin(string name) - Component { - id: pluginWindow - Rectangle { - anchors.fill: parent - Label { - id: pluginTitle - anchors.centerIn: parent - text: "Hello world" - } - Component.onCompleted: setView(this) - } - } - - onAddPlugin: { - var pluginWin = pluginWindow.createObject(mainView) - console.log(pluginWin) - pluginWin.pluginTitle.text = "Test" - } - */ + signal addPlugin(string name) + Component { + id: pluginWindow + Rectangle { + anchors.fill: parent + Label { + id: pluginTitle + anchors.centerIn: parent + text: "Hello world" + } + Component.onCompleted: setView(this) + } + } + + onAddPlugin: { + var pluginWin = pluginWindow.createObject(mainView) + console.log(pluginWin) + pluginWin.pluginTitle.text = "Test" + } + */ } } @@ -665,52 +696,52 @@ ApplicationWindow { function setAsm(asm) { //for(var i = 0; i < asm.length; i++) { - asmModel.append({asm: asm}) - //} - } - function clearAsm() { - asmModel.clear() - } + asmModel.append({asm: asm}) + //} + } + function clearAsm() { + asmModel.clear() + } - function setMem(mem) { - memModel.append({num: mem.num, value: mem.value}) - } - function clearMem(){ - memModel.clear() - } + function setMem(mem) { + memModel.append({num: mem.num, value: mem.value}) + } + function clearMem(){ + memModel.clear() + } - function setStack(stack) { - stackModel.append({value: stack}) - } + function setStack(stack) { + stackModel.append({value: stack}) + } - function clearStack() { - stackModel.clear() - } + function clearStack() { + stackModel.clear() + } - function loadPlugin(name) { - console.log("Loading plugin" + name) - mainView.addPlugin(name) - } + function loadPlugin(name) { + console.log("Loading plugin" + name) + mainView.addPlugin(name) + } - function setWalletValue(value) { - walletValueLabel.text = value - } + function setWalletValue(value) { + walletValueLabel.text = value + } - function addTx(tx) { - txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) - } + function addTx(tx) { + txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) + } - function addBlock(block) { - blockModel.insert(0, {number: block.number, hash: block.hash}) - } + function addBlock(block) { + blockModel.insert(0, {number: block.number, hash: block.hash}) + } - function addLog(str) { - if(str.len != 0) { - logModel.append({description: str}) + function addLog(str) { + if(str.len != 0) { + logModel.append({description: str}) + } } - } - function setPeers(text) { - peerLabel.text = text + function setPeers(text) { + peerLabel.text = text + } } -} -- cgit v1.2.3 From 11aa7da6c31c18d6f9b9901b1588d7f9adb2d724 Mon Sep 17 00:00:00 2001 From: Maran Date: Fri, 11 Apr 2014 16:44:13 -0400 Subject: Initial refactor for wallet qml --- .../assets/qml/newTransaction/_new_contract.qml | 162 +++++++++++++ .../assets/qml/newTransaction/_simple_send.qml | 111 +++++++++ ethereal/assets/qml/wallet.qml | 265 +-------------------- 3 files changed, 280 insertions(+), 258 deletions(-) create mode 100644 ethereal/assets/qml/newTransaction/_new_contract.qml create mode 100644 ethereal/assets/qml/newTransaction/_simple_send.qml diff --git a/ethereal/assets/qml/newTransaction/_new_contract.qml b/ethereal/assets/qml/newTransaction/_new_contract.qml new file mode 100644 index 000000000..8ce81a799 --- /dev/null +++ b/ethereal/assets/qml/newTransaction/_new_contract.qml @@ -0,0 +1,162 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Component { + id: newContract + Column { + id: mainContractColumn + function contractFormReady(){ + if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { + txButton.state = "READY" + }else{ + txButton.state = "NOTREADY" + } + } + states: [ + State{ + name: "ERROR" + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: codeView; visible:true} + }, + State { + name: "DONE" + PropertyChanges { target: txValue; visible:false} + PropertyChanges { target: txGas; visible:false} + PropertyChanges { target: txGasPrice; visible:false} + PropertyChanges { target: codeView; visible:false} + PropertyChanges { target: txButton; visible:false} + PropertyChanges { target: txDataLabel; visible:false} + + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: txOutput; visible:true} + PropertyChanges { target: newTxButton; visible:true} + }, + State { + name: "SETUP" + PropertyChanges { target: txValue; visible:true; text: ""} + PropertyChanges { target: txGas; visible:true; text: ""} + PropertyChanges { target: txGasPrice; visible:true; text: ""} + PropertyChanges { target: codeView; visible:true; text: ""} + PropertyChanges { target: txButton; visible:true} + PropertyChanges { target: txDataLabel; visible:true} + + PropertyChanges { target: txResult; visible:false} + PropertyChanges { target: txOutput; visible:false} + PropertyChanges { target: newTxButton; visible:false} + } + ] + width: 400 + spacing: 5 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 5 + anchors.topMargin: 5 + + TextField { + id: txValue + width: 200 + placeholderText: "Amount" + validator: IntValidator { } + onTextChanged: { + contractFormReady() + } + } + TextField { + id: txGas + width: 200 + validator: IntValidator { } + placeholderText: "Gas" + onTextChanged: { + contractFormReady() + } + } + TextField { + id: txGasPrice + width: 200 + placeholderText: "Gas price" + validator: IntValidator { } + onTextChanged: { + contractFormReady() + } + } + + Label { + id: txDataLabel + text: "Transaction data" + } + + TextArea { + id: codeView + anchors.topMargin: 5 + Layout.fillWidth: true + width: parent.width /2 + onTextChanged: { + contractFormReady() + } + } + + Button { + id: txButton + states: [ + State { + name: "READY" + PropertyChanges { target: txButton; enabled: true} + }, + State { + name: "NOTREADY" + PropertyChanges { target: txButton; enabled:false} + } + ] + text: "Send" + enabled: false + onClicked: { + //this.enabled = false + var res = eth.createTx("", txValue.text, txGas.text, txGasPrice.text, codeView.text) + if(res[1]) { + txResult.text = "Your contract could not be send over the network:\n" + txResult.text += res[1].error() + txResult.text += "" + mainContractColumn.state = "ERROR" + } else { + txResult.text = "Your contract has been submitted:\n" + txOutput.text = res[0] + mainContractColumn.state = "DONE" + } + } + } + Text { + id: txResult + visible: false + } + TextField { + id: txOutput + visible: false + width: 530 + } + Button { + id: newTxButton + visible: false + text: "Create an other contract" + onClicked: { + this.visible = false + txResult.text = "" + txOutput.text = "" + mainContractColumn.state = "SETUP" + } + } + + Button { + id: debugButton + text: "Debug" + onClicked: { + var res = ui.debugTx("", txValue.text, txGas.text, txGasPrice.text, codeView.text) + debugWindow.visible = true + } + } + } +} diff --git a/ethereal/assets/qml/newTransaction/_simple_send.qml b/ethereal/assets/qml/newTransaction/_simple_send.qml new file mode 100644 index 000000000..981766160 --- /dev/null +++ b/ethereal/assets/qml/newTransaction/_simple_send.qml @@ -0,0 +1,111 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Component { + id: newTransaction + Column { + id: simpleSendColumn + states: [ + State{ + name: "ERROR" + }, + State { + name: "DONE" + PropertyChanges { target: txSimpleValue; visible:false} + PropertyChanges { target: txSimpleRecipient; visible:false} + PropertyChanges { target:newSimpleTxButton; visible:false} + + PropertyChanges { target: txSimpleResult; visible:true} + PropertyChanges { target: txSimpleOutput; visible:true} + PropertyChanges { target:newSimpleTxButton; visible:true} + }, + State { + name: "SETUP" + PropertyChanges { target: txSimpleValue; visible:true; text: ""} + PropertyChanges { target: txSimpleRecipient; visible:true; text: ""} + PropertyChanges { target: txSimpleButton; visible:true} + PropertyChanges { target:newSimpleTxButton; visible:false} + } + ] + spacing: 5 + anchors.leftMargin: 5 + anchors.topMargin: 5 + anchors.top: parent.top + anchors.left: parent.left + + function checkFormState(){ + if(txSimpleRecipient.text.length == 40 && txSimpleValue.text.length > 0) { + txSimpleButton.state = "READY" + }else{ + txSimpleButton.state = "NOTREADY" + } + } + + TextField { + id: txSimpleRecipient + placeholderText: "Recipient address" + Layout.fillWidth: true + validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } + width: 530 + onTextChanged: { checkFormState() } + } + TextField { + id: txSimpleValue + placeholderText: "Amount" + anchors.rightMargin: 5 + validator: IntValidator { } + onTextChanged: { checkFormState() } + } + Button { + id: txSimpleButton + states: [ + State { + name: "READY" + PropertyChanges { target: txSimpleButton; enabled: true} + }, + State { + name: "NOTREADY" + PropertyChanges { target: txSimpleButton; enabled: false} + } + ] + text: "Send" + enabled: false + onClicked: { + //this.enabled = false + var res = eth.createTx(txSimpleRecipient.text, txSimpleValue.text,"","","") + if(res[1]) { + txSimpleResult.text = "There has been an error broadcasting your transaction:" + res[1].error() + } else { + txSimpleResult.text = "Your transaction has been broadcasted over the network.\nYour transaction id is:" + txSimpleOutput.text = res[0] + this.visible = false + simpleSendColumn.state = "DONE" + } + } + } + Text { + id: txSimpleResult + visible: false + + } + TextField { + id: txSimpleOutput + visible: false + width: 530 + } + Button { + id: newSimpleTxButton + visible: false + text: "Create an other transaction" + onClicked: { + this.visible = false + simpleSendColumn.state = "SETUP" + } + } + } +} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 1a14697e6..1452b1f97 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -108,6 +108,7 @@ ApplicationWindow { //color: "#D9DDE7" color: "#252525" + ColumnLayout { y: 50 anchors.left: parent.left @@ -187,270 +188,18 @@ ApplicationWindow { anchors.bottomMargin: 5 id: newTransactionTab Component.onCompleted:{ - addTab("Simple send", newTransaction) - addTab("Create contract", newContract) - } - } - Component { - id: newTransaction - Column { - id: simpleSendColumn - states: [ - State{ - name: "ERROR" - }, - State { - name: "DONE" - PropertyChanges { target: txSimpleValue; visible:false} - PropertyChanges { target: txSimpleRecipient; visible:false} - PropertyChanges { target:newSimpleTxButton; visible:false} - - PropertyChanges { target: txSimpleResult; visible:true} - PropertyChanges { target: txSimpleOutput; visible:true} - PropertyChanges { target:newSimpleTxButton; visible:true} - }, - State { - name: "SETUP" - PropertyChanges { target: txSimpleValue; visible:true; text: ""} - PropertyChanges { target: txSimpleRecipient; visible:true; text: ""} - PropertyChanges { target: txSimpleButton; visible:true} - PropertyChanges { target:newSimpleTxButton; visible:false} - } - ] - spacing: 5 - anchors.leftMargin: 5 - anchors.topMargin: 5 - anchors.top: parent.top - anchors.left: parent.left - - function checkFormState(){ - if(txSimpleRecipient.text.length == 40 && txSimpleValue.text.length > 0) { - txSimpleButton.state = "READY" - }else{ - txSimpleButton.state = "NOTREADY" - } - } - - TextField { - id: txSimpleRecipient - placeholderText: "Recipient address" - Layout.fillWidth: true - validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } - width: 530 - onTextChanged: { checkFormState() } - } - TextField { - id: txSimpleValue - placeholderText: "Amount" - anchors.rightMargin: 5 - validator: IntValidator { } - onTextChanged: { checkFormState() } - } - Button { - id: txSimpleButton - states: [ - State { - name: "READY" - PropertyChanges { target: txSimpleButton; enabled: true} - }, - State { - name: "NOTREADY" - PropertyChanges { target: txSimpleButton; enabled: false} - } - ] - text: "Send" - enabled: false - onClicked: { - //this.enabled = false - var res = eth.createTx(txSimpleRecipient.text, txSimpleValue.text,"","","") - if(res[1]) { - txSimpleResult.text = "There has been an error broadcasting your transaction:" + res[1].error() - } else { - txSimpleResult.text = "Your transaction has been broadcasted over the network.\nYour transaction id is:" - txSimpleOutput.text = res[0] - this.visible = false - simpleSendColumn.state = "DONE" - } - } - } - Text { - id: txSimpleResult - visible: false - - } - TextField { - id: txSimpleOutput - visible: false - width: 530 - } - Button { - id: newSimpleTxButton - visible: false - text: "Create an other transaction" - onClicked: { - this.visible = false - simpleSendColumn.state = "SETUP" - } - } - } - } - Component { - id: newContract - Column { - id: mainContractColumn - function contractFormReady(){ - if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { - txButton.state = "READY" - }else{ - txButton.state = "NOTREADY" - } - } - states: [ - State{ - name: "ERROR" - PropertyChanges { target: txResult; visible:true} - PropertyChanges { target: codeView; visible:true} - }, - State { - name: "DONE" - PropertyChanges { target: txValue; visible:false} - PropertyChanges { target: txGas; visible:false} - PropertyChanges { target: txGasPrice; visible:false} - PropertyChanges { target: codeView; visible:false} - PropertyChanges { target: txButton; visible:false} - PropertyChanges { target: txDataLabel; visible:false} - - PropertyChanges { target: txResult; visible:true} - PropertyChanges { target: txOutput; visible:true} - PropertyChanges { target: newTxButton; visible:true} - }, - State { - name: "SETUP" - PropertyChanges { target: txValue; visible:true; text: ""} - PropertyChanges { target: txGas; visible:true; text: ""} - PropertyChanges { target: txGasPrice; visible:true; text: ""} - PropertyChanges { target: codeView; visible:true; text: ""} - PropertyChanges { target: txButton; visible:true} - PropertyChanges { target: txDataLabel; visible:true} - - PropertyChanges { target: txResult; visible:false} - PropertyChanges { target: txOutput; visible:false} - PropertyChanges { target: newTxButton; visible:false} - } - ] - width: 400 - spacing: 5 - anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: 5 - anchors.topMargin: 5 - - TextField { - id: txValue - width: 200 - placeholderText: "Amount" - validator: IntValidator { } - onTextChanged: { - contractFormReady() - } - } - TextField { - id: txGas - width: 200 - validator: IntValidator { } - placeholderText: "Gas" - onTextChanged: { - contractFormReady() - } - } - TextField { - id: txGasPrice - width: 200 - placeholderText: "Gas price" - validator: IntValidator { } - onTextChanged: { - contractFormReady() - } - } - - Label { - id: txDataLabel - text: "Transaction data" - } - - TextArea { - id: codeView - anchors.topMargin: 5 - Layout.fillWidth: true - width: parent.width /2 - onTextChanged: { - contractFormReady() - } - } + var component = Qt.createComponent("newTransaction/_simple_send.qml") + var newTransaction = component.createObject("newTransaction") - Button { - id: txButton - states: [ - State { - name: "READY" - PropertyChanges { target: txButton; enabled: true} - }, - State { - name: "NOTREADY" - PropertyChanges { target: txButton; enabled:false} - } - ] - text: "Send" - enabled: false - onClicked: { - //this.enabled = false - var res = eth.createTx("", txValue.text, txGas.text, txGasPrice.text, codeView.text) - if(res[1]) { - txResult.text = "Your contract could not be send over the network:\n" - txResult.text += res[1].error() - txResult.text += "" - mainContractColumn.state = "ERROR" - } else { - txResult.text = "Your contract has been submitted:\n" - txOutput.text = res[0] - mainContractColumn.state = "DONE" - } - } - } - Text { - id: txResult - visible: false - } - TextField { - id: txOutput - visible: false - width: 530 - } - Button { - id: newTxButton - visible: false - text: "Create an other contract" - onClicked: { - this.visible = false - txResult.text = "" - txOutput.text = "" - mainContractColumn.state = "SETUP" - } - } + component = Qt.createComponent("newTransaction/_new_contract.qml") + var newContract = component.createObject("newContract") - Button { - id: debugButton - text: "Debug" - onClicked: { - var res = ui.debugTx("", txValue.text, txGas.text, txGasPrice.text, codeView.text) - debugWindow.visible = true - } - } + addTab("Simple send", newTransaction) + addTab("Create contract", newContract) } } } - Rectangle { id: networkView property var title: "Network" -- cgit v1.2.3 From 3f82d5172f372b5f5d8d9ede4967c5d2b9867980 Mon Sep 17 00:00:00 2001 From: Maran Date: Fri, 11 Apr 2014 16:47:55 -0400 Subject: remove test menu --- ethereal/assets/qml/wallet.qml | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 1452b1f97..95512f661 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -25,35 +25,6 @@ ApplicationWindow { } } - Menu { - title: "Test" - MenuItem { - text: "Test test" - shortcut: "Ctrl+t" - onTriggered: { - var win - function finishedLoading(){ - console.log("Trigged") - win = wizard.createObject(root) - } - console.log("Loading wizard") - - var wizard = Qt.createComponent("first_run.qml") - if(wizard.status== Component.Ready){ - console.log("Component is ready") - finishedLoading() - }else if( wizard.status == Component.Error){ - console.log("Error loading component:", wizard.errorString()) - } - else{ - wizard.statusChanged.connect(finishedLoading) - console.log("Component is NOT ready") - win = wizard.createObject(root) - } - } - } - } - Menu { title: "Network" MenuItem { -- cgit v1.2.3 From ab8d96258ea11c828a149dde176fe8e2efce0294 Mon Sep 17 00:00:00 2001 From: Maran Date: Fri, 11 Apr 2014 17:05:02 -0400 Subject: Added isContract to gui --- ethereal/assets/qml/wallet.qml | 9 ++++++++- ethereal/ui/gui.go | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 95512f661..0c8c91e13 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -140,6 +140,7 @@ ApplicationWindow { anchors.fill: parent TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } + TableViewColumn{ role: "contract" ; title: "Contract" ; width: 100 } model: txModel } @@ -448,7 +449,13 @@ ApplicationWindow { } function addTx(tx) { - txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) + var isContract + if (tx.contract == true){ + isContract = "Yes" + }else{ + isContract = "No" + } + txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value, contract: isContract}) } function addBlock(block) { diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index fa4a5c833..c09c5954f 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -21,13 +21,15 @@ type Block struct { type Tx struct { Value, Hash, Address string + Contract bool } func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { hash := hex.EncodeToString(tx.Hash()) sender := hex.EncodeToString(tx.Recipient) + isContract := len(tx.Data) > 0 - return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender} + return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender, Contract: isContract} } // Creates a new QML Block from a chain block -- cgit v1.2.3 From ce43a9500f38bae426eef6c3c9d33e006c32c26d Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 12 Apr 2014 00:12:10 -0400 Subject: Debug steps --- ethereal/assets/qml/wallet.qml | 19 ++++++++- ethereal/ui/gui.go | 4 +- ethereal/ui/library.go | 1 + ethereal/ui/ui_lib.go | 89 ++++++++++++++++++++++-------------------- 4 files changed, 68 insertions(+), 45 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 2c8d1f241..22fe96e79 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -619,7 +619,18 @@ ApplicationWindow { width: 800 height: 600 + + Item { + id: keyHandler + focus: true + Keys.onPressed: { + if (event.key == Qt.Key_Space) { + ui.next() + } + } + } SplitView { + anchors.fill: parent property var asmModel: ListModel { id: asmModel @@ -664,10 +675,14 @@ ApplicationWindow { } function setAsm(asm) { - //for(var i = 0; i < asm.length; i++) { asmModel.append({asm: asm}) - //} } + + function setInstruction(num) { + asmTableView.selection.clear() + asmTableView.selection.select(num-1) + } + function clearAsm() { asmModel.clear() } diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index fa4a5c833..d6510bbb6 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -113,10 +113,12 @@ func (ui *Gui) Start(assetPath string) { ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") panic(err) } - ui.engine.LoadFile(uiLib.AssetPath("qml/transactions.qml")) ui.win = component.CreateWindow(nil) uiLib.win = ui.win + db := &Debugger{ui.win, make(chan bool)} + ui.lib.Db = db + uiLib.Db = db // Register the ui as a block processor //ui.eth.BlockManager.SecondaryBlockProcessor = ui diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 08f99e7db..42aebcd87 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -15,6 +15,7 @@ type EthLib struct { stateManager *ethchain.StateManager blockChain *ethchain.BlockChain txPool *ethchain.TxPool + Db *Debugger } func (lib *EthLib) ImportAndSetPrivKey(privKey string) bool { diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index fde2697b8..86855290f 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -16,6 +16,11 @@ import ( "strings" ) +type memAddr struct { + Num string + Value string +} + // UI Library that has some basic functionality exposed type UiLib struct { engine *qml.Engine @@ -24,6 +29,7 @@ type UiLib struct { assetPath string // The main application window win *qml.Window + Db *Debugger } func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { @@ -89,48 +95,11 @@ func DefaultAssetPath() string { return base } -type memAddr struct { - Num string - Value string -} - -type Debugger struct { - ui *UiLib - next chan bool -} - -func (d *Debugger) halting(op ethchain.OpCode, mem *ethchain.Memory, stack *ethchain.Stack) { - d.ui.win.Root().Call("clearMem") - d.ui.win.Root().Call("clearStack") - - addr := 0 - for i := 0; i+32 <= mem.Len(); i += 32 { - d.ui.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("% x", mem.Data()[i:i+32])}) - addr++ - } - - for _, val := range stack.Data() { - d.ui.win.Root().Call("setStack", val.String()) - } - -out: - for { - select { - case <-d.next: - break out - default: - } - } -} - -func (d *Debugger) Next() { - d.next <- true -} - func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) { state := ui.eth.BlockChain().CurrentBlock.State() - asm, err := mutan.Compile(strings.NewReader(data), false) + mainInput, _ := ethutil.PreProcess(data) + asm, err := mutan.Compile(strings.NewReader(mainInput), false) if err != nil { fmt.Println(err) } @@ -160,11 +129,47 @@ func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) TxData: nil, }) - db := &Debugger{ui, make(chan bool)} - ui.engine.Context().SetVar("db", db) go func() { - callerClosure.Call(vm, nil, db.halting) + callerClosure.Call(vm, nil, ui.Db.halting) state.Reset() }() } + +func (ui *UiLib) Next() { + ui.Db.Next() +} + +type Debugger struct { + win *qml.Window + N chan bool +} + +func (d *Debugger) halting(pc int, op ethchain.OpCode, mem *ethchain.Memory, stack *ethchain.Stack) { + d.win.Root().Call("setInstruction", pc) + d.win.Root().Call("clearMem") + d.win.Root().Call("clearStack") + + addr := 0 + for i := 0; i+32 <= mem.Len(); i += 32 { + d.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("% x", mem.Data()[i:i+32])}) + addr++ + } + + for _, val := range stack.Data() { + d.win.Root().Call("setStack", val.String()) + } + +out: + for { + select { + case <-d.N: + break out + default: + } + } +} + +func (d *Debugger) Next() { + d.N <- true +} -- cgit v1.2.3 From 8a2698ad5e3d47db9175e838b0a16c3f59b6e071 Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 14 Apr 2014 13:46:59 -0400 Subject: Add send to contract --- .../assets/qml/newTransaction/_new_contract.qml | 39 ++++++++++++++++++++-- ethereal/assets/qml/wallet.qml | 2 +- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/ethereal/assets/qml/newTransaction/_new_contract.qml b/ethereal/assets/qml/newTransaction/_new_contract.qml index 8ce81a799..abaac1695 100644 --- a/ethereal/assets/qml/newTransaction/_new_contract.qml +++ b/ethereal/assets/qml/newTransaction/_new_contract.qml @@ -85,9 +85,34 @@ Component { } } + Row { + id: rowContract + ExclusiveGroup { id: contractTypeGroup } + RadioButton { + id: createContractRadio + text: "Create contract" + checked: true + exclusiveGroup: contractTypeGroup + onClicked: { + txFuelRecipient.visible = false + txDataLabel.text = "Contract code" + } + } + RadioButton { + id: runContractRadio + text: "Run contract" + exclusiveGroup: contractTypeGroup + onClicked: { + txFuelRecipient.visible = true + txDataLabel.text = "Contract arguments" + } + } + } + + Label { id: txDataLabel - text: "Transaction data" + text: "Contract code" } TextArea { @@ -100,6 +125,14 @@ Component { } } + TextField { + id: txFuelRecipient + placeholderText: "Contract address" + validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } + visible: false + width: 530 + } + Button { id: txButton states: [ @@ -116,14 +149,14 @@ Component { enabled: false onClicked: { //this.enabled = false - var res = eth.createTx("", txValue.text, txGas.text, txGasPrice.text, codeView.text) + var res = eth.createTx(txFuelRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) if(res[1]) { txResult.text = "Your contract could not be send over the network:\n" txResult.text += res[1].error() txResult.text += "" mainContractColumn.state = "ERROR" } else { - txResult.text = "Your contract has been submitted:\n" + txResult.text = "Your transaction has been submitted:\n" txOutput.text = res[0] mainContractColumn.state = "DONE" } diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 0c8c91e13..a4d5cb642 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -167,7 +167,7 @@ ApplicationWindow { var newContract = component.createObject("newContract") addTab("Simple send", newTransaction) - addTab("Create contract", newContract) + addTab("Contracts", newContract) } } } -- cgit v1.2.3 From 91c75c9305e7554c21e84ed1a07ec0e750bb775a Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 14 Apr 2014 17:08:15 -0400 Subject: Adding log messages to debug panel --- ethereal/assets/qml/wallet.qml | 15 +++++++++++++++ ethereal/ui/ui_lib.go | 3 +++ 2 files changed, 18 insertions(+) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index c3ddfe4b8..2d2a9db9f 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -412,6 +412,17 @@ ApplicationWindow { model: memModel } + SplitView { + orientation: Qt.Vertical + anchors.fill: parent + TableView { + property var debuggerLog: ListModel { + id: debuggerLog + } + TableViewColumn{ role: "value"; title: "Debug messages" } + model: debuggerLog + } + } TableView { property var stackModel: ListModel { id: stackModel @@ -449,6 +460,10 @@ ApplicationWindow { function setStack(stack) { stackModel.append({value: stack}) } + function addDebugMessage(message){ + console.log("WOOP:") + debuggerLog.append({value: message}) + } function clearStack() { stackModel.clear() diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 86855290f..b2552cdce 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -102,6 +102,9 @@ func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) asm, err := mutan.Compile(strings.NewReader(mainInput), false) if err != nil { fmt.Println(err) + for _, e := range err { + ui.win.Root().Call("addDebugMessage", e.Error()) + } } callerScript := ethutil.Assemble(asm...) -- cgit v1.2.3 From c23a971a1f98cb6f45079846d8ff5efc4f488244 Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 14 Apr 2014 17:14:07 -0400 Subject: Fix up paneling --- ethereal/assets/qml/wallet.qml | 61 +++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 2d2a9db9f..37224c7b4 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -401,37 +401,36 @@ ApplicationWindow { orientation: Qt.Vertical anchors.fill: parent - TableView { - property var memModel: ListModel { - id: memModel - } - height: parent.height/2 - width: parent.width - TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50} - TableViewColumn{ role: "value" ; title: "Memory" ; width: 750} - model: memModel - } - - SplitView { - orientation: Qt.Vertical - anchors.fill: parent - TableView { - property var debuggerLog: ListModel { - id: debuggerLog - } - TableViewColumn{ role: "value"; title: "Debug messages" } - model: debuggerLog - } - } - TableView { - property var stackModel: ListModel { - id: stackModel - } - height: parent.height/2 - width: parent.width - TableViewColumn{ role: "value" ; title: "Stack" ; width: parent.width } - model: stackModel - } + TableView { + property var memModel: ListModel { + id: memModel + } + height: parent.height/2 + width: parent.width + TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50} + TableViewColumn{ role: "value" ; title: "Memory" ; width: 750} + model: memModel + } + + SplitView { + orientation: Qt.Horizontal + TableView { + property var debuggerLog: ListModel { + id: debuggerLog + } + TableViewColumn{ role: "value"; title: "Debug messages" } + model: debuggerLog + } + TableView { + property var stackModel: ListModel { + id: stackModel + } + height: parent.height/2 + width: parent.width + TableViewColumn{ role: "value" ; title: "Stack" ; width: parent.width } + model: stackModel + } + } } } } -- cgit v1.2.3 From 7cb065489c3d664f5924afad4f72e2a0a3a5c800 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 15 Apr 2014 16:13:04 -0400 Subject: added init and main functions to script --- ethereal/ui/library.go | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 42aebcd87..76032f400 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -44,6 +44,22 @@ func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { return mnemonicString, fmt.Sprintf("%x", pair.Address()), fmt.Sprintf("%x", prv), fmt.Sprintf("%x", pub) } +// General compiler and preprocessor function +func compile(script string) ([]byte, error) { + asm, errors := mutan.Compile(strings.NewReader(script), false) + if len(errors) > 0 { + var errs string + for _, er := range errors { + if er != nil { + errs += er.Error() + } + } + return nil, fmt.Errorf("%v", errs) + } + + return ethutil.Assemble(asm...), nil +} + func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { var hash []byte var contractCreation bool @@ -64,19 +80,19 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin var tx *ethchain.Transaction // Compile and assemble the given data if contractCreation { - asm, errors := mutan.Compile(strings.NewReader(data), false) - if len(errors) > 0 { - var errs string - for _, er := range errors { - if er != nil { - errs += er.Error() - } - } - return "", fmt.Errorf(errs) + mainInput, initInput := ethutil.PreProcess(data) + mainScript, err := compile(mainInput) + if err != nil { + return "", err + } + initScript, err := compile(initInput) + if err != nil { + return "", err } - code := ethutil.Assemble(asm...) - tx = ethchain.NewContractCreationTx(value, gasPrice, code) + // TODO + fmt.Println(initScript) + tx = ethchain.NewContractCreationTx(value, gasPrice, mainScript) } else { tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, nil) } -- cgit v1.2.3 From 1cd7d4456b80c38f343cb54a624408c28c5acb13 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 16 Apr 2014 04:08:25 +0200 Subject: Updated to use new state object --- ethereal/ui/gui.go | 2 +- ethereal/ui/library.go | 25 +++---------------------- ethereal/ui/ui_lib.go | 18 ++++++++---------- ethereum/dev_console.go | 18 ++++++++++++------ 4 files changed, 24 insertions(+), 39 deletions(-) diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 1065b716e..fd29c4820 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -170,7 +170,7 @@ func (ui *Gui) update() { txChan := make(chan ethchain.TxMsg, 1) ui.eth.TxPool().Subscribe(txChan) - account := ui.eth.StateManager().GetAddrState(ui.addr).Account + account := ui.eth.StateManager().GetAddrState(ui.addr).Object unconfirmedFunds := new(big.Int) ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) for { diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 76032f400..6c6f7557a 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -6,7 +6,6 @@ import ( "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" - "github.com/obscuren/mutan" "github.com/obscuren/secp256k1-go" "strings" ) @@ -44,22 +43,6 @@ func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { return mnemonicString, fmt.Sprintf("%x", pair.Address()), fmt.Sprintf("%x", prv), fmt.Sprintf("%x", pub) } -// General compiler and preprocessor function -func compile(script string) ([]byte, error) { - asm, errors := mutan.Compile(strings.NewReader(script), false) - if len(errors) > 0 { - var errs string - for _, er := range errors { - if er != nil { - errs += er.Error() - } - } - return nil, fmt.Errorf("%v", errs) - } - - return ethutil.Assemble(asm...), nil -} - func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { var hash []byte var contractCreation bool @@ -81,18 +64,16 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin // Compile and assemble the given data if contractCreation { mainInput, initInput := ethutil.PreProcess(data) - mainScript, err := compile(mainInput) + mainScript, err := utils.Compile(mainInput) if err != nil { return "", err } - initScript, err := compile(initInput) + initScript, err := utils.Compile(initInput) if err != nil { return "", err } - // TODO - fmt.Println(initScript) - tx = ethchain.NewContractCreationTx(value, gasPrice, mainScript) + tx = ethchain.NewContractCreationTx(value, gasPrice, mainScript, initScript) } else { tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, nil) } diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index b2552cdce..a0d2f557a 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -6,14 +6,13 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" "github.com/niemeyer/qml" - "github.com/obscuren/mutan" "math/big" "os" "path" "path/filepath" "runtime" - "strings" ) type memAddr struct { @@ -99,25 +98,24 @@ func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) state := ui.eth.BlockChain().CurrentBlock.State() mainInput, _ := ethutil.PreProcess(data) - asm, err := mutan.Compile(strings.NewReader(mainInput), false) + callerScript, err := utils.Compile(mainInput) if err != nil { - fmt.Println(err) - for _, e := range err { - ui.win.Root().Call("addDebugMessage", e.Error()) - } + ethutil.Config.Log.Debugln(err) + + return } - callerScript := ethutil.Assemble(asm...) dis := ethchain.Disassemble(callerScript) ui.win.Root().Call("clearAsm") + for _, str := range dis { ui.win.Root().Call("setAsm", str) } - callerTx := ethchain.NewContractCreationTx(ethutil.Big(valueStr), ethutil.Big(gasPriceStr), callerScript) + callerTx := ethchain.NewContractCreationTx(ethutil.Big(valueStr), ethutil.Big(gasPriceStr), callerScript, nil) // Contract addr as test address keyPair := ethutil.Config.Db.GetKeys()[0] - account := ui.eth.StateManager().GetAddrState(keyPair.Address()).Account + account := ui.eth.StateManager().GetAddrState(keyPair.Address()).Object c := ethchain.MakeContract(callerTx, state) callerClosure := ethchain.NewClosure(account, c, c.Script(), state, ethutil.Big(gasStr), new(big.Int)) diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go index 421c3fa60..0f03b5e53 100644 --- a/ethereum/dev_console.go +++ b/ethereum/dev_console.go @@ -11,8 +11,7 @@ import ( "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethwire" - "github.com/obscuren/mutan" - _ "math/big" + "github.com/ethereum/go-ethereum/utils" "os" "strings" ) @@ -171,7 +170,7 @@ func (i *Console) ParseInput(input string) bool { if err != nil { fmt.Println("recipient err:", err) } else { - tx := ethchain.NewTransactionMessage(recipient, ethutil.Big(tokens[2]), ethutil.Big(tokens[3]), ethutil.Big(tokens[4]), []string{""}) + tx := ethchain.NewTransactionMessage(recipient, ethutil.Big(tokens[2]), ethutil.Big(tokens[3]), ethutil.Big(tokens[4]), nil) key := ethutil.Config.Db.GetKeys()[0] tx.Sign(key.PrivateKey) @@ -190,15 +189,22 @@ func (i *Console) ParseInput(input string) bool { } case "contract": fmt.Println("Contract editor (Ctrl-D = done)") - asm, err := mutan.Compile(strings.NewReader(i.Editor()), false) + + mainInput, initInput := ethutil.PreProcess(i.Editor()) + mainScript, err := utils.Compile(mainInput) if err != nil { fmt.Println(err) + break } + initScript, err := utils.Compile(initInput) + if err != nil { + fmt.Println(err) - code := ethutil.Assemble(asm) + break + } - contract := ethchain.NewContractCreationTx(ethutil.Big(tokens[0]), ethutil.Big(tokens[1]), code) + contract := ethchain.NewContractCreationTx(ethutil.Big(tokens[0]), ethutil.Big(tokens[1]), mainScript, initScript) key := ethutil.Config.Db.GetKeys()[0] contract.Sign(key.PrivateKey) -- cgit v1.2.3 From 9cf77cdbadd8249498d62542502def6ecb2fb6b8 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 16 Apr 2014 04:08:37 +0200 Subject: Moved compiling related object to utils package --- utils/compile.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 utils/compile.go diff --git a/utils/compile.go b/utils/compile.go new file mode 100644 index 000000000..e5ea50ad4 --- /dev/null +++ b/utils/compile.go @@ -0,0 +1,24 @@ +package utils + +import ( + "fmt" + "github.com/ethereum/eth-go/ethutil" + "github.com/obscuren/mutan" + "strings" +) + +// General compile function +func Compile(script string) ([]byte, error) { + asm, errors := mutan.Compile(strings.NewReader(script), false) + if len(errors) > 0 { + var errs string + for _, er := range errors { + if er != nil { + errs += er.Error() + } + } + return nil, fmt.Errorf("%v", errs) + } + + return ethutil.Assemble(asm...), nil +} -- cgit v1.2.3 From 7f0c974008b62115c5c2685dee909b379ca1c6fc Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 16 Apr 2014 13:36:52 +0100 Subject: empty string -> empty byte array --- ethereum/dev_console.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go index 421c3fa60..577b039ad 100644 --- a/ethereum/dev_console.go +++ b/ethereum/dev_console.go @@ -171,7 +171,7 @@ func (i *Console) ParseInput(input string) bool { if err != nil { fmt.Println("recipient err:", err) } else { - tx := ethchain.NewTransactionMessage(recipient, ethutil.Big(tokens[2]), ethutil.Big(tokens[3]), ethutil.Big(tokens[4]), []string{""}) + tx := ethchain.NewTransactionMessage(recipient, ethutil.Big(tokens[2]), ethutil.Big(tokens[3]), ethutil.Big(tokens[4]), []byte{}) key := ethutil.Config.Db.GetKeys()[0] tx.Sign(key.PrivateKey) -- cgit v1.2.3 From f4c13f865634bae6c9cc7cd0478b7765a24fa695 Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 16 Apr 2014 13:37:04 +0100 Subject: logfile - add logfile option to ethereum client flags - fallback to StdOut - Logger appended to ethutil.Config.Log loggers - wrapper uses ethutil.Config.Log --- ethereum/config.go | 4 ++-- ethereum/ethereum.go | 26 +++++++++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/ethereum/config.go b/ethereum/config.go index e4935dfed..899a17ca6 100644 --- a/ethereum/config.go +++ b/ethereum/config.go @@ -15,8 +15,7 @@ var GenAddr bool var UseSeed bool var ImportKey string var ExportKey bool - -//var UseGui bool +var LogFile string var DataDir string func Init() { @@ -29,6 +28,7 @@ func Init() { flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") flag.BoolVar(&ExportKey, "export", false, "export private key") flag.StringVar(&OutboundPort, "p", "30303", "listening port") + flag.StringVar(&LogFile, "logfile", "", "log file (defaults to standard output)") flag.StringVar(&DataDir, "dir", ".ethereum", "ethereum data directory") flag.StringVar(&ImportKey, "import", "", "imports the given private key (hex)") flag.IntVar(&MaxPeer, "x", 5, "maximum desired peers") diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index e1e803771..f00c75ad0 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -25,7 +25,6 @@ func RegisterInterupts(s *eth.Ethereum) { go func() { for sig := range c { fmt.Printf("Shutting down (%v) ... \n", sig) - s.Stop() } }() @@ -36,8 +35,25 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) - ethchain.InitFees() + // set logger + var logger *log.Logger + flags := log.LstdFlags + + if LogFile != "" { + logfile, err := os.OpenFile(LogFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) + if err != nil { + panic(fmt.Sprintf("error opening log file '%s': %v", LogFile, err)) + } + defer logfile.Close() + log.SetOutput(logfile) + logger = log.New(logfile, "", flags) + } else { + logger = log.New(os.Stdout, "", flags) + } ethutil.ReadConfig(DataDir) + ethutil.Config.Log.AddLogSystem(logger) + + ethchain.InitFees() ethutil.Config.Seed = UseSeed // Instantiated a eth stack @@ -108,7 +124,7 @@ func main() { os.Exit(0) } - log.Printf("Starting Ethereum v%s\n", ethutil.Config.Ver) + ethutil.Config.Log.Infoln(fmt.Sprintf("Starting Ethereum v%s", ethutil.Config.Ver)) // Set the max peers ethereum.MaxPeers = MaxPeer @@ -128,13 +144,13 @@ func main() { ethereum.Start() if StartMining { - log.Printf("Miner started\n") + ethutil.Config.Log.Infoln("Miner started") // Fake block mining. It broadcasts a new block every 5 seconds go func() { if StartMining { - log.Printf("Miner started\n") + ethutil.Config.Log.Infoln("Miner started") go func() { data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) -- cgit v1.2.3 From 32b09d652de90d1626888c4ed6b61fb5bce0a7dc Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 16 Apr 2014 14:57:51 +0100 Subject: non-interactive option - add -y flag for non-interactive use - refactor main - output to logfile (not ideal..) but not to all ethutil loggers for privacy --- ethereum/config.go | 2 + ethereum/ethereum.go | 104 +++++++++++++++++++++++---------------------------- 2 files changed, 49 insertions(+), 57 deletions(-) diff --git a/ethereum/config.go b/ethereum/config.go index 899a17ca6..b796af5cd 100644 --- a/ethereum/config.go +++ b/ethereum/config.go @@ -17,12 +17,14 @@ var ImportKey string var ExportKey bool var LogFile string var DataDir string +var NonInteractive bool func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") //flag.BoolVar(&UseGui, "gui", true, "use the gui") + flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index f00c75ad0..4f5c3756a 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -30,13 +30,27 @@ func RegisterInterupts(s *eth.Ethereum) { }() } +func confirm(message string) bool { + fmt.Println(message, "Are you sure? (y/n)") + var r string + fmt.Scanln(&r) + for ; ; fmt.Scanln(&r) { + if r == "n" || r == "y" { + break + } else { + fmt.Printf("Yes or no?", r) + } + } + return r == "y" +} + func main() { Init() runtime.GOMAXPROCS(runtime.NumCPU()) // set logger - var logger *log.Logger + var logSys *log.Logger flags := log.LstdFlags if LogFile != "" { @@ -46,12 +60,13 @@ func main() { } defer logfile.Close() log.SetOutput(logfile) - logger = log.New(logfile, "", flags) + logSys = log.New(logfile, "", flags) } else { - logger = log.New(os.Stdout, "", flags) + logSys = log.New(os.Stdout, "", flags) } ethutil.ReadConfig(DataDir) - ethutil.Config.Log.AddLogSystem(logger) + logger := ethutil.Config.Log + logger.AddLogSystem(logSys) ethchain.InitFees() ethutil.Config.Seed = UseSeed @@ -64,67 +79,42 @@ func main() { } ethereum.Port = OutboundPort - if GenAddr { - fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") - - var r string - fmt.Scanln(&r) - for ; ; fmt.Scanln(&r) { - if r == "n" || r == "y" { - break - } else { - fmt.Printf("Yes or no?", r) - } - } - - if r == "y" { + // bookkeeping tasks + switch { + case GenAddr: + if NonInteractive || confirm("This action overwrites your old private key.") { utils.CreateKeyPair(true) } os.Exit(0) - } else { - if len(ImportKey) > 0 { - fmt.Println("This action overwrites your old private key. Are you sure? (y/n)") - var r string - fmt.Scanln(&r) - for ; ; fmt.Scanln(&r) { - if r == "n" || r == "y" { - break - } else { - fmt.Printf("Yes or no?", r) - } - } - - if r == "y" { - mnemonic := strings.Split(ImportKey, " ") - if len(mnemonic) == 24 { - fmt.Println("Got mnemonic key, importing.") - key := ethutil.MnemonicDecode(mnemonic) - utils.ImportPrivateKey(key) - } else if len(mnemonic) == 1 { - fmt.Println("Got hex key, importing.") - utils.ImportPrivateKey(ImportKey) - } else { - fmt.Println("Did not recognise format, exiting.") - } - os.Exit(0) + case len(ImportKey) > 0: + if NonInteractive || confirm("This action overwrites your old private key.") { + mnemonic := strings.Split(ImportKey, " ") + if len(mnemonic) == 24 { + logSys.Println("Got mnemonic key, importing.") + key := ethutil.MnemonicDecode(mnemonic) + utils.ImportPrivateKey(key) + } else if len(mnemonic) == 1 { + logSys.Println("Got hex key, importing.") + utils.ImportPrivateKey(ImportKey) + } else { + logSys.Println("Did not recognise format, exiting.") } - } else { - utils.CreateKeyPair(false) } - } - - if ExportKey { + os.Exit(0) + case len(ImportKey) == 0: + utils.CreateKeyPair(false) + fallthrough + case ExportKey: key := ethutil.Config.Db.GetKeys()[0] - fmt.Printf("%x\n", key.PrivateKey) + logSys.Println(fmt.Sprintf("prvk: %x\n", key.PrivateKey)) os.Exit(0) - } - - if ShowGenesis { - fmt.Println(ethereum.BlockChain().Genesis()) + case ShowGenesis: + logSys.Println(ethereum.BlockChain().Genesis()) os.Exit(0) } - ethutil.Config.Log.Infoln(fmt.Sprintf("Starting Ethereum v%s", ethutil.Config.Ver)) + // client + logger.Infoln(fmt.Sprintf("Starting Ethereum v%s", ethutil.Config.Ver)) // Set the max peers ethereum.MaxPeers = MaxPeer @@ -144,13 +134,13 @@ func main() { ethereum.Start() if StartMining { - ethutil.Config.Log.Infoln("Miner started") + logger.Infoln("Miner started") // Fake block mining. It broadcasts a new block every 5 seconds go func() { if StartMining { - ethutil.Config.Log.Infoln("Miner started") + logger.Infoln("Miner started") go func() { data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) -- cgit v1.2.3 From 59a7b130191286f141a40d294981805677414eb5 Mon Sep 17 00:00:00 2001 From: zelig Date: Wed, 16 Apr 2014 15:01:22 +0100 Subject: typo interrupt --- ethereum/ethereum.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index 4f5c3756a..881f39ece 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -17,7 +17,7 @@ import ( const Debug = true // Register interrupt handlers so we can stop the ethereum -func RegisterInterupts(s *eth.Ethereum) { +func RegisterInterrupts(s *eth.Ethereum) { // Buffered chan of one is enough c := make(chan os.Signal, 1) // Notify about interrupts for now @@ -130,7 +130,7 @@ func main() { go console.Start() } - RegisterInterupts(ethereum) + RegisterInterrupts(ethereum) ethereum.Start() if StartMining { -- cgit v1.2.3 From a0c97b663dcb8de940c17479877e3165834f0c8a Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 20 Apr 2014 02:05:20 +0200 Subject: Updated closure call --- ethereal/ui/ui_lib.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index a0d2f557a..95743fa5d 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" "github.com/niemeyer/qml" - "math/big" "os" "path" "path/filepath" @@ -117,7 +116,7 @@ func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) keyPair := ethutil.Config.Db.GetKeys()[0] account := ui.eth.StateManager().GetAddrState(keyPair.Address()).Object c := ethchain.MakeContract(callerTx, state) - callerClosure := ethchain.NewClosure(account, c, c.Script(), state, ethutil.Big(gasStr), new(big.Int)) + callerClosure := ethchain.NewClosure(account, c, c.Script(), state, ethutil.Big(gasStr), ethutil.Big(gasPriceStr), ethutil.Big(valueStr)) block := ui.eth.BlockChain().CurrentBlock vm := ethchain.NewVm(state, ethchain.RuntimeVars{ -- cgit v1.2.3 From 6d5d539a859cae43a1e97acd6fc5675d45b09063 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 21 Apr 2014 00:57:57 +0200 Subject: Round one HTML external applications using QML(Qt5) WebKit2 w/o native bindings --- ethereal/assets/qml/wallet.qml | 7 ++++++- ethereal/ui/ui_lib.go | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 37224c7b4..ed06f3518 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -18,11 +18,13 @@ ApplicationWindow { MenuBar { Menu { title: "File" + /* MenuItem { text: "Import App" shortcut: "Ctrl+o" onTriggered: openAppDialog.open() } + */ } Menu { @@ -240,7 +242,10 @@ ApplicationWindow { id: openAppDialog title: "Open QML Application" onAccepted: { - ui.open(openAppDialog.fileUrl.toString()) + //ui.open(openAppDialog.fileUrl.toString()) + //ui.openHtml(Qt.resolvedUrl(ui.assetPath("test.html"))) + ui.openHtml(openAppDialog.fileUrl.toString()) + } } diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 95743fa5d..096af16db 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -51,6 +51,22 @@ func (ui *UiLib) Open(path string) { }() } +func (ui *UiLib) OpenHtml(path string) { + component, err := ui.engine.LoadFile(ui.AssetPath("qml/webapp.qml")) + if err != nil { + ethutil.Config.Log.Debugln(err) + + return + } + win := component.CreateWindow(nil) + win.Set("url", path) + + go func() { + win.Show() + win.Wait() + }() +} + func (ui *UiLib) Connect(button qml.Object) { if !ui.connected { ui.eth.Start() -- cgit v1.2.3 From aec3e26ea0074842ca36a5d918cc3ed049a547cf Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 21 Apr 2014 00:58:02 +0200 Subject: Round one HTML external applications using QML(Qt5) WebKit2 w/o native bindings --- ethereal/assets/ethereum.js | 48 ++++++++++++++++++++++++ ethereal/assets/qml/webapp.qml | 85 ++++++++++++++++++++++++++++++++++++++++++ ethereal/assets/test.html | 27 ++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 ethereal/assets/ethereum.js create mode 100644 ethereal/assets/qml/webapp.qml create mode 100644 ethereal/assets/test.html diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js new file mode 100644 index 000000000..b8908913d --- /dev/null +++ b/ethereal/assets/ethereum.js @@ -0,0 +1,48 @@ +// Helper function for generating pseudo callbacks and sending data to the QML part of the application +function postData(data, cb) { + data._seed = Math.floor(Math.random() * 1000000) + if(cb) { + eth._callbacks[data._seed] = cb; + } + + navigator.qt.postMessage(JSON.stringify(data)); +} + +// Main Ethereum library +window.eth = { + prototype: Object(), + + send: function(cb) { + document.getElementById("out").innerHTML = "clicked"; + postData({message: "Hello world"}, cb); + } +} +window.eth._callbacks = {} + +function debug(/**/) { + var args = arguments; + var msg = "" + for(var i=0; i + +Epic Works (TM) + +

It just works!

+ +

Play with me...

+ + +
+
+
+ + + + + + -- cgit v1.2.3 From a3c8f83562c7a740ac89e63bf36f2ce44ae6627a Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 23 Apr 2014 11:51:34 +0200 Subject: Updated test coin --- ethereal/assets/ethereum.js | 28 ++++++-- ethereal/assets/qml/webapp.qml | 158 ++++++++++++++++++++++++----------------- ethereal/assets/test.html | 37 ++++++---- 3 files changed, 139 insertions(+), 84 deletions(-) diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index b8908913d..173eaff22 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -12,17 +12,35 @@ function postData(data, cb) { window.eth = { prototype: Object(), - send: function(cb) { - document.getElementById("out").innerHTML = "clicked"; - postData({message: "Hello world"}, cb); - } + // Retrieve block + // + // Either supply a number or a string. Type is determent for the lookup method + // string - Retrieves the block by looking up the hash + // number - Retrieves the block by looking up the block number + getBlock: function(numberOrHash, cb) { + var func; + if(typeof numberOrHash == "string") { + func = "getBlockByHash" + } else { + func = "getBlockByNumber" + } + postData({call: func, args: [numberOrHash]}, cb) + }, + + // Create transaction + // + // Creates a transaction with the current account + // If no recipient is set, the Ethereum API will see it as a contract creation + createTx: function(recipient, value, gas, gasPrice, data, cb) { + postData({call: "createTx", args: [recipient, value, gas, gasPrice, data]}, cb) + }, } window.eth._callbacks = {} function debug(/**/) { var args = arguments; var msg = "" - for(var i=0; i -Epic Works (TM) +jeffcoin -

It just works!

+

Jeff Coin

-

Play with me...

+ - -
-
-
+
+
+
+ +
+ +
+
+ -- cgit v1.2.3 From b962779a1318138e08c6e84a537fdbc6c9ebfd97 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 23 Apr 2014 11:51:48 +0200 Subject: Minor update and fixes to the gui and console --- ethereal/assets/qml/newTransaction/_new_contract.qml | 6 +++--- ethereal/assets/qml/newTransaction/_simple_send.qml | 6 +++--- ethereal/ui/gui.go | 1 + ethereal/ui/library.go | 9 ++++++--- ethereal/ui/ui_lib.go | 10 +++++++++- ethereum/dev_console.go | 2 +- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/ethereal/assets/qml/newTransaction/_new_contract.qml b/ethereal/assets/qml/newTransaction/_new_contract.qml index abaac1695..29e26a562 100644 --- a/ethereal/assets/qml/newTransaction/_new_contract.qml +++ b/ethereal/assets/qml/newTransaction/_new_contract.qml @@ -135,18 +135,18 @@ Component { Button { id: txButton + /* enabled: false */ states: [ State { name: "READY" - PropertyChanges { target: txButton; enabled: true} + PropertyChanges { target: txButton; /*enabled: true*/} }, State { name: "NOTREADY" - PropertyChanges { target: txButton; enabled:false} + PropertyChanges { target: txButton; /*enabled:false*/} } ] text: "Send" - enabled: false onClicked: { //this.enabled = false var res = eth.createTx(txFuelRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) diff --git a/ethereal/assets/qml/newTransaction/_simple_send.qml b/ethereal/assets/qml/newTransaction/_simple_send.qml index 981766160..d460797ea 100644 --- a/ethereal/assets/qml/newTransaction/_simple_send.qml +++ b/ethereal/assets/qml/newTransaction/_simple_send.qml @@ -63,18 +63,18 @@ Component { } Button { id: txSimpleButton + /*enabled: false*/ states: [ State { name: "READY" - PropertyChanges { target: txSimpleButton; enabled: true} + PropertyChanges { target: txSimpleButton; /*enabled: true*/} }, State { name: "NOTREADY" - PropertyChanges { target: txSimpleButton; enabled: false} + PropertyChanges { target: txSimpleButton; /*enabled: false*/} } ] text: "Send" - enabled: false onClicked: { //this.enabled = false var res = eth.createTx(txSimpleRecipient.text, txSimpleValue.text,"","","") diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index fd29c4820..0e5d57c93 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -113,6 +113,7 @@ func (ui *Gui) Start(assetPath string) { } if err != nil { ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") + panic(err) } diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 6c6f7557a..b097ddbb2 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -44,6 +44,7 @@ func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { } func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { + fmt.Println("Create tx") var hash []byte var contractCreation bool if len(recipient) == 0 { @@ -64,18 +65,21 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin // Compile and assemble the given data if contractCreation { mainInput, initInput := ethutil.PreProcess(data) + fmt.Println("Precompile done") + fmt.Println("main", mainInput) mainScript, err := utils.Compile(mainInput) if err != nil { return "", err } + fmt.Println("init", initInput) initScript, err := utils.Compile(initInput) if err != nil { return "", err } - tx = ethchain.NewContractCreationTx(value, gasPrice, mainScript, initScript) + tx = ethchain.NewContractCreationTx(value, gas, gasPrice, mainScript, initScript) } else { - tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, nil) + tx = ethchain.NewTransactionMessage(hash, value, gas, gasPrice, nil) } acc := lib.stateManager.GetAddrState(keyPair.Address()) tx.Nonce = acc.Nonce @@ -99,7 +103,6 @@ func (lib *EthLib) GetBlock(hexHash string) *Block { } block := lib.blockChain.GetBlock(hash) - fmt.Println(block) return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} } diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 096af16db..09f81c67e 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -59,6 +59,14 @@ func (ui *UiLib) OpenHtml(path string) { return } win := component.CreateWindow(nil) + if filepath.Ext(path) == "eth" { + fmt.Println("Ethereum package not yet supported") + + return + + // TODO + ethutil.OpenPackage(path) + } win.Set("url", path) go func() { @@ -126,7 +134,7 @@ func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) for _, str := range dis { ui.win.Root().Call("setAsm", str) } - callerTx := ethchain.NewContractCreationTx(ethutil.Big(valueStr), ethutil.Big(gasPriceStr), callerScript, nil) + callerTx := ethchain.NewContractCreationTx(ethutil.Big(valueStr), ethutil.Big(gasStr), ethutil.Big(gasPriceStr), callerScript, nil) // Contract addr as test address keyPair := ethutil.Config.Db.GetKeys()[0] diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go index 0f03b5e53..583b8bd0b 100644 --- a/ethereum/dev_console.go +++ b/ethereum/dev_console.go @@ -204,7 +204,7 @@ func (i *Console) ParseInput(input string) bool { break } - contract := ethchain.NewContractCreationTx(ethutil.Big(tokens[0]), ethutil.Big(tokens[1]), mainScript, initScript) + contract := ethchain.NewContractCreationTx(ethutil.Big(tokens[0]), ethutil.Big(tokens[1]), ethutil.Big(tokens[1]), mainScript, initScript) key := ethutil.Config.Db.GetKeys()[0] contract.Sign(key.PrivateKey) -- cgit v1.2.3 From 43f1214f97e52863b1f1ef7913991bef3d00c58e Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 23 Apr 2014 15:54:34 +0200 Subject: Refactored code --- ethereal/ui/library.go | 23 ++++++++++------------- utils/compile.go | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index b097ddbb2..537cfa994 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -43,8 +43,7 @@ func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { return mnemonicString, fmt.Sprintf("%x", pair.Address()), fmt.Sprintf("%x", prv), fmt.Sprintf("%x", pub) } -func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { - fmt.Println("Create tx") +func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { var hash []byte var contractCreation bool if len(recipient) == 0 { @@ -64,26 +63,24 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin var tx *ethchain.Transaction // Compile and assemble the given data if contractCreation { - mainInput, initInput := ethutil.PreProcess(data) - fmt.Println("Precompile done") - fmt.Println("main", mainInput) - mainScript, err := utils.Compile(mainInput) - if err != nil { - return "", err - } - fmt.Println("init", initInput) - initScript, err := utils.Compile(initInput) + // Compile script + mainScript, initScript, err := utils.CompileScript(dataStr) if err != nil { return "", err } tx = ethchain.NewContractCreationTx(value, gas, gasPrice, mainScript, initScript) } else { - tx = ethchain.NewTransactionMessage(hash, value, gas, gasPrice, nil) + lines := strings.Split(dataStr, "\n") + var data []byte + for _, line := range lines { + data = append(data, ethutil.BigToBytes(ethutil.Big(line), 256)...) + } + + tx = ethchain.NewTransactionMessage(hash, value, gas, gasPrice, data) } acc := lib.stateManager.GetAddrState(keyPair.Address()) tx.Nonce = acc.Nonce - //acc.Nonce++ tx.Sign(keyPair.PrivateKey) lib.txPool.QueueTransaction(tx) diff --git a/utils/compile.go b/utils/compile.go index e5ea50ad4..894fc2d09 100644 --- a/utils/compile.go +++ b/utils/compile.go @@ -22,3 +22,21 @@ func Compile(script string) ([]byte, error) { return ethutil.Assemble(asm...), nil } + +func CompileScript(script string) ([]byte, []byte, error) { + // Preprocess + mainInput, initInput := ethutil.PreProcess(script) + // Compile main script + mainScript, err := Compile(mainInput) + if err != nil { + return nil, nil, err + } + + // Compile init script + initScript, err := Compile(initInput) + if err != nil { + return nil, nil, err + } + + return mainScript, initScript, nil +} -- cgit v1.2.3 From bb72347acf8a82d1c20e8aae25c84e5dc75903dd Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 24 Apr 2014 00:01:22 +0200 Subject: Minor fixes and sample coin "improvements" --- ethereal/assets/ethereum.js | 12 +++++++++ ethereal/assets/icon.png | Bin 0 -> 86700 bytes ethereal/assets/qml/webapp.qml | 23 +++++++++++++++--- ethereal/assets/test.html | 54 ++++++++++++++++++++++++++++------------- ethereal/ethereum.go | 2 +- ethereal/ui/gui.go | 14 ++++++++++- ethereal/ui/library.go | 24 ++++++++++++++++++ ethereal/ui/ui_lib.go | 2 +- 8 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 ethereal/assets/icon.png diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index 173eaff22..74f851936 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -5,6 +5,10 @@ function postData(data, cb) { eth._callbacks[data._seed] = cb; } + if(data.args === undefined) { + data.args = [] + } + navigator.qt.postMessage(JSON.stringify(data)); } @@ -34,6 +38,14 @@ window.eth = { createTx: function(recipient, value, gas, gasPrice, data, cb) { postData({call: "createTx", args: [recipient, value, gas, gasPrice, data]}, cb) }, + + getStorage: function(address, storageAddress, cb) { + postData({call: "getStorage", args: [address, storageAddress]}, cb) + }, + + getKey: function(cb) { + postData({call: "getKey"}, cb) + }, } window.eth._callbacks = {} diff --git a/ethereal/assets/icon.png b/ethereal/assets/icon.png new file mode 100644 index 000000000..73e0ceb75 Binary files /dev/null and b/ethereal/assets/icon.png differ diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 496beb65a..ee7dea0ca 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -25,12 +25,15 @@ ApplicationWindow { WebView { objectName: "webView" id: webview + anchors.fill: parent + /* anchors { left: parent.left right: parent.right bottom: sizeGrip.top top: parent.top } + */ onTitleChanged: { window.title = title } experimental.preferences.javascriptEnabled: true @@ -38,7 +41,7 @@ ApplicationWindow { experimental.preferences.developerExtrasEnabled: true experimental.userScripts: [ui.assetPath("ethereum.js")] experimental.onMessageReceived: { - console.log("[onMessageReceived]: ", message.data) + //console.log("[onMessageReceived]: ", message.data) var data = JSON.parse(message.data) switch(data.call) { @@ -57,6 +60,20 @@ ApplicationWindow { var tx = eth.createTx(data.args[0], data.args[1],data.args[2],data.args[3],data.args[4]) postData(data._seed, tx) } + break + case "getStorage": + if(data.args.length < 2) { + postData(data._seed, null) + } else { + var stateObject = eth.getStateObject(data.args[0]) + var storage = stateObject.getStorage(data.args[1]) + postData(data._seed, storage) + } + break + case "getKey": + var keys = eth.getKey() + postData(data._seed, keys) + break } } function postData(seed, data) { @@ -67,7 +84,7 @@ ApplicationWindow { Rectangle { id: sizeGrip color: "gray" - visible: true + visible: false height: 10 anchors { left: root.left @@ -86,7 +103,7 @@ ApplicationWindow { WebView { id: inspector - visible: true + visible: false url: webview.experimental.remoteInspectorUrl anchors { left: root.left diff --git a/ethereal/assets/test.html b/ethereal/assets/test.html index eb55bf667..1cfcad6bb 100644 --- a/ethereal/assets/test.html +++ b/ethereal/assets/test.html @@ -1,37 +1,57 @@ jeffcoin - -

Jeff Coin

- - - -
-
-
- -
- -
- + + + + +

Jeff Coin

+ + +
+ +
+
+
+ +
+ +
+
diff --git a/ethereal/ethereum.go b/ethereal/ethereum.go index 9cc52039d..0adb9f151 100644 --- a/ethereal/ethereum.go +++ b/ethereal/ethereum.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/ethereal/ui" "github.com/ethereum/go-ethereum/utils" - "github.com/niemeyer/qml" + "github.com/go-qml/qml" "log" "os" "os/signal" diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 0e5d57c93..80498d718 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" - "github.com/niemeyer/qml" + "github.com/go-qml/qml" "math/big" "strings" ) @@ -24,6 +24,18 @@ type Tx struct { Contract bool } +type Key struct { + Address string +} + +type KeyRing struct { + Keys []interface{} +} + +func NewKeyRing(keys []interface{}) *KeyRing { + return &KeyRing{Keys: keys} +} + func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { hash := hex.EncodeToString(tx.Hash()) sender := hex.EncodeToString(tx.Recipient) diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 537cfa994..5ca2b4273 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -10,6 +10,20 @@ import ( "strings" ) +type Contract struct { + object *ethchain.StateObject +} + +func NewContract(object *ethchain.StateObject) *Contract { + return &Contract{object: object} +} + +func (c *Contract) GetStorage(address string) string { + val := c.object.GetMem(ethutil.Big("0x" + address)) + + return val.BigInt().String() +} + type EthLib struct { stateManager *ethchain.StateManager blockChain *ethchain.BlockChain @@ -43,6 +57,16 @@ func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { return mnemonicString, fmt.Sprintf("%x", pair.Address()), fmt.Sprintf("%x", prv), fmt.Sprintf("%x", pub) } +func (lib *EthLib) GetKey() string { + return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) +} + +func (lib *EthLib) GetStateObject(address string) *Contract { + stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) + + return NewContract(stateObject) +} + func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { var hash []byte var contractCreation bool diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 09f81c67e..9191e5ea9 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" - "github.com/niemeyer/qml" + "github.com/go-qml/qml" "os" "path" "path/filepath" -- cgit v1.2.3 From c535d0d24623caafcab084546f85f1f70cb2ac67 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 24 Apr 2014 14:42:31 +0200 Subject: Added new block sub for webapp --- ethereal/ui/ui_lib.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 9191e5ea9..08e2267a7 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -70,8 +70,34 @@ func (ui *UiLib) OpenHtml(path string) { win.Set("url", path) go func() { + blockChan := make(chan ethutil.React, 1) + quitChan := make(chan bool) + + go func() { + out: + for { + select { + case <-quitChan: + ui.eth.Reactor().Unsubscribe("newBlock", blockChan) + break out + case block := <-blockChan: + if block, ok := block.Resource.(*ethchain.Block); ok { + b := &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} + win.ObjectByName("webView").Call("onNewBlockCb", b) + } + } + } + + // Clean up + close(blockChan) + close(quitChan) + }() + ui.eth.Reactor().Subscribe("newBlock", blockChan) + win.Show() win.Wait() + + quitChan <- true }() } -- cgit v1.2.3 From bbde892d5012203bb984b83fcb2fe11467841643 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 24 Apr 2014 14:43:00 +0200 Subject: Added callback mechanism and updated UI * UI Now updates when a new block has been broadcasted * Added a on, off and trigger --- ethereal/assets/ethereum.js | 65 +++++++++++++++++----- .../assets/qml/newTransaction/_new_contract.qml | 1 + ethereal/assets/qml/wallet.qml | 2 - ethereal/assets/qml/webapp.qml | 4 ++ ethereal/assets/test.html | 11 +++- 5 files changed, 64 insertions(+), 19 deletions(-) diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index 74f851936..fefad584a 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -6,7 +6,7 @@ function postData(data, cb) { } if(data.args === undefined) { - data.args = [] + data.args = []; } navigator.qt.postMessage(JSON.stringify(data)); @@ -24,11 +24,11 @@ window.eth = { getBlock: function(numberOrHash, cb) { var func; if(typeof numberOrHash == "string") { - func = "getBlockByHash" + func = "getBlockByHash"; } else { - func = "getBlockByNumber" + func = "getBlockByNumber"; } - postData({call: func, args: [numberOrHash]}, cb) + postData({call: func, args: [numberOrHash]}, cb); }, // Create transaction @@ -36,18 +36,51 @@ window.eth = { // Creates a transaction with the current account // If no recipient is set, the Ethereum API will see it as a contract creation createTx: function(recipient, value, gas, gasPrice, data, cb) { - postData({call: "createTx", args: [recipient, value, gas, gasPrice, data]}, cb) + postData({call: "createTx", args: [recipient, value, gas, gasPrice, data]}, cb); }, getStorage: function(address, storageAddress, cb) { - postData({call: "getStorage", args: [address, storageAddress]}, cb) + postData({call: "getStorage", args: [address, storageAddress]}, cb); }, getKey: function(cb) { - postData({call: "getKey"}, cb) + postData({call: "getKey"}, cb); + }, + + + on: function(event, cb) { + if(eth._onCallbacks[event] === undefined) { + eth._onCallbacks[event] = []; + } + + eth._onCallbacks[event].push(cb); + + return this + }, + off: function(event, cb) { + if(eth._onCallbacks[event] !== undefined) { + var callbacks = eth._onCallbacks[event]; + for(var i = 0; i < callbacks.length; i++) { + if(callbacks[i] === cb) { + delete callbacks[i]; + } + } + } + + return this + }, + + trigger: function(event, data) { + var callbacks = eth._onCallbacks[event]; + if(callbacks !== undefined) { + for(var i = 0; i < callbacks.length; i++) { + callbacks[i](data); + } + } }, } window.eth._callbacks = {} +window.eth._onCallbacks = {} function debug(/**/) { var args = arguments; @@ -66,13 +99,17 @@ function debug(/**/) { navigator.qt.onmessage = function(ev) { var data = JSON.parse(ev.data) - if(data._seed) { - var cb = eth._callbacks[data._seed]; - if(cb) { - // Call the callback - cb(data.data); - // Remove the "trigger" callback - delete eth._callbacks[ev._seed]; + if(data._event !== undefined) { + eth.trigger(data._event, data.data); + } else { + if(data._seed) { + var cb = eth._callbacks[data._seed]; + if(cb) { + // Call the callback + cb(data.data); + // Remove the "trigger" callback + delete eth._callbacks[ev._seed]; + } } } } diff --git a/ethereal/assets/qml/newTransaction/_new_contract.qml b/ethereal/assets/qml/newTransaction/_new_contract.qml index 29e26a562..0794d3dcd 100644 --- a/ethereal/assets/qml/newTransaction/_new_contract.qml +++ b/ethereal/assets/qml/newTransaction/_new_contract.qml @@ -117,6 +117,7 @@ Component { TextArea { id: codeView + height: 300 anchors.topMargin: 5 Layout.fillWidth: true width: parent.width /2 diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index ed06f3518..574fbef86 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -18,13 +18,11 @@ ApplicationWindow { MenuBar { Menu { title: "File" - /* MenuItem { text: "Import App" shortcut: "Ctrl+o" onTriggered: openAppDialog.open() } - */ } Menu { diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index ee7dea0ca..9cf154e9b 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -79,6 +79,10 @@ ApplicationWindow { function postData(seed, data) { webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) } + + function onNewBlockCb(block) { + webview.experimental.postMessage(JSON.stringify({data: block, _event: "block:new"})) + } } Rectangle { diff --git a/ethereal/assets/test.html b/ethereal/assets/test.html index 1cfcad6bb..476283f60 100644 --- a/ethereal/assets/test.html +++ b/ethereal/assets/test.html @@ -23,11 +23,16 @@ function tests() { function init() { eth.getKey(function(key) { + eth.getStorage(jefcoinAddr, key, function(storage) { + document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; + }); + + eth.on("block:new", function() { eth.getStorage(jefcoinAddr, key, function(storage) { - debug("Currently in storage: ", storage); document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; - }) - }) + }); + }); + }); } -- cgit v1.2.3 From d0438ac10ab55cd12c1ab5ec3aaf0030185bf131 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 25 Apr 2014 19:24:39 +0200 Subject: Moved --- ethereal/assets/samplecoin.html | 65 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 ethereal/assets/samplecoin.html diff --git a/ethereal/assets/samplecoin.html b/ethereal/assets/samplecoin.html new file mode 100644 index 000000000..476283f60 --- /dev/null +++ b/ethereal/assets/samplecoin.html @@ -0,0 +1,65 @@ + + +jeffcoin + + + + + +

Jeff Coin

+ + +
+ +
+
+
+ +
+ +
+ + +
+ + + + -- cgit v1.2.3 From e16fd323e800297602a60b7a0e7b7897a55d2fa0 Mon Sep 17 00:00:00 2001 From: obscuren Date: Sat, 26 Apr 2014 01:47:04 +0200 Subject: Leverage the new watch & address:changed functionality --- ethereal/assets/ethereum.js | 4 +++ ethereal/assets/qml/webapp.qml | 12 ++++++++- ethereal/assets/samplecoin.html | 5 +++- ethereal/ui/gui.go | 52 +++++--------------------------------- ethereal/ui/library.go | 8 ++++-- ethereal/ui/types.go | 56 +++++++++++++++++++++++++++++++++++++++++ ethereal/ui/ui_lib.go | 13 +++++++--- ethereum/ethereum.go | 32 ++++++++++++----------- 8 files changed, 114 insertions(+), 68 deletions(-) create mode 100644 ethereal/ui/types.go diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index fefad584a..0f9f68c43 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -47,6 +47,10 @@ window.eth = { postData({call: "getKey"}, cb); }, + watch: function(address) { + postData({call: "watch", args: [address]}); + }, + on: function(event, cb) { if(eth._onCallbacks[event] === undefined) { diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 9cf154e9b..c34e0dc55 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -74,14 +74,24 @@ ApplicationWindow { var keys = eth.getKey() postData(data._seed, keys) break + case "watch": + if(data.args.length > 0) { + eth.watch(data.args[0]); + } } } function postData(seed, data) { webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) } + function postEvent(event, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) + } function onNewBlockCb(block) { - webview.experimental.postMessage(JSON.stringify({data: block, _event: "block:new"})) + postEvent("block:new", block) + } + function onObjectChangeCb(stateObject) { + postEvent("object:change", stateObject) } } diff --git a/ethereal/assets/samplecoin.html b/ethereal/assets/samplecoin.html index 476283f60..1b89be877 100644 --- a/ethereal/assets/samplecoin.html +++ b/ethereal/assets/samplecoin.html @@ -22,12 +22,15 @@ function tests() { } function init() { + eth.watch(jefcoinAddr); + eth.getKey(function(key) { eth.getStorage(jefcoinAddr, key, function(storage) { document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; }); - eth.on("block:new", function() { + eth.on("object:change", function(stateObject) { + debug(stateObject); eth.getStorage(jefcoinAddr, key, function(storage) { document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; }); diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 80498d718..d3a179496 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -2,7 +2,6 @@ package ethui import ( "bytes" - "encoding/hex" "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" @@ -13,45 +12,6 @@ import ( "strings" ) -// Block interface exposed to QML -type Block struct { - Number int - Hash string -} - -type Tx struct { - Value, Hash, Address string - Contract bool -} - -type Key struct { - Address string -} - -type KeyRing struct { - Keys []interface{} -} - -func NewKeyRing(keys []interface{}) *KeyRing { - return &KeyRing{Keys: keys} -} - -func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { - hash := hex.EncodeToString(tx.Hash()) - sender := hex.EncodeToString(tx.Recipient) - isContract := len(tx.Data) > 0 - - return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender, Contract: isContract} -} - -// Creates a new QML Block from a chain block -func NewBlockFromBlock(block *ethchain.Block) *Block { - info := block.BlockInfo() - hash := hex.EncodeToString(block.Hash()) - - return &Block{Number: int(info.Number), Hash: hash} -} - type Gui struct { // The main application window win *qml.Window @@ -96,9 +56,9 @@ func (ui *Gui) Start(assetPath string) { // Register ethereum functions qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ - Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, + Init: func(p *QBlock, obj qml.Object) { p.Number = 0; p.Hash = "" }, }, { - Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, + Init: func(p *QTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, }}) ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.1")) @@ -169,13 +129,13 @@ func (ui *Gui) readPreviousTransactions() { for it.Next() { tx := ethchain.NewTransactionFromBytes(it.Value()) - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.win.Root().Call("addTx", NewQTx(tx)) } it.Release() } func (ui *Gui) ProcessBlock(block *ethchain.Block) { - ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) + ui.win.Root().Call("addBlock", NewQBlock(block)) } // Simple go routine function that updates the list of peers in the GUI @@ -193,13 +153,13 @@ func (ui *Gui) update() { if txMsg.Type == ethchain.TxPre { if bytes.Compare(tx.Sender(), ui.addr) == 0 { - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.win.Root().Call("addTx", NewQTx(tx)) ui.txDb.Put(tx.Hash(), tx.RlpEncode()) ui.eth.StateManager().GetAddrState(ui.addr).Nonce += 1 unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) + ui.win.Root().Call("addTx", NewQTx(tx)) ui.txDb.Put(tx.Hash(), tx.RlpEncode()) unconfirmedFunds.Add(unconfirmedFunds, tx.Value) diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 5ca2b4273..7f667f2c1 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -67,6 +67,10 @@ func (lib *EthLib) GetStateObject(address string) *Contract { return NewContract(stateObject) } +func (lib *EthLib) Watch(addr string) { + lib.stateManager.Watch(ethutil.FromHex(addr)) +} + func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { var hash []byte var contractCreation bool @@ -117,7 +121,7 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr st return ethutil.Hex(tx.Hash()), nil } -func (lib *EthLib) GetBlock(hexHash string) *Block { +func (lib *EthLib) GetBlock(hexHash string) *QBlock { hash, err := hex.DecodeString(hexHash) if err != nil { return nil @@ -125,5 +129,5 @@ func (lib *EthLib) GetBlock(hexHash string) *Block { block := lib.blockChain.GetBlock(hash) - return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} + return &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} } diff --git a/ethereal/ui/types.go b/ethereal/ui/types.go new file mode 100644 index 000000000..5c39f0217 --- /dev/null +++ b/ethereal/ui/types.go @@ -0,0 +1,56 @@ +package ethui + +import ( + "encoding/hex" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" +) + +// Block interface exposed to QML +type QBlock struct { + Number int + Hash string +} + +// Creates a new QML Block from a chain block +func NewQBlock(block *ethchain.Block) *QBlock { + info := block.BlockInfo() + hash := hex.EncodeToString(block.Hash()) + + return &QBlock{Number: int(info.Number), Hash: hash} +} + +type QTx struct { + Value, Hash, Address string + Contract bool +} + +func NewQTx(tx *ethchain.Transaction) *QTx { + hash := hex.EncodeToString(tx.Hash()) + sender := hex.EncodeToString(tx.Recipient) + isContract := len(tx.Data) > 0 + + return &QTx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender, Contract: isContract} +} + +type QKey struct { + Address string +} + +type QKeyRing struct { + Keys []interface{} +} + +func NewQKeyRing(keys []interface{}) *QKeyRing { + return &QKeyRing{Keys: keys} +} + +type QStateObject struct { + Address string + Amount string + Nonce int +} + +func NewQStateObject(stateObject *ethchain.StateObject) *QStateObject { + return &QStateObject{ethutil.Hex(stateObject.Address()), stateObject.Amount.String(), int(stateObject.Nonce)} +} diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 08e2267a7..6217c0065 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -69,8 +69,10 @@ func (ui *UiLib) OpenHtml(path string) { } win.Set("url", path) + webView := win.ObjectByName("webView") go func() { blockChan := make(chan ethutil.React, 1) + addrChan := make(chan ethutil.React, 1) quitChan := make(chan bool) go func() { @@ -82,8 +84,12 @@ func (ui *UiLib) OpenHtml(path string) { break out case block := <-blockChan: if block, ok := block.Resource.(*ethchain.Block); ok { - b := &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} - win.ObjectByName("webView").Call("onNewBlockCb", b) + b := &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} + webView.Call("onNewBlockCb", b) + } + case stateObject := <-addrChan: + if stateObject, ok := stateObject.Resource.(*ethchain.StateObject); ok { + webView.Call("onObjectChangeCb", NewQStateObject(stateObject)) } } } @@ -93,6 +99,7 @@ func (ui *UiLib) OpenHtml(path string) { close(quitChan) }() ui.eth.Reactor().Subscribe("newBlock", blockChan) + ui.eth.Reactor().Subscribe("addressChanged", addrChan) win.Show() win.Wait() @@ -169,7 +176,7 @@ func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) callerClosure := ethchain.NewClosure(account, c, c.Script(), state, ethutil.Big(gasStr), ethutil.Big(gasPriceStr), ethutil.Big(valueStr)) block := ui.eth.BlockChain().CurrentBlock - vm := ethchain.NewVm(state, ethchain.RuntimeVars{ + vm := ethchain.NewVm(state, ui.eth.StateManager(), ethchain.RuntimeVars{ Origin: account.Address(), BlockNumber: block.BlockInfo().Number, PrevHash: block.PrevHash, diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index 881f39ece..04851d2bd 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -53,22 +53,24 @@ func main() { var logSys *log.Logger flags := log.LstdFlags + ethutil.ReadConfig(DataDir) + logger := ethutil.Config.Log + if LogFile != "" { - logfile, err := os.OpenFile(LogFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) - if err != nil { - panic(fmt.Sprintf("error opening log file '%s': %v", LogFile, err)) - } - defer logfile.Close() + logfile, err := os.OpenFile(LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + panic(fmt.Sprintf("error opening log file '%s': %v", LogFile, err)) + } + defer logfile.Close() log.SetOutput(logfile) logSys = log.New(logfile, "", flags) - } else { - logSys = log.New(os.Stdout, "", flags) - } - ethutil.ReadConfig(DataDir) - logger := ethutil.Config.Log - logger.AddLogSystem(logSys) + logger.AddLogSystem(logSys) + } + /*else { + logSys = log.New(os.Stdout, "", flags) + }*/ - ethchain.InitFees() + ethchain.InitFees() ethutil.Config.Seed = UseSeed // Instantiated a eth stack @@ -101,9 +103,6 @@ func main() { } } os.Exit(0) - case len(ImportKey) == 0: - utils.CreateKeyPair(false) - fallthrough case ExportKey: key := ethutil.Config.Db.GetKeys()[0] logSys.Println(fmt.Sprintf("prvk: %x\n", key.PrivateKey)) @@ -111,6 +110,9 @@ func main() { case ShowGenesis: logSys.Println(ethereum.BlockChain().Genesis()) os.Exit(0) + default: + // Creates a keypair if non exists + utils.CreateKeyPair(false) } // client -- cgit v1.2.3 From 0e8ca84b67465c0211a0cb46fc7bb6b9c4988dfd Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 27 Apr 2014 16:52:48 +0200 Subject: Updated version number --- README.md | 2 +- ethereal/ui/gui.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c473c780..08104b075 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Ethereum Ethereum Go Client © 2014 Jeffrey Wilcke. -Current state: Proof of Concept 3.5. +Current state: Proof of Concept 5.0. For the development package please see the [eth-go package](https://github.com/ethereum/eth-go). diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index d3a179496..8ec09459b 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -61,7 +61,7 @@ func (ui *Gui) Start(assetPath string) { Init: func(p *QTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, }}) - ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.1")) + ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.2")) ethutil.Config.Log.Infoln("[GUI] Starting GUI") // Create a new QML engine ui.engine = qml.NewEngine() -- cgit v1.2.3 From 883810b53328b302aa765ccf9d228bd73c046cac Mon Sep 17 00:00:00 2001 From: obscuren Date: Sun, 27 Apr 2014 18:05:48 +0200 Subject: Using mutan assembler stage --- utils/compile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/compile.go b/utils/compile.go index 894fc2d09..12e3a60c3 100644 --- a/utils/compile.go +++ b/utils/compile.go @@ -9,7 +9,7 @@ import ( // General compile function func Compile(script string) ([]byte, error) { - asm, errors := mutan.Compile(strings.NewReader(script), false) + byteCode, errors := mutan.Compile(strings.NewReader(script), false) if len(errors) > 0 { var errs string for _, er := range errors { @@ -20,7 +20,7 @@ func Compile(script string) ([]byte, error) { return nil, fmt.Errorf("%v", errs) } - return ethutil.Assemble(asm...), nil + return byteCode, nil } func CompileScript(script string) ([]byte, []byte, error) { -- cgit v1.2.3 From 68e5568804cc99d217e7a3aaca796406e428f221 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 28 Apr 2014 00:25:01 +0200 Subject: Draft mut(an)ed(itor) --- ethereal/assets/muted/codemirror.css | 272 + ethereal/assets/muted/debugger.html | 53 + ethereal/assets/muted/eclipse.css | 23 + ethereal/assets/muted/index.html | 38 + ethereal/assets/muted/lib/codemirror.js | 7526 ++++++++++++++++++++++++++++ ethereal/assets/muted/lib/go.js | 182 + ethereal/assets/muted/lib/matchbrackets.js | 117 + ethereal/assets/qml/muted.qml | 65 + ethereal/assets/qml/wallet.qml | 5 + ethereal/ui/ui_lib.go | 19 + 10 files changed, 8300 insertions(+) create mode 100644 ethereal/assets/muted/codemirror.css create mode 100644 ethereal/assets/muted/debugger.html create mode 100644 ethereal/assets/muted/eclipse.css create mode 100644 ethereal/assets/muted/index.html create mode 100644 ethereal/assets/muted/lib/codemirror.js create mode 100644 ethereal/assets/muted/lib/go.js create mode 100644 ethereal/assets/muted/lib/matchbrackets.js create mode 100644 ethereal/assets/qml/muted.qml diff --git a/ethereal/assets/muted/codemirror.css b/ethereal/assets/muted/codemirror.css new file mode 100644 index 000000000..098a317a2 --- /dev/null +++ b/ethereal/assets/muted/codemirror.css @@ -0,0 +1,272 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; +} +.CodeMirror-scroll { + /* Set scrolling behaviour here */ + overflow: auto; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +/* CURSOR */ + +.CodeMirror div.CodeMirror-cursor { + border-left: 1px solid black; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +/* Can style cursor different in overwrite (non-insert) mode */ +div.CodeMirror-overwrite div.CodeMirror-cursor {} + +.cm-tab { display: inline-block; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + line-height: 1; + position: relative; + overflow: hidden; + background: white; + color: black; +} + +.CodeMirror-scroll { + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actuall scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + padding-bottom: 30px; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + -moz-box-sizing: content-box; + box-sizing: content-box; + padding-bottom: 30px; + margin-bottom: -32px; + display: inline-block; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} + +.CodeMirror-lines { + cursor: text; +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-wrap .CodeMirror-scroll { + overflow-x: hidden; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} +.CodeMirror-measure pre { position: static; } + +.CodeMirror div.CodeMirror-cursor { + position: absolute; + border-right: none; + width: 0; +} + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 1; +} +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} diff --git a/ethereal/assets/muted/debugger.html b/ethereal/assets/muted/debugger.html new file mode 100644 index 000000000..077c59bcf --- /dev/null +++ b/ethereal/assets/muted/debugger.html @@ -0,0 +1,53 @@ + + + + + + + + +
+
+
+ > +
+
+
+
+
+ + + diff --git a/ethereal/assets/muted/eclipse.css b/ethereal/assets/muted/eclipse.css new file mode 100644 index 000000000..317218e3d --- /dev/null +++ b/ethereal/assets/muted/eclipse.css @@ -0,0 +1,23 @@ +.cm-s-eclipse span.cm-meta {color: #FF1717;} +.cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } +.cm-s-eclipse span.cm-atom {color: #219;} +.cm-s-eclipse span.cm-number {color: #164;} +.cm-s-eclipse span.cm-def {color: #00f;} +.cm-s-eclipse span.cm-variable {color: black;} +.cm-s-eclipse span.cm-variable-2 {color: #0000C0;} +.cm-s-eclipse span.cm-variable-3 {color: #0000C0;} +.cm-s-eclipse span.cm-property {color: black;} +.cm-s-eclipse span.cm-operator {color: black;} +.cm-s-eclipse span.cm-comment {color: #3F7F5F;} +.cm-s-eclipse span.cm-string {color: #2A00FF;} +.cm-s-eclipse span.cm-string-2 {color: #f50;} +.cm-s-eclipse span.cm-qualifier {color: #555;} +.cm-s-eclipse span.cm-builtin {color: #30a;} +.cm-s-eclipse span.cm-bracket {color: #cc7;} +.cm-s-eclipse span.cm-tag {color: #170;} +.cm-s-eclipse span.cm-attribute {color: #00c;} +.cm-s-eclipse span.cm-link {color: #219;} +.cm-s-eclipse span.cm-error {color: #f00;} + +.cm-s-eclipse .CodeMirror-activeline-background {background: #e8f2ff !important;} +.cm-s-eclipse .CodeMirror-matchingbracket {outline:1px solid grey; color:black !important;} diff --git a/ethereal/assets/muted/index.html b/ethereal/assets/muted/index.html new file mode 100644 index 000000000..c750d780b --- /dev/null +++ b/ethereal/assets/muted/index.html @@ -0,0 +1,38 @@ + + + +Mutan Editor + + + + + + + + + + + + + + + diff --git a/ethereal/assets/muted/lib/codemirror.js b/ethereal/assets/muted/lib/codemirror.js new file mode 100644 index 000000000..0ab217711 --- /dev/null +++ b/ethereal/assets/muted/lib/codemirror.js @@ -0,0 +1,7526 @@ +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + module.exports = mod(); + else if (typeof define == "function" && define.amd) // AMD + return define([], mod); + else // Plain browser env + this.CodeMirror = mod(); +})(function() { + "use strict"; + + // BROWSER SNIFFING + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + + var gecko = /gecko\/\d/i.test(navigator.userAgent); + // ie_uptoN means Internet Explorer version N or lower + var ie_upto10 = /MSIE \d/.test(navigator.userAgent); + var ie_upto7 = ie_upto10 && (document.documentMode == null || document.documentMode < 8); + var ie_upto8 = ie_upto10 && (document.documentMode == null || document.documentMode < 9); + var ie_upto9 = ie_upto10 && (document.documentMode == null || document.documentMode < 10); + var ie_11up = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent); + var ie = ie_upto10 || ie_11up; + var webkit = /WebKit\//.test(navigator.userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var presto = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var khtml = /KHTML\//.test(navigator.userAgent); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); + var phantom = /PhantomJS/.test(navigator.userAgent); + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var windows = /win/i.test(navigator.platform); + + var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) presto_version = Number(presto_version[1]); + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && !ie_upto8); + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + // EDITOR CONSTRUCTOR + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); + + this.options = options = options || {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") doc = new Doc(doc, options.mode); + this.doc = doc; + + var display = this.display = new Display(place, doc); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap"; + if (options.autofocus && !mobile) focusInput(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput + draggingText: false, + highlight: new Delayed() // stores highlight worker timeout + }; + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie_upto10) setTimeout(bind(resetInput, this, true), 20); + + registerEventHandlers(this); + + var cm = this; + runInOp(this, function() { + cm.curOp.forceUpdate = true; + attachDoc(cm, doc); + + if ((options.autofocus && !mobile) || activeElt() == display.input) + setTimeout(bind(onFocus, cm), 20); + else + onBlur(cm); + + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](cm, options[opt], Init); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](cm); + }); + } + + // DISPLAY CONSTRUCTOR + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc) { + var d = this; + + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) input.style.width = "1000px"; + else input.setAttribute("wrap", "off"); + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) input.style.border = "1px solid black"; + input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false"); + + // Wraps and hides input textarea + d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The fake scrollbar elements. + d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = elt("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, + d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie_upto7) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) d.scroller.draggable = true; + // Needed to handle Tab key in KHTML + if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie_upto7) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px"; + + if (place.appendChild) place.appendChild(d.wrapper); + else place(d.wrapper); + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + // Information about the rendered lines. + d.view = []; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastSizeC = 0; + d.updateLineNumbers = null; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // See readInput and resetInput + d.prevInput = ""; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + d.pollingFast = false; + // Self-resetting timeout for the poller + d.poll = new Delayed(); + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks when resetInput has punted to just putting a short + // string into the textarea instead of the full selection. + d.inaccurateSelection = false; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + } + + // STATE UPDATES + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); + cm.doc.frontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) regChange(cm); + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function(){updateScrollbars(cm);}, 100); + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function(line) { + if (lineIsHidden(cm.doc, line)) return 0; + + var widgetsHeight = 0; + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; + } + + if (wrapping) + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; + else + return widgetsHeight + th; + }; + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function(line) { + var estHeight = est(line); + if (estHeight != line.height) updateLineHeight(line, estHeight); + }); + } + + function keyMapChanged(cm) { + var map = keyMap[cm.options.keyMap], style = map.style; + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + for (var i = 0; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0; + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) return 0; + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found = merged.find(0, true); + len -= cur.text.length - found.from.ch; + cur = found.to.line; + len += cur.text.length - found.to.ch; + } + return len; + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function(line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var scroll = cm.display.scroller; + return { + clientHeight: scroll.clientHeight, + barHeight: cm.display.scrollbarV.clientHeight, + scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth, + barWidth: cm.display.scrollbarH.clientWidth, + docHeight: Math.round(cm.doc.height + paddingVert(cm.display)) + }; + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm); + var d = cm.display; + var scrollHeight = measure.docHeight + scrollerCutOff; + var needsH = measure.scrollWidth > measure.clientWidth; + var needsV = scrollHeight > measure.clientHeight; + if (needsV) { + d.scrollbarV.style.display = "block"; + d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; + // A bug in IE8 can cause this value to be negative, so guard it. + d.scrollbarV.firstChild.style.height = + Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px"; + } else { + d.scrollbarV.style.display = ""; + d.scrollbarV.firstChild.style.height = "0"; + } + if (needsH) { + d.scrollbarH.style.display = "block"; + d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; + d.scrollbarH.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px"; + } else { + d.scrollbarH.style.display = ""; + d.scrollbarH.firstChild.style.width = "0"; + } + if (needsH && needsV) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; + } else d.scrollbarFiller.style.display = ""; + if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px"; + d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; + } else d.gutterFiller.style.display = ""; + + if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) { + if (scrollbarWidth(d.measure) === 0) { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w; + var barMouseDown = function(e) { + if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH) + operation(cm, onMouseDown)(e); + }; + on(d.scrollbarV, "mousedown", barMouseDown); + on(d.scrollbarH, "mousedown", barMouseDown); + } + cm.state.checkedOverlayScrollbar = true; + } + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewPort may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewPort) { + var top = viewPort && viewPort.top != null ? viewPort.top : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewPort && viewPort.bottom != null ? viewPort.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewPort && viewPort.ensure) { + var ensureFrom = viewPort.ensure.from.line, ensureTo = viewPort.ensure.to.line; + if (ensureFrom < from) + return {from: ensureFrom, + to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)}; + if (Math.min(ensureTo, doc.lastLine()) >= to) + return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight), + to: ensureTo}; + } + return {from: from, to: to}; + } + + // LINE NUMBERS + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) if (!view[i].hidden) { + if (cm.options.fixedGutter && view[i].gutter) + view[i].gutter.style.left = left; + var align = view[i].alignable; + if (align) for (var j = 0; j < align.length; j++) + align[j].style.left = left; + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false; + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true; + } + return false; + } + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)); + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; + } + + // DISPLAY DRAWING + + // Updates the display, selection, and scrollbars, using the + // information in display.view to find out which nodes are no longer + // up-to-date. Tries to bail out early when no changes are needed, + // unless forced is true. + // Returns true if an actual update happened, false otherwise. + function updateDisplay(cm, viewPort, forced) { + var oldFrom = cm.display.viewFrom, oldTo = cm.display.viewTo, updated; + var visible = visibleLines(cm.display, cm.doc, viewPort); + for (var first = true;; first = false) { + var oldWidth = cm.display.scroller.clientWidth; + if (!updateDisplayInner(cm, visible, forced)) break; + updated = true; + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + if (cm.display.maxLineChanged && !cm.options.lineWrapping) + adjustContentWidth(cm); + + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + if (webkit && cm.options.lineWrapping) + checkForWebkitWidthBug(cm, barMeasure); // (Issue #2420) + if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) { + forced = true; + continue; + } + forced = false; + + // Clip forced viewport to actual scrollable area. + if (viewPort && viewPort.top != null) + viewPort = {top: Math.min(barMeasure.docHeight - scrollerCutOff - barMeasure.clientHeight, viewPort.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + visible = visibleLines(cm.display, cm.doc, viewPort); + if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo) + break; + } + + cm.display.updateLineNumbers = null; + if (updated) { + signalLater(cm, "update", cm); + if (cm.display.viewFrom != oldFrom || cm.display.viewTo != oldTo) + signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + } + return updated; + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayInner(cm, visible, forced) { + var display = cm.display, doc = cm.doc; + if (!display.wrapper.offsetWidth) { + resetView(cm); + return; + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!forced && visible.from >= display.viewFrom && visible.to <= display.viewTo && + countDirtyView(cm) == 0) + return; + + if (maybeUpdateLineNumberWidth(cm)) + resetView(cm); + var dims = getDimensions(cm); + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastSizeC != display.wrapper.clientHeight; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !forced) return; + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt(); + if (toUpdate > 4) display.lineDiv.style.display = "none"; + patchDisplay(cm, display.updateLineNumbers, dims); + if (toUpdate > 4) display.lineDiv.style.display = ""; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); + + // Prevent selection and cursors from interfering with the scroll + // width. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + + if (different) { + display.lastSizeC = display.wrapper.clientHeight; + startWorker(cm, 400); + } + + updateHeightsInViewport(cm); + + return true; + } + + function adjustContentWidth(cm) { + var display = cm.display; + var width = measureChar(cm, display.maxLine, display.maxLine.text.length).left; + display.maxLineChanged = false; + var minWidth = Math.max(0, width + 3); + var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + minWidth + scrollerCutOff - display.scroller.clientWidth); + display.sizer.style.minWidth = minWidth + "px"; + if (maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px"; + cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px"; + } + + + function checkForWebkitWidthBug(cm, measure) { + // Work around Webkit bug where it sometimes reserves space for a + // non-existing phantom scrollbar in the scroller (Issue #2420) + if (cm.display.sizer.offsetWidth + cm.display.gutters.offsetWidth < cm.display.scroller.clientWidth - 1) { + cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = "0px"; + cm.display.gutters.style.height = measure.docHeight + "px"; + } + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height; + if (cur.hidden) continue; + if (ie_upto7) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + } + var diff = cur.line.height - height; + if (height < 2) height = textHeight(display); + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) for (var j = 0; j < cur.rest.length; j++) + updateWidgetHeight(cur.rest[j]); + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) + line.widgets[i].height = line.widgets[i].node.offsetHeight; + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft; + width[cm.options.gutters[i]] = n.offsetWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none"; + else + node.parentNode.removeChild(node); + return next; + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) { + } else if (!lineView.node) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur); + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) cur = rm(cur); + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") updateLineText(cm, lineView); + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); + else if (type == "class") updateLineClasses(lineView); + else if (type == "widget") updateLineWidgets(lineView, dims); + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + lineView.text.parentNode.replaceChild(lineView.node, lineView.text); + lineView.node.appendChild(lineView.text); + if (ie_upto7) lineView.node.style.zIndex = 2; + } + return lineView.node; + } + + function updateLineBackground(lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) cls += " CodeMirror-linebackground"; + if (lineView.background) { + if (cls) lineView.background.className = cls; + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built; + } + return buildLineContent(cm, lineView); + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) lineView.node = built.pre; + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(lineView) { + updateLineBackground(lineView); + if (lineView.line.wrapClass) + ensureLineWrapped(lineView).className = lineView.line.wrapClass; + else if (lineView.node != lineView.text) + lineView.node.className = ""; + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = + wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), + lineView.text); + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + cm.display.lineNumInnerWidth + "px")); + if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + } + + function updateLineWidgets(lineView, dims) { + if (lineView.alignable) lineView.alignable = null; + for (var node = lineView.node.firstChild, next; node; node = next) { + var next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + lineView.node.removeChild(node); + } + insertLineWidgets(lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) lineView.bgClass = built.bgClass; + if (built.textClass) lineView.textClass = built.textClass; + + updateLineClasses(lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(lineView, dims); + return lineView.node; + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(lineView, dims) { + insertLineWidgetsFor(lineView.line, lineView, dims, true); + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + insertLineWidgetsFor(lineView.rest[i], lineView, dims, false); + } + + function insertLineWidgetsFor(line, lineView, dims, allowAbove) { + if (!line.widgets) return; + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.ignoreEvents = true; + positionLineWidget(widget, node, lineView, dims); + if (allowAbove && widget.above) + wrap.insertBefore(node, lineView.gutter || lineView.text); + else + wrap.appendChild(node); + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + } + + // POSITION OBJECT + + // A Pos instance represents a position within the text. + var Pos = CodeMirror.Pos = function(line, ch) { + if (!(this instanceof Pos)) return new Pos(line, ch); + this.line = line; this.ch = ch; + }; + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; + + function copyPos(x) {return Pos(x.line, x.ch);} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } + + // SELECTION / CURSOR + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + function Selection(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + } + + Selection.prototype = { + primary: function() { return this.ranges[this.primIndex]; }, + equals: function(other) { + if (other == this) return true; + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; + for (var i = 0; i < this.ranges.length; i++) { + var here = this.ranges[i], there = other.ranges[i]; + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; + } + return true; + }, + deepCopy: function() { + for (var out = [], i = 0; i < this.ranges.length; i++) + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); + return new Selection(out, this.primIndex); + }, + somethingSelected: function() { + for (var i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].empty()) return true; + return false; + }, + contains: function(pos, end) { + if (!end) end = pos; + for (var i = 0; i < this.ranges.length; i++) { + var range = this.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + return i; + } + return -1; + } + }; + + function Range(anchor, head) { + this.anchor = anchor; this.head = head; + } + + Range.prototype = { + from: function() { return minPos(this.anchor, this.head); }, + to: function() { return maxPos(this.anchor, this.head); }, + empty: function() { + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; + } + }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex]; + ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) --primIndex; + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex); + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0); + } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} + function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0); + var last = doc.first + doc.size - 1; + if (pos.line > last) return Pos(last, getLine(doc, last).text.length); + return clipToLen(pos, getLine(doc, pos.line).text.length); + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) return Pos(pos.line, linelen); + else if (ch < 0) return Pos(pos.line, 0); + else return pos; + } + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} + function clipPosArray(doc, array) { + for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); + return out; + } + + // SELECTION UPDATES + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head); + } else { + return new Range(other || head, head); + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + for (var out = [], i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); + var newSel = normalizeSelection(out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); + } + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); + else return sel; + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel); + + var bias = cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1; + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm); + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return; + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i); + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel; + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, bias, mayClear) { + var flipped = false, curPos = pos; + var dir = bias || 1; + doc.cantEdit = false; + search: for (;;) { + var line = getLine(doc, curPos.line); + if (line.markedSpans) { + for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; + var newPos = m.find(dir < 0 ? -1 : 1); + if (cmp(newPos, curPos) == 0) { + newPos.ch += dir; + if (newPos.ch < 0) { + if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); + else newPos = null; + } else if (newPos.ch > line.text.length) { + if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); + else newPos = null; + } + if (!newPos) { + if (flipped) { + // Driven in a corner -- no valid cursor position found at all + // -- try again *with* clearing, if we didn't already + if (!mayClear) return skipAtomic(doc, pos, bias, true); + // Otherwise, turn off editing until further notice, and return the start of the doc + doc.cantEdit = true; + return Pos(doc.first, 0); + } + flipped = true; newPos = pos; dir = -dir; + } + } + curPos = newPos; + continue search; + } + } + } + return curPos; + } + } + + // SELECTION DRAWING + + // Redraw the selection and/or cursor + function updateSelection(cm) { + var display = cm.display, doc = cm.doc; + var curFragment = document.createDocumentFragment(); + var selFragment = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + var collapsed = range.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range, curFragment); + if (!collapsed) + drawSelectionRange(cm, range, selFragment); + } + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + var top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + var left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + display.inputDiv.style.top = top + "px"; + display.inputDiv.style.left = left + "px"; + } + + removeChildrenAndAdd(display.cursorDiv, curFragment); + removeChildrenAndAdd(display.selectionDiv, selFragment); + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, range, output) { + var pos = cursorCoords(cm, range.head, "div"); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right; + + function add(left, top, width, bottom) { + if (top < 0) top = 0; + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + + "px; height: " + (bottom - top) + "px")); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = leftSide; + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom); + left = leftSide; + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); + } + if (toArg == null && to == lineLen) right = rightSide; + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; + if (left < leftSide + 1) left = leftSide; + add(left, rightPos.top, right - left, rightPos.bottom); + }); + return {start: start, end: end}; + } + + var sFrom = range.from(), sTo = range.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top); + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) return; + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)); + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.frontier < doc.first) doc.frontier = doc.first; + if (doc.frontier >= cm.display.viewTo) return; + var end = +new Date + cm.options.workTime; + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + + runInOp(cm, function() { + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var highlighted = highlightLine(cm, line, state, true); + line.styles = highlighted.styles; + if (highlighted.classes) line.styleClasses = highlighted.classes; + else if (line.styleClasses) line.styleClasses = null; + var ischange = !oldStyles || oldStyles.length != line.styles.length; + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; + if (ischange) regLineChange(cm, doc.frontier, "text"); + line.stateAfter = copyState(doc.mode, state); + } else { + processLine(cm, line.text, state); + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + } + ++doc.frontier; + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true; + } + }); + }); + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) return doc.first; + var line = getLine(doc, search - 1); + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!state) state = startState(doc.mode); + else state = copyState(doc.mode, state); + doc.iter(pos, n, function(line) { + processLine(cm, line.text, state); + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; + line.stateAfter = save ? copyState(doc.mode, state) : null; + ++pos; + }); + if (precise) doc.frontier = pos; + return state; + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} + function paddingH(display) { + if (display.cachedPaddingH) return display.cachedPaddingH; + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; + return data; + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && cm.display.scroller.clientWidth; + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + heights.push((cur.bottom + next.top) / 2 - rect.top); + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + return {map: lineView.measure.map, cache: lineView.measure.cache}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineView.rest[i] == line) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineNo(lineView.rest[i]) > lineN) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view; + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + return cm.display.view[findViewIndex(cm, lineN)]; + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + return ext; + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) + view = null; + else if (view && view.changes) + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + if (!view) + view = updateExternalMeasurement(cm, line); + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + }; + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias) { + if (prepared.before) ch = -1; + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + prepared.rect = prepared.view.text.getBoundingClientRect(); + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) prepared.cache[key] = found; + } + return {left: found.left, right: found.right, top: found.top, bottom: found.bottom}; + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function measureCharInner(cm, prepared, ch, bias) { + var map = prepared.map; + + var node, start, end, collapse; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + var mStart = map[i], mEnd = map[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) collapse = "right"; + } + if (start != null) { + node = map[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + collapse = bias; + if (bias == "left" && start == 0) + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2]; + collapse = "left"; + } + if (bias == "right" && start == mEnd - mStart) + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2]; + collapse = "right"; + } + break; + } + } + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start; + while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end; + if (ie_upto8 && start == 0 && end == mEnd - mStart) { + rect = node.parentNode.getBoundingClientRect(); + } else if (ie && cm.options.lineWrapping) { + var rects = range(node, start, end).getClientRects(); + if (rects.length) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = nullRect; + } else { + rect = range(node, start, end).getBoundingClientRect(); + } + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) collapse = bias = "right"; + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = node.getBoundingClientRect(); + } + if (ie_upto8 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; + else + rect = nullRect; + } + + var top, bot = (rect.bottom + rect.top) / 2 - prepared.rect.top; + var heights = prepared.view.measure.heights; + for (var i = 0; i < heights.length - 1; i++) + if (bot < heights[i]) break; + top = i ? heights[i - 1] : 0; bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) result.bogus = true; + return result; + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + lineView.measure.caches[i] = {}; + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + clearLineMeasurementCacheFor(cm.display.view[i]); + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; + cm.display.lineNumChars = null; + } + + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"/null (editor), or "page". + function intoCoordSystem(cm, lineObj, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]); + rect.top += size; rect.bottom += size; + } + if (context == "line") return rect; + if (!context) context = "local"; + var yOff = heightAtLine(lineObj); + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect; + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"/null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left"); + if (right) m.left = m.right; else m.right = m.left; + return intoCoordSystem(cm, lineObj, m, context); + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } + var order = getOrder(lineObj), ch = pos.ch; + if (!order) return get(ch); + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0, pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height}; + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, outside, xRel) { + var pos = Pos(line, ch); + pos.xRel = xRel; + if (outside) pos.outside = true; + return pos; + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); + if (x < 0) x = 0; + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var merged = collapsedSpanAtEnd(lineObj); + var mergedPos = merged && merged.find(0, true); + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + lineN = lineNo(lineObj = mergedPos.to.line); + else + return found; + } + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(lineObj); + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); + wrongLine = true; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; + else wrongLine = false; + return sp.left; + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length; + var from = lineLeft(lineObj), to = lineRight(lineObj); + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; + + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); + return pos; + } + var step = Math.ceil(dist / 2), middle = from + step; + if (bidi) { + middle = from; + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); + } + var middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} + } + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight; + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) display.cachedTextHeight = height; + removeChildren(display.measure); + return height || 1; + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth; + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) display.cachedCharWidth = width; + return width || 10; + } + + // OPERATIONS + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + id: ++nextOpId // Unique ID + }; + if (!delayedCallbackDepth++) delayedCallbacks = []; + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp, doc = cm.doc, display = cm.display; + cm.curOp = null; + + if (op.updateMaxLine) findMaxLine(cm); + + // If it looks like an update might be needed, call updateDisplay + if (op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping) { + var updated = updateDisplay(cm, {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; + } + // If no update was run, but the selection changed, redraw that. + if (!updated && op.selectionChanged) updateSelection(cm); + if (!updated && op.startHeight != cm.doc.height) updateScrollbars(cm); + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && display.scroller.scrollTop != op.scrollTop) { + var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); + display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top; + } + if (op.scrollLeft != null && display.scroller.scrollLeft != op.scrollLeft) { + var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)); + display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left; + alignHorizontally(cm); + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from), + clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin); + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + } + + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + resetInput(cm, op.typing); + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) for (var i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide"); + if (unhidden) for (var i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + + var delayed; + if (!--delayedCallbackDepth) { + delayed = delayedCallbacks; + delayedCallbacks = null; + } + // Fire change events, and delayed event handlers + if (op.changeObjs) + signal(cm, "changes", cm, op.changeObjs); + if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); + if (op.cursorActivityHandlers) + for (var i = 0; i < op.cursorActivityHandlers.length; i++) + op.cursorActivityHandlers[i](cm); + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) return f(); + startOperation(cm); + try { return f(); } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) return f.apply(cm, arguments); + startOperation(cm); + try { return f.apply(cm, arguments); } + finally { endOperation(cm); } + }; + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) return f.apply(this, arguments); + startOperation(this); + try { return f.apply(this, arguments); } + finally { endOperation(this); } + }; + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) return f.apply(this, arguments); + startOperation(cm); + try { return f.apply(this, arguments); } + finally { endOperation(cm); } + }; + } + + // VIEW TRACKING + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array; + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first; + if (to == null) to = cm.doc.first + cm.doc.size; + if (!lendiff) lendiff = 0; + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + display.updateLineNumbers = from; + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + resetView(cm); + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut = viewCuttingPoint(cm, from, from, -1); + if (cut) { + display.view = display.view.slice(0, cut.index); + display.viewTo = cut.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + ext.lineN += lendiff; + else if (from < ext.lineN + ext.size) + display.externalMeasured = null; + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + display.externalMeasured = null; + + if (line < display.viewFrom || line >= display.viewTo) return; + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) return; + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) arr.push(type); + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) return null; + n -= cm.display.viewFrom; + if (n < 0) return null; + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) return i; + } + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans) return {index: index, lineN: newN}; + for (var i = 0, n = cm.display.viewFrom; i < index; i++) + n += view[i].size; + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) return null; + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) return null; + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN}; + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); + else if (display.viewFrom < from) + display.view = display.view.slice(findViewIndex(cm, from)); + display.viewFrom = from; + if (display.viewTo < to) + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); + else if (display.viewTo > to) + display.view = display.view.slice(0, findViewIndex(cm, to)); + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; + } + return dirty; + } + + // INPUT HANDLING + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + function slowPoll(cm) { + if (cm.display.pollingFast) return; + cm.display.poll.set(cm.options.pollInterval, function() { + readInput(cm); + if (cm.state.focused) slowPoll(cm); + }); + } + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + function fastPoll(cm) { + var missed = false; + cm.display.pollingFast = true; + function p() { + var changed = readInput(cm); + if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} + else {cm.display.pollingFast = false; slowPoll(cm);} + } + cm.display.poll.set(20, p); + } + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + function readInput(cm) { + var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput) + return false; + // See paste handler for more on the fakedLastChar kludge + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false; + // Work around nonsensical selection resetting in IE9/10 + if (ie && !ie_upto8 && cm.display.inputHasSelection === text) { + resetInput(cm); + return false; + } + + var withOp = !cm.curOp; + if (withOp) startOperation(cm); + cm.display.shift = false; + + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; + var inserted = text.slice(same), textLines = splitLines(inserted); + + // When pasing N lines into N selections, insert one line per selection + var multiPaste = cm.state.pasteIncoming && textLines.length > 1 && doc.sel.ranges.length == textLines.length; + + // Normal behavior is to insert the new text into every selection + for (var i = doc.sel.ranges.length - 1; i >= 0; i--) { + var range = doc.sel.ranges[i]; + var from = range.from(), to = range.to(); + // Handle deletion + if (same < prevInput.length) + from = Pos(from.line, from.ch - (prevInput.length - same)); + // Handle overwrite + else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming) + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: multiPaste ? [textLines[i]] : textLines, + origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + // When an 'electric' character is inserted, immediately trigger a reindent + if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && + cm.options.smartIndent && range.head.ch < 100 && + (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) { + var mode = cm.getModeAt(range.head); + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indentLine(cm, range.head.line, "smart"); + break; + } + } else if (mode.electricInput) { + var end = changeEnd(changeEvent); + if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch))) + indentLine(cm, range.head.line, "smart"); + } + } + } + ensureCursorVisible(cm); + cm.curOp.updateInput = updateInput; + cm.curOp.typing = true; + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; + else cm.display.prevInput = text; + if (withOp) endOperation(cm); + cm.state.pasteIncoming = cm.state.cutIncoming = false; + return true; + } + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + function resetInput(cm, typing) { + var minimal, selected, doc = cm.doc; + if (cm.somethingSelected()) { + cm.display.prevInput = ""; + var range = doc.sel.primary(); + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); + var content = minimal ? "-" : selected || cm.getSelection(); + cm.display.input.value = content; + if (cm.state.focused) selectInput(cm.display.input); + if (ie && !ie_upto8) cm.display.inputHasSelection = content; + } else if (!typing) { + cm.display.prevInput = cm.display.input.value = ""; + if (ie && !ie_upto8) cm.display.inputHasSelection = null; + } + cm.display.inaccurateSelection = minimal; + } + + function focusInput(cm) { + if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input)) + cm.display.input.focus(); + } + + function ensureFocus(cm) { + if (!cm.state.focused) { focusInput(cm); onFocus(cm); } + } + + function isReadOnly(cm) { + return cm.options.readOnly || cm.doc.cantEdit; + } + + // EVENT HANDLERS + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie_upto10) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = findWordAt(cm.doc, pos); + extendSelection(cm.doc, word.anchor, word.head); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); + // Prevent normal selection in the editor (we handle our own) + on(d.lineSpace, "selectstart", function(e) { + if (!eventInWidget(d, e)) e_preventDefault(e); + }); + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function() { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + on(d.scrollbarV, "scroll", function() { + if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop); + }); + on(d.scrollbarH, "scroll", function() { + if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft); + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + + // Prevent clicks in the scrollbars from killing focus + function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); } + on(d.scrollbarH, "mousedown", reFocus); + on(d.scrollbarV, "mousedown", reFocus); + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + // When the window resizes, we need to refresh active editors. + var resizeTimer; + function onResize() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = knownScrollbarWidth = null; + cm.setSize(); + }, 100); + } + on(window, "resize", onResize); + // The above handler holds on to the editor and its data + // structures. Here we poll to unregister it when the editor is no + // longer in the document, so that it can be garbage-collected. + function unregister() { + if (contains(document.body, d.wrapper)) setTimeout(unregister, 5000); + else off(window, "resize", onResize); + } + setTimeout(unregister, 5000); + + on(d.input, "keyup", operation(cm, onKeyUp)); + on(d.input, "input", function() { + if (ie && !ie_upto8 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; + fastPoll(cm); + }); + on(d.input, "keydown", operation(cm, onKeyDown)); + on(d.input, "keypress", operation(cm, onKeyPress)); + on(d.input, "focus", bind(onFocus, cm)); + on(d.input, "blur", bind(onBlur, cm)); + + function drag_(e) { + if (!signalDOMEvent(cm, e)) e_stop(e); + } + if (cm.options.dragDrop) { + on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); + on(d.scroller, "dragenter", drag_); + on(d.scroller, "dragover", drag_); + on(d.scroller, "drop", operation(cm, onDrop)); + } + on(d.scroller, "paste", function(e) { + if (eventInWidget(d, e)) return; + cm.state.pasteIncoming = true; + focusInput(cm); + fastPoll(cm); + }); + on(d.input, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = d.input.selectionStart, end = d.input.selectionEnd; + d.input.value += "$"; + d.input.selectionStart = start; + d.input.selectionEnd = end; + cm.state.fakedLastChar = true; + } + cm.state.pasteIncoming = true; + fastPoll(cm); + }); + + function prepareCopyCut(e) { + if (cm.somethingSelected()) { + if (d.inaccurateSelection) { + d.prevInput = ""; + d.inaccurateSelection = false; + d.input.value = cm.getSelection(); + selectInput(d.input); + } + } else { + var text = "", ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text += cm.getRange(lineRange.anchor, lineRange.head); + } + if (e.type == "cut") { + cm.setSelections(ranges, null, sel_dontScroll); + } else { + d.prevInput = ""; + d.input.value = text; + selectInput(d.input); + } + } + if (e.type == "cut") cm.state.cutIncoming = true; + } + on(d.input, "cut", prepareCopyCut); + on(d.input, "copy", prepareCopyCut); + + // Needed to handle Tab key in KHTML + if (khtml) on(d.sizer, "mouseup", function() { + if (activeElt() == d.input) d.input.blur(); + focusInput(cm); + }); + } + + // MOUSE EVENTS + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true; + } + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal) { + var target = e_target(e); + if (target == display.scrollbarH || target == display.scrollbarV || + target == display.scrollbarFiller || target == display.gutterFiller) return null; + } + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null; } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords; + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + if (signalDOMEvent(this, e)) return; + var cm = this, display = cm.display; + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function(){display.scroller.draggable = true;}, 100); + } + return; + } + if (clickInGutter(cm, e)) return; + var start = posFromMouse(cm, e); + window.focus(); + + switch (e_button(e)) { + case 1: + if (start) + leftButtonDown(cm, e, start); + else if (e_target(e) == display.scroller) + e_preventDefault(e); + break; + case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; + if (start) extendSelection(cm.doc, start); + setTimeout(bind(focusInput, cm), 20); + e_preventDefault(e); + break; + case 3: + if (captureRightClick) onContextMenu(cm, e); + break; + } + } + + var lastClick, lastDoubleClick; + function leftButtonDown(cm, e, start) { + setTimeout(bind(ensureFocus, cm), 0); + + var now = +new Date, type; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple"; + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + } else { + type = "single"; + lastClick = {time: now, pos: start}; + } + + var sel = cm.doc.sel, addNew = mac ? e.metaKey : e.ctrlKey; + if (cm.options.dragDrop && dragAndDrop && !addNew && !isReadOnly(cm) && + type == "single" && sel.contains(start) > -1 && sel.somethingSelected()) + leftButtonStartDrag(cm, e, start); + else + leftButtonSelect(cm, e, start, type, addNew); + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, e, start) { + var display = cm.display; + var dragEnd = operation(cm, function(e2) { + if (webkit) display.scroller.draggable = false; + cm.state.draggingText = false; + off(document, "mouseup", dragEnd); + off(display.scroller, "drop", dragEnd); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + extendSelection(cm.doc, start); + focusInput(cm); + // Work around unexplainable focus problem in IE9 (#2127) + if (ie_upto10 && !ie_upto8) + setTimeout(function() {document.body.focus(); focusInput(cm);}, 20); + } + }); + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true; + cm.state.draggingText = dragEnd; + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop(); + on(document, "mouseup", dragEnd); + on(display.scroller, "drop", dragEnd); + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc; + e_preventDefault(e); + + var ourRange, ourIndex, startSel = doc.sel; + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + ourRange = doc.sel.ranges[ourIndex]; + else + ourRange = new Range(start, start); + } else { + ourRange = doc.sel.primary(); + } + + if (e.altKey) { + type = "rect"; + if (!addNew) ourRange = new Range(start, start); + start = posFromMouse(cm, e, true, true); + ourIndex = -1; + } else if (type == "double") { + var word = findWordAt(doc, start); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, word.anchor, word.head); + else + ourRange = word; + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, line.anchor, line.head); + else + ourRange = line; + } else { + ourRange = extendRange(doc, ourRange, start); + } + + if (!addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex > -1) { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } else { + ourIndex = doc.sel.ranges.length; + setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) return; + lastPos = pos; + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); + else if (text.length > leftPos) + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); + } + if (!ranges.length) ranges.push(new Range(start, start)); + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), sel_mouse); + } else { + var oldRange = ourRange; + var anchor = oldRange.anchor, head = pos; + if (type != "single") { + if (type == "double") + var range = findWordAt(doc, pos); + else + var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); + if (cmp(range.anchor, anchor) > 0) { + head = range.head; + anchor = minPos(oldRange.from(), range.anchor); + } else { + head = range.anchor; + anchor = maxPos(oldRange.to(), range.head); + } + } + var ranges = startSel.ranges.slice(0); + ranges[ourIndex] = new Range(clipPos(doc, anchor), head); + setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, type == "rect"); + if (!cur) return; + if (cmp(cur, lastPos) != 0) { + ensureFocus(cm); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) setTimeout(operation(cm, function() { + if (counter != curCount) return; + display.scroller.scrollTop += outside; + extend(e); + }), 50); + } + } + + function done(e) { + counter = Infinity; + e_preventDefault(e); + focusInput(cm); + off(document, "mousemove", move); + off(document, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function(e) { + if ((ie && !ie_upto9) ? !e.buttons : !e_button(e)) done(e); + else extend(e); + }); + var up = operation(cm, done); + on(document, "mousemove", move); + on(document, "mouseup", up); + } + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent, signalfn) { + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; + if (prevent) e_preventDefault(e); + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return; + e_preventDefault(e); + if (ie) lastDrop = +new Date; + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || isReadOnly(cm)) return; + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + var reader = new FileReader; + reader.onload = operation(cm, function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(bind(focusInput, cm), 20); + return; + } + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + var selected = cm.state.draggingText && cm.listSelections(); + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) for (var i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); + cm.replaceSelection(text, "around", "paste"); + focusInput(cm); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + + e.dataTransfer.setData("Text", cm.getSelection()); + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) img.parentNode.removeChild(img); + } + } + + // SCROLL EVENTS + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return; + cm.doc.scrollTop = val; + if (!gecko) updateDisplay(cm, {top: val}); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; + if (gecko) updateDisplay(cm); + startWorker(cm, 100); + } + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) wheelPixelsPerUnit = -.53; + else if (gecko) wheelPixelsPerUnit = 15; + else if (chrome) wheelPixelsPerUnit = -.7; + else if (safari) wheelPixelsPerUnit = -1/3; + + function onScrollWheel(cm, e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) return; + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer; + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy) + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); + e_preventDefault(e); + display.wheelStartX = null; // Abort measurement, if in progress + return; + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) top = Math.max(0, top + pixels - 50); + else bot = Math.min(cm.doc.height, bot + pixels + 50); + updateDisplay(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function() { + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) return; + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // KEY EVENTS + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; + var prevShift = cm.display.shift, done = false; + try { + if (isReadOnly(cm)) cm.state.suppressEdits = true; + if (dropShift) cm.display.shift = false; + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done; + } + + // Collect the currently active keymaps. + function allKeyMaps(cm) { + var maps = cm.state.keyMaps.slice(0); + if (cm.options.extraKeys) maps.push(cm.options.extraKeys); + maps.push(cm.options.keyMap); + return maps; + } + + var maybeTransition; + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + // Handle automatic keymap transitions + var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { + if (getKeyMap(cm.options.keyMap) == startMap) { + cm.options.keyMap = (next.call ? next.call(null, cm) : next); + keyMapChanged(cm); + } + }, 50); + + var name = keyName(e, true), handled = false; + if (!name) return false; + var keymaps = allKeyMaps(cm); + + if (e.shiftKey) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);}) + || lookupKey(name, keymaps, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); + } else { + handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); + } + + if (handled) { + e_preventDefault(e); + restartBlink(cm); + signalLater(cm, "keyHandled", cm, name, e); + } + return handled; + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + var handled = lookupKey("'" + ch + "'", allKeyMaps(cm), + function(b) { return doHandleBinding(cm, b, true); }); + if (handled) { + e_preventDefault(e); + restartBlink(cm); + signalLater(cm, "keyHandled", cm, "'" + ch + "'", e); + } + return handled; + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + ensureFocus(cm); + if (signalDOMEvent(cm, e)) return; + // IE does strange things with escape. + if (ie_upto10 && e.keyCode == 27) e.returnValue = false; + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection("", null, "cut"); + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm); + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (signalDOMEvent(this, e)) return; + if (e.keyCode == 16) this.doc.sel.shift = false; + } + + function onKeyPress(e) { + var cm = this; + if (signalDOMEvent(cm, e)) return; + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (handleCharBinding(cm, e, ch)) return; + if (ie && !ie_upto8) cm.display.inputHasSelection = null; + fastPoll(cm); + } + + // FOCUS/BLUR EVENTS + + function onFocus(cm) { + if (cm.options.readOnly == "nocursor") return; + if (!cm.state.focused) { + signal(cm, "focus", cm); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // The prevInput test prevents this from firing when a context + // menu is closed (since the resetInput would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu == cm.doc.sel) { + resetInput(cm); + if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730 + } + } + slowPoll(cm); + restartBlink(cm); + } + function onBlur(cm) { + if (cm.state.focused) { + signal(cm, "blur", cm); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); + } + + // CONTEXT MENU HANDLING + + var detectingSelectAll; + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (signalDOMEvent(cm, e, "contextmenu")) return; + var display = cm.display; + if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return; + + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) return; // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); + + var oldCSS = display.input.style.cssText; + display.inputDiv.style.position = "absolute"; + display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + focusInput(cm); + resetInput(cm); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) display.input.value = display.prevInput = " "; + display.selForContextMenu = cm.doc.sel; + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (display.input.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = display.input.value = "\u200b" + (selected ? display.input.value : ""); + display.prevInput = selected ? "" : "\u200b"; + display.input.selectionStart = 1; display.input.selectionEnd = extval.length; + } + } + function rehide() { + display.inputDiv.style.position = "relative"; + display.input.style.cssText = oldCSS; + if (ie_upto8) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; + slowPoll(cm); + + // Try to detect the user choosing select-all + if (display.input.selectionStart != null) { + if (!ie || ie_upto8) prepareSelectAllHack(); + clearTimeout(detectingSelectAll); + var i = 0, poll = function() { + if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0) + operation(cm, commands.selectAll)(cm); + else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); + else resetInput(cm); + }; + detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && !ie_upto8) prepareSelectAllHack(); + if (captureRightClick) { + e_stop(e); + var mouseup = function() { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + // UPDATING + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); + }; + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) return pos; + if (cmp(pos, change.to) <= 0) return changeEnd(change); + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; + return Pos(line, ch); + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(out, doc.sel.primIndex); + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + return Pos(nw.line, pos.ch - old.ch + nw.ch); + else + return Pos(nw.line + (pos.line - old.line), pos.ch); + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex); + } + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function() { this.canceled = true; } + }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; + signal(doc, "beforeChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); + + if (obj.canceled) return null; + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); + if (doc.cm.state.suppressEdits) return; + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) return; + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits) return; + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + for (var i = 0; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + break; + } + if (i == source.length) return; + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return; + } + selAfter = event; + } + else break; + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + for (var i = event.changes.length - 1; i >= 0; --i) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return; + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change, null) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (doc.cm) ensureCursorVisible(doc.cm); + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function(range) { + return new Range(Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch)); + }), doc.sel.primIndex); + if (doc.cm) regChange(doc.cm, doc.first, doc.first - distance, distance); + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return; + } + if (change.from.line > doc.lastLine()) return; + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); + else updateDoc(doc, change, spans); + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function(line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true; + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + signalCursorActivity(cm); + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function(line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) cm.curOp.updateMaxLine = true; + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + regLineChange(cm, from.line, "text"); + else + regChange(cm, from.line, to.line + 1, lendiff); + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) signalLater(cm, "change", cm, obj); + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); + } + } + + function replaceRange(doc, code, from, to, origin) { + if (!to) to = from; + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } + if (typeof code == "string") code = splitLines(code); + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, coords) { + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + + (coords.bottom - coords.top + scrollerCutOff) + "px; left: " + + coords.left + "px; width: 2px;"); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0; + for (;;) { + var changed = false, coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) return coords; + } + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = display.scroller.clientHeight - scrollerCutOff, result = {}; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = display.scroller.clientWidth - scrollerCutOff; + x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; + var gutterw = display.gutters.offsetWidth; + var atLeft = x1 < gutterw + 10; + if (x1 < screenleft + gutterw || atLeft) { + if (atLeft) x1 = 0; + result.scrollLeft = Math.max(0, x1 - 10 - gutterw); + } else if (x2 > screenw + screenleft - 3) { + result.scrollLeft = x2 + 10 - screenw; + } + return result; + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollPos(cm, left, top) { + if (left != null || top != null) resolveScrollToPos(cm); + if (left != null) + cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; + if (top != null) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(), from = cur, to = cur; + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; + to = Pos(cur.line, cur.ch + 1); + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos; + if (range) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); + var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), + Math.min(from.top, to.top) - range.margin, + Math.max(from.right, to.right), + Math.max(from.bottom, to.bottom) + range.margin); + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + } + + // API UTILITIES + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) how = "add"; + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!cm.doc.mode.indent) how = "prev"; + else state = getStateBefore(cm, n); + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) line.stateAfter = null; + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass) { + if (!aggressive) return; + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); + else indentation = 0; + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) { + replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i, new Range(pos, pos)); + break; + } + } + } + line.stateAfter = null; + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(cm, handle, changeType, op) { + var no = handle, line = handle, doc = cm.doc; + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no)) regLineChange(cm, no, changeType); + return line; + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break; + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function() { + for (var i = kill.length - 1; i >= 0; i--) + replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); + ensureCursorVisible(cm); + }); + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir; + var lineObj = getLine(doc, line); + var possible = true; + function findNextLine() { + var l = line + dir; + if (l < doc.first || l >= doc.first + doc.size) return (possible = false); + line = l; + return lineObj = getLine(doc, l); + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); + else ch = dir < 0 ? lineObj.text.length : 0; + } else return (possible = false); + } else ch = next; + return true; + } + + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) type = "s"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; + } + } + var result = skipAtomic(doc, Pos(line, ch), origDir, true); + if (!possible) result.hitSide = true; + return result; + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + for (;;) { + var target = coordsChar(cm, x, y); + if (!target.outside) break; + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } + y += dir * 5; + } + return target; + } + + // Find the word at the given position (as returned by coordsChar). + function findWordAt(doc, pos) { + var line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar) ? isWordChar + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)); + } + + // EDITOR METHODS + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); focusInput(this); fastPoll(this);}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") return; + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old); + }, + + getOption: function(option) {return this.options[option];}, + getDoc: function() {return this.doc;}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](map); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) { + maps.splice(i, 1); + return true; + } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return; + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); + }), + indentSelection: methodOp(function(how) { + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!range.empty()) { + var start = Math.max(end, range.from().line); + var to = range.to(); + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + indentLine(this, j, how); + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true); + end = range.head.line; + if (i == this.doc.sel.primIndex) ensureCursorVisible(this); + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + var doc = this.doc; + pos = clipPos(doc, pos); + var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode; + var line = getLine(doc, pos.line); + var stream = new StringStream(line.text, this.options.tabSize); + while (stream.pos < pos.ch && !stream.eol()) { + stream.start = stream.pos; + var style = readToken(mode, stream, state); + } + return {start: stream.start, + end: stream.pos, + string: stream.current(), + type: style || null, + state: state}; + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) type = styles[2]; + else for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else { type = styles[mid * 2 + 2]; break; } + } + var cut = type ? type.indexOf("cm-overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return helpers; + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getStateBefore(this, line + 1, precise); + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary(); + if (start == null) pos = range.head; + else if (typeof start == "object") pos = clipPos(this.doc, start); + else pos = start ? range.from() : range.to(); + return cursorCoords(this, pos, mode || "page"); + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page"); + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + var lineObj = getLine(this.doc, line); + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + + (end ? this.doc.height - heightAtLine(lineObj) : 0); + }, + + defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, + + setGutterMarker: methodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", function(line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) line.gutterMarkers = null; + return true; + }); + }), + + clearGutter: methodOp(function(gutterID) { + var cm = this, doc = cm.doc, i = doc.first; + doc.iter(function(line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + line.gutterMarkers[gutterID] = null; + regLineChange(cm, i, "gutter"); + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; + } + ++i; + }); + }), + + addLineClass: methodOp(function(handle, where, cls) { + return changeLine(this, handle, "class", function(line) { + var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + + removeLineClass: methodOp(function(handle, where, cls) { + return changeLine(this, handle, "class", function(line) { + var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)")); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + + addLineWidget: methodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + + removeLineWidget: function(widget) { widget.clear(); }, + + lineInfo: function(line) { + if (typeof line == "number") { + if (!isLine(this.doc, line)) return null; + var n = line; + line = getLine(this.doc, line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets}; + }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight; + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = left + "px"; + } + if (scroll) + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: methodOp(onKeyUp), + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd](this); + }, + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) break; + } + return cur; + }, + + moveH: methodOp(function(dir, unit) { + var cm = this; + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); + else + return dir < 0 ? range.from() : range.to(); + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete"); + else + deleteNearSelection(this, function(range) { + var other = findPosH(doc, range.head, dir, unit, false); + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; + }); + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) x = coords.left; + else coords.left = x; + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) break; + } + return cur; + }, + + moveV: methodOp(function(dir, unit) { + var cm = this, doc = this.doc, goals = []; + var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function(range) { + if (collapse) + return dir < 0 ? range.from() : range.to(); + var headPos = cursorCoords(cm, range.head, "div"); + if (range.goalColumn != null) headPos.left = range.goalColumn; + goals.push(headPos.left); + var pos = findPosV(cm, headPos, dir, unit); + if (unit == "page" && range == doc.sel.primary()) + addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); + return pos; + }, sel_move); + if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i]; + }), + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite"); + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return activeElt() == this.display.input; }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) resolveScrollToPos(this); + if (x != null) this.curOp.scrollLeft = x; + if (y != null) this.curOp.scrollTop = y; + }), + getScrollInfo: function() { + var scroller = this.display.scroller, co = scrollerCutOff; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, + clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) margin = this.options.cursorScrollMargin; + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null}; + } else if (range.from == null) { + range = {from: range, to: null}; + } + if (!range.to) range.to = range.from; + range.margin = margin || 0; + + if (range.from.line != null) { + resolveScrollToPos(this); + this.curOp.scrollToPos = range; + } else { + var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), + Math.min(range.from.top, range.to.top) - range.margin, + Math.max(range.from.right, range.to.right), + Math.max(range.from.bottom, range.to.bottom) + range.margin); + this.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + }), + + setSize: methodOp(function(width, height) { + function interpret(val) { + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; + } + if (width != null) this.display.wrapper.style.width = interpret(width); + if (height != null) this.display.wrapper.style.height = interpret(height); + if (this.options.lineWrapping) clearLineMeasurementCache(this); + this.curOp.forceUpdate = true; + signal(this, "refresh", this); + }), + + operation: function(f){return runInOp(this, f);}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this); + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + resetInput(this); + this.scrollTo(doc.scrollLeft, doc.scrollTop); + signalLater(this, "swapDoc", this, old); + return old; + }), + + getInputField: function(){return this.display.input;}, + getWrapperElement: function(){return this.display.wrapper;}, + getScrollerElement: function(){return this.display.scroller;}, + getGutterElement: function(){return this.display.gutters;} + }; + eventMixin(CodeMirror); + + // OPTION DEFAULTS + + // The default configuration options. + var defaults = CodeMirror.defaults = {}; + // Functions to run when options are changed. + var optionHandlers = CodeMirror.optionHandlers = {}; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) optionHandlers[name] = + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; + } + + // Passed to option handlers when there is no old value. + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function(cm, val) { + cm.setValue(val); + }, true); + option("mode", null, function(cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function(cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) { + cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + cm.refresh(); + }, true); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); + option("electricChars", true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function(cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", keyMapChanged); + option("extraKeys", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, updateScrollbars, true); + option("lineNumbers", false, function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + + option("readOnly", false, function(cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + if (!val) resetInput(cm); + } + }); + option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true); + option("dragDrop", true); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; + }); + + option("tabindex", null, function(cm, val) { + cm.display.input.tabIndex = val || ""; + }); + option("autofocus", null); + + // MODE DEFINITION AND QUERYING + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } + modes[name] = mode; + }; + + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") found = {name: found}; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return CodeMirror.resolveMode("application/xml"); + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) continue; + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; + + return modeObj; + }; + + // Minimal default mode. + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + }; + + // EXTENSIONS + + CodeMirror.defineExtension = function(name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; + CodeMirror.defineOption = option; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + + // MODE STATE HANDLING + + // Utility functions for working with state. Exported because nested + // modes need to do this for their inner modes. + + var copyState = CodeMirror.copyState = function(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + }; + + var startState = CodeMirror.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + }; + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // STANDARD COMMANDS + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, + singleSelection: function(cm) { + cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); + }, + killLine: function(cm) { + deleteNearSelection(cm, function(range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + return {from: range.head, to: Pos(range.head.line + 1, 0)}; + else + return {from: range.head, to: Pos(range.head.line, len)}; + } else { + return {from: range.from(), to: range.to()}; + } + }); + }, + deleteLine: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; + }); + }, + delLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), to: range.from()}; + }); + }, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + undoSelection: function(cm) {cm.undoSelection();}, + redoSelection: function(cm) {cm.redoSelection();}, + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, + goLineStart: function(cm) { + cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, sel_move); + }, + goLineStartSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var start = lineStart(cm, range.head.line); + var line = cm.getLineHandle(start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = range.head.line == start.line && range.head.ch <= firstNonWS && range.head.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + }, sel_move); + }, + goLineEnd: function(cm) { + cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, sel_move); + }, + goLineRight: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + }, sel_move); + }, + goLineLeft: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div"); + }, sel_move); + }, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharBefore: function(cm) {cm.deleteH(-1, "char");}, + delCharAfter: function(cm) {cm.deleteH(1, "char");}, + delWordBefore: function(cm) {cm.deleteH(-1, "word");}, + delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t");}, + insertSoftTab: function(cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); + } + cm.replaceSelections(spaces); + }, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.execCommand("insertTab"); + }, + transposeChars: function(cm) { + runInOp(cm, function() { + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (cur.ch > 0 && cur.ch < line.length - 1) + cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), + Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1)); + } + }); + }, + newlineAndIndent: function(cm) { + runInOp(cm, function() { + var len = cm.listSelections().length; + for (var i = 0; i < len; i++) { + var range = cm.listSelections()[i]; + cm.replaceRange("\n", range.anchor, range.head, "+input"); + cm.indentLine(range.from().line + 1, null, true); + ensureCursorVisible(cm); + } + }); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + // STANDARD KEYMAPS + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", + fallthrough: ["basic", "emacsy"] + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + + // Given an array of keymaps and a key name, call handle on any + // bindings found, until that returns a truthy value, at which point + // we consider the key handled. Implements things like binding a key + // to false stopping further handling and keymap fallthrough. + var lookupKey = CodeMirror.lookupKey = function(name, maps, handle) { + function lookup(map) { + map = getKeyMap(map); + var found = map[name]; + if (found === false) return "stop"; + if (found != null && handle(found)) return true; + if (map.nofallthrough) return "stop"; + + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0; i < fallthrough.length; ++i) { + var done = lookup(fallthrough[i]); + if (done) return done; + } + return false; + } + + for (var i = 0; i < maps.length; ++i) { + var done = lookup(maps[i]); + if (done) return done != "stop"; + } + }; + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + var isModifierKey = CodeMirror.isModifierKey = function(event) { + var name = keyNames[event.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + }; + + // Look up the name of a key as indicated by an event object. + var keyName = CodeMirror.keyName = function(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false; + var name = keyNames[event.keyCode]; + if (name == null || event.altGraphKey) return false; + if (event.altKey) name = "Alt-" + name; + if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name; + if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name; + if (!noShift && event.shiftKey) name = "Shift-" + name; + return name; + }; + + // FROMTEXTAREA + + CodeMirror.fromTextArea = function(textarea, options) { + if (!options) options = {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabindex) + options.tabindex = textarea.tabindex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + textarea.style.display = "none"; + var cm = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + return cm; + }; + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = CodeMirror.StringStream = function(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + }; + + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == this.lineStart;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } + }; + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + var TextMarker = CodeMirror.TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + }; + eventMixin(TextMarker); + + // Clear the marker. + TextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); + else if (cm) { + if (span.to != null) max = lineNo(line); + if (span.from != null) min = lineNo(line); + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)); + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(this.lines[i]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) reCheckSelection(cm.doc); + } + if (cm) signalLater(cm, "markerCleared", cm, this); + if (withOp) endOperation(cm); + if (this.parent) this.parent.clear(); + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1; + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) return from; + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) return to; + } + } + return from && {from: from, to: to}; + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function() { + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) return; + runInOp(cm, function() { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + updateLineHeight(line, line.height + dHeight); + } + }); + }; + + TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); + } + this.lines.push(line); + }; + TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) return markTextShared(doc, from, to, options, type); + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) copyObj(options, marker, false); + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + return marker; + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.widgetNode.ignoreEvents = true; + if (options.insertLeft) marker.widgetNode.insertLeft = true; + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one"); + sawCollapsedSpans = true; + } + + if (marker.addToHistory) + addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function(line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + updateMaxLine = true; + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0); + }); + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + + if (marker.readOnly) { + sawReadOnlySpans = true; + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory(); + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) cm.curOp.updateMaxLine = true; + if (marker.collapsed) + regChange(cm, from.line, to.line + 1); + else if (marker.className || marker.title || marker.startStyle || marker.endStyle) + for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); + if (marker.atomic) reCheckSelection(cm.doc); + signalLater(cm, "markerAdded", cm, marker); + } + return marker; + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + markers[i].parent = this; + }; + eventMixin(SharedTextMarker); + + SharedTextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + this.markers[i].clear(); + signalLater(this, "clear"); + }; + SharedTextMarker.prototype.find = function(side, lineObj) { + return this.primary.find(side, lineObj); + }; + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function(doc) { + if (widget) options.widgetNode = widget.cloneNode(true); + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return; + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary); + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), + function(m) { return m.parent; }); + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], linked = [marker.primary.doc];; + linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + } + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } + return nw; + } + function markedSpansAfter(old, endCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } + return nw; + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) return null; + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first); + if (last && last != first) last = clearEmptySpans(last); + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); + for (var i = 0; i < gap; ++i) + newMarkers.push(gapMarkers); + newMarkers.push(last); + } + return newMarkers; + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1); + } + if (!spans.length) return null; + return spans; + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) return stretched; + if (!stretched) return old; + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans; + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old; + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function(line) { + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark); + } + }); + if (!markers) return null; + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}); + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}); + parts.splice.apply(parts, newParts); + j += newParts.length - 1; + } + } + return parts; + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line); + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line); + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) return lenDiff; + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) return -fromCmp; + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) return toCmp; + return b.id - a.id; + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker; + } + return found; + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) continue; + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; + if (fromCmp <= 0 && (cmp(found.to, from) || extraRight(sp.marker) - extraLeft(marker)) > 0 || + fromCmp >= 0 && (cmp(found.from, to) || extraLeft(sp.marker) - extraRight(marker)) < 0) + return true; + } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line; + return line; + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + (lines || (lines = [])).push(line); + } + return lines; + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) return lineN; + return lineNo(vis); + } + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN; + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) return lineN; + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line; + return lineNo(line) + 1; + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if (sp.from == null) return true; + if (sp.marker.widgetNode) continue; + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true; + } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true; + for (var sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true; + } + } + + // LINE WIDGETS + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.cm = cm; + this.node = node; + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + addToScrollPos(cm, null, diff); + } + + LineWidget.prototype.clear = function() { + var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + if (!ws.length) line.widgets = null; + var height = widgetHeight(this); + runInOp(cm, function() { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + updateLineHeight(line, Math.max(0, line.height - height)); + }); + }; + LineWidget.prototype.changed = function() { + var oldH = this.height, cm = this.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + runInOp(cm, function() { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + updateLineHeight(line, line.height + diff); + }); + }; + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + if (!contains(document.body, widget.node)) + removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative")); + return widget.height = widget.node.offsetHeight; + } + + function addLineWidget(cm, handle, node, options) { + var widget = new LineWidget(cm, node, options); + if (widget.noHScroll) cm.display.alignWidgets = true; + changeLine(cm, handle, "widget", function(line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); + widget.line = line; + if (!lineIsHidden(cm.doc, line)) { + var aboveVisible = heightAtLine(line) < cm.doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) addToScrollPos(cm, null, widget.height); + cm.curOp.forceUpdate = true; + } + return true; + }); + return widget; + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) updateLineHeight(line, estHeight); + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + function extractLineClasses(type, output) { + if (type) for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) break; + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + output[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2]; + } + return type; + } + + function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state); + if (!mode.innerMode) return; + var inner = CodeMirror.innerMode(mode, state); + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); + } + + function readToken(mode, stream, state) { + var style = mode.token(stream, state); + if (stream.pos <= stream.start) + throw new Error("Mode " + mode.name + " failed to advance stream."); + return style; + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; + if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, state), lineClasses); + } + if (cm.options.addModeClass) { + var mName = CodeMirror.innerMode(mode, state).mode.name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } + if (!flattenSpans || curStyle != style) { + if (curStart < stream.start) f(stream.start, curStyle); + curStart = stream.start; curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } + } + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, lineClasses, forceToEnd); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.state.overlays.length; ++o) { + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); + i += 2; + at = Math.min(end, i_end); + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, end, "cm-overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; + } + } + }, lineClasses); + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; + } + + function getLineStyles(cm, line) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + line.styles = result.styles; + if (result.classes) line.styleClasses = result.classes; + else if (line.styleClasses) line.styleClasses = null; + } + return line.styles; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "") callBlankLine(mode, state); + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { + readToken(mode, stream, state); + stream.start = stream.pos; + } + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null; + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")); + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order; + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if ((ie || webkit) && cm.getOption("lineWrapping")) + builder.addToken = buildTokenSplitSpaces(builder.addToken); + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) + builder.addToken = buildTokenBadBidi(builder.addToken, order); + builder.map = []; + insertLineContent(line, builder, getLineStyles(cm, line)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); + (lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + return token; + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, title) { + if (!text) return; + var special = builder.cm.options.specialChars, mustWrap = false; + if (!special.test(text)) { + builder.col += text.length; + var content = document.createTextNode(text); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie_upto8) mustWrap = true; + builder.pos += text.length; + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(text.slice(pos, pos + skipped)); + if (ie_upto8) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + builder.col += tabWidth; + } else { + var txt = builder.cm.options.specialCharPlaceholder(m[0]); + if (ie_upto8) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt); + builder.pos++; + } + } + if (style || startStyle || endStyle || mustWrap) { + var fullStyle = style || ""; + if (startStyle) fullStyle += startStyle; + if (endStyle) fullStyle += endStyle; + var token = elt("span", [content], fullStyle); + if (title) token.title = title; + return builder.content.appendChild(token); + } + builder.content.appendChild(content); + } + + function buildTokenSplitSpaces(inner) { + function split(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; + } + return function(builder, text, style, startStyle, endStyle, title) { + inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title); + }; + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function(builder, text, style, startStyle, endStyle, title) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + for (var i = 0; i < order.length; i++) { + var part = order[i]; + if (part.to > start && part.from <= start) break; + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title); + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) { + builder.map.push(builder.pos, builder.pos + size, widget); + builder.content.appendChild(widget); + } + builder.pos += size; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); + return; + } + + var len = allText.length, pos = 0, i = 1, text = "", style; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = ""; + collapsed = null; nextChange = Infinity; + var foundBookmarks = []; + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (sp.from <= pos && (sp.to == null || sp.to > pos)) { + if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } + if (m.className) spanStyle += " " + m.className; + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; + if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp; + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m); + } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) return; + } + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); + } + if (pos >= len) break; + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore); + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + for (var i = 0, added = []; i < text.length - 1; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + update(lastLine, lastLine.text, lastSpans); + if (nlines) doc.remove(from.line, nlines); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + for (var added = [], i = 1; i < text.length - 1; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + for (var i = 1, added = []; i < text.length - 1; ++i) + added.push(new Line(text[i], spansFor(i), estimateHeight)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1); + doc.insert(from.line + 1, added); + } + + signalLater(doc, "change", doc, change); + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, height = 0; i < lines.length; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) lines[i].parent = this; + }, + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); + }, + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iterN: function(at, n, op) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + var nextDocId = 0; + var Doc = CodeMirror.Doc = function(text, mode, firstLine) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); + if (firstLine == null) firstLine = 0; + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.frontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + + if (typeof text == "string") text = splitLines(text); + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op); + else this.iterN(this.first, this.first + this.size, from); + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) height += lines[i].height; + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: splitLines(code), origin: "setValue"}, true); + setSelection(this, simpleSelection(top)); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, + getLineNumber: function(line) {return lineNo(line);}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(line); + }, + + lineCount: function() {return this.size;}, + firstLine: function() {return this.first;}, + lastLine: function() {return this.first + this.size - 1;}, + + clipPos: function(pos) {return clipPos(this, pos);}, + + getCursor: function(start) { + var range = this.sel.primary(), pos; + if (start == null || start == "head") pos = range.head; + else if (start == "anchor") pos = range.anchor; + else if (start == "end" || start == "to" || start === false) pos = range.to(); + else pos = range.from(); + return pos; + }, + listSelections: function() { return this.sel.ranges; }, + somethingSelected: function() {return this.sel.somethingSelected();}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads, options)); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + extendSelections(this, map(this.sel.ranges, f), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return; + for (var i = 0, out = []; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)); + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); + setSelection(this, normalizeSelection(out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) return lines; + else return lines.join(lineSep || "\n"); + }, + getSelections: function(lineSep) { + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) sel = sel.join(lineSep || "\n"); + parts[i] = sel; + } + return parts; + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + dup[i] = code; + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]); + if (newSel) setSelectionReplaceHistory(this, newSel); + else if (this.cm) ensureCursorVisible(this.cm); + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend;}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; + for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; + return {undo: done, redo: undone}; + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)}; + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark"); + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker); + } + return markers; + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo = from.line; + this.iter(from.line, to.line + 1, function(line) { + var spans = line.markedSpans; + if (spans) for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(lineNo == from.line && from.ch > span.to || + span.from == null && lineNo != from.line|| + lineNo == to.line && span.from > to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker); + } + ++lineNo; + }); + return found; + }, + getAllMarks: function() { + var markers = []; + this.iter(function(line) { + var sps = line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker); + }); + return markers; + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first; + this.iter(function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)); + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) return 0; + this.iter(this.first, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc; + }, + + linkedDoc: function(options) { + if (!options) options = {}; + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) from = options.from; + if (options.to != null && options.to < to) to = options.to; + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); + if (options.sharedHist) copy.history = this.history; + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy; + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc; + if (this.linked) for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) continue; + this.linked.splice(i, 1); + other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); + break; + } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode;}, + getEditor: function() {return this.cm;} + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor".split(" "); + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments);}; + })(Doc.prototype[prop]); + + eventMixin(Doc); + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) continue; + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) continue; + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use."); + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + if (!cm.options.lineWrapping) findMaxLine(cm); + cm.options.mode = doc.modeOption; + regChange(cm); + } + + // LINE UTILITIES + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); + for (var chunk = doc; !chunk.lines;) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function(line) { + var text = line.text; + if (n == end.line) text = text.slice(0, end.ch); + if (n == start.line) text = text.slice(start.ch); + out.push(text); + ++n; + }); + return out; + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function(line) { out.push(line.text); }); + return out; + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) for (var n = line; n; n = n.parent) n.height += diff; + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first; + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i = 0; i < chunk.children.length; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) break; + else h += line.height; + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i = 0; i < p.children.length; ++i) { + var cur = p.children[i]; + if (cur == chunk) break; + else h += cur.height; + } + } + return h; + } + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line) { + var order = line.order; + if (order == null) order = line.order = bidiOrdering(line.text); + return order; + } + + // HISTORY + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); + return histChange; + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) array.pop(); + else break; + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done); + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done); + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done); + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, ore are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + var last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done); + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) hist.done.shift(); + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) signal(doc, "historyAdded"); + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel; + else + pushSelectionToHistory(sel, hist.done); + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastOp = opId; + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone); + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel); + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) return null; + for (var i = 0, out; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) return null; + for (var i = 0, nw = []; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])); + return nw; + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + for (var i = 0, copy = []; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue; + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m; + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } + } + } + return copy; + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue; + } + for (var j = 0; j < sub.changes.length; ++j) { + var cur = sub.changes[j]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break; + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // EVENT UTILITIES + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + var e_preventDefault = CodeMirror.e_preventDefault = function(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + }; + var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + }; + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } + var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var on = CodeMirror.on = function(emitter, type, f) { + if (emitter.addEventListener) + emitter.addEventListener(type, f, false); + else if (emitter.attachEvent) + emitter.attachEvent("on" + type, f); + else { + var map = emitter._handlers || (emitter._handlers = {}); + var arr = map[type] || (map[type] = []); + arr.push(f); + } + }; + + var off = CodeMirror.off = function(emitter, type, f) { + if (emitter.removeEventListener) + emitter.removeEventListener(type, f, false); + else if (emitter.detachEvent) + emitter.detachEvent("on" + type, f); + else { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + for (var i = 0; i < arr.length; ++i) + if (arr[i] == f) { arr.splice(i, 1); break; } + } + }; + + var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); + }; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + var delayedCallbacks, delayedCallbackDepth = 0; + function signalLater(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + if (!delayedCallbacks) { + ++delayedCallbackDepth; + delayedCallbacks = []; + setTimeout(fireDelayed, 0); + } + function bnd(f) {return function(){f.apply(null, args);};}; + for (var i = 0; i < arr.length; ++i) + delayedCallbacks.push(bnd(arr[i])); + } + + function fireDelayed() { + --delayedCallbackDepth; + var delayed = delayedCallbacks; + delayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) return; + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]); + } + + function hasHandler(emitter, type) { + var arr = emitter._handlers && emitter._handlers[type]; + return arr && arr.length > 0; + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // MISC UTILITIES + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerCutOff = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + function Delayed() {this.id = null;} + Delayed.prototype.set = function(ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + return n + (end - i); + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + }; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) nextTab = string.length; + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + return pos + Math.min(skipped, goal - col); + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) return pos; + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; + else if (ie) // Suppress mysterious IE10 errors + selectInput = function(node) { try { node.select(); } catch(_e) {} }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + if (array[i] == elt) return i; + return -1; + } + if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); }; + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); + return out; + } + if ([].map) map = function(array, f) { return array.map(f); }; + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + var ctor = function() {}; + ctor.prototype = base; + inst = new ctor(); + } + if (props) copyObj(props, inst); + return inst; + }; + + function copyObj(obj, target, overwrite) { + if (!target) target = {}; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; + return target; + } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args);}; + } + + var nonASCIISingleCaseWordChar = /[\u00df\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var isWordChar = CodeMirror.isWordChar = function(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); + }; + + function isEmpty(obj) { + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; + return true; + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } + + // DOM UTILITIES + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + var range; + if (document.createRange) range = function(node, start, end) { + var r = document.createRange(); + r.setEnd(node, end); + r.setStart(node, start); + return r; + }; + else range = function(node, start, end) { + var r = document.body.createTextRange(); + r.moveToElementText(node.parentNode); + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r; + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); + return e; + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e); + } + + function contains(parent, child) { + if (parent.contains) + return parent.contains(child); + while (child = child.parentNode) + if (child == parent) return true; + } + + function activeElt() { return document.activeElement; } + // Older versions of IE throws unspecified error when touching + // document.activeElement in some cases (during loading, in iframe) + if (ie_upto10) activeElt = function() { + try { return document.activeElement; } + catch(e) { return document.body; } + }; + + function classTest(cls) { return new RegExp("\\b" + cls + "\\b\\s*"); } + function rmClass(node, cls) { + var test = classTest(cls); + if (test.test(node.className)) node.className = node.className.replace(test, ""); + } + function addClass(node, cls) { + if (!classTest(cls).test(node.className)) node.className += " " + cls; + } + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; + return b; + } + + // FEATURE DETECTION + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie_upto8) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + var knownScrollbarWidth; + function scrollbarWidth(measure) { + if (knownScrollbarWidth != null) return knownScrollbarWidth; + var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll"); + removeChildrenAndAdd(measure, test); + if (test.offsetWidth) + knownScrollbarWidth = test.offsetHeight - test.clientHeight; + return knownScrollbarWidth || 0; + } + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_upto7; + } + if (zwspSupported) return elt("span", "\u200b"); + else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects; + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + if (r0.left == r0.right) return false; + var r1 = range(txt, 1, 2).getBoundingClientRect(); + return badBidiRects = (r1.right - r0.right < 3); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + var hasCopyEvent = (function() { + var e = elt("div"); + if ("oncopy" in e) return true; + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function"; + })(); + + // KEY NAMES + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } + } + if (!found) f(from, to, "ltr"); + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return bidiRight(lst(order)); + } + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) lineN = lineNo(visual); + var order = getOrder(visual); + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); + return Pos(lineN, ch); + } + function lineEnd(cm, lineN) { + var merged, line = getLine(cm.doc, lineN); + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + lineN = null; + } + var order = getOrder(line); + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); + return Pos(lineN == null ? lineNo(line) : lineN, ch); + } + + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + bidiOther = null; + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + if (cur.from != cur.to) bidiOther = found; + return i; + } else { + if (cur.from != cur.to) bidiOther = i; + return found; + } + } + } + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); + return pos; + } + + // This is needed in order to move 'visually' through bi-directional + // text -- i.e., pressing left should make the cursor go left, even + // when in RTL text. The tricky part is the 'jumps', where RTL and + // LTR text touch each other. This often requires the cursor offset + // to move more than one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); + + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; + } else { + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); + } + } + } + + function moveLogically(line, start, dir, byUnit) { + var target = start + dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; + return target < 0 || target > line.text.length ? null : target; + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6ff + var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code); + else if (0x590 <= code && code <= 0x5f4) return "R"; + else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); + else if (0x6ee <= code && code <= 0x8ac) return "r"; + else if (0x2000 <= code && code <= 0x200b) return "w"; + else if (code == 0x200c) return "b"; + else return "L"; + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str) { + if (!bidiRE.test(str)) return false; + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) + types.push(type = charType(str.charCodeAt(i))); + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i = 0, prev = outerType; i < len; ++i) { + var type = types[i]; + if (type == "m") types[i] = prev; + else prev = type; + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (type == "1" && cur == "r") types[i] = "n"; + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i = 1, prev = types[0]; i < len - 1; ++i) { + var type = types[i]; + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev; + prev = type; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i = 0; i < len; ++i) { + var type = types[i]; + if (type == ",") types[i] = "N"; + else if (type == "%") { + for (var end = i + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (cur == "L" && type == "1") types[i] = "L"; + else if (isStrong.test(type)) cur = type; + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; + var replace = before || after ? "L" : "R"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + var start = i; + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)); + } else { + var pos = i, at = order.length; + for (++i; i < len && types[i] != "L"; ++i) {} + for (var j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); + var nstart = j; + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)); + pos = j; + } else ++j; + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + if (order[0].level != lst(order).level) + order.push(new BidiSpan(order[0].level, len, len)); + + return order; + }; + })(); + + // THE END + + CodeMirror.version = "4.1.0"; + + return CodeMirror; +}); diff --git a/ethereal/assets/muted/lib/go.js b/ethereal/assets/muted/lib/go.js new file mode 100644 index 000000000..9f1c1c4ab --- /dev/null +++ b/ethereal/assets/muted/lib/go.js @@ -0,0 +1,182 @@ +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("go", function(config) { + var indentUnit = config.indentUnit; + + var keywords = { + "break":true, "case":true, "chan":true, "const":true, "continue":true, + "default":true, "defer":true, "else":true, "fallthrough":true, "for":true, + "func":true, "go":true, "goto":true, "if":true, "import":true, + "interface":true, "map":true, "package":true, "range":true, "return":true, + "select":true, "struct":true, "switch":true, "type":true, "var":true, + "bool":true, "byte":true, "complex64":true, "complex128":true, + "float32":true, "float64":true, "int8":true, "int16":true, "int32":true, + "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true, + "uint64":true, "int":true, "uint":true, "uintptr":true, "big": true, + "main": true, "init": true, "this":true + }; + + var atoms = { + "true":true, "false":true, "iota":true, "nil":true, "append":true, + "cap":true, "close":true, "complex":true, "copy":true, "imag":true, + "len":true, "make":true, "new":true, "panic":true, "print":true, + "println":true, "real":true, "recover":true, + }; + + var isOperatorChar = /[+\-*&^%:=<>!|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'" || ch == "`") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\d\.]/.test(ch)) { + if (ch == ".") { + stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); + } else if (ch == "0") { + stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); + } else { + stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); + } + return "number"; + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) { + if (cur == "case" || cur == "default") curPunc = "case"; + return "keyword"; + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || quote == "`")) + state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + if (ctx.type == "case") ctx.type = "}"; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "case") ctx.type = "case"; + else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state); + else if (curPunc == ctx.type) popContext(state); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) { + state.context.type = "}"; + return ctx.indented; + } + var closing = firstChar == ctx.type; + if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}):", + fold: "brace", + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//" + }; +}); + +CodeMirror.defineMIME("text/x-go", "go"); + +}); diff --git a/ethereal/assets/muted/lib/matchbrackets.js b/ethereal/assets/muted/lib/matchbrackets.js new file mode 100644 index 000000000..dcdde81df --- /dev/null +++ b/ethereal/assets/muted/lib/matchbrackets.js @@ -0,0 +1,117 @@ +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && + (document.documentMode == null || document.documentMode < 8); + + var Pos = CodeMirror.Pos; + + var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; + + function findMatchingBracket(cm, where, strict, config) { + var line = cm.getLineHandle(where.line), pos = where.ch - 1; + var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; + if (!match) return null; + var dir = match.charAt(1) == ">" ? 1 : -1; + if (strict && (dir > 0) != (pos == where.ch)) return null; + var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); + + var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config); + if (found == null) return null; + return {from: Pos(where.line, pos), to: found && found.pos, + match: found && found.ch == match.charAt(0), forward: dir > 0}; + } + + // bracketRegex is used to specify which type of bracket to scan + // should be a regexp, e.g. /[[\]]/ + // + // Note: If "where" is on an open bracket, then this bracket is ignored. + // + // Returns false when no bracket was found, null when it reached + // maxScanLines and gave up + function scanForBracket(cm, where, dir, style, config) { + var maxScanLen = (config && config.maxScanLineLength) || 10000; + var maxScanLines = (config && config.maxScanLines) || 1000; + + var stack = []; + var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/; + var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) + : Math.max(cm.firstLine() - 1, where.line - maxScanLines); + for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { + var line = cm.getLine(lineNo); + if (!line) continue; + var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; + if (line.length > maxScanLen) continue; + if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0); + for (; pos != end; pos += dir) { + var ch = line.charAt(pos); + if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) { + var match = matching[ch]; + if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch); + else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch}; + else stack.pop(); + } + } + } + return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; + } + + function matchBrackets(cm, autoclear, config) { + // Disable brace matching in long lines, since it'll cause hugely slow updates + var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000; + var marks = [], ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config); + if (match && cm.getLine(match.from.line).length <= maxHighlightLen) { + var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); + if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) + marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); + } + } + + if (marks.length) { + // Kludge to work around the IE bug from issue #1193, where text + // input stops going to the textare whever this fires. + if (ie_lt8 && cm.state.focused) cm.display.input.focus(); + + var clear = function() { + cm.operation(function() { + for (var i = 0; i < marks.length; i++) marks[i].clear(); + }); + }; + if (autoclear) setTimeout(clear, 800); + else return clear; + } + } + + var currentlyHighlighted = null; + function doMatchBrackets(cm) { + cm.operation(function() { + if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} + currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets); + }); + } + + CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) + cm.off("cursorActivity", doMatchBrackets); + if (val) { + cm.state.matchBrackets = typeof val == "object" ? val : {}; + cm.on("cursorActivity", doMatchBrackets); + } + }); + + CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); + CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){ + return findMatchingBracket(this, pos, strict, config); + }); + CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ + return scanForBracket(this, pos, dir, style, config); + }); +}); diff --git a/ethereal/assets/qml/muted.qml b/ethereal/assets/qml/muted.qml new file mode 100644 index 000000000..dbeec2167 --- /dev/null +++ b/ethereal/assets/qml/muted.qml @@ -0,0 +1,65 @@ +import QtQuick 2.0 +import QtWebKit 3.0 +import QtWebKit.experimental 1.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Window 2.1; +import Ethereum 1.0 + +ApplicationWindow { + id: window + title: "muted" + width: 900 + height: 600 + minimumHeight: 300 + + property alias url: webView.url + property alias debugUrl: debugView.url + property alias webView: webView + + + Item { + id: root + anchors.fill: parent + WebView { + objectName: "webView" + id: webView + anchors { + top: root.top + right: root.right + left: root.left + bottom: sizeGrip.top + } + } + + Rectangle { + id: sizeGrip + color: "gray" + height: 5 + anchors { + left: root.left + right: root.right + } + y: Math.round(root.height * 2 / 3) + + MouseArea { + anchors.fill: parent + drag.target: sizeGrip + drag.minimumY: 0 + drag.maximumY: root.height - sizeGrip.height + drag.axis: Drag.YAxis + } + } + + WebView { + id: debugView + objectName: "debugView" + anchors { + left: root.left + right: root.right + bottom: root.bottom + top: sizeGrip.bottom + } + } + } +} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 574fbef86..4813aaede 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -23,6 +23,11 @@ ApplicationWindow { shortcut: "Ctrl+o" onTriggered: openAppDialog.open() } + MenuItem { + text: "Muted" + shortcut: "Ctrl+e" + onTriggered: ui.muted("") + } } Menu { diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 6217c0065..309ab7928 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -108,6 +108,25 @@ func (ui *UiLib) OpenHtml(path string) { }() } +func (ui *UiLib) Muted(content string) { + component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml")) + if err != nil { + ethutil.Config.Log.Debugln(err) + + return + } + win := component.CreateWindow(nil) + go func() { + path := "file://" + ui.AssetPath("muted/index.html") + win.Set("url", path) + debuggerPath := "file://" + ui.AssetPath("muted/debugger.html") + win.Set("debugUrl", debuggerPath) + + win.Show() + win.Wait() + }() +} + func (ui *UiLib) Connect(button qml.Object) { if !ui.connected { ui.eth.Start() -- cgit v1.2.3 From a0f35d324822fc66951f8a33526ff3d15b0de09d Mon Sep 17 00:00:00 2001 From: Casey Kuhlman Date: Mon, 28 Apr 2014 11:37:44 +0200 Subject: PreProcess moved to Mutan package --- ethereal/ui/ui_lib.go | 3 ++- utils/compile.go | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 309ab7928..a9bde74b0 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/obscuren/mutan" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" "os" @@ -172,7 +173,7 @@ func DefaultAssetPath() string { func (ui *UiLib) DebugTx(recipient, valueStr, gasStr, gasPriceStr, data string) { state := ui.eth.BlockChain().CurrentBlock.State() - mainInput, _ := ethutil.PreProcess(data) + mainInput, _ := mutan.PreProcess(data) callerScript, err := utils.Compile(mainInput) if err != nil { ethutil.Config.Log.Debugln(err) diff --git a/utils/compile.go b/utils/compile.go index 12e3a60c3..6d75f73d1 100644 --- a/utils/compile.go +++ b/utils/compile.go @@ -2,7 +2,6 @@ package utils import ( "fmt" - "github.com/ethereum/eth-go/ethutil" "github.com/obscuren/mutan" "strings" ) @@ -25,7 +24,7 @@ func Compile(script string) ([]byte, error) { func CompileScript(script string) ([]byte, []byte, error) { // Preprocess - mainInput, initInput := ethutil.PreProcess(script) + mainInput, initInput := mutan.PreProcess(script) // Compile main script mainScript, err := Compile(mainInput) if err != nil { -- cgit v1.2.3 From 922974c760278b6d49cb6f286b663d60f77d5248 Mon Sep 17 00:00:00 2001 From: obscuren Date: Mon, 28 Apr 2014 23:24:42 +0200 Subject: Added muted --- ethereal/assets/muted/debugger.html | 2 +- ethereal/assets/muted/index.html | 46 ++++++- ethereal/assets/muted/muted.js | 61 ++++++++++ ethereal/assets/qml/muted.qml | 107 ++++++++-------- ethereal/assets/qml/webapp.qml | 237 ++++++++++++++++++------------------ ethereal/ui/ui_lib.go | 4 +- ethereum/dev_console.go | 3 +- 7 files changed, 287 insertions(+), 173 deletions(-) create mode 100644 ethereal/assets/muted/muted.js diff --git a/ethereal/assets/muted/debugger.html b/ethereal/assets/muted/debugger.html index 077c59bcf..b7552f030 100644 --- a/ethereal/assets/muted/debugger.html +++ b/ethereal/assets/muted/debugger.html @@ -4,7 +4,7 @@ + +
+
+
+ > +
+
+
+
+
+ - diff --git a/ethereal/assets/muted/muted.js b/ethereal/assets/muted/muted.js new file mode 100644 index 000000000..72e858d7a --- /dev/null +++ b/ethereal/assets/muted/muted.js @@ -0,0 +1,61 @@ +// Helper function for generating pseudo callbacks and sending data to the QML part of the application +function postData(data, cb) { + data._seed = Math.floor(Math.random() * 1000000) + if(cb) { + Muted._callbacks[data._seed] = cb; + } + + if(data.args === undefined) { + data.args = []; + } + + navigator.qt.postMessage(JSON.stringify(data)); +} + +window.Muted = { + prototype: Object(), +} + +window.Muted._callbacks = {} +window.Muted._onCallbacks = {} + +function debug(/**/) { + console.log("hello world") + + var args = arguments; + var msg = "" + for(var i = 0; i < args.length; i++){ + if(typeof args[i] == "object") { + msg += " " + JSON.stringify(args[i]) + } else { + msg += args[i] + } + } + + document.querySelector("#debugger").innerHTML += "
"+msg+"
"; +} +console.log = function() { + var args = [] + for(var i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + postData({call:"log", args:args}) +} + +navigator.qt.onmessage = function(ev) { + var data = JSON.parse(ev.data) + + if(data._event !== undefined) { + Muted.trigger(data._event, data.data); + } else { + if(data._seed) { + var cb = Muted._callbacks[data._seed]; + if(cb) { + // Call the callback + cb(data.data); + // Remove the "trigger" callback + delete Muted._callbacks[ev._seed]; + } + } + } +} diff --git a/ethereal/assets/qml/muted.qml b/ethereal/assets/qml/muted.qml index dbeec2167..fac8267c4 100644 --- a/ethereal/assets/qml/muted.qml +++ b/ethereal/assets/qml/muted.qml @@ -7,59 +7,68 @@ import QtQuick.Window 2.1; import Ethereum 1.0 ApplicationWindow { - id: window - title: "muted" - width: 900 - height: 600 - minimumHeight: 300 + id: window + title: "muted" + width: 900 + height: 600 + minimumHeight: 300 - property alias url: webView.url - property alias debugUrl: debugView.url - property alias webView: webView + property alias url: webView.url + property alias webView: webView - Item { - id: root - anchors.fill: parent - WebView { - objectName: "webView" - id: webView - anchors { - top: root.top - right: root.right - left: root.left - bottom: sizeGrip.top - } - } + Item { + id: root + anchors.fill: parent + WebView { + objectName: "webView" + id: webView + anchors { + top: root.top + right: root.right + left: root.left + bottom: root.bottom + //bottom: sizeGrip.top + } - Rectangle { - id: sizeGrip - color: "gray" - height: 5 - anchors { - left: root.left - right: root.right - } - y: Math.round(root.height * 2 / 3) + experimental.preferences.javascriptEnabled: true + experimental.preferences.navigatorQtObjectEnabled: true + experimental.onMessageReceived: { + var data = JSON.parse(message.data) - MouseArea { - anchors.fill: parent - drag.target: sizeGrip - drag.minimumY: 0 - drag.maximumY: root.height - sizeGrip.height - drag.axis: Drag.YAxis - } - } + switch(data.call) { + case "log": + console.log.apply(this, data.args) + break; + } + } + function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) + } + function postEvent(event, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) + } + } - WebView { - id: debugView - objectName: "debugView" - anchors { - left: root.left - right: root.right - bottom: root.bottom - top: sizeGrip.bottom - } - } - } + /* + Rectangle { + id: sizeGrip + color: "gray" + height: 5 + anchors { + left: root.left + right: root.right + } + y: Math.round(root.height * 2 / 3) + + MouseArea { + anchors.fill: parent + drag.target: sizeGrip + drag.minimumY: 0 + drag.maximumY: root.height - sizeGrip.height + drag.axis: Drag.YAxis + } + } + */ + } } diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index c34e0dc55..3afc0def0 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -7,134 +7,135 @@ import QtQuick.Window 2.1; import Ethereum 1.0 ApplicationWindow { - id: window - title: "Ethereum" - width: 900 - height: 600 - minimumHeight: 300 + id: window + title: "Ethereum" + width: 900 + height: 600 + minimumHeight: 300 - property alias url: webview.url - property alias webView: webview + property alias url: webview.url + property alias webView: webview - Item { - objectName: "root" - id: root - anchors.fill: parent - state: "inspectorShown" + Item { + objectName: "root" + id: root + anchors.fill: parent + state: "inspectorShown" - WebView { - objectName: "webView" - id: webview - anchors.fill: parent - /* - anchors { - left: parent.left - right: parent.right - bottom: sizeGrip.top - top: parent.top - } - */ + WebView { + objectName: "webView" + id: webview + anchors.fill: parent + /* + anchors { + left: parent.left + right: parent.right + bottom: sizeGrip.top + top: parent.top + } + */ - onTitleChanged: { window.title = title } - experimental.preferences.javascriptEnabled: true - experimental.preferences.navigatorQtObjectEnabled: true - experimental.preferences.developerExtrasEnabled: true - experimental.userScripts: [ui.assetPath("ethereum.js")] - experimental.onMessageReceived: { - //console.log("[onMessageReceived]: ", message.data) - var data = JSON.parse(message.data) + onTitleChanged: { window.title = title } + experimental.preferences.javascriptEnabled: true + experimental.preferences.navigatorQtObjectEnabled: true + experimental.preferences.developerExtrasEnabled: true + experimental.userScripts: [ui.assetPath("ethereum.js")] + experimental.onMessageReceived: { + //console.log("[onMessageReceived]: ", message.data) + var data = JSON.parse(message.data) - switch(data.call) { - case "getBlockByNumber": - var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") - postData(data._seed, block) - break - case "getBlockByHash": - var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") - postData(data._seed, block) - break - case "createTx": - if(data.args.length < 5) { - postData(data._seed, null) - } else { - var tx = eth.createTx(data.args[0], data.args[1],data.args[2],data.args[3],data.args[4]) - postData(data._seed, tx) - } - break - case "getStorage": - if(data.args.length < 2) { - postData(data._seed, null) - } else { - var stateObject = eth.getStateObject(data.args[0]) - var storage = stateObject.getStorage(data.args[1]) - postData(data._seed, storage) - } - break - case "getKey": - var keys = eth.getKey() - postData(data._seed, keys) - break - case "watch": - if(data.args.length > 0) { - eth.watch(data.args[0]); - } - } + switch(data.call) { + case "getBlockByNumber": + var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") + postData(data._seed, block) + break + case "getBlockByHash": + var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") + postData(data._seed, block) + break + case "createTx": + if(data.args.length < 5) { + postData(data._seed, null) + } else { + var tx = eth.createTx(data.args[0], data.args[1],data.args[2],data.args[3],data.args[4]) + postData(data._seed, tx) } - function postData(seed, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) - } - function postEvent(event, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) - } - - function onNewBlockCb(block) { - postEvent("block:new", block) - } - function onObjectChangeCb(stateObject) { - postEvent("object:change", stateObject) - } - } - - Rectangle { - id: sizeGrip - color: "gray" - visible: false - height: 10 - anchors { - left: root.left - right: root.right + break + case "getStorage": + if(data.args.length < 2) { + postData(data._seed, null) + } else { + var stateObject = eth.getStateObject(data.args[0]) + var storage = stateObject.getStorage(data.args[1]) + postData(data._seed, storage) } - y: Math.round(root.height * 2 / 3) - - MouseArea { - anchors.fill: parent - drag.target: sizeGrip - drag.minimumY: 0 - drag.maximumY: root.height - drag.axis: Drag.YAxis + break + case "getKey": + var keys = eth.getKey() + postData(data._seed, keys) + break + case "watch": + if(data.args.length > 0) { + eth.watch(data.args[0]); } + break } + } + function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) + } + function postEvent(event, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) + } - WebView { - id: inspector - visible: false - url: webview.experimental.remoteInspectorUrl - anchors { - left: root.left - right: root.right - top: sizeGrip.bottom - bottom: root.bottom - } - } + function onNewBlockCb(block) { + postEvent("block:new", block) + } + function onObjectChangeCb(stateObject) { + postEvent("object:change", stateObject) + } + } - states: [ - State { - name: "inspectorShown" - PropertyChanges { - target: inspector - url: webview.experimental.remoteInspectorUrl - } - } - ] + Rectangle { + id: sizeGrip + color: "gray" + visible: false + height: 10 + anchors { + left: root.left + right: root.right + } + y: Math.round(root.height * 2 / 3) + + MouseArea { + anchors.fill: parent + drag.target: sizeGrip + drag.minimumY: 0 + drag.maximumY: root.height + drag.axis: Drag.YAxis + } } + + WebView { + id: inspector + visible: false + url: webview.experimental.remoteInspectorUrl + anchors { + left: root.left + right: root.right + top: sizeGrip.bottom + bottom: root.bottom + } + } + + states: [ + State { + name: "inspectorShown" + PropertyChanges { + target: inspector + url: webview.experimental.remoteInspectorUrl + } + } + ] + } } diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index a9bde74b0..07cd0ac8a 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -120,8 +120,8 @@ func (ui *UiLib) Muted(content string) { go func() { path := "file://" + ui.AssetPath("muted/index.html") win.Set("url", path) - debuggerPath := "file://" + ui.AssetPath("muted/debugger.html") - win.Set("debugUrl", debuggerPath) + //debuggerPath := "file://" + ui.AssetPath("muted/debugger.html") + //win.Set("debugUrl", debuggerPath) win.Show() win.Wait() diff --git a/ethereum/dev_console.go b/ethereum/dev_console.go index 583b8bd0b..d2be43205 100644 --- a/ethereum/dev_console.go +++ b/ethereum/dev_console.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethwire" "github.com/ethereum/go-ethereum/utils" + "github.com/obscuren/mutan" "os" "strings" ) @@ -190,7 +191,7 @@ func (i *Console) ParseInput(input string) bool { case "contract": fmt.Println("Contract editor (Ctrl-D = done)") - mainInput, initInput := ethutil.PreProcess(i.Editor()) + mainInput, initInput := mutan.PreProcess(i.Editor()) mainScript, err := utils.Compile(mainInput) if err != nil { fmt.Println(err) -- cgit v1.2.3 From 64c2550b3154df7f2c75dda559d91046cb559ffd Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 30 Apr 2014 01:44:02 +0200 Subject: Split off External applications from main library External applications now accept containers which function as the frontend where the ExtApplication functions as the backend. Containers execute within their own engine and have their own context and are destroyed when released. --- ethereal/ui/ext_app.go | 175 ++++++++++++++++++++++++++++++++++++++++++ ethereal/ui/html_container.go | 73 ++++++++++++++++++ ethereal/ui/library.go | 33 ++++++-- ethereal/ui/ui_lib.go | 62 +++------------ 4 files changed, 286 insertions(+), 57 deletions(-) create mode 100644 ethereal/ui/ext_app.go create mode 100644 ethereal/ui/html_container.go diff --git a/ethereal/ui/ext_app.go b/ethereal/ui/ext_app.go new file mode 100644 index 000000000..b90a2192c --- /dev/null +++ b/ethereal/ui/ext_app.go @@ -0,0 +1,175 @@ +package ethui + +import ( + "fmt" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" + "math/big" +) + +type AppContainer interface { + Create() error + Destroy() + + Window() *qml.Window + Engine() *qml.Engine + + NewBlock(*ethchain.Block) + ObjectChanged(*ethchain.StateObject) + StorageChanged(*ethchain.StateObject, []byte, *big.Int) +} + +type ExtApplication struct { + *QEthereum + + blockChan chan ethutil.React + changeChan chan ethutil.React + quitChan chan bool + + container AppContainer + lib *UiLib + registeredEvents []string +} + +func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication { + app := &ExtApplication{ + NewQEthereum(lib.eth), + make(chan ethutil.React, 1), + make(chan ethutil.React, 1), + make(chan bool), + container, + lib, + nil, + } + + return app +} + +func (app *ExtApplication) run() { + // Set the "eth" api on to the containers context + context := app.container.Engine().Context() + context.SetVar("eth", app) + context.SetVar("ui", app.lib) + + err := app.container.Create() + if err != nil { + fmt.Println(err) + + return + } + + // Call the main loop + go app.mainLoop() + + // Subscribe to events + reactor := app.lib.eth.Reactor() + reactor.Subscribe("newBlock", app.blockChan) + + win := app.container.Window() + win.Show() + win.Wait() + + app.stop() +} + +func (app *ExtApplication) stop() { + // Clean up + reactor := app.lib.eth.Reactor() + reactor.Unsubscribe("newBlock", app.blockChan) + for _, event := range app.registeredEvents { + reactor.Unsubscribe(event, app.changeChan) + } + + // Kill the main loop + app.quitChan <- true + + close(app.blockChan) + close(app.quitChan) + close(app.changeChan) + + app.container.Destroy() +} + +func (app *ExtApplication) mainLoop() { +out: + for { + select { + case <-app.quitChan: + break out + case block := <-app.blockChan: + if block, ok := block.Resource.(*ethchain.Block); ok { + app.container.NewBlock(block) + } + case object := <-app.changeChan: + if stateObject, ok := object.Resource.(*ethchain.StateObject); ok { + app.container.ObjectChanged(stateObject) + } else if _, ok := object.Resource.(*big.Int); ok { + // + } + } + } + +} + +func (app *ExtApplication) Watch(addr, storageAddr string) { + var event string + if len(storageAddr) == 0 { + event = "storage:" + string(ethutil.FromHex(addr)) + ":" + string(ethutil.FromHex(storageAddr)) + app.lib.eth.Reactor().Subscribe(event, app.changeChan) + } else { + event = "object:" + string(ethutil.FromHex(addr)) + app.lib.eth.Reactor().Subscribe(event, app.changeChan) + } + + app.registeredEvents = append(app.registeredEvents, event) +} + +type QEthereum struct { + stateManager *ethchain.StateManager + blockChain *ethchain.BlockChain + txPool *ethchain.TxPool +} + +func NewQEthereum(eth *eth.Ethereum) *QEthereum { + return &QEthereum{ + eth.StateManager(), + eth.BlockChain(), + eth.TxPool(), + } +} + +func (lib *QEthereum) GetBlock(hexHash string) *QBlock { + hash := ethutil.FromHex(hexHash) + + block := lib.blockChain.GetBlock(hash) + + return &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} +} + +func (lib *QEthereum) GetKey() string { + return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) +} + +func (lib *QEthereum) GetStateObject(address string) *Contract { + stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) + if stateObject != nil { + return NewContract(stateObject) + } + + // See GetStorage for explanation on "nil" + return NewContract(nil) +} + +func (lib *QEthereum) Watch(addr, storageAddr string) { + // lib.stateManager.Watch(ethutil.FromHex(addr), ethutil.FromHex(storageAddr)) +} + +func (lib *QEthereum) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + return lib.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr) +} + +func (lib *QEthereum) Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + return "", nil +} diff --git a/ethereal/ui/html_container.go b/ethereal/ui/html_container.go new file mode 100644 index 000000000..8e3ef0fc7 --- /dev/null +++ b/ethereal/ui/html_container.go @@ -0,0 +1,73 @@ +package ethui + +import ( + "errors" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" + "github.com/go-qml/qml" + "math/big" + "path/filepath" +) + +type HtmlApplication struct { + win *qml.Window + webView qml.Object + engine *qml.Engine + lib *UiLib + path string +} + +func NewHtmlApplication(path string, lib *UiLib) *HtmlApplication { + engine := qml.NewEngine() + + return &HtmlApplication{engine: engine, lib: lib, path: path} + +} + +func (app *HtmlApplication) Create() error { + component, err := app.engine.LoadFile(app.lib.AssetPath("qml/webapp.qml")) + if err != nil { + return err + } + + if filepath.Ext(app.path) == "eth" { + return errors.New("Ethereum package not yet supported") + + // TODO + ethutil.OpenPackage(app.path) + } + + win := component.CreateWindow(nil) + win.Set("url", app.path) + webView := win.ObjectByName("webView") + + app.win = win + app.webView = webView + + return nil +} + +func (app *HtmlApplication) Engine() *qml.Engine { + return app.engine +} + +func (app *HtmlApplication) Window() *qml.Window { + return app.win +} + +func (app *HtmlApplication) NewBlock(block *ethchain.Block) { + b := &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} + app.webView.Call("onNewBlockCb", b) +} + +func (app *HtmlApplication) ObjectChanged(stateObject *ethchain.StateObject) { + app.webView.Call("onObjectChangeCb", NewQStateObject(stateObject)) +} + +func (app *HtmlApplication) StorageChanged(stateObject *ethchain.StateObject, addr []byte, value *big.Int) { + app.webView.Call("onStorageChangeCb", nil) +} + +func (app *HtmlApplication) Destroy() { + app.engine.Destroy() +} diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 7f667f2c1..ec5f29f95 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -19,9 +19,24 @@ func NewContract(object *ethchain.StateObject) *Contract { } func (c *Contract) GetStorage(address string) string { - val := c.object.GetMem(ethutil.Big("0x" + address)) + // Because somehow, even if you return nil to QML it + // still has some magical object so we can't rely on + // undefined or null at the QML side + if c.object != nil { + val := c.object.GetMem(ethutil.Big("0x" + address)) - return val.BigInt().String() + return val.BigInt().String() + } + + return "" +} + +func (c *Contract) Value() string { + if c.object != nil { + return c.object.Amount.String() + } + + return "" } type EthLib struct { @@ -63,15 +78,23 @@ func (lib *EthLib) GetKey() string { func (lib *EthLib) GetStateObject(address string) *Contract { stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) + if stateObject != nil { + return NewContract(stateObject) + } - return NewContract(stateObject) + // See GetStorage for explanation on "nil" + return NewContract(nil) } -func (lib *EthLib) Watch(addr string) { - lib.stateManager.Watch(ethutil.FromHex(addr)) +func (lib *EthLib) Watch(addr, storageAddr string) { + // lib.stateManager.Watch(ethutil.FromHex(addr), ethutil.FromHex(storageAddr)) } func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + return lib.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr) +} + +func (lib *EthLib) Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { var hash []byte var contractCreation bool if len(recipient) == 0 { diff --git a/ethereal/ui/ui_lib.go b/ethereal/ui/ui_lib.go index 07cd0ac8a..0feb522bc 100644 --- a/ethereal/ui/ui_lib.go +++ b/ethereal/ui/ui_lib.go @@ -6,9 +6,9 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" - "github.com/obscuren/mutan" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" + "github.com/obscuren/mutan" "os" "path" "path/filepath" @@ -53,60 +53,18 @@ func (ui *UiLib) Open(path string) { } func (ui *UiLib) OpenHtml(path string) { - component, err := ui.engine.LoadFile(ui.AssetPath("qml/webapp.qml")) - if err != nil { - ethutil.Config.Log.Debugln(err) + container := NewHtmlApplication(path, ui) + app := NewExtApplication(container, ui) - return - } - win := component.CreateWindow(nil) - if filepath.Ext(path) == "eth" { - fmt.Println("Ethereum package not yet supported") - - return + go app.run() +} - // TODO - ethutil.OpenPackage(path) +func (ui *UiLib) Watch(addr, storageAddr string) { + if len(storageAddr) == 0 { + ui.eth.Reactor().Subscribe("storage:"+string(ethutil.FromHex(addr))+":"+string(ethutil.FromHex(storageAddr)), nil) + } else { + ui.eth.Reactor().Subscribe("object:"+string(ethutil.FromHex(addr)), nil) } - win.Set("url", path) - - webView := win.ObjectByName("webView") - go func() { - blockChan := make(chan ethutil.React, 1) - addrChan := make(chan ethutil.React, 1) - quitChan := make(chan bool) - - go func() { - out: - for { - select { - case <-quitChan: - ui.eth.Reactor().Unsubscribe("newBlock", blockChan) - break out - case block := <-blockChan: - if block, ok := block.Resource.(*ethchain.Block); ok { - b := &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} - webView.Call("onNewBlockCb", b) - } - case stateObject := <-addrChan: - if stateObject, ok := stateObject.Resource.(*ethchain.StateObject); ok { - webView.Call("onObjectChangeCb", NewQStateObject(stateObject)) - } - } - } - - // Clean up - close(blockChan) - close(quitChan) - }() - ui.eth.Reactor().Subscribe("newBlock", blockChan) - ui.eth.Reactor().Subscribe("addressChanged", addrChan) - - win.Show() - win.Wait() - - quitChan <- true - }() } func (ui *UiLib) Muted(content string) { -- cgit v1.2.3 From e85d5dd428c71dd45060082a9af28a40a68a25e4 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 30 Apr 2014 01:44:12 +0200 Subject: API changes --- ethereal/assets/ethereum.js | 70 +++++++--- ethereal/assets/qml/webapp.qml | 286 ++++++++++++++++++++++------------------ ethereal/assets/samplecoin.html | 4 +- 3 files changed, 208 insertions(+), 152 deletions(-) diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index 0f9f68c43..bb6346cab 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -1,17 +1,3 @@ -// Helper function for generating pseudo callbacks and sending data to the QML part of the application -function postData(data, cb) { - data._seed = Math.floor(Math.random() * 1000000) - if(cb) { - eth._callbacks[data._seed] = cb; - } - - if(data.args === undefined) { - data.args = []; - } - - navigator.qt.postMessage(JSON.stringify(data)); -} - // Main Ethereum library window.eth = { prototype: Object(), @@ -35,8 +21,12 @@ window.eth = { // // Creates a transaction with the current account // If no recipient is set, the Ethereum API will see it as a contract creation - createTx: function(recipient, value, gas, gasPrice, data, cb) { - postData({call: "createTx", args: [recipient, value, gas, gasPrice, data]}, cb); + transact: function(sec, recipient, value, gas, gasPrice, data, cb) { + postData({call: "transact", args: [sec, recipient, value, gas, gasPrice, data]}, cb); + }, + + create: function(sec, value, gas, gasPrice, init, body, cb) { + postData({call: "create", args: [sec, value, gas, gasPrice, init, body]}, cb); }, getStorage: function(address, storageAddress, cb) { @@ -47,10 +37,39 @@ window.eth = { postData({call: "getKey"}, cb); }, - watch: function(address) { - postData({call: "watch", args: [address]}); + getBalance: function(address, cb) { + postData({call: "getBalance", args: [address]}, cb); + }, + + watch: function(address, storageAddrOrCb, cb) { + var ev = "changed:"+address; + + if(cb === undefined) { + cb = storageAddrOrCb; + storageAddrOrCb = ""; + } else { + ev += ":"+storageAddrOrCb; + } + + eth.on(ev, cb) + + postData({call: "watch", args: [address, storageAddrOrCb]}); }, + disconnect: function(address, storageAddrOrCb, cb) { + var ev = "changed:"+address; + + if(cb === undefined) { + cb = storageAddrOrCb; + storageAddrOrCb = null; + } else { + ev += ":"+storageAddrOrCb; + } + + eth.off(ev, cb) + + postData({call: "disconnect", args: [address, storageAddrOrCb]}); + }, on: function(event, cb) { if(eth._onCallbacks[event] === undefined) { @@ -61,6 +80,7 @@ window.eth = { return this }, + off: function(event, cb) { if(eth._onCallbacks[event] !== undefined) { var callbacks = eth._onCallbacks[event]; @@ -100,6 +120,20 @@ function debug(/**/) { document.getElementById("debug").innerHTML += "
" + msg } +// Helper function for generating pseudo callbacks and sending data to the QML part of the application +function postData(data, cb) { + data._seed = Math.floor(Math.random() * 1000000) + if(cb) { + eth._callbacks[data._seed] = cb; + } + + if(data.args === undefined) { + data.args = []; + } + + navigator.qt.postMessage(JSON.stringify(data)); +} + navigator.qt.onmessage = function(ev) { var data = JSON.parse(ev.data) diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 3afc0def0..eebe8921f 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -7,135 +7,159 @@ import QtQuick.Window 2.1; import Ethereum 1.0 ApplicationWindow { - id: window - title: "Ethereum" - width: 900 - height: 600 - minimumHeight: 300 - - property alias url: webview.url - property alias webView: webview - - Item { - objectName: "root" - id: root - anchors.fill: parent - state: "inspectorShown" - - WebView { - objectName: "webView" - id: webview - anchors.fill: parent - /* - anchors { - left: parent.left - right: parent.right - bottom: sizeGrip.top - top: parent.top - } - */ - - onTitleChanged: { window.title = title } - experimental.preferences.javascriptEnabled: true - experimental.preferences.navigatorQtObjectEnabled: true - experimental.preferences.developerExtrasEnabled: true - experimental.userScripts: [ui.assetPath("ethereum.js")] - experimental.onMessageReceived: { - //console.log("[onMessageReceived]: ", message.data) - var data = JSON.parse(message.data) - - switch(data.call) { - case "getBlockByNumber": - var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") - postData(data._seed, block) - break - case "getBlockByHash": - var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") - postData(data._seed, block) - break - case "createTx": - if(data.args.length < 5) { - postData(data._seed, null) - } else { - var tx = eth.createTx(data.args[0], data.args[1],data.args[2],data.args[3],data.args[4]) - postData(data._seed, tx) - } - break - case "getStorage": - if(data.args.length < 2) { - postData(data._seed, null) - } else { - var stateObject = eth.getStateObject(data.args[0]) - var storage = stateObject.getStorage(data.args[1]) - postData(data._seed, storage) - } - break - case "getKey": - var keys = eth.getKey() - postData(data._seed, keys) - break - case "watch": - if(data.args.length > 0) { - eth.watch(data.args[0]); - } - break - } - } - function postData(seed, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) - } - function postEvent(event, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) - } - - function onNewBlockCb(block) { - postEvent("block:new", block) - } - function onObjectChangeCb(stateObject) { - postEvent("object:change", stateObject) - } - } - - Rectangle { - id: sizeGrip - color: "gray" - visible: false - height: 10 - anchors { - left: root.left - right: root.right - } - y: Math.round(root.height * 2 / 3) - - MouseArea { - anchors.fill: parent - drag.target: sizeGrip - drag.minimumY: 0 - drag.maximumY: root.height - drag.axis: Drag.YAxis - } - } - - WebView { - id: inspector - visible: false - url: webview.experimental.remoteInspectorUrl - anchors { - left: root.left - right: root.right - top: sizeGrip.bottom - bottom: root.bottom - } - } - - states: [ - State { - name: "inspectorShown" - PropertyChanges { - target: inspector - url: webview.experimental.remoteInspectorUrl - } - } - ] - } + id: window + title: "Ethereum" + width: 900 + height: 600 + minimumHeight: 300 + + property alias url: webview.url + property alias webView: webview + + Item { + objectName: "root" + id: root + anchors.fill: parent + state: "inspectorShown" + + WebView { + objectName: "webView" + id: webview + anchors.fill: parent + /* + anchors { + left: parent.left + right: parent.right + bottom: sizeGrip.top + top: parent.top + } + */ + + onTitleChanged: { window.title = title } + experimental.preferences.javascriptEnabled: true + experimental.preferences.navigatorQtObjectEnabled: true + experimental.preferences.developerExtrasEnabled: true + experimental.userScripts: [ui.assetPath("ethereum.js")] + experimental.onMessageReceived: { + //console.log("[onMessageReceived]: ", message.data) + // TODO move to messaging.js + var data = JSON.parse(message.data) + + try { + switch(data.call) { + case "getBlockByNumber": + var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") + postData(data._seed, block) + break + case "getBlockByHash": + var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") + postData(data._seed, block) + break + case "transact": + require(5) + + // TODO this will change to 6 soon with sec being teh first argument + var tx = eth.transact(data.args[0], data.args[1],data.args[2],data.args[3],data.args[4]) + postData(data._seed, tx) + break + case "create": + postData(data._seed, null) + + break + case "getStorage": + require(2); + + var stateObject = eth.getStateObject(data.args[0]) + var storage = stateObject.getStorage(data.args[1]) + postData(data._seed, storage) + + break + case "getBalance": + require(1); + + postData(data._seed, eth.getStateObject(data.args[0]).Value()); + + break + case "getKey": + var keys = eth.getKey() + postData(data._seed, keys) + break + case "watch": + require(1) + eth.watch(data.args[0], data.args[1]); + break + case "disconnect": + require(1) + postData(data._seed, null) + break; + } + } catch(e) { + console.log(data.call + ": " + e) + + postData(data._seed, null); + } + } + + function require(args, num) { + if(args.length < num) { + throw("required argument count of "+num+" got "+args.length); + } + } + function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) + } + function postEvent(event, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) + } + + function onNewBlockCb(block) { + postEvent("block:new", block) + } + function onObjectChangeCb(stateObject) { + postEvent("object:change", stateObject) + } + } + + Rectangle { + id: sizeGrip + color: "gray" + visible: false + height: 10 + anchors { + left: root.left + right: root.right + } + y: Math.round(root.height * 2 / 3) + + MouseArea { + anchors.fill: parent + drag.target: sizeGrip + drag.minimumY: 0 + drag.maximumY: root.height + drag.axis: Drag.YAxis + } + } + + WebView { + id: inspector + visible: false + url: webview.experimental.remoteInspectorUrl + anchors { + left: root.left + right: root.right + top: sizeGrip.bottom + bottom: root.bottom + } + } + + states: [ + State { + name: "inspectorShown" + PropertyChanges { + target: inspector + url: webview.experimental.remoteInspectorUrl + } + } + ] + } } diff --git a/ethereal/assets/samplecoin.html b/ethereal/assets/samplecoin.html index 1b89be877..73368a0b8 100644 --- a/ethereal/assets/samplecoin.html +++ b/ethereal/assets/samplecoin.html @@ -22,14 +22,12 @@ function tests() { } function init() { - eth.watch(jefcoinAddr); - eth.getKey(function(key) { eth.getStorage(jefcoinAddr, key, function(storage) { document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; }); - eth.on("object:change", function(stateObject) { + eth.watch(jefcoinAddr, function(stateObject) { debug(stateObject); eth.getStorage(jefcoinAddr, key, function(storage) { document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; -- cgit v1.2.3 From 183dbcc6a0e56bf46f6406d47134f3b88eb5eef1 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 30 Apr 2014 14:42:57 +0200 Subject: fixed state object changes for eth api --- ethereal/assets/ethereum.js | 14 +++++----- ethereal/assets/qml/webapp.qml | 5 ++-- ethereal/assets/samplecoin.html | 3 +- ethereal/ui/ext_app.go | 62 +++++++++++++++++++++++++++++++++++------ ethereal/ui/library.go | 35 ++--------------------- ethereal/ui/types.go | 37 ++++++++++++++++++++---- 6 files changed, 98 insertions(+), 58 deletions(-) diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index bb6346cab..916655d5e 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -42,13 +42,13 @@ window.eth = { }, watch: function(address, storageAddrOrCb, cb) { - var ev = "changed:"+address; - + var ev; if(cb === undefined) { cb = storageAddrOrCb; storageAddrOrCb = ""; + ev = "object:"+address; } else { - ev += ":"+storageAddrOrCb; + ev = "storage:"+address+":"+storageAddrOrCb; } eth.on(ev, cb) @@ -57,13 +57,13 @@ window.eth = { }, disconnect: function(address, storageAddrOrCb, cb) { - var ev = "changed:"+address; - + var ev; if(cb === undefined) { cb = storageAddrOrCb; - storageAddrOrCb = null; + storageAddrOrCb = ""; + ev = "object:"+address; } else { - ev += ":"+storageAddrOrCb; + ev = "storage:"+address+":"+storageAddrOrCb; } eth.off(ev, cb) diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index eebe8921f..2f3fef4d2 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -58,8 +58,7 @@ ApplicationWindow { case "transact": require(5) - // TODO this will change to 6 soon with sec being teh first argument - var tx = eth.transact(data.args[0], data.args[1],data.args[2],data.args[3],data.args[4]) + var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5]) postData(data._seed, tx) break case "create": @@ -116,7 +115,7 @@ ApplicationWindow { postEvent("block:new", block) } function onObjectChangeCb(stateObject) { - postEvent("object:change", stateObject) + postEvent("object:"+stateObject.address(), stateObject) } } diff --git a/ethereal/assets/samplecoin.html b/ethereal/assets/samplecoin.html index 73368a0b8..02efa8e01 100644 --- a/ethereal/assets/samplecoin.html +++ b/ethereal/assets/samplecoin.html @@ -9,7 +9,7 @@ function createTransaction() { var amount = document.querySelector("#amount").value; var data = "0x" + addr + "\n" + amount - eth.createTx(jefcoinAddr, 0, "10000000", "250", data, function(tx) { + eth.transact("", jefcoinAddr, 0, "10000000", "250", data, function(tx) { debug("received tx hash:", tx) }) } @@ -28,7 +28,6 @@ function init() { }); eth.watch(jefcoinAddr, function(stateObject) { - debug(stateObject); eth.getStorage(jefcoinAddr, key, function(storage) { document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; }); diff --git a/ethereal/ui/ext_app.go b/ethereal/ui/ext_app.go index b90a2192c..c02ffb7b2 100644 --- a/ethereal/ui/ext_app.go +++ b/ethereal/ui/ext_app.go @@ -5,8 +5,10 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" "math/big" + "strings" ) type AppContainer interface { @@ -116,10 +118,10 @@ out: func (app *ExtApplication) Watch(addr, storageAddr string) { var event string if len(storageAddr) == 0 { - event = "storage:" + string(ethutil.FromHex(addr)) + ":" + string(ethutil.FromHex(storageAddr)) + event = "object:" + string(ethutil.FromHex(addr)) app.lib.eth.Reactor().Subscribe(event, app.changeChan) } else { - event = "object:" + string(ethutil.FromHex(addr)) + event = "storage:" + string(ethutil.FromHex(addr)) + ":" + string(ethutil.FromHex(storageAddr)) app.lib.eth.Reactor().Subscribe(event, app.changeChan) } @@ -152,24 +154,66 @@ func (lib *QEthereum) GetKey() string { return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) } -func (lib *QEthereum) GetStateObject(address string) *Contract { +func (lib *QEthereum) GetStateObject(address string) *QStateObject { stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) if stateObject != nil { - return NewContract(stateObject) + return NewQStateObject(stateObject) } // See GetStorage for explanation on "nil" - return NewContract(nil) + return NewQStateObject(nil) } func (lib *QEthereum) Watch(addr, storageAddr string) { // lib.stateManager.Watch(ethutil.FromHex(addr), ethutil.FromHex(storageAddr)) } -func (lib *QEthereum) CreateTx(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { - return lib.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr) +func (lib *QEthereum) CreateTx(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + return lib.Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr) } -func (lib *QEthereum) Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { - return "", nil +func (lib *QEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + var hash []byte + var contractCreation bool + if len(recipient) == 0 { + contractCreation = true + } else { + hash = ethutil.FromHex(recipient) + } + + keyPair := ethutil.Config.Db.GetKeys()[0] + value := ethutil.Big(valueStr) + gas := ethutil.Big(gasStr) + gasPrice := ethutil.Big(gasPriceStr) + var tx *ethchain.Transaction + // Compile and assemble the given data + if contractCreation { + // Compile script + mainScript, initScript, err := utils.CompileScript(dataStr) + if err != nil { + return "", err + } + + tx = ethchain.NewContractCreationTx(value, gas, gasPrice, mainScript, initScript) + } else { + lines := strings.Split(dataStr, "\n") + var data []byte + for _, line := range lines { + data = append(data, ethutil.BigToBytes(ethutil.Big(line), 256)...) + } + + tx = ethchain.NewTransactionMessage(hash, value, gas, gasPrice, data) + } + acc := lib.stateManager.GetAddrState(keyPair.Address()) + tx.Nonce = acc.Nonce + tx.Sign(keyPair.PrivateKey) + lib.txPool.QueueTransaction(tx) + + if contractCreation { + ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } else { + ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) + } + + return ethutil.Hex(tx.Hash()), nil } diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index ec5f29f95..70462a93d 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -10,35 +10,6 @@ import ( "strings" ) -type Contract struct { - object *ethchain.StateObject -} - -func NewContract(object *ethchain.StateObject) *Contract { - return &Contract{object: object} -} - -func (c *Contract) GetStorage(address string) string { - // Because somehow, even if you return nil to QML it - // still has some magical object so we can't rely on - // undefined or null at the QML side - if c.object != nil { - val := c.object.GetMem(ethutil.Big("0x" + address)) - - return val.BigInt().String() - } - - return "" -} - -func (c *Contract) Value() string { - if c.object != nil { - return c.object.Amount.String() - } - - return "" -} - type EthLib struct { stateManager *ethchain.StateManager blockChain *ethchain.BlockChain @@ -76,14 +47,14 @@ func (lib *EthLib) GetKey() string { return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) } -func (lib *EthLib) GetStateObject(address string) *Contract { +func (lib *EthLib) GetStateObject(address string) *QStateObject { stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) if stateObject != nil { - return NewContract(stateObject) + return NewQStateObject(stateObject) } // See GetStorage for explanation on "nil" - return NewContract(nil) + return NewQStateObject(nil) } func (lib *EthLib) Watch(addr, storageAddr string) { diff --git a/ethereal/ui/types.go b/ethereal/ui/types.go index 5c39f0217..9e12a8892 100644 --- a/ethereal/ui/types.go +++ b/ethereal/ui/types.go @@ -46,11 +46,38 @@ func NewQKeyRing(keys []interface{}) *QKeyRing { } type QStateObject struct { - Address string - Amount string - Nonce int + object *ethchain.StateObject +} + +func NewQStateObject(object *ethchain.StateObject) *QStateObject { + return &QStateObject{object: object} } -func NewQStateObject(stateObject *ethchain.StateObject) *QStateObject { - return &QStateObject{ethutil.Hex(stateObject.Address()), stateObject.Amount.String(), int(stateObject.Nonce)} +func (c *QStateObject) GetStorage(address string) string { + // Because somehow, even if you return nil to QML it + // still has some magical object so we can't rely on + // undefined or null at the QML side + if c.object != nil { + val := c.object.GetMem(ethutil.Big("0x" + address)) + + return val.BigInt().String() + } + + return "" +} + +func (c *QStateObject) Value() string { + if c.object != nil { + return c.object.Amount.String() + } + + return "" +} + +func (c *QStateObject) Address() string { + if c.object != nil { + return ethutil.Hex(c.object.Address()) + } + + return "" } -- cgit v1.2.3 From c5481b7654b8ab35b2242befc3a5b235e6d70685 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 30 Apr 2014 14:54:59 +0200 Subject: getBalanceAt getStorageAt, fixed get balance api call --- ethereal/assets/ethereum.js | 4 ++-- ethereal/assets/qml/webapp.qml | 2 +- ethereal/assets/samplecoin.html | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index 916655d5e..fd5091bd1 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -29,7 +29,7 @@ window.eth = { postData({call: "create", args: [sec, value, gas, gasPrice, init, body]}, cb); }, - getStorage: function(address, storageAddress, cb) { + getStorageAt: function(address, storageAddress, cb) { postData({call: "getStorage", args: [address, storageAddress]}, cb); }, @@ -37,7 +37,7 @@ window.eth = { postData({call: "getKey"}, cb); }, - getBalance: function(address, cb) { + getBalanceAt: function(address, cb) { postData({call: "getBalance", args: [address]}, cb); }, diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 2f3fef4d2..c0df0b66e 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -76,7 +76,7 @@ ApplicationWindow { case "getBalance": require(1); - postData(data._seed, eth.getStateObject(data.args[0]).Value()); + postData(data._seed, eth.getStateObject(data.args[0]).value()); break case "getKey": diff --git a/ethereal/assets/samplecoin.html b/ethereal/assets/samplecoin.html index 02efa8e01..3b039fb04 100644 --- a/ethereal/assets/samplecoin.html +++ b/ethereal/assets/samplecoin.html @@ -23,15 +23,19 @@ function tests() { function init() { eth.getKey(function(key) { - eth.getStorage(jefcoinAddr, key, function(storage) { + eth.getStorageAt(jefcoinAddr, key, function(storage) { document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; }); eth.watch(jefcoinAddr, function(stateObject) { - eth.getStorage(jefcoinAddr, key, function(storage) { + eth.getStorageAt(jefcoinAddr, key, function(storage) { document.querySelector("#currentAmount").innerHTML = "Amount: " + storage; }); }); + + eth.getBalanceAt(key, function(balance) { + debug("balance", balance); + }) }); } -- cgit v1.2.3 From da7828f336cb323c78810d2963f8353787c03077 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 30 Apr 2014 17:13:12 +0200 Subject: Fixed tx nonce --- ethereal/ui/gui.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 8ec09459b..c821fa824 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -146,17 +146,20 @@ func (ui *Gui) update() { account := ui.eth.StateManager().GetAddrState(ui.addr).Object unconfirmedFunds := new(big.Int) ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) + + addrState := ui.eth.StateManager().GetAddrState(ui.addr) + for { select { case txMsg := <-txChan: tx := txMsg.Tx if txMsg.Type == ethchain.TxPre { - if bytes.Compare(tx.Sender(), ui.addr) == 0 { + if bytes.Compare(tx.Sender(), ui.addr) == 0 && addrState.Nonce <= tx.Nonce { ui.win.Root().Call("addTx", NewQTx(tx)) ui.txDb.Put(tx.Hash(), tx.RlpEncode()) - ui.eth.StateManager().GetAddrState(ui.addr).Nonce += 1 + addrState.Nonce += 1 unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { ui.win.Root().Call("addTx", NewQTx(tx)) -- cgit v1.2.3 From abb2bebf7f7fd6b0bb18ca8c49aec3a8badf3861 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 1 May 2014 19:02:35 +0200 Subject: Fixed minor issue with upnp where 'err' wasn't checked resulting in crash --- README.md | 10 +++++- ethereal/assets/icon.png | Bin 86700 -> 0 bytes ethereal/assets/samplecoin.html | 69 ---------------------------------------- ethereal/assets/test.html | 55 -------------------------------- 4 files changed, 9 insertions(+), 125 deletions(-) delete mode 100644 ethereal/assets/icon.png delete mode 100644 ethereal/assets/samplecoin.html delete mode 100644 ethereal/assets/test.html diff --git a/README.md b/README.md index 08104b075..ddf8f2bed 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,15 @@ For the development package please see the [eth-go package](https://github.com/e Build ======= -For build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go)) +To build Ethereal (GUI): + +`go get github.com/ethereum/go-ethereum/ethereal` + +To build the node (CLI): + +`go get github.com/ethereum/go-ethereum/ethereum` + +For further, detailed, build instruction please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go)) General command line options ==================== diff --git a/ethereal/assets/icon.png b/ethereal/assets/icon.png deleted file mode 100644 index 73e0ceb75..000000000 Binary files a/ethereal/assets/icon.png and /dev/null differ diff --git a/ethereal/assets/samplecoin.html b/ethereal/assets/samplecoin.html deleted file mode 100644 index 3b039fb04..000000000 --- a/ethereal/assets/samplecoin.html +++ /dev/null @@ -1,69 +0,0 @@ - - -jeffcoin - - - - - -

Jeff Coin

- - -
- -
-
-
- -
- -
- - -
- - - - diff --git a/ethereal/assets/test.html b/ethereal/assets/test.html deleted file mode 100644 index beb888685..000000000 --- a/ethereal/assets/test.html +++ /dev/null @@ -1,55 +0,0 @@ - - -jeffcoin - - - - - -

Jeff Coin

- - -
- -
-
-
- -
- -
- - - - -- cgit v1.2.3 From 76cd14ab7b2e0d96652ffd63aa08c046294e06d6 Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 1 May 2014 22:15:34 +0200 Subject: Moved and improved sample coin --- ethereal/assets/samplecoin/bootstrap-theme.min.css | 7 +++ ethereal/assets/samplecoin/bootstrap.min.css | 7 +++ ethereal/assets/samplecoin/icon.png | Bin 0 -> 86700 bytes ethereal/assets/samplecoin/samplecoin.css | 34 +++++++++++ ethereal/assets/samplecoin/samplecoin.html | 66 +++++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100755 ethereal/assets/samplecoin/bootstrap-theme.min.css create mode 100755 ethereal/assets/samplecoin/bootstrap.min.css create mode 100644 ethereal/assets/samplecoin/icon.png create mode 100644 ethereal/assets/samplecoin/samplecoin.css create mode 100644 ethereal/assets/samplecoin/samplecoin.html diff --git a/ethereal/assets/samplecoin/bootstrap-theme.min.css b/ethereal/assets/samplecoin/bootstrap-theme.min.css new file mode 100755 index 000000000..8dee07209 --- /dev/null +++ b/ethereal/assets/samplecoin/bootstrap-theme.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top, #fff 0, #e0e0e0 100%);background-image:linear-gradient(to bottom, #fff 0, #e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top, #428bca 0, #2d6ca2 100%);background-image:linear-gradient(to bottom, #428bca 0, #2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #419641 100%);background-image:linear-gradient(to bottom, #5cb85c 0, #419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top, #5bc0de 0, #2aabd2 100%);background-image:linear-gradient(to bottom, #5bc0de 0, #2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #eb9316 100%);background-image:linear-gradient(to bottom, #f0ad4e 0, #eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c12e2a 100%);background-image:linear-gradient(to bottom, #d9534f 0, #c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top, #fff 0, #f8f8f8 100%);background-image:linear-gradient(to bottom, #fff 0, #f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #ebebeb 0, #f3f3f3 100%);background-image:linear-gradient(to bottom, #ebebeb 0, #f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top, #3c3c3c 0, #222 100%);background-image:linear-gradient(to bottom, #3c3c3c 0, #222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #222 0, #282828 100%);background-image:linear-gradient(to bottom, #222 0, #282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);background-image:linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top, #d9edf7 0, #b9def0 100%);background-image:linear-gradient(to bottom, #d9edf7 0, #b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);background-image:linear-gradient(to bottom, #fcf8e3 0, #f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top, #f2dede 0, #e7c3c3 100%);background-image:linear-gradient(to bottom, #f2dede 0, #e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top, #ebebeb 0, #f5f5f5 100%);background-image:linear-gradient(to bottom, #ebebeb 0, #f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top, #428bca 0, #3071a9 100%);background-image:linear-gradient(to bottom, #428bca 0, #3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #449d44 100%);background-image:linear-gradient(to bottom, #5cb85c 0, #449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top, #5bc0de 0, #31b0d5 100%);background-image:linear-gradient(to bottom, #5bc0de 0, #31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #ec971f 100%);background-image:linear-gradient(to bottom, #f0ad4e 0, #ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c9302c 100%);background-image:linear-gradient(to bottom, #d9534f 0, #c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top, #428bca 0, #3278b3 100%);background-image:linear-gradient(to bottom, #428bca 0, #3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top, #dff0d8 0, #d0e9c6 100%);background-image:linear-gradient(to bottom, #dff0d8 0, #d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top, #d9edf7 0, #c4e3f3 100%);background-image:linear-gradient(to bottom, #d9edf7 0, #c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #faf2cc 100%);background-image:linear-gradient(to bottom, #fcf8e3 0, #faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top, #f2dede 0, #ebcccc 100%);background-image:linear-gradient(to bottom, #f2dede 0, #ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top, #e8e8e8 0, #f5f5f5 100%);background-image:linear-gradient(to bottom, #e8e8e8 0, #f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} \ No newline at end of file diff --git a/ethereal/assets/samplecoin/bootstrap.min.css b/ethereal/assets/samplecoin/bootstrap.min.css new file mode 100755 index 000000000..e281af6ed --- /dev/null +++ b/ethereal/assets/samplecoin/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.table td,.table th{background-color:#fff !important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#999}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}input[type="date"]{line-height:34px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.has-feedback .form-control-feedback{position:absolute;top:25px;right:0;display:block;width:34px;height:34px;line-height:34px;text-align:center}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:7px}@media (min-width:768px){.form-horizontal .control-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:normal;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%} \ No newline at end of file diff --git a/ethereal/assets/samplecoin/icon.png b/ethereal/assets/samplecoin/icon.png new file mode 100644 index 000000000..73e0ceb75 Binary files /dev/null and b/ethereal/assets/samplecoin/icon.png differ diff --git a/ethereal/assets/samplecoin/samplecoin.css b/ethereal/assets/samplecoin/samplecoin.css new file mode 100644 index 000000000..bc4e7f53c --- /dev/null +++ b/ethereal/assets/samplecoin/samplecoin.css @@ -0,0 +1,34 @@ +/* Space out content a bit */ +body { + padding-top: 20px; + padding-bottom: 20px; +} + +/* Everything but the jumbotron gets side spacing for mobile first + * views */ +.header, +.marketing, +.footer { + padding-right: 15px; + padding-left: 15px; +} + +/* Custom page header */ +.header { + border-bottom: 1px solid #e5e5e5; +} +/* Make the masthead heading the same height as the navigation */ +.header h3 { + padding-bottom: 19px; + margin-top: 0; + margin-bottom: 0; + line-height: 40px; +} + +.jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; + + margin: 0 auto; + width: 300px; +} diff --git a/ethereal/assets/samplecoin/samplecoin.html b/ethereal/assets/samplecoin/samplecoin.html new file mode 100644 index 000000000..1f4d1e3e1 --- /dev/null +++ b/ethereal/assets/samplecoin/samplecoin.html @@ -0,0 +1,66 @@ + + +jeffcoin + + + + + + + + + + +
+
+

JeffCoin

+
+ +
+ +
Amount:
+ +
+
+
+
+
+
+ + +
+
+ +
+
+ + + + -- cgit v1.2.3 From 9e481804a72ce78792826e215cf3660819bbb18a Mon Sep 17 00:00:00 2001 From: obscuren Date: Thu, 1 May 2014 22:59:16 +0200 Subject: Added a 'set' method to change window settings for external applications --- ethereal/assets/ethereum.js | 4 ++++ ethereal/assets/qml/webapp.qml | 6 ++++++ ethereal/assets/samplecoin/samplecoin.html | 2 ++ 3 files changed, 12 insertions(+) diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index fd5091bd1..e5dae9248 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -71,6 +71,10 @@ window.eth = { postData({call: "disconnect", args: [address, storageAddrOrCb]}); }, + set: function(props) { + postData({call: "set", args: props}); + }, + on: function(event, cb) { if(eth._onCallbacks[event] === undefined) { eth._onCallbacks[event] = []; diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index c0df0b66e..1c1ac852d 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -91,6 +91,12 @@ ApplicationWindow { require(1) postData(data._seed, null) break; + case "set": + for(var key in data.args) { + if(webview.hasOwnProperty(key)) { + window[key] = data.args[key]; + } + } } } catch(e) { console.log(data.call + ": " + e) diff --git a/ethereal/assets/samplecoin/samplecoin.html b/ethereal/assets/samplecoin/samplecoin.html index 1f4d1e3e1..ba60cf951 100644 --- a/ethereal/assets/samplecoin/samplecoin.html +++ b/ethereal/assets/samplecoin/samplecoin.html @@ -21,6 +21,8 @@ function createTransaction() { } function init() { + eth.set({width: 500, title: "Hello world"}) + eth.getKey(function(key) { eth.getStorageAt(jefcoinAddr, key, function(storage) { document.querySelector("#current-amount").innerHTML = storage; -- cgit v1.2.3 From 1b597b8ca9ee9564f29b93e1ba5877e012a8d989 Mon Sep 17 00:00:00 2001 From: Maran Date: Fri, 2 May 2014 12:01:50 +0200 Subject: Initial commit --- etherpc/packages.go | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++ etherpc/server.go | 82 +++++++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 etherpc/packages.go create mode 100644 etherpc/server.go diff --git a/etherpc/packages.go b/etherpc/packages.go new file mode 100644 index 000000000..283da1a18 --- /dev/null +++ b/etherpc/packages.go @@ -0,0 +1,185 @@ +package main + +import ( + "encoding/json" + "errors" + "math/big" +) + +type MainPackage struct{} + +type JsonResponse interface { + requirements() error +} + +type BlockResponse struct { + Name string + Id int +} +type GetBlockArgs struct { + JsonResponse + BlockNumber int + Hash string +} + +type ErrorResponse struct { + Error bool + ErrorText string +} + +type SuccessRes struct { + Error bool + Message string +} + +func NewSuccessRes(msg string) string { + e := SuccessRes{Error: true, Message: msg} + res, err := json.Marshal(e) + if err != nil { + // This should never happen + panic("Creating json error response failed, help") + } + return string(res) +} + +func NewErrorResponse(msg string) error { + e := ErrorResponse{Error: true, ErrorText: msg} + res, err := json.Marshal(e) + if err != nil { + // This should never happen + panic("Creating json error response failed, help") + } + newErr := errors.New(string(res)) + return newErr +} + +func (b *GetBlockArgs) requirements() error { + if b.BlockNumber == 0 && b.Hash == "" { + return NewErrorResponse("This call requires either a block 'number' or a block 'hash' as argument") + } + return nil +} + +func (p *MainPackage) GetBlock(args *GetBlockArgs, reply *BlockResponse) error { + err := args.requirements() + if err != nil { + return err + } + // Do something + + return nil +} + +type NewTxArgs struct { + Sec string + Recipient string + Value *big.Int + Gas *big.Int + GasPrice *big.Int + Init string + Body string +} +type TxResponse struct { +} + +func (a *NewTxArgs) requirements() error { + if a.Recipient == "" { + return NewErrorResponse("Transact requires a 'recipient' address as argument") + } + if a.Value == nil { + return NewErrorResponse("Transact requires a 'value' as argument") + } + if a.Gas == nil { + return NewErrorResponse("Transact requires a 'gas' value as argument") + } + if a.GasPrice == nil { + return NewErrorResponse("Transact requires a 'gasprice' value as argument") + } + return nil +} + +func (a *NewTxArgs) requirementsContract() error { + if a.Value == nil { + return NewErrorResponse("Create requires a 'value' as argument") + } + if a.Gas == nil { + return NewErrorResponse("Create requires a 'gas' value as argument") + } + if a.GasPrice == nil { + return NewErrorResponse("Create requires a 'gasprice' value as argument") + } + if a.Init == "" { + return NewErrorResponse("Create requires a 'init' value as argument") + } + if a.Body == "" { + return NewErrorResponse("Create requires a 'body' value as argument") + } + return nil +} + +func (p *MainPackage) Transact(args *NewTxArgs, reply *TxResponse) error { + err := args.requirements() + if err != nil { + return err + } + return nil +} + +func (p *MainPackage) Create(args *NewTxArgs, reply *string) error { + err := args.requirementsContract() + if err != nil { + return err + } + return nil +} + +func (p *MainPackage) getKey(args interface{}, reply *string) error { + return nil +} + +type GetStorageArgs struct { + Address string + Key string +} + +func (a *GetStorageArgs) requirements() error { + if a.Address == "" { + return NewErrorResponse("GetStorageAt requires an 'address' value as argument") + } + if a.Key == "" { + return NewErrorResponse("GetStorageAt requires an 'key' value as argument") + } + return nil +} + +func (p *MainPackage) getStorageAt(args *GetStorageArgs, reply *string) error { + err := args.requirements() + if err != nil { + return err + } + return nil +} + +type GetBalanceArgs struct { + Address string +} + +func (a *GetBalanceArgs) requirements() error { + if a.Address == "" { + return NewErrorResponse("GetBalanceAt requires an 'address' value as argument") + } + return nil +} + +func (p *MainPackage) GetBalanceAt(args *GetBalanceArgs, reply *string) error { + err := args.requirements() + if err != nil { + return err + } + return nil +} + +func (p *MainPackage) Test(args *GetBlockArgs, reply *int) error { + *reply = 15 + return nil +} diff --git a/etherpc/server.go b/etherpc/server.go new file mode 100644 index 000000000..291ca2d95 --- /dev/null +++ b/etherpc/server.go @@ -0,0 +1,82 @@ +package main + +import ( + "log" + "net" + "net/rpc" + "net/rpc/jsonrpc" +) + +type JsonRpcServer struct { + quit chan bool + listener net.Listener +} + +func (s *JsonRpcServer) exitHandler() { +out: + for { + select { + case <-s.quit: + s.listener.Close() + break out + } + } + + // ethutil.Config.Log.Infoln("[JSON] Shutdown JSON-RPC server") + log.Println("[JSON] Shutdown JSON-RPC server") +} + +func (s *JsonRpcServer) Stop() { + close(s.quit) +} + +func (s *JsonRpcServer) Start() { + // ethutil.Config.Log.Infoln("[JSON] Starting JSON-RPC server") + log.Println("[JSON] Starting JSON-RPC server") + go s.exitHandler() + rpc.Register(new(MainPackage)) + rpc.HandleHTTP() + + for { + conn, err := s.listener.Accept() + if err != nil { + // ethutil.Config.Log.Infoln("[JSON] Error starting JSON-RPC:", err) + log.Println("[JSON] Error starting JSON-RPC:", err) + continue + } + log.Println("Incoming request") + go jsonrpc.ServeConn(conn) + } +} + +func NewJsonRpcServer() *JsonRpcServer { + l, err := net.Listen("tcp", ":30304") + if err != nil { + // ethutil.Config.Log.Infoln("Error starting JSON-RPC") + log.Println("Error starting JSON-RPC") + } + + return &JsonRpcServer{ + listener: l, + quit: make(chan bool), + } +} + +func main() { + s := NewJsonRpcServer() + s.Start() + /* + + conn, err := net.Dial("tcp", "localhost:30304") + + if err != nil { + panic(err) + } + defer conn.Close() + c := jsonrpc.NewClient(conn) + var reply int + err = c.Call("MainPackage.Test", nil, &reply) + log.Println("ERR:", err) + log.Println("result:", reply) + */ +} -- cgit v1.2.3 From 471bd398f380bc26ba3144a0834092036565e429 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 2 May 2014 12:07:24 +0200 Subject: Moved Ext ethereum api Moved the external ethereum api which can be used by any 3rd party application. --- ethereal/ui/types.go | 83 --------------------------------------- utils/ethereum.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ utils/types.go | 83 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 83 deletions(-) delete mode 100644 ethereal/ui/types.go create mode 100644 utils/ethereum.go create mode 100644 utils/types.go diff --git a/ethereal/ui/types.go b/ethereal/ui/types.go deleted file mode 100644 index 9e12a8892..000000000 --- a/ethereal/ui/types.go +++ /dev/null @@ -1,83 +0,0 @@ -package ethui - -import ( - "encoding/hex" - "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethutil" -) - -// Block interface exposed to QML -type QBlock struct { - Number int - Hash string -} - -// Creates a new QML Block from a chain block -func NewQBlock(block *ethchain.Block) *QBlock { - info := block.BlockInfo() - hash := hex.EncodeToString(block.Hash()) - - return &QBlock{Number: int(info.Number), Hash: hash} -} - -type QTx struct { - Value, Hash, Address string - Contract bool -} - -func NewQTx(tx *ethchain.Transaction) *QTx { - hash := hex.EncodeToString(tx.Hash()) - sender := hex.EncodeToString(tx.Recipient) - isContract := len(tx.Data) > 0 - - return &QTx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender, Contract: isContract} -} - -type QKey struct { - Address string -} - -type QKeyRing struct { - Keys []interface{} -} - -func NewQKeyRing(keys []interface{}) *QKeyRing { - return &QKeyRing{Keys: keys} -} - -type QStateObject struct { - object *ethchain.StateObject -} - -func NewQStateObject(object *ethchain.StateObject) *QStateObject { - return &QStateObject{object: object} -} - -func (c *QStateObject) GetStorage(address string) string { - // Because somehow, even if you return nil to QML it - // still has some magical object so we can't rely on - // undefined or null at the QML side - if c.object != nil { - val := c.object.GetMem(ethutil.Big("0x" + address)) - - return val.BigInt().String() - } - - return "" -} - -func (c *QStateObject) Value() string { - if c.object != nil { - return c.object.Amount.String() - } - - return "" -} - -func (c *QStateObject) Address() string { - if c.object != nil { - return ethutil.Hex(c.object.Address()) - } - - return "" -} diff --git a/utils/ethereum.go b/utils/ethereum.go new file mode 100644 index 000000000..c383c4c91 --- /dev/null +++ b/utils/ethereum.go @@ -0,0 +1,107 @@ +package utils + +import ( + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" +) + +type PEthereum struct { + stateManager *ethchain.StateManager + blockChain *ethchain.BlockChain + txPool *ethchain.TxPool +} + +func NewPEthereum(eth *eth.Ethereum) *PEthereum { + return &PEthereum{ + eth.StateManager(), + eth.BlockChain(), + eth.TxPool(), + } +} + +func (lib *PEthereum) GetBlock(hexHash string) *PBlock { + hash := ethutil.FromHex(hexHash) + + block := lib.blockChain.GetBlock(hash) + + return &PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} +} + +func (lib *PEthereum) GetKey() string { + return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) +} + +func (lib *PEthereum) GetStateObject(address string) *PStateObject { + stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) + if stateObject != nil { + return NewPStateObject(stateObject) + } + + // See GetStorage for explanation on "nil" + return NewPStateObject(nil) +} + +func (lib *PEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { + return lib.createTx(key, recipient, valueStr, gasStr, gasPriceStr, dataStr, "") +} + +func (lib *PEthereum) Create(key, valueStr, gasStr, gasPriceStr, initStr, bodyStr string) (string, error) { + return lib.createTx(key, "", valueStr, gasStr, gasPriceStr, initStr, bodyStr) +} + +func (lib *PEthereum) createTx(key, recipient, valueStr, gasStr, gasPriceStr, initStr, scriptStr string) (string, error) { + var hash []byte + var contractCreation bool + if len(recipient) == 0 { + contractCreation = true + } else { + hash = ethutil.FromHex(recipient) + } + + keyPair, err := ethchain.NewKeyPairFromSec([]byte(key)) + if err != nil { + return "", err + } + + value := ethutil.Big(valueStr) + gas := ethutil.Big(gasStr) + gasPrice := ethutil.Big(gasPriceStr) + var tx *ethchain.Transaction + // Compile and assemble the given data + if contractCreation { + initScript, err := Compile(initStr) + if err != nil { + return "", err + } + mainScript, err := Compile(scriptStr) + if err != nil { + return "", err + } + + tx = ethchain.NewContractCreationTx(value, gas, gasPrice, mainScript, initScript) + } else { + /* + lines := strings.Split(dataStr, "\n") + var data []byte + for _, line := range lines { + data = append(data, ethutil.BigToBytes(ethutil.Big(line), 256)...) + } + */ + + tx = ethchain.NewTransactionMessage(hash, value, gas, gasPrice, []byte(initStr)) + } + + acc := lib.stateManager.GetAddrState(keyPair.Address()) + tx.Nonce = acc.Nonce + tx.Sign(keyPair.PrivateKey) + lib.txPool.QueueTransaction(tx) + + if contractCreation { + ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) + } else { + ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) + } + + return ethutil.Hex(tx.Hash()), nil +} diff --git a/utils/types.go b/utils/types.go new file mode 100644 index 000000000..44264aa5e --- /dev/null +++ b/utils/types.go @@ -0,0 +1,83 @@ +package utils + +import ( + "encoding/hex" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethutil" +) + +// Block interface exposed to QML +type PBlock struct { + Number int + Hash string +} + +// Creates a new QML Block from a chain block +func NewPBlock(block *ethchain.Block) *PBlock { + info := block.BlockInfo() + hash := hex.EncodeToString(block.Hash()) + + return &PBlock{Number: int(info.Number), Hash: hash} +} + +type PTx struct { + Value, Hash, Address string + Contract bool +} + +func NewPTx(tx *ethchain.Transaction) *PTx { + hash := hex.EncodeToString(tx.Hash()) + sender := hex.EncodeToString(tx.Recipient) + isContract := len(tx.Data) > 0 + + return &PTx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender, Contract: isContract} +} + +type PKey struct { + Address string +} + +type PKeyRing struct { + Keys []interface{} +} + +func NewPKeyRing(keys []interface{}) *PKeyRing { + return &PKeyRing{Keys: keys} +} + +type PStateObject struct { + object *ethchain.StateObject +} + +func NewPStateObject(object *ethchain.StateObject) *PStateObject { + return &PStateObject{object: object} +} + +func (c *PStateObject) GetStorage(address string) string { + // Because somehow, even if you return nil to QML it + // still has some magical object so we can't rely on + // undefined or null at the QML side + if c.object != nil { + val := c.object.GetMem(ethutil.Big("0x" + address)) + + return val.BigInt().String() + } + + return "" +} + +func (c *PStateObject) Value() string { + if c.object != nil { + return c.object.Amount.String() + } + + return "" +} + +func (c *PStateObject) Address() string { + if c.object != nil { + return ethutil.Hex(c.object.Address()) + } + + return "" +} -- cgit v1.2.3 From 5a692b9f2bf265251b6f1faf171f55489b65b3de Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 2 May 2014 12:08:15 +0200 Subject: Moved API --- ethereal/ui/ext_app.go | 96 +------------------------------------------ ethereal/ui/gui.go | 13 +++--- ethereal/ui/html_container.go | 5 ++- ethereal/ui/library.go | 10 ++--- 4 files changed, 17 insertions(+), 107 deletions(-) diff --git a/ethereal/ui/ext_app.go b/ethereal/ui/ext_app.go index c02ffb7b2..1021afea9 100644 --- a/ethereal/ui/ext_app.go +++ b/ethereal/ui/ext_app.go @@ -2,13 +2,11 @@ package ethui import ( "fmt" - "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" "math/big" - "strings" ) type AppContainer interface { @@ -24,7 +22,7 @@ type AppContainer interface { } type ExtApplication struct { - *QEthereum + *utils.PEthereum blockChan chan ethutil.React changeChan chan ethutil.React @@ -37,7 +35,7 @@ type ExtApplication struct { func NewExtApplication(container AppContainer, lib *UiLib) *ExtApplication { app := &ExtApplication{ - NewQEthereum(lib.eth), + utils.NewPEthereum(lib.eth), make(chan ethutil.React, 1), make(chan ethutil.React, 1), make(chan bool), @@ -127,93 +125,3 @@ func (app *ExtApplication) Watch(addr, storageAddr string) { app.registeredEvents = append(app.registeredEvents, event) } - -type QEthereum struct { - stateManager *ethchain.StateManager - blockChain *ethchain.BlockChain - txPool *ethchain.TxPool -} - -func NewQEthereum(eth *eth.Ethereum) *QEthereum { - return &QEthereum{ - eth.StateManager(), - eth.BlockChain(), - eth.TxPool(), - } -} - -func (lib *QEthereum) GetBlock(hexHash string) *QBlock { - hash := ethutil.FromHex(hexHash) - - block := lib.blockChain.GetBlock(hash) - - return &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} -} - -func (lib *QEthereum) GetKey() string { - return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) -} - -func (lib *QEthereum) GetStateObject(address string) *QStateObject { - stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) - if stateObject != nil { - return NewQStateObject(stateObject) - } - - // See GetStorage for explanation on "nil" - return NewQStateObject(nil) -} - -func (lib *QEthereum) Watch(addr, storageAddr string) { - // lib.stateManager.Watch(ethutil.FromHex(addr), ethutil.FromHex(storageAddr)) -} - -func (lib *QEthereum) CreateTx(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { - return lib.Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr) -} - -func (lib *QEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) (string, error) { - var hash []byte - var contractCreation bool - if len(recipient) == 0 { - contractCreation = true - } else { - hash = ethutil.FromHex(recipient) - } - - keyPair := ethutil.Config.Db.GetKeys()[0] - value := ethutil.Big(valueStr) - gas := ethutil.Big(gasStr) - gasPrice := ethutil.Big(gasPriceStr) - var tx *ethchain.Transaction - // Compile and assemble the given data - if contractCreation { - // Compile script - mainScript, initScript, err := utils.CompileScript(dataStr) - if err != nil { - return "", err - } - - tx = ethchain.NewContractCreationTx(value, gas, gasPrice, mainScript, initScript) - } else { - lines := strings.Split(dataStr, "\n") - var data []byte - for _, line := range lines { - data = append(data, ethutil.BigToBytes(ethutil.Big(line), 256)...) - } - - tx = ethchain.NewTransactionMessage(hash, value, gas, gasPrice, data) - } - acc := lib.stateManager.GetAddrState(keyPair.Address()) - tx.Nonce = acc.Nonce - tx.Sign(keyPair.PrivateKey) - lib.txPool.QueueTransaction(tx) - - if contractCreation { - ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) - } else { - ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) - } - - return ethutil.Hex(tx.Hash()), nil -} diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index c821fa824..8e6433207 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethdb" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" "math/big" "strings" @@ -56,9 +57,9 @@ func (ui *Gui) Start(assetPath string) { // Register ethereum functions qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ - Init: func(p *QBlock, obj qml.Object) { p.Number = 0; p.Hash = "" }, + Init: func(p *utils.PBlock, obj qml.Object) { p.Number = 0; p.Hash = "" }, }, { - Init: func(p *QTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, + Init: func(p *utils.PTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, }}) ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.2")) @@ -129,13 +130,13 @@ func (ui *Gui) readPreviousTransactions() { for it.Next() { tx := ethchain.NewTransactionFromBytes(it.Value()) - ui.win.Root().Call("addTx", NewQTx(tx)) + ui.win.Root().Call("addTx", utils.NewPTx(tx)) } it.Release() } func (ui *Gui) ProcessBlock(block *ethchain.Block) { - ui.win.Root().Call("addBlock", NewQBlock(block)) + ui.win.Root().Call("addBlock", utils.NewPBlock(block)) } // Simple go routine function that updates the list of peers in the GUI @@ -156,13 +157,13 @@ func (ui *Gui) update() { if txMsg.Type == ethchain.TxPre { if bytes.Compare(tx.Sender(), ui.addr) == 0 && addrState.Nonce <= tx.Nonce { - ui.win.Root().Call("addTx", NewQTx(tx)) + ui.win.Root().Call("addTx", utils.NewPTx(tx)) ui.txDb.Put(tx.Hash(), tx.RlpEncode()) addrState.Nonce += 1 unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) } else if bytes.Compare(tx.Recipient, ui.addr) == 0 { - ui.win.Root().Call("addTx", NewQTx(tx)) + ui.win.Root().Call("addTx", utils.NewPTx(tx)) ui.txDb.Put(tx.Hash(), tx.RlpEncode()) unconfirmedFunds.Add(unconfirmedFunds, tx.Value) diff --git a/ethereal/ui/html_container.go b/ethereal/ui/html_container.go index 8e3ef0fc7..16cc531f2 100644 --- a/ethereal/ui/html_container.go +++ b/ethereal/ui/html_container.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" "github.com/go-qml/qml" "math/big" "path/filepath" @@ -56,12 +57,12 @@ func (app *HtmlApplication) Window() *qml.Window { } func (app *HtmlApplication) NewBlock(block *ethchain.Block) { - b := &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} + b := &utils.PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} app.webView.Call("onNewBlockCb", b) } func (app *HtmlApplication) ObjectChanged(stateObject *ethchain.StateObject) { - app.webView.Call("onObjectChangeCb", NewQStateObject(stateObject)) + app.webView.Call("onObjectChangeCb", utils.NewPStateObject(stateObject)) } func (app *HtmlApplication) StorageChanged(stateObject *ethchain.StateObject, addr []byte, value *big.Int) { diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 70462a93d..231fd96e7 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -47,14 +47,14 @@ func (lib *EthLib) GetKey() string { return ethutil.Hex(ethutil.Config.Db.GetKeys()[0].Address()) } -func (lib *EthLib) GetStateObject(address string) *QStateObject { +func (lib *EthLib) GetStateObject(address string) *utils.PStateObject { stateObject := lib.stateManager.ProcState().GetContract(ethutil.FromHex(address)) if stateObject != nil { - return NewQStateObject(stateObject) + return utils.NewPStateObject(stateObject) } // See GetStorage for explanation on "nil" - return NewQStateObject(nil) + return utils.NewPStateObject(nil) } func (lib *EthLib) Watch(addr, storageAddr string) { @@ -115,7 +115,7 @@ func (lib *EthLib) Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr st return ethutil.Hex(tx.Hash()), nil } -func (lib *EthLib) GetBlock(hexHash string) *QBlock { +func (lib *EthLib) GetBlock(hexHash string) *utils.PBlock { hash, err := hex.DecodeString(hexHash) if err != nil { return nil @@ -123,5 +123,5 @@ func (lib *EthLib) GetBlock(hexHash string) *QBlock { block := lib.blockChain.GetBlock(hash) - return &QBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} + return &utils.PBlock{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} } -- cgit v1.2.3 From ee04c6ff6790a9b39ea96a630a60bdcf7f261b97 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 2 May 2014 12:08:52 +0200 Subject: Added string conversion API * bin * pad * unpad * conversion bin/hex/dec --- ethereal/assets/ethereum.js | 59 +++++++++++++++++++++++++++++- ethereal/assets/qml/webapp.qml | 2 + ethereal/assets/samplecoin/samplecoin.html | 2 +- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index e5dae9248..1f36f691e 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -114,7 +114,7 @@ function debug(/**/) { var args = arguments; var msg = "" for(var i = 0; i < args.length; i++){ - if(typeof args[i] == "object") { + if(typeof args[i] === "object") { msg += " " + JSON.stringify(args[i]) } else { msg += args[i] @@ -155,3 +155,60 @@ navigator.qt.onmessage = function(ev) { } } } + +window.eth._0 = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +String.prototype.pad = function(len) { + var bin = this.bin(); + var l = bin.length; + if(l < 32) { + return eth._0.substr(0, 32 - bin.length) + bin; + } + + return bin; +} + +String.prototype.unpad = function() { + var i, l; + for(i = 0, l = this.length; i < l; i++) { + if(this[i] != "\0") { + return this.substr(i, this.length); + } + } + + return this.substr(i, this.length); +} + +String.prototype.bin = function() { + if(this.substr(0, 2) == "0x") { + return this.hex2bin(); + } else if(/^\d+$/.test(this)) { + return this.num2bin() + } + + // Otherwise we'll return the "String" object instead of an actual string + return this.substr(0, this.length) +} + +String.prototype.unbin = function() { + var i, l, o = ''; + for(i = 0, l = this.length; i < l; i++) { + var n = this.charCodeAt(i).toString(16); + o += n.length < 2 ? '0' + n : n; + } + + return "0x" + o; +} + +String.prototype.hex2bin = function() { + bytes = [] + + for(var i=2; i< this.length-1; i+=2){ + bytes.push(parseInt(this.substr(i, 2), 16)); + } + + return String.fromCharCode.apply(String, bytes); +} + +String.prototype.num2bin = function() { + return ("0x"+parseInt(this).toString(16)).bin() +} diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 1c1ac852d..11ccd6998 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -123,6 +123,8 @@ ApplicationWindow { function onObjectChangeCb(stateObject) { postEvent("object:"+stateObject.address(), stateObject) } + function onStorageChangeCb() { + } } Rectangle { diff --git a/ethereal/assets/samplecoin/samplecoin.html b/ethereal/assets/samplecoin/samplecoin.html index ba60cf951..0f61c613a 100644 --- a/ethereal/assets/samplecoin/samplecoin.html +++ b/ethereal/assets/samplecoin/samplecoin.html @@ -21,7 +21,7 @@ function createTransaction() { } function init() { - eth.set({width: 500, title: "Hello world"}) + eth.set({width: 500}) eth.getKey(function(key) { eth.getStorageAt(jefcoinAddr, key, function(storage) { -- cgit v1.2.3 From 3424bf17ca791b97ea512bef5290d1a52c1758af Mon Sep 17 00:00:00 2001 From: Maran Date: Fri, 2 May 2014 13:35:03 +0200 Subject: Moved RPC Server and implemented it as a package --- ethereal/config.go | 2 + ethereum/config.go | 2 + ethereum/ethereum.go | 5 ++ etherpc/packages.go | 185 --------------------------------------------------- etherpc/server.go | 82 ----------------------- 5 files changed, 9 insertions(+), 267 deletions(-) delete mode 100644 etherpc/packages.go delete mode 100644 etherpc/server.go diff --git a/ethereal/config.go b/ethereal/config.go index ac4484d0b..94f896c5f 100644 --- a/ethereal/config.go +++ b/ethereal/config.go @@ -6,6 +6,7 @@ import ( var StartConsole bool var StartMining bool +var StartRpc bool var UseUPnP bool var OutboundPort string var ShowGenesis bool @@ -21,6 +22,7 @@ var AssetPath string func Init() { flag.BoolVar(&StartConsole, "c", false, "debug and testing console") flag.BoolVar(&StartMining, "m", false, "start dagger mining") + flag.BoolVar(&StartRpc, "r", false, "start rpc server") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") diff --git a/ethereum/config.go b/ethereum/config.go index b796af5cd..234e79f12 100644 --- a/ethereum/config.go +++ b/ethereum/config.go @@ -6,6 +6,7 @@ import ( var StartConsole bool var StartMining bool +var StartRpc bool var UseUPnP bool var OutboundPort string var ShowGenesis bool @@ -24,6 +25,7 @@ func Init() { flag.BoolVar(&StartMining, "m", false, "start dagger mining") flag.BoolVar(&ShowGenesis, "g", false, "prints genesis header and exits") //flag.BoolVar(&UseGui, "gui", true, "use the gui") + flag.BoolVar(&StartRpc, "r", false, "start rpc server") flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)") flag.BoolVar(&UseUPnP, "upnp", false, "enable UPnP support") flag.BoolVar(&UseSeed, "seed", true, "seed peers") diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index 04851d2bd..0b56855e0 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/etherpc" "github.com/ethereum/eth-go/ethminer" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" @@ -131,6 +132,10 @@ func main() { console := NewConsole(ethereum) go console.Start() } + if StartRpc { + ethereum.RpcServer = etherpc.NewJsonRpcServer() + go ethereum.RpcServer.Start() + } RegisterInterrupts(ethereum) ethereum.Start() diff --git a/etherpc/packages.go b/etherpc/packages.go deleted file mode 100644 index 283da1a18..000000000 --- a/etherpc/packages.go +++ /dev/null @@ -1,185 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "math/big" -) - -type MainPackage struct{} - -type JsonResponse interface { - requirements() error -} - -type BlockResponse struct { - Name string - Id int -} -type GetBlockArgs struct { - JsonResponse - BlockNumber int - Hash string -} - -type ErrorResponse struct { - Error bool - ErrorText string -} - -type SuccessRes struct { - Error bool - Message string -} - -func NewSuccessRes(msg string) string { - e := SuccessRes{Error: true, Message: msg} - res, err := json.Marshal(e) - if err != nil { - // This should never happen - panic("Creating json error response failed, help") - } - return string(res) -} - -func NewErrorResponse(msg string) error { - e := ErrorResponse{Error: true, ErrorText: msg} - res, err := json.Marshal(e) - if err != nil { - // This should never happen - panic("Creating json error response failed, help") - } - newErr := errors.New(string(res)) - return newErr -} - -func (b *GetBlockArgs) requirements() error { - if b.BlockNumber == 0 && b.Hash == "" { - return NewErrorResponse("This call requires either a block 'number' or a block 'hash' as argument") - } - return nil -} - -func (p *MainPackage) GetBlock(args *GetBlockArgs, reply *BlockResponse) error { - err := args.requirements() - if err != nil { - return err - } - // Do something - - return nil -} - -type NewTxArgs struct { - Sec string - Recipient string - Value *big.Int - Gas *big.Int - GasPrice *big.Int - Init string - Body string -} -type TxResponse struct { -} - -func (a *NewTxArgs) requirements() error { - if a.Recipient == "" { - return NewErrorResponse("Transact requires a 'recipient' address as argument") - } - if a.Value == nil { - return NewErrorResponse("Transact requires a 'value' as argument") - } - if a.Gas == nil { - return NewErrorResponse("Transact requires a 'gas' value as argument") - } - if a.GasPrice == nil { - return NewErrorResponse("Transact requires a 'gasprice' value as argument") - } - return nil -} - -func (a *NewTxArgs) requirementsContract() error { - if a.Value == nil { - return NewErrorResponse("Create requires a 'value' as argument") - } - if a.Gas == nil { - return NewErrorResponse("Create requires a 'gas' value as argument") - } - if a.GasPrice == nil { - return NewErrorResponse("Create requires a 'gasprice' value as argument") - } - if a.Init == "" { - return NewErrorResponse("Create requires a 'init' value as argument") - } - if a.Body == "" { - return NewErrorResponse("Create requires a 'body' value as argument") - } - return nil -} - -func (p *MainPackage) Transact(args *NewTxArgs, reply *TxResponse) error { - err := args.requirements() - if err != nil { - return err - } - return nil -} - -func (p *MainPackage) Create(args *NewTxArgs, reply *string) error { - err := args.requirementsContract() - if err != nil { - return err - } - return nil -} - -func (p *MainPackage) getKey(args interface{}, reply *string) error { - return nil -} - -type GetStorageArgs struct { - Address string - Key string -} - -func (a *GetStorageArgs) requirements() error { - if a.Address == "" { - return NewErrorResponse("GetStorageAt requires an 'address' value as argument") - } - if a.Key == "" { - return NewErrorResponse("GetStorageAt requires an 'key' value as argument") - } - return nil -} - -func (p *MainPackage) getStorageAt(args *GetStorageArgs, reply *string) error { - err := args.requirements() - if err != nil { - return err - } - return nil -} - -type GetBalanceArgs struct { - Address string -} - -func (a *GetBalanceArgs) requirements() error { - if a.Address == "" { - return NewErrorResponse("GetBalanceAt requires an 'address' value as argument") - } - return nil -} - -func (p *MainPackage) GetBalanceAt(args *GetBalanceArgs, reply *string) error { - err := args.requirements() - if err != nil { - return err - } - return nil -} - -func (p *MainPackage) Test(args *GetBlockArgs, reply *int) error { - *reply = 15 - return nil -} diff --git a/etherpc/server.go b/etherpc/server.go deleted file mode 100644 index 291ca2d95..000000000 --- a/etherpc/server.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "log" - "net" - "net/rpc" - "net/rpc/jsonrpc" -) - -type JsonRpcServer struct { - quit chan bool - listener net.Listener -} - -func (s *JsonRpcServer) exitHandler() { -out: - for { - select { - case <-s.quit: - s.listener.Close() - break out - } - } - - // ethutil.Config.Log.Infoln("[JSON] Shutdown JSON-RPC server") - log.Println("[JSON] Shutdown JSON-RPC server") -} - -func (s *JsonRpcServer) Stop() { - close(s.quit) -} - -func (s *JsonRpcServer) Start() { - // ethutil.Config.Log.Infoln("[JSON] Starting JSON-RPC server") - log.Println("[JSON] Starting JSON-RPC server") - go s.exitHandler() - rpc.Register(new(MainPackage)) - rpc.HandleHTTP() - - for { - conn, err := s.listener.Accept() - if err != nil { - // ethutil.Config.Log.Infoln("[JSON] Error starting JSON-RPC:", err) - log.Println("[JSON] Error starting JSON-RPC:", err) - continue - } - log.Println("Incoming request") - go jsonrpc.ServeConn(conn) - } -} - -func NewJsonRpcServer() *JsonRpcServer { - l, err := net.Listen("tcp", ":30304") - if err != nil { - // ethutil.Config.Log.Infoln("Error starting JSON-RPC") - log.Println("Error starting JSON-RPC") - } - - return &JsonRpcServer{ - listener: l, - quit: make(chan bool), - } -} - -func main() { - s := NewJsonRpcServer() - s.Start() - /* - - conn, err := net.Dial("tcp", "localhost:30304") - - if err != nil { - panic(err) - } - defer conn.Close() - c := jsonrpc.NewClient(conn) - var reply int - err = c.Call("MainPackage.Test", nil, &reply) - log.Println("ERR:", err) - log.Println("result:", reply) - */ -} -- cgit v1.2.3 From f1da6f0564696f4fb5a6c04d1b9e24ed12432d63 Mon Sep 17 00:00:00 2001 From: obscuren Date: Fri, 2 May 2014 13:35:12 +0200 Subject: Fixed samplecoin --- ethereal/assets/ethereum.js | 5 ++--- ethereal/assets/qml/webapp.qml | 1 + ethereal/assets/samplecoin/samplecoin.html | 28 +++++++++++++++------------- utils/ethereum.go | 27 +++++++++++++++------------ utils/types.go | 10 +++++++++- 5 files changed, 42 insertions(+), 29 deletions(-) diff --git a/ethereal/assets/ethereum.js b/ethereal/assets/ethereum.js index 1f36f691e..64a7ff47c 100644 --- a/ethereal/assets/ethereum.js +++ b/ethereal/assets/ethereum.js @@ -19,8 +19,7 @@ window.eth = { // Create transaction // - // Creates a transaction with the current account - // If no recipient is set, the Ethereum API will see it as a contract creation + // Transact between two state objects transact: function(sec, recipient, value, gas, gasPrice, data, cb) { postData({call: "transact", args: [sec, recipient, value, gas, gasPrice, data]}, cb); }, @@ -202,7 +201,7 @@ String.prototype.unbin = function() { String.prototype.hex2bin = function() { bytes = [] - for(var i=2; i< this.length-1; i+=2){ + for(var i=2; i< this.length-1; i+=2) { bytes.push(parseInt(this.substr(i, 2), 16)); } diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 11ccd6998..4bac12ef0 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -60,6 +60,7 @@ ApplicationWindow { var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5]) postData(data._seed, tx) + break case "create": postData(data._seed, null) diff --git a/ethereal/assets/samplecoin/samplecoin.html b/ethereal/assets/samplecoin/samplecoin.html index 0f61c613a..6f35a1312 100644 --- a/ethereal/assets/samplecoin/samplecoin.html +++ b/ethereal/assets/samplecoin/samplecoin.html @@ -9,13 +9,14 @@ + + + -- cgit v1.2.3 From edc281ac5fb3f77aa826507390d374d5bbe1bb81 Mon Sep 17 00:00:00 2001 From: Maran Date: Tue, 13 May 2014 14:53:46 +0200 Subject: Depcrecated set --- ethereal/assets/qml/webapp.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 86eb7fe2f..7af006f77 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -118,11 +118,14 @@ ApplicationWindow { postData(data._seed, null) break; case "set": + console.log("'Set' has been depcrecated") + /* for(var key in data.args) { if(webview.hasOwnProperty(key)) { window[key] = data.args[key]; } } + */ break; case "getSecretToAddress": require(1) -- cgit v1.2.3 From b71094b01cdc51a44d81eb7780a9d7d5216b6197 Mon Sep 17 00:00:00 2001 From: Maran Date: Tue, 13 May 2014 16:32:35 +0200 Subject: Removed harcoded addresses from GetBlock JS bindings. Fixes #39 --- ethereal/assets/qml/webapp.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 7af006f77..381ca08fa 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -70,12 +70,12 @@ ApplicationWindow { break case "getBlockByNumber": - var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") + var block = eth.getBlock(data.args[0]) postData(data._seed, block) break case "getBlockByHash": - var block = eth.getBlock("b9b56cf6f907fbee21db0cd7cbc0e6fea2fe29503a3943e275c5e467d649cb06") + var block = eth.getBlock((data.args[0]) postData(data._seed, block) break -- cgit v1.2.3 From fca36cc03db129619b27133285b18b9877f82d19 Mon Sep 17 00:00:00 2001 From: Maran Date: Tue, 13 May 2014 16:36:29 +0200 Subject: Typo --- ethereal/assets/qml/webapp.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml index 381ca08fa..d3cffeeca 100644 --- a/ethereal/assets/qml/webapp.qml +++ b/ethereal/assets/qml/webapp.qml @@ -75,7 +75,7 @@ ApplicationWindow { break case "getBlockByHash": - var block = eth.getBlock((data.args[0]) + var block = eth.getBlock(data.args[0]) postData(data._seed, block) break -- cgit v1.2.3 From 9caf53f8c63cb30a174d2b33ef85176c8f6f8204 Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 13 May 2014 16:37:15 +0200 Subject: Bumped --- ethereal/ui/gui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 3393b1101..7f84272d6 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -68,7 +68,7 @@ func (gui *Gui) Start(assetPath string) { Init: func(p *ethpub.PTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, }}) - ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.5.0 RC3")) + ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", "0.5.0 RC4")) ethutil.Config.Log.Infoln("[GUI] Starting GUI") // Create a new QML engine gui.engine = qml.NewEngine() -- cgit v1.2.3 From faa307362543bb89f10458e83aa03c557977937f Mon Sep 17 00:00:00 2001 From: obscuren Date: Tue, 13 May 2014 22:25:05 +0200 Subject: Changed validators to regexp validators IntValidator limits to 32bit int (JavaScript limitation) and therefor the input fields are limited in length. --- ethereal/assets/qml/newTransaction/_new_contract.qml | 6 +++--- ethereal/assets/qml/newTransaction/_simple_send.qml | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ethereal/assets/qml/newTransaction/_new_contract.qml b/ethereal/assets/qml/newTransaction/_new_contract.qml index f8f3d53a0..e3c7229eb 100644 --- a/ethereal/assets/qml/newTransaction/_new_contract.qml +++ b/ethereal/assets/qml/newTransaction/_new_contract.qml @@ -61,7 +61,7 @@ Component { id: txValue width: 200 placeholderText: "Amount" - validator: IntValidator { } + validator: RegExpValidator { regExp: /\d*/ } onTextChanged: { contractFormReady() } @@ -69,7 +69,7 @@ Component { TextField { id: txGas width: 200 - validator: IntValidator { } + validator: RegExpValidator { regExp: /\d*/ } placeholderText: "Gas" onTextChanged: { contractFormReady() @@ -79,7 +79,7 @@ Component { id: txGasPrice width: 200 placeholderText: "Gas price" - validator: IntValidator { } + validator: RegExpValidator { regExp: /\d*/ } onTextChanged: { contractFormReady() } diff --git a/ethereal/assets/qml/newTransaction/_simple_send.qml b/ethereal/assets/qml/newTransaction/_simple_send.qml index 12420c15a..cd1ef55b6 100644 --- a/ethereal/assets/qml/newTransaction/_simple_send.qml +++ b/ethereal/assets/qml/newTransaction/_simple_send.qml @@ -56,9 +56,10 @@ Component { } TextField { id: txSimpleValue + width: 200 placeholderText: "Amount" anchors.rightMargin: 5 - validator: IntValidator { } + validator: RegExpValidator { regExp: /\d*/ } onTextChanged: { checkFormState() } } Button { -- cgit v1.2.3 From 0d9c948b9b2c5eef6f0069f9aa05e9868f939444 Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 14 May 2014 12:24:49 +0200 Subject: Generate coinbase from privatekey, not pubkey. Partily fixes #43 --- ethereum/ethereum.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index babebbb48..055cc0bc4 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -138,7 +138,7 @@ func main() { go func() { data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(1).Bytes() + addr := keyRing.Get(0).Bytes() pair, _ := ethchain.NewKeyPairFromSec(ethutil.FromHex(hex.EncodeToString(addr))) -- cgit v1.2.3 From e8147cf7c6f508910698e6743ad347c78010ffe3 Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 14 May 2014 12:41:30 +0200 Subject: Refactored mining into utils and exposed it to ethereal. Partly fixes #43 --- ethereal/ethereum.go | 4 ++++ ethereum/ethereum.go | 25 +------------------------ utils/cmd.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 24 deletions(-) create mode 100644 utils/cmd.go diff --git a/ethereal/ethereum.go b/ethereal/ethereum.go index 15a454bdf..32c16f64f 100644 --- a/ethereal/ethereum.go +++ b/ethereal/ethereum.go @@ -99,6 +99,10 @@ func main() { os.Exit(0) } + if StartMining { + utils.DoMining(ethereum) + } + if StartRpc { ethereum.RpcServer, err = ethrpc.NewJsonRpcServer(ethpub.NewPEthereum(ethereum), RpcPort) if err != nil { diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index 055cc0bc4..207e61c88 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -1,11 +1,9 @@ package main import ( - "encoding/hex" "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethminer" "github.com/ethereum/eth-go/ethpub" "github.com/ethereum/eth-go/ethrpc" "github.com/ethereum/eth-go/ethutil" @@ -127,28 +125,7 @@ func main() { ethereum.Mining = StartMining if StartMining { - logger.Infoln("Miner started") - - // Fake block mining. It broadcasts a new block every 5 seconds - go func() { - - if StartMining { - logger.Infoln("Miner started") - - go func() { - data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) - keyRing := ethutil.NewValueFromBytes(data) - addr := keyRing.Get(0).Bytes() - - pair, _ := ethchain.NewKeyPairFromSec(ethutil.FromHex(hex.EncodeToString(addr))) - - miner := ethminer.NewDefaultMiner(pair.Address(), ethereum) - miner.Start() - - }() - } - }() - + utils.DoMining(ethereum) } if StartConsole { diff --git a/utils/cmd.go b/utils/cmd.go new file mode 100644 index 000000000..a99fd9eed --- /dev/null +++ b/utils/cmd.go @@ -0,0 +1,31 @@ +package utils + +import ( + "encoding/hex" + "github.com/ethereum/eth-go" + "github.com/ethereum/eth-go/ethchain" + "github.com/ethereum/eth-go/ethminer" + _ "github.com/ethereum/eth-go/ethrpc" + "github.com/ethereum/eth-go/ethutil" + "log" +) + +func DoMining(ethereum *eth.Ethereum) { + // Set Mining status + ethereum.Mining = true + + log.Println("Miner started") + + // Fake block mining. It broadcasts a new block every 5 seconds + go func() { + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + keyRing := ethutil.NewValueFromBytes(data) + addr := keyRing.Get(0).Bytes() + + pair, _ := ethchain.NewKeyPairFromSec(ethutil.FromHex(hex.EncodeToString(addr))) + + miner := ethminer.NewDefaultMiner(pair.Address(), ethereum) + miner.Start() + + }() +} -- cgit v1.2.3 From 2012e0c67abd8e3012a3eb1fae2e282e2a442d01 Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 14 May 2014 13:26:15 +0200 Subject: Rewritten a check to only start mining once we are caught up with all peers --- utils/cmd.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/utils/cmd.go b/utils/cmd.go index a99fd9eed..44924ce91 100644 --- a/utils/cmd.go +++ b/utils/cmd.go @@ -8,24 +8,27 @@ import ( _ "github.com/ethereum/eth-go/ethrpc" "github.com/ethereum/eth-go/ethutil" "log" + "time" ) func DoMining(ethereum *eth.Ethereum) { // Set Mining status ethereum.Mining = true - log.Println("Miner started") - - // Fake block mining. It broadcasts a new block every 5 seconds go func() { + // Give it some time to connect with peers + time.Sleep(3 * time.Second) + + for ethereum.IsUpToDate() == false { + time.Sleep(5 * time.Second) + } + log.Println("Miner started") + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) keyRing := ethutil.NewValueFromBytes(data) addr := keyRing.Get(0).Bytes() - pair, _ := ethchain.NewKeyPairFromSec(ethutil.FromHex(hex.EncodeToString(addr))) - miner := ethminer.NewDefaultMiner(pair.Address(), ethereum) miner.Start() - }() } -- cgit v1.2.3 From 9fce273ce97a8db091a0bf9d0b503a2ea7261f81 Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 14 May 2014 13:32:49 +0200 Subject: Refactored RPC client to utils so it can be reused --- ethereal/ethereum.go | 9 +-------- ethereum/ethereum.go | 9 +-------- utils/cmd.go | 13 ++++++++++++- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/ethereal/ethereum.go b/ethereal/ethereum.go index 32c16f64f..a97d7f498 100644 --- a/ethereal/ethereum.go +++ b/ethereal/ethereum.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethpub" - "github.com/ethereum/eth-go/ethrpc" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/ethereal/ui" "github.com/ethereum/go-ethereum/utils" @@ -104,12 +102,7 @@ func main() { } if StartRpc { - ethereum.RpcServer, err = ethrpc.NewJsonRpcServer(ethpub.NewPEthereum(ethereum), RpcPort) - if err != nil { - log.Println("Could not start RPC interface:", err) - } else { - go ethereum.RpcServer.Start() - } + utils.DoRpc(ethereum, RpcPort) } log.Printf("Starting Ethereum GUI v%s\n", ethutil.Config.Ver) diff --git a/ethereum/ethereum.go b/ethereum/ethereum.go index 207e61c88..448223c37 100644 --- a/ethereum/ethereum.go +++ b/ethereum/ethereum.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" - "github.com/ethereum/eth-go/ethpub" - "github.com/ethereum/eth-go/ethrpc" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/utils" "log" @@ -139,12 +137,7 @@ func main() { go console.Start() } if StartRpc { - ethereum.RpcServer, err = ethrpc.NewJsonRpcServer(ethpub.NewPEthereum(ethereum), RpcPort) - if err != nil { - logger.Infoln("Could not start RPC interface:", err) - } else { - go ethereum.RpcServer.Start() - } + utils.DoRpc(ethereum, RpcPort) } RegisterInterrupts(ethereum) diff --git a/utils/cmd.go b/utils/cmd.go index 44924ce91..5a100ca4f 100644 --- a/utils/cmd.go +++ b/utils/cmd.go @@ -5,12 +5,23 @@ import ( "github.com/ethereum/eth-go" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethminer" - _ "github.com/ethereum/eth-go/ethrpc" + "github.com/ethereum/eth-go/ethpub" + "github.com/ethereum/eth-go/ethrpc" "github.com/ethereum/eth-go/ethutil" "log" "time" ) +func DoRpc(ethereum *eth.Ethereum, RpcPort int) { + var err error + ethereum.RpcServer, err = ethrpc.NewJsonRpcServer(ethpub.NewPEthereum(ethereum), RpcPort) + if err != nil { + log.Println("Could not start RPC interface:", err) + } else { + go ethereum.RpcServer.Start() + } +} + func DoMining(ethereum *eth.Ethereum) { // Set Mining status ethereum.Mining = true -- cgit v1.2.3 From f18ec51cb3959cc662bfc7b84314cd1d3b1541b5 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 14 May 2014 13:55:08 +0200 Subject: Switched to new keyring methods --- README.md | 2 +- ethereal/assets/muted/index.html | 1 + ethereal/assets/samplecoin/samplecoin.html | 4 +-- ethereal/ethereum.go | 15 ++++++-- ethereal/ui/gui.go | 49 +++---------------------- ethereal/ui/library.go | 13 ++++--- ethereal/ui/ui_lib.go | 2 +- ethereum/dev_console.go | 8 ++--- ethereum/ethereum.go | 15 ++++++-- utils/cmd.go | 11 ++---- utils/keys.go | 57 ++++++++++++++++++++++++++++++ 11 files changed, 106 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 685d4c6d9..35c97c8ee 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Ethereum Ethereum Go Client © 2014 Jeffrey Wilcke. -Current state: Proof of Concept 5.0 RC3. +Current state: Proof of Concept 5.0 RC4. For the development package please see the [eth-go package](https://github.com/ethereum/eth-go). diff --git a/ethereal/assets/muted/index.html b/ethereal/assets/muted/index.html index 84584e373..14949b5ac 100644 --- a/ethereal/assets/muted/index.html +++ b/ethereal/assets/muted/index.html @@ -46,6 +46,7 @@ .CodeMirror { height: 70%; + font-size: 14pt; } diff --git a/ethereal/assets/samplecoin/samplecoin.html b/ethereal/assets/samplecoin/samplecoin.html index 384936780..3f8eacc00 100644 --- a/ethereal/assets/samplecoin/samplecoin.html +++ b/ethereal/assets/samplecoin/samplecoin.html @@ -9,7 +9,7 @@