огледало од https://github.com/codesoap/atto.git
mirror of https://github.com/codesoap/atto
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.1 KiB
207 lines
5.1 KiB
package atto |
|
|
|
import ( |
|
"encoding/hex" |
|
"encoding/json" |
|
"fmt" |
|
"math/big" |
|
|
|
"golang.org/x/crypto/blake2b" |
|
) |
|
|
|
var errInvalidSignature = fmt.Errorf("invalid block signature") |
|
|
|
// ErrSignatureMissing is used when the Signature of a Block is missing |
|
// but required for the attempted operation. |
|
var ErrSignatureMissing = fmt.Errorf("signature is missing") |
|
|
|
// ErrWorkMissing is used when the Work of a Block is missing but |
|
// required for the attempted operation. |
|
var ErrWorkMissing = fmt.Errorf("work is missing") |
|
|
|
// BlockSubType represents the sub-type of a block. |
|
type BlockSubType int64 |
|
|
|
const ( |
|
// SubTypeReceive denotes blocks which raise the balance. |
|
SubTypeReceive BlockSubType = iota |
|
|
|
// SubTypeChange denotes blocks which change the representative. |
|
SubTypeChange |
|
|
|
// SubTypeSend denotes blocks which lower the balance. |
|
SubTypeSend |
|
) |
|
|
|
// Block represents a block in the block chain of an account. |
|
type Block struct { |
|
Type string `json:"type"` |
|
Account string `json:"account"` |
|
Previous string `json:"previous"` |
|
Representative string `json:"representative"` |
|
Balance string `json:"balance"` |
|
Link string `json:"link"` |
|
Signature string `json:"signature"` |
|
Work string `json:"work"` |
|
|
|
// This field is not part of the JSON but needed to improve the |
|
// performance of FetchWork and the security of Submit. |
|
SubType BlockSubType `json:"-"` |
|
} |
|
|
|
type workGenerateResponse struct { |
|
Error string `json:"error"` |
|
Work string `json:"work"` |
|
} |
|
|
|
// Sign computes and sets the Signature of b. |
|
func (b *Block) Sign(privateKey *big.Int) error { |
|
publicKey, err := getPublicKeyFromAddress(b.Account) |
|
if err != nil { |
|
return err |
|
} |
|
hash, err := b.hashBytes() |
|
if err != nil { |
|
return err |
|
} |
|
signature, err := sign(publicKey, privateKey, hash) |
|
if err != nil { |
|
return err |
|
} |
|
b.Signature = fmt.Sprintf("%0128X", signature) |
|
return nil |
|
} |
|
|
|
func (b *Block) verifySignature(a Account) (err error) { |
|
sig, ok := big.NewInt(0).SetString(b.Signature, 16) |
|
if !ok { |
|
return fmt.Errorf("cannot parse '%s' as an integer", b.Signature) |
|
} |
|
hash, err := b.hashBytes() |
|
if err != nil { |
|
return err |
|
} |
|
if !isValidSignature(a.PublicKey, hash, bigIntToBytes(sig, 64)) { |
|
err = errInvalidSignature |
|
} |
|
return |
|
} |
|
|
|
// FetchWork uses the generate_work RPC on node to fetch and then set |
|
// the Work of b. |
|
func (b *Block) FetchWork(node string) error { |
|
var hash string |
|
if b.Previous == "0000000000000000000000000000000000000000000000000000000000000000" { |
|
publicKey, err := getPublicKeyFromAddress(b.Account) |
|
if err != nil { |
|
return err |
|
} |
|
hash = fmt.Sprintf("%064X", bigIntToBytes(publicKey, 32)) |
|
} else { |
|
hash = b.Previous |
|
} |
|
|
|
requestBody := fmt.Sprintf(`{"action":"work_generate", "hash":"%s"`, string(hash)) |
|
if b.SubType == SubTypeReceive { |
|
// Receive blocks need less work, so lower the difficulty. |
|
var receiveWorkThreshold uint64 = 0xfffffe0000000000 |
|
requestBody += fmt.Sprintf(`, "difficulty":"%016x"`, receiveWorkThreshold) |
|
} |
|
requestBody += `}` |
|
|
|
responseBytes, err := doRPC(requestBody, node) |
|
if err != nil { |
|
return err |
|
} |
|
var response workGenerateResponse |
|
if err = json.Unmarshal(responseBytes, &response); err != nil { |
|
return err |
|
} |
|
// Need to check response.Error because of |
|
// https://github.com/nanocurrency/nano-node/issues/1782. |
|
if response.Error != "" { |
|
return fmt.Errorf("could not get work for block: %s", response.Error) |
|
} |
|
b.Work = response.Work |
|
return nil |
|
} |
|
|
|
// Hash calculates the block's hash and returns it's string |
|
// representation. |
|
func (b Block) Hash() (string, error) { |
|
hashBytes, err := b.hashBytes() |
|
if err != nil { |
|
return "", err |
|
} |
|
return fmt.Sprintf("%064X", hashBytes), nil |
|
} |
|
|
|
func (b Block) hashBytes() ([]byte, error) { |
|
// See https://nanoo.tools/block for a reference. |
|
|
|
msg := make([]byte, 176, 176) |
|
|
|
msg[31] = 0x6 // block preamble |
|
|
|
publicKey, err := getPublicKeyFromAddress(b.Account) |
|
if err != nil { |
|
return nil, err |
|
} |
|
copy(msg[32:64], bigIntToBytes(publicKey, 32)) |
|
|
|
previous, err := hex.DecodeString(b.Previous) |
|
if err != nil { |
|
return nil, err |
|
} |
|
copy(msg[64:96], previous) |
|
|
|
representative, err := getPublicKeyFromAddress(b.Representative) |
|
if err != nil { |
|
return nil, err |
|
} |
|
copy(msg[96:128], bigIntToBytes(representative, 32)) |
|
|
|
balance, ok := big.NewInt(0).SetString(b.Balance, 10) |
|
if !ok { |
|
return nil, fmt.Errorf("cannot parse '%s' as an integer", b.Balance) |
|
} |
|
copy(msg[128:144], bigIntToBytes(balance, 16)) |
|
|
|
link, err := hex.DecodeString(b.Link) |
|
if err != nil { |
|
return nil, err |
|
} |
|
copy(msg[144:176], link) |
|
|
|
hash := blake2b.Sum256(msg) |
|
return hash[:], nil |
|
} |
|
|
|
// Submit submits the Block to the given node. Work and Signature of b |
|
// must be populated beforehand. |
|
// |
|
// May return ErrWorkMissing or ErrSignatureMissing. |
|
func (b Block) Submit(node string) error { |
|
if b.Work == "" { |
|
return ErrWorkMissing |
|
} |
|
if b.Signature == "" { |
|
return ErrSignatureMissing |
|
} |
|
var subType string |
|
switch b.SubType { |
|
case SubTypeReceive: |
|
subType = "receive" |
|
case SubTypeChange: |
|
subType = "change" |
|
case SubTypeSend: |
|
subType = "send" |
|
} |
|
process := process{ |
|
Action: "process", |
|
JsonBlock: "true", |
|
SubType: subType, |
|
Block: b, |
|
} |
|
return doProcessRPC(process, node) |
|
}
|
|
|