diff --git a/alphabet/alphabet.tpl b/alphabet/alphabet.tpl index ffce5e2..963b04d 100644 --- a/alphabet/alphabet.tpl +++ b/alphabet/alphabet.tpl @@ -1,16 +1,26 @@ package alphabetcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/binary" + "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) type ( irNode struct { key []byte } + + ballot struct { + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote + } ) const ( @@ -24,6 +34,13 @@ const ( index = {{ .Index }} netmapContractKey = "netmapScriptHash" + + blockDiff = 20 // amount of blocks when ballot get discarded + + threshold = totalAlphabetContracts * 2 / 3 + 1 + voteKey = "ballots" + + totalAlphabetContracts = 7 ) var ( @@ -49,6 +66,8 @@ func Init(addrNetmap []byte) { storage.Put(ctx, netmapContractKey, addrNetmap) + setSerialized(ctx, voteKey, []ballot{}) + runtime.Log(name + " contract initialized") } @@ -81,6 +100,21 @@ func checkPermission(ir []irNode) bool { return runtime.CheckWitness(node.key) } +func innerRingInvoker(ir []irNode) []byte { + for i := 0; i < len(ir); i++ { + if i >= totalAlphabetContracts { + return nil + } + + node := ir[i] + if runtime.CheckWitness(node.key) { + return node.key + } + } + + return nil +} + func Emit() bool { innerRingKeys := irList() if !checkPermission(innerRingKeys) { @@ -111,25 +145,129 @@ func Emit() bool { return true } -func Vote(candidate []byte) { +func Vote(epoch int, candidates [][]byte) { innerRingKeys := irList() - if !checkPermission(innerRingKeys) { + + key := innerRingInvoker(innerRingKeys) + if len(key) == 0 { panic("invalid invoker") } - address := runtime.GetExecutingScriptHash() + id := voteID(epoch, candidates) + n := vote(ctx, id, key) - ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) - if ok { - runtime.Log("successfully voted for validator") + if n >= threshold { + candidate := candidates[index%len(candidates)] + address := runtime.GetExecutingScriptHash() + + ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) + if ok { + runtime.Log(name + ": successfully voted for validator") + removeVotes(ctx, id) + } else { + runtime.Log(name + ": vote has been failed") + } } else { - runtime.Log("vote has been failed") + runtime.Log(name + ": saved vote for validator") } - return } func Name() string { return name } + +func vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = blockchain.GetHeight() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if bytesEqual(cnd.id, id) { + voters := cnd.n + + for j := range voters { + if bytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = ballot{id: id, n: voters, block: blockHeight} + found = len(voters) + } + + // do not add old ballots, they are invalid + if blockHeight-cnd.block <= blockDiff { + newCandidates = append(newCandidates, cnd) + } + } + + if found < 0 { + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) + found = 1 + } + + setSerialized(ctx, voteKey, newCandidates) + + return found +} + +func removeVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !bytesEqual(cnd.id, id) { + newCandidates = append(newCandidates, cnd) + } + } + + setSerialized(ctx, voteKey, newCandidates) +} + +func getBallots(ctx storage.Context) []ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return binary.Deserialize(data.([]byte)).([]ballot) + } + + return []ballot{} +} + +func setSerialized(ctx storage.Context, key interface{}, value interface{}) { + data := binary.Serialize(value) + storage.Put(ctx, key, data) +} + +// neo-go#1176 +func bytesEqual(a []byte, b []byte) bool { + return util.Equals(string(a), string(b)) +} + +func voteID(epoch interface{}, args [][]byte) []byte { + var ( + result []byte + epochBytes = epoch.([]byte) + ) + + result = append(result, epochBytes...) + + for i := range args { + result = append(result, args[i]...) + } + + return crypto.SHA256(result) +} diff --git a/alphabet/az/az_contract.go b/alphabet/az/az_contract.go index 8528be9..32d9d22 100644 --- a/alphabet/az/az_contract.go +++ b/alphabet/az/az_contract.go @@ -1,16 +1,26 @@ package alphabetcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/binary" + "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) type ( irNode struct { key []byte } + + ballot struct { + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote + } ) const ( @@ -24,6 +34,13 @@ const ( index = 0 netmapContractKey = "netmapScriptHash" + + blockDiff = 20 // amount of blocks when ballot get discarded + + threshold = totalAlphabetContracts*2/3 + 1 + voteKey = "ballots" + + totalAlphabetContracts = 7 ) var ( @@ -49,6 +66,8 @@ func Init(addrNetmap []byte) { storage.Put(ctx, netmapContractKey, addrNetmap) + setSerialized(ctx, voteKey, []ballot{}) + runtime.Log(name + " contract initialized") } @@ -81,6 +100,21 @@ func checkPermission(ir []irNode) bool { return runtime.CheckWitness(node.key) } +func innerRingInvoker(ir []irNode) []byte { + for i := 0; i < len(ir); i++ { + if i >= totalAlphabetContracts { + return nil + } + + node := ir[i] + if runtime.CheckWitness(node.key) { + return node.key + } + } + + return nil +} + func Emit() bool { innerRingKeys := irList() if !checkPermission(innerRingKeys) { @@ -111,19 +145,30 @@ func Emit() bool { return true } -func Vote(candidate []byte) { +func Vote(epoch int, candidates [][]byte) { innerRingKeys := irList() - if !checkPermission(innerRingKeys) { + + key := innerRingInvoker(innerRingKeys) + if len(key) == 0 { panic("invalid invoker") } - address := runtime.GetExecutingScriptHash() + id := voteID(epoch, candidates) + n := vote(ctx, id, key) - ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) - if ok { - runtime.Log("successfully voted for validator") + if n >= threshold { + candidate := candidates[index%len(candidates)] + address := runtime.GetExecutingScriptHash() + + ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) + if ok { + runtime.Log(name + ": successfully voted for validator") + removeVotes(ctx, id) + } else { + runtime.Log(name + ": vote has been failed") + } } else { - runtime.Log("vote has been failed") + runtime.Log(name + ": saved vote for validator") } return @@ -132,3 +177,97 @@ func Vote(candidate []byte) { func Name() string { return name } + +func vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = blockchain.GetHeight() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if bytesEqual(cnd.id, id) { + voters := cnd.n + + for j := range voters { + if bytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = ballot{id: id, n: voters, block: blockHeight} + found = len(voters) + } + + // do not add old ballots, they are invalid + if blockHeight-cnd.block <= blockDiff { + newCandidates = append(newCandidates, cnd) + } + } + + if found < 0 { + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) + found = 1 + } + + setSerialized(ctx, voteKey, newCandidates) + + return found +} + +func removeVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !bytesEqual(cnd.id, id) { + newCandidates = append(newCandidates, cnd) + } + } + + setSerialized(ctx, voteKey, newCandidates) +} + +func getBallots(ctx storage.Context) []ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return binary.Deserialize(data.([]byte)).([]ballot) + } + + return []ballot{} +} + +func setSerialized(ctx storage.Context, key interface{}, value interface{}) { + data := binary.Serialize(value) + storage.Put(ctx, key, data) +} + +// neo-go#1176 +func bytesEqual(a []byte, b []byte) bool { + return util.Equals(string(a), string(b)) +} + +func voteID(epoch interface{}, args [][]byte) []byte { + var ( + result []byte + epochBytes = epoch.([]byte) + ) + + result = append(result, epochBytes...) + + for i := range args { + result = append(result, args[i]...) + } + + return crypto.SHA256(result) +} diff --git a/alphabet/buky/buky_contract.go b/alphabet/buky/buky_contract.go index 7cf223c..0bd270d 100644 --- a/alphabet/buky/buky_contract.go +++ b/alphabet/buky/buky_contract.go @@ -1,16 +1,26 @@ package alphabetcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/binary" + "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) type ( irNode struct { key []byte } + + ballot struct { + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote + } ) const ( @@ -24,6 +34,13 @@ const ( index = 1 netmapContractKey = "netmapScriptHash" + + blockDiff = 20 // amount of blocks when ballot get discarded + + threshold = totalAlphabetContracts*2/3 + 1 + voteKey = "ballots" + + totalAlphabetContracts = 7 ) var ( @@ -49,6 +66,8 @@ func Init(addrNetmap []byte) { storage.Put(ctx, netmapContractKey, addrNetmap) + setSerialized(ctx, voteKey, []ballot{}) + runtime.Log(name + " contract initialized") } @@ -81,6 +100,21 @@ func checkPermission(ir []irNode) bool { return runtime.CheckWitness(node.key) } +func innerRingInvoker(ir []irNode) []byte { + for i := 0; i < len(ir); i++ { + if i >= totalAlphabetContracts { + return nil + } + + node := ir[i] + if runtime.CheckWitness(node.key) { + return node.key + } + } + + return nil +} + func Emit() bool { innerRingKeys := irList() if !checkPermission(innerRingKeys) { @@ -111,19 +145,30 @@ func Emit() bool { return true } -func Vote(candidate []byte) { +func Vote(epoch int, candidates [][]byte) { innerRingKeys := irList() - if !checkPermission(innerRingKeys) { + + key := innerRingInvoker(innerRingKeys) + if len(key) == 0 { panic("invalid invoker") } - address := runtime.GetExecutingScriptHash() + id := voteID(epoch, candidates) + n := vote(ctx, id, key) - ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) - if ok { - runtime.Log("successfully voted for validator") + if n >= threshold { + candidate := candidates[index%len(candidates)] + address := runtime.GetExecutingScriptHash() + + ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) + if ok { + runtime.Log(name + ": successfully voted for validator") + removeVotes(ctx, id) + } else { + runtime.Log(name + ": vote has been failed") + } } else { - runtime.Log("vote has been failed") + runtime.Log(name + ": saved vote for validator") } return @@ -132,3 +177,97 @@ func Vote(candidate []byte) { func Name() string { return name } + +func vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = blockchain.GetHeight() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if bytesEqual(cnd.id, id) { + voters := cnd.n + + for j := range voters { + if bytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = ballot{id: id, n: voters, block: blockHeight} + found = len(voters) + } + + // do not add old ballots, they are invalid + if blockHeight-cnd.block <= blockDiff { + newCandidates = append(newCandidates, cnd) + } + } + + if found < 0 { + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) + found = 1 + } + + setSerialized(ctx, voteKey, newCandidates) + + return found +} + +func removeVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !bytesEqual(cnd.id, id) { + newCandidates = append(newCandidates, cnd) + } + } + + setSerialized(ctx, voteKey, newCandidates) +} + +func getBallots(ctx storage.Context) []ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return binary.Deserialize(data.([]byte)).([]ballot) + } + + return []ballot{} +} + +func setSerialized(ctx storage.Context, key interface{}, value interface{}) { + data := binary.Serialize(value) + storage.Put(ctx, key, data) +} + +// neo-go#1176 +func bytesEqual(a []byte, b []byte) bool { + return util.Equals(string(a), string(b)) +} + +func voteID(epoch interface{}, args [][]byte) []byte { + var ( + result []byte + epochBytes = epoch.([]byte) + ) + + result = append(result, epochBytes...) + + for i := range args { + result = append(result, args[i]...) + } + + return crypto.SHA256(result) +} diff --git a/alphabet/dobro/dobro_contract.go b/alphabet/dobro/dobro_contract.go index b804fc7..aaa72df 100644 --- a/alphabet/dobro/dobro_contract.go +++ b/alphabet/dobro/dobro_contract.go @@ -1,16 +1,26 @@ package alphabetcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/binary" + "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) type ( irNode struct { key []byte } + + ballot struct { + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote + } ) const ( @@ -24,6 +34,13 @@ const ( index = 4 netmapContractKey = "netmapScriptHash" + + blockDiff = 20 // amount of blocks when ballot get discarded + + threshold = totalAlphabetContracts*2/3 + 1 + voteKey = "ballots" + + totalAlphabetContracts = 7 ) var ( @@ -49,6 +66,8 @@ func Init(addrNetmap []byte) { storage.Put(ctx, netmapContractKey, addrNetmap) + setSerialized(ctx, voteKey, []ballot{}) + runtime.Log(name + " contract initialized") } @@ -81,6 +100,21 @@ func checkPermission(ir []irNode) bool { return runtime.CheckWitness(node.key) } +func innerRingInvoker(ir []irNode) []byte { + for i := 0; i < len(ir); i++ { + if i >= totalAlphabetContracts { + return nil + } + + node := ir[i] + if runtime.CheckWitness(node.key) { + return node.key + } + } + + return nil +} + func Emit() bool { innerRingKeys := irList() if !checkPermission(innerRingKeys) { @@ -111,19 +145,30 @@ func Emit() bool { return true } -func Vote(candidate []byte) { +func Vote(epoch int, candidates [][]byte) { innerRingKeys := irList() - if !checkPermission(innerRingKeys) { + + key := innerRingInvoker(innerRingKeys) + if len(key) == 0 { panic("invalid invoker") } - address := runtime.GetExecutingScriptHash() + id := voteID(epoch, candidates) + n := vote(ctx, id, key) - ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) - if ok { - runtime.Log("successfully voted for validator") + if n >= threshold { + candidate := candidates[index%len(candidates)] + address := runtime.GetExecutingScriptHash() + + ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) + if ok { + runtime.Log(name + ": successfully voted for validator") + removeVotes(ctx, id) + } else { + runtime.Log(name + ": vote has been failed") + } } else { - runtime.Log("vote has been failed") + runtime.Log(name + ": saved vote for validator") } return @@ -132,3 +177,97 @@ func Vote(candidate []byte) { func Name() string { return name } + +func vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = blockchain.GetHeight() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if bytesEqual(cnd.id, id) { + voters := cnd.n + + for j := range voters { + if bytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = ballot{id: id, n: voters, block: blockHeight} + found = len(voters) + } + + // do not add old ballots, they are invalid + if blockHeight-cnd.block <= blockDiff { + newCandidates = append(newCandidates, cnd) + } + } + + if found < 0 { + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) + found = 1 + } + + setSerialized(ctx, voteKey, newCandidates) + + return found +} + +func removeVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !bytesEqual(cnd.id, id) { + newCandidates = append(newCandidates, cnd) + } + } + + setSerialized(ctx, voteKey, newCandidates) +} + +func getBallots(ctx storage.Context) []ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return binary.Deserialize(data.([]byte)).([]ballot) + } + + return []ballot{} +} + +func setSerialized(ctx storage.Context, key interface{}, value interface{}) { + data := binary.Serialize(value) + storage.Put(ctx, key, data) +} + +// neo-go#1176 +func bytesEqual(a []byte, b []byte) bool { + return util.Equals(string(a), string(b)) +} + +func voteID(epoch interface{}, args [][]byte) []byte { + var ( + result []byte + epochBytes = epoch.([]byte) + ) + + result = append(result, epochBytes...) + + for i := range args { + result = append(result, args[i]...) + } + + return crypto.SHA256(result) +} diff --git a/alphabet/glagoli/glagoli_contract.go b/alphabet/glagoli/glagoli_contract.go index 5905322..377f70a 100644 --- a/alphabet/glagoli/glagoli_contract.go +++ b/alphabet/glagoli/glagoli_contract.go @@ -1,16 +1,26 @@ package alphabetcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/binary" + "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) type ( irNode struct { key []byte } + + ballot struct { + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote + } ) const ( @@ -24,6 +34,13 @@ const ( index = 3 netmapContractKey = "netmapScriptHash" + + blockDiff = 20 // amount of blocks when ballot get discarded + + threshold = totalAlphabetContracts*2/3 + 1 + voteKey = "ballots" + + totalAlphabetContracts = 7 ) var ( @@ -49,6 +66,8 @@ func Init(addrNetmap []byte) { storage.Put(ctx, netmapContractKey, addrNetmap) + setSerialized(ctx, voteKey, []ballot{}) + runtime.Log(name + " contract initialized") } @@ -81,6 +100,21 @@ func checkPermission(ir []irNode) bool { return runtime.CheckWitness(node.key) } +func innerRingInvoker(ir []irNode) []byte { + for i := 0; i < len(ir); i++ { + if i >= totalAlphabetContracts { + return nil + } + + node := ir[i] + if runtime.CheckWitness(node.key) { + return node.key + } + } + + return nil +} + func Emit() bool { innerRingKeys := irList() if !checkPermission(innerRingKeys) { @@ -111,19 +145,30 @@ func Emit() bool { return true } -func Vote(candidate []byte) { +func Vote(epoch int, candidates [][]byte) { innerRingKeys := irList() - if !checkPermission(innerRingKeys) { + + key := innerRingInvoker(innerRingKeys) + if len(key) == 0 { panic("invalid invoker") } - address := runtime.GetExecutingScriptHash() + id := voteID(epoch, candidates) + n := vote(ctx, id, key) - ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) - if ok { - runtime.Log("successfully voted for validator") + if n >= threshold { + candidate := candidates[index%len(candidates)] + address := runtime.GetExecutingScriptHash() + + ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) + if ok { + runtime.Log(name + ": successfully voted for validator") + removeVotes(ctx, id) + } else { + runtime.Log(name + ": vote has been failed") + } } else { - runtime.Log("vote has been failed") + runtime.Log(name + ": saved vote for validator") } return @@ -132,3 +177,97 @@ func Vote(candidate []byte) { func Name() string { return name } + +func vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = blockchain.GetHeight() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if bytesEqual(cnd.id, id) { + voters := cnd.n + + for j := range voters { + if bytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = ballot{id: id, n: voters, block: blockHeight} + found = len(voters) + } + + // do not add old ballots, they are invalid + if blockHeight-cnd.block <= blockDiff { + newCandidates = append(newCandidates, cnd) + } + } + + if found < 0 { + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) + found = 1 + } + + setSerialized(ctx, voteKey, newCandidates) + + return found +} + +func removeVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !bytesEqual(cnd.id, id) { + newCandidates = append(newCandidates, cnd) + } + } + + setSerialized(ctx, voteKey, newCandidates) +} + +func getBallots(ctx storage.Context) []ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return binary.Deserialize(data.([]byte)).([]ballot) + } + + return []ballot{} +} + +func setSerialized(ctx storage.Context, key interface{}, value interface{}) { + data := binary.Serialize(value) + storage.Put(ctx, key, data) +} + +// neo-go#1176 +func bytesEqual(a []byte, b []byte) bool { + return util.Equals(string(a), string(b)) +} + +func voteID(epoch interface{}, args [][]byte) []byte { + var ( + result []byte + epochBytes = epoch.([]byte) + ) + + result = append(result, epochBytes...) + + for i := range args { + result = append(result, args[i]...) + } + + return crypto.SHA256(result) +} diff --git a/alphabet/jest/jest_contract.go b/alphabet/jest/jest_contract.go index 36b87d3..2415477 100644 --- a/alphabet/jest/jest_contract.go +++ b/alphabet/jest/jest_contract.go @@ -1,16 +1,26 @@ package alphabetcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/binary" + "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) type ( irNode struct { key []byte } + + ballot struct { + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote + } ) const ( @@ -24,6 +34,13 @@ const ( index = 5 netmapContractKey = "netmapScriptHash" + + blockDiff = 20 // amount of blocks when ballot get discarded + + threshold = totalAlphabetContracts*2/3 + 1 + voteKey = "ballots" + + totalAlphabetContracts = 7 ) var ( @@ -49,6 +66,8 @@ func Init(addrNetmap []byte) { storage.Put(ctx, netmapContractKey, addrNetmap) + setSerialized(ctx, voteKey, []ballot{}) + runtime.Log(name + " contract initialized") } @@ -81,6 +100,21 @@ func checkPermission(ir []irNode) bool { return runtime.CheckWitness(node.key) } +func innerRingInvoker(ir []irNode) []byte { + for i := 0; i < len(ir); i++ { + if i >= totalAlphabetContracts { + return nil + } + + node := ir[i] + if runtime.CheckWitness(node.key) { + return node.key + } + } + + return nil +} + func Emit() bool { innerRingKeys := irList() if !checkPermission(innerRingKeys) { @@ -111,19 +145,30 @@ func Emit() bool { return true } -func Vote(candidate []byte) { +func Vote(epoch int, candidates [][]byte) { innerRingKeys := irList() - if !checkPermission(innerRingKeys) { + + key := innerRingInvoker(innerRingKeys) + if len(key) == 0 { panic("invalid invoker") } - address := runtime.GetExecutingScriptHash() + id := voteID(epoch, candidates) + n := vote(ctx, id, key) - ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) - if ok { - runtime.Log("successfully voted for validator") + if n >= threshold { + candidate := candidates[index%len(candidates)] + address := runtime.GetExecutingScriptHash() + + ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) + if ok { + runtime.Log(name + ": successfully voted for validator") + removeVotes(ctx, id) + } else { + runtime.Log(name + ": vote has been failed") + } } else { - runtime.Log("vote has been failed") + runtime.Log(name + ": saved vote for validator") } return @@ -132,3 +177,97 @@ func Vote(candidate []byte) { func Name() string { return name } + +func vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = blockchain.GetHeight() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if bytesEqual(cnd.id, id) { + voters := cnd.n + + for j := range voters { + if bytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = ballot{id: id, n: voters, block: blockHeight} + found = len(voters) + } + + // do not add old ballots, they are invalid + if blockHeight-cnd.block <= blockDiff { + newCandidates = append(newCandidates, cnd) + } + } + + if found < 0 { + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) + found = 1 + } + + setSerialized(ctx, voteKey, newCandidates) + + return found +} + +func removeVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !bytesEqual(cnd.id, id) { + newCandidates = append(newCandidates, cnd) + } + } + + setSerialized(ctx, voteKey, newCandidates) +} + +func getBallots(ctx storage.Context) []ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return binary.Deserialize(data.([]byte)).([]ballot) + } + + return []ballot{} +} + +func setSerialized(ctx storage.Context, key interface{}, value interface{}) { + data := binary.Serialize(value) + storage.Put(ctx, key, data) +} + +// neo-go#1176 +func bytesEqual(a []byte, b []byte) bool { + return util.Equals(string(a), string(b)) +} + +func voteID(epoch interface{}, args [][]byte) []byte { + var ( + result []byte + epochBytes = epoch.([]byte) + ) + + result = append(result, epochBytes...) + + for i := range args { + result = append(result, args[i]...) + } + + return crypto.SHA256(result) +} diff --git a/alphabet/vedi/vedi_contract.go b/alphabet/vedi/vedi_contract.go index 090e15f..565e820 100644 --- a/alphabet/vedi/vedi_contract.go +++ b/alphabet/vedi/vedi_contract.go @@ -1,16 +1,26 @@ package alphabetcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/binary" + "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) type ( irNode struct { key []byte } + + ballot struct { + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote + } ) const ( @@ -24,6 +34,13 @@ const ( index = 2 netmapContractKey = "netmapScriptHash" + + blockDiff = 20 // amount of blocks when ballot get discarded + + threshold = totalAlphabetContracts*2/3 + 1 + voteKey = "ballots" + + totalAlphabetContracts = 7 ) var ( @@ -49,6 +66,8 @@ func Init(addrNetmap []byte) { storage.Put(ctx, netmapContractKey, addrNetmap) + setSerialized(ctx, voteKey, []ballot{}) + runtime.Log(name + " contract initialized") } @@ -81,6 +100,21 @@ func checkPermission(ir []irNode) bool { return runtime.CheckWitness(node.key) } +func innerRingInvoker(ir []irNode) []byte { + for i := 0; i < len(ir); i++ { + if i >= totalAlphabetContracts { + return nil + } + + node := ir[i] + if runtime.CheckWitness(node.key) { + return node.key + } + } + + return nil +} + func Emit() bool { innerRingKeys := irList() if !checkPermission(innerRingKeys) { @@ -111,19 +145,30 @@ func Emit() bool { return true } -func Vote(candidate []byte) { +func Vote(epoch int, candidates [][]byte) { innerRingKeys := irList() - if !checkPermission(innerRingKeys) { + + key := innerRingInvoker(innerRingKeys) + if len(key) == 0 { panic("invalid invoker") } - address := runtime.GetExecutingScriptHash() + id := voteID(epoch, candidates) + n := vote(ctx, id, key) - ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) - if ok { - runtime.Log("successfully voted for validator") + if n >= threshold { + candidate := candidates[index%len(candidates)] + address := runtime.GetExecutingScriptHash() + + ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) + if ok { + runtime.Log(name + ": successfully voted for validator") + removeVotes(ctx, id) + } else { + runtime.Log(name + ": vote has been failed") + } } else { - runtime.Log("vote has been failed") + runtime.Log(name + ": saved vote for validator") } return @@ -132,3 +177,97 @@ func Vote(candidate []byte) { func Name() string { return name } + +func vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = blockchain.GetHeight() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if bytesEqual(cnd.id, id) { + voters := cnd.n + + for j := range voters { + if bytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = ballot{id: id, n: voters, block: blockHeight} + found = len(voters) + } + + // do not add old ballots, they are invalid + if blockHeight-cnd.block <= blockDiff { + newCandidates = append(newCandidates, cnd) + } + } + + if found < 0 { + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) + found = 1 + } + + setSerialized(ctx, voteKey, newCandidates) + + return found +} + +func removeVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !bytesEqual(cnd.id, id) { + newCandidates = append(newCandidates, cnd) + } + } + + setSerialized(ctx, voteKey, newCandidates) +} + +func getBallots(ctx storage.Context) []ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return binary.Deserialize(data.([]byte)).([]ballot) + } + + return []ballot{} +} + +func setSerialized(ctx storage.Context, key interface{}, value interface{}) { + data := binary.Serialize(value) + storage.Put(ctx, key, data) +} + +// neo-go#1176 +func bytesEqual(a []byte, b []byte) bool { + return util.Equals(string(a), string(b)) +} + +func voteID(epoch interface{}, args [][]byte) []byte { + var ( + result []byte + epochBytes = epoch.([]byte) + ) + + result = append(result, epochBytes...) + + for i := range args { + result = append(result, args[i]...) + } + + return crypto.SHA256(result) +} diff --git a/alphabet/zhivete/zhivete_contract.go b/alphabet/zhivete/zhivete_contract.go index 8aa2824..4480b52 100644 --- a/alphabet/zhivete/zhivete_contract.go +++ b/alphabet/zhivete/zhivete_contract.go @@ -1,16 +1,26 @@ package alphabetcontract import ( + "github.com/nspcc-dev/neo-go/pkg/interop/binary" + "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/engine" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) type ( irNode struct { key []byte } + + ballot struct { + id []byte // id of the voting decision + n [][]byte // already voted inner ring nodes + block int // block with the last vote + } ) const ( @@ -24,6 +34,13 @@ const ( index = 6 netmapContractKey = "netmapScriptHash" + + blockDiff = 20 // amount of blocks when ballot get discarded + + threshold = totalAlphabetContracts*2/3 + 1 + voteKey = "ballots" + + totalAlphabetContracts = 7 ) var ( @@ -49,6 +66,8 @@ func Init(addrNetmap []byte) { storage.Put(ctx, netmapContractKey, addrNetmap) + setSerialized(ctx, voteKey, []ballot{}) + runtime.Log(name + " contract initialized") } @@ -81,6 +100,21 @@ func checkPermission(ir []irNode) bool { return runtime.CheckWitness(node.key) } +func innerRingInvoker(ir []irNode) []byte { + for i := 0; i < len(ir); i++ { + if i >= totalAlphabetContracts { + return nil + } + + node := ir[i] + if runtime.CheckWitness(node.key) { + return node.key + } + } + + return nil +} + func Emit() bool { innerRingKeys := irList() if !checkPermission(innerRingKeys) { @@ -111,19 +145,30 @@ func Emit() bool { return true } -func Vote(candidate []byte) { +func Vote(epoch int, candidates [][]byte) { innerRingKeys := irList() - if !checkPermission(innerRingKeys) { + + key := innerRingInvoker(innerRingKeys) + if len(key) == 0 { panic("invalid invoker") } - address := runtime.GetExecutingScriptHash() + id := voteID(epoch, candidates) + n := vote(ctx, id, key) - ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) - if ok { - runtime.Log("successfully voted for validator") + if n >= threshold { + candidate := candidates[index%len(candidates)] + address := runtime.GetExecutingScriptHash() + + ok := engine.AppCall([]byte(neoHash), "vote", address, candidate).(bool) + if ok { + runtime.Log(name + ": successfully voted for validator") + removeVotes(ctx, id) + } else { + runtime.Log(name + ": vote has been failed") + } } else { - runtime.Log("vote has been failed") + runtime.Log(name + ": saved vote for validator") } return @@ -132,3 +177,97 @@ func Vote(candidate []byte) { func Name() string { return name } + +func vote(ctx storage.Context, id, from []byte) int { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + found = -1 + blockHeight = blockchain.GetHeight() + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if bytesEqual(cnd.id, id) { + voters := cnd.n + + for j := range voters { + if bytesEqual(voters[j], from) { + return len(voters) + } + } + + voters = append(voters, from) + cnd = ballot{id: id, n: voters, block: blockHeight} + found = len(voters) + } + + // do not add old ballots, they are invalid + if blockHeight-cnd.block <= blockDiff { + newCandidates = append(newCandidates, cnd) + } + } + + if found < 0 { + voters := [][]byte{from} + newCandidates = append(newCandidates, ballot{ + id: id, + n: voters, + block: blockHeight}) + found = 1 + } + + setSerialized(ctx, voteKey, newCandidates) + + return found +} + +func removeVotes(ctx storage.Context, id []byte) { + var ( + newCandidates []ballot + candidates = getBallots(ctx) + ) + + for i := 0; i < len(candidates); i++ { + cnd := candidates[i] + if !bytesEqual(cnd.id, id) { + newCandidates = append(newCandidates, cnd) + } + } + + setSerialized(ctx, voteKey, newCandidates) +} + +func getBallots(ctx storage.Context) []ballot { + data := storage.Get(ctx, voteKey) + if data != nil { + return binary.Deserialize(data.([]byte)).([]ballot) + } + + return []ballot{} +} + +func setSerialized(ctx storage.Context, key interface{}, value interface{}) { + data := binary.Serialize(value) + storage.Put(ctx, key, data) +} + +// neo-go#1176 +func bytesEqual(a []byte, b []byte) bool { + return util.Equals(string(a), string(b)) +} + +func voteID(epoch interface{}, args [][]byte) []byte { + var ( + result []byte + epochBytes = epoch.([]byte) + ) + + result = append(result, epochBytes...) + + for i := range args { + result = append(result, args[i]...) + } + + return crypto.SHA256(result) +}