Add new setticketsvotebits command

The dcrwallet command to set multiple tickets vote bits fields at
the same time has been added.  Functions to encode/decode relevant
hex strings (as passed over JSON) were also added, along with tests.
This commit is contained in:
cjepson 2016-10-13 12:22:19 -04:00
parent f26d1e4b34
commit cc25399f6a
4 changed files with 224 additions and 0 deletions

View File

@ -176,6 +176,13 @@ var (
rangeLimitMax = uint16(63)
)
// VoteBits is a field representing the mandatory 2-byte field of voteBits along
// with the optional 73-byte extended field for votes.
type VoteBits struct {
Bits uint16
ExtendedBits []byte
}
// --------------------------------------------------------------------------------
// Accessory Stake Functions
// --------------------------------------------------------------------------------

View File

@ -569,6 +569,22 @@ func NewSetTicketVoteBitsCmd(txHash string, voteBits uint16, voteBitsExt *string
}
}
// SetTicketsVoteBitsCmd is a type handling custom marshaling and
// unmarshaling of setticketsvotebits JSON RPC commands.
type SetTicketsVoteBitsCmd struct {
TxHashes string
VoteBitsBytes string
}
// NewSetTicketsVoteBitsCmd creates a new instance of the setticketsvotebits
// command.
func NewSetTicketsVoteBitsCmd(txHashes string, voteBitsBytes string) *SetTicketsVoteBitsCmd {
return &SetTicketsVoteBitsCmd{
TxHashes: txHashes,
VoteBitsBytes: voteBitsBytes,
}
}
// SignRawTransactionsCmd defines the signrawtransactions JSON-RPC command.
type SignRawTransactionsCmd struct {
RawTxs []string
@ -651,6 +667,7 @@ func init() {
MustRegisterCmd("setticketfee", (*SetTicketFeeCmd)(nil), flags)
MustRegisterCmd("setticketmaxprice", (*SetTicketMaxPriceCmd)(nil), flags)
MustRegisterCmd("setticketvotebits", (*SetTicketVoteBitsCmd)(nil), flags)
MustRegisterCmd("setticketsvotebits", (*SetTicketsVoteBitsCmd)(nil), flags)
MustRegisterCmd("signrawtransactions", (*SignRawTransactionsCmd)(nil), flags)
MustRegisterCmd("stakepooluserinfo", (*StakePoolUserInfoCmd)(nil), flags)
MustRegisterCmd("walletinfo", (*WalletInfoCmd)(nil), flags)

View File

@ -5,8 +5,11 @@
package dcrjson
import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg/chainhash"
)
@ -44,3 +47,87 @@ func DecodeConcatenatedHashes(hashes string) ([]chainhash.Hash, error) {
}
return decoded, nil
}
// EncodeConcatenatedVoteBits encodes a slice of VoteBits into a serialized byte
// slice. The entirety of the voteBits are encoded individually in series as
// follows:
//
// Size Description
// 1 byte Length of the concatenated voteBits in bytes
// 2 bytes Vote bits
// up to 73 bytes Extended vote bits
//
// The result may be concatenated into a slice and then passed to callers
func EncodeConcatenatedVoteBits(voteBitsSlice []stake.VoteBits) (string, error) {
length := 0
for i := range voteBitsSlice {
if len(voteBitsSlice[i].ExtendedBits) > stake.MaxSingleBytePushLength-2 {
return "", fmt.Errorf("extended votebits too long (got %v, want "+
"%v max", len(voteBitsSlice[i].ExtendedBits),
stake.MaxSingleBytePushLength-2)
}
length += 1 + 2 + len(voteBitsSlice[i].ExtendedBits)
}
vbBytes := make([]byte, length)
offset := 0
for i := range voteBitsSlice {
vbBytes[offset] = 2 + uint8(len(voteBitsSlice[i].ExtendedBits))
offset++
binary.LittleEndian.PutUint16(vbBytes[offset:offset+2],
voteBitsSlice[i].Bits)
offset += 2
copy(vbBytes[offset:], voteBitsSlice[i].ExtendedBits[:])
offset += len(voteBitsSlice[i].ExtendedBits)
}
return hex.EncodeToString(vbBytes), nil
}
// DecodeConcatenatedVoteBits decodes a string encoded as a slice of concatenated
// voteBits and extended voteBits, and returns the slice of DecodedVoteBits to
// the caller.
func DecodeConcatenatedVoteBits(voteBitsString string) ([]stake.VoteBits, error) {
asBytes, err := hex.DecodeString(voteBitsString)
if err != nil {
return nil, err
}
var dvbs []stake.VoteBits
cursor := 0
for {
var dvb stake.VoteBits
length := int(asBytes[cursor])
if length < 2 {
return nil, &RPCError{
Code: ErrRPCInvalidParameter,
Message: "invalid length byte for votebits (short)",
}
}
if cursor+length >= len(asBytes) {
return nil, &RPCError{
Code: ErrRPCInvalidParameter,
Message: "cursor read past memory when decoding " +
"votebits",
}
}
cursor++
dvb.Bits = binary.LittleEndian.Uint16(asBytes[cursor : cursor+2])
cursor += 2
dvb.ExtendedBits = asBytes[cursor : cursor+length-2]
cursor += length - 2
dvbs = append(dvbs, dvb)
if cursor == len(asBytes) {
break
}
}
return dvbs, nil
}

View File

@ -5,9 +5,12 @@
package dcrjson_test
import (
"bytes"
"encoding/hex"
"reflect"
"testing"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrjson"
)
@ -46,3 +49,113 @@ func TestDecodeConcatenatedHashes(t *testing.T) {
}
}
}
func TestEncodeConcatenatedVoteBits(t *testing.T) {
testVbs := []stake.VoteBits{
stake.VoteBits{Bits: 0, ExtendedBits: []byte{}},
stake.VoteBits{Bits: 0, ExtendedBits: []byte{0x00}},
stake.VoteBits{Bits: 0x1223, ExtendedBits: []byte{0x01, 0x02, 0x03, 0x04}},
stake.VoteBits{Bits: 0xaaaa, ExtendedBits: []byte{0x01, 0x02, 0x03, 0x04, 0x05}},
}
encodedResults, err := dcrjson.EncodeConcatenatedVoteBits(testVbs)
if err != nil {
t.Fatalf("Encode failed: %v", err)
}
expectedEncoded := []byte{
0x02, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x06,
0x23, 0x12, 0x01, 0x02,
0x03, 0x04, 0x07, 0xaa,
0xaa, 0x01, 0x02, 0x03,
0x04, 0x05,
}
encodedResultsStr, _ := hex.DecodeString(encodedResults)
if !bytes.Equal(expectedEncoded, encodedResultsStr) {
t.Fatalf("Encoded votebits `%x` does not match expected `%x`",
encodedResults, expectedEncoded)
}
// Test too long voteBits extended.
testVbs = []stake.VoteBits{
stake.VoteBits{Bits: 0, ExtendedBits: bytes.Repeat([]byte{0x00}, 74)},
}
_, err = dcrjson.EncodeConcatenatedVoteBits(testVbs)
if err == nil {
t.Fatalf("expected too long error")
}
}
func TestDecodeConcatenatedVoteBits(t *testing.T) {
encodedBytes := []byte{
0x03, 0x00, 0x00, 0x00,
0x06, 0x23, 0x12, 0x01,
0x02, 0x03, 0x04, 0x07,
0xaa, 0xaa, 0x01, 0x02,
0x03, 0x04, 0x05,
}
encodedBytesStr := hex.EncodeToString(encodedBytes)
expectedVbs := []stake.VoteBits{
stake.VoteBits{Bits: 0, ExtendedBits: []byte{0x00}},
stake.VoteBits{Bits: 0x1223, ExtendedBits: []byte{0x01, 0x02, 0x03, 0x04}},
stake.VoteBits{Bits: 0xaaaa, ExtendedBits: []byte{0x01, 0x02, 0x03, 0x04, 0x05}},
}
decodedSlice, err :=
dcrjson.DecodeConcatenatedVoteBits(encodedBytesStr)
if err != nil {
t.Fatalf("unexpected error decoding votebits: %v", err.Error())
}
if !reflect.DeepEqual(expectedVbs, decodedSlice) {
t.Fatalf("Decoded votebits `%v` does not match expected `%v`",
decodedSlice, expectedVbs)
}
// Test short read.
encodedBytes = []byte{
0x03, 0x00, 0x00, 0x00,
0x06, 0x23, 0x12, 0x01,
0x02, 0x03, 0x04, 0x07,
0xaa, 0xaa, 0x01, 0x02,
0x03, 0x04,
}
encodedBytesStr = hex.EncodeToString(encodedBytes)
decodedSlice, err = dcrjson.DecodeConcatenatedVoteBits(encodedBytesStr)
if err == nil {
t.Fatalf("expected short read error")
}
// Test too long read.
encodedBytes = []byte{
0x03, 0x00, 0x00, 0x00,
0x06, 0x23, 0x12, 0x01,
0x02, 0x03, 0x04, 0x07,
0xaa, 0xaa, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06,
}
encodedBytesStr = hex.EncodeToString(encodedBytes)
decodedSlice, err = dcrjson.DecodeConcatenatedVoteBits(encodedBytesStr)
if err == nil {
t.Fatalf("expected corruption error")
}
// Test invalid length.
encodedBytes = []byte{
0x01, 0x00, 0x00, 0x00,
0x06, 0x23, 0x12, 0x01,
0x02, 0x03, 0x04, 0x07,
0xaa, 0xaa, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06,
}
encodedBytesStr = hex.EncodeToString(encodedBytes)
decodedSlice, err = dcrjson.DecodeConcatenatedVoteBits(encodedBytesStr)
if err == nil {
t.Fatalf("expected corruption error")
}
}