initial
This commit is contained in:
commit
1cf33e5ffd
87 changed files with 29835 additions and 0 deletions
353
accounting/types.go
Normal file
353
accounting/types.go
Normal file
|
@ -0,0 +1,353 @@
|
|||
package accounting
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||
"github.com/nspcc-dev/neofs-proto/chain"
|
||||
"github.com/nspcc-dev/neofs-proto/decimal"
|
||||
"github.com/nspcc-dev/neofs-proto/internal"
|
||||
"github.com/nspcc-dev/neofs-proto/refs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
// Cheque structure that describes a user request for withdrawal of funds.
|
||||
Cheque struct {
|
||||
ID ChequeID
|
||||
Owner refs.OwnerID
|
||||
Amount *decimal.Decimal
|
||||
Height uint64
|
||||
Signatures []ChequeSignature
|
||||
}
|
||||
|
||||
// BalanceReceiver interface that is used to retrieve user balance by address.
|
||||
BalanceReceiver interface {
|
||||
Balance(accountAddress string) (*Account, error)
|
||||
}
|
||||
|
||||
// ChequeID is identifier of user request for withdrawal of funds.
|
||||
ChequeID string
|
||||
|
||||
// CID type alias.
|
||||
CID = refs.CID
|
||||
|
||||
// SGID type alias.
|
||||
SGID = refs.SGID
|
||||
|
||||
// ChequeSignature contains public key and hash, and is used to verify signatures.
|
||||
ChequeSignature struct {
|
||||
Key *ecdsa.PublicKey
|
||||
Hash []byte
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrWrongSignature is raised when wrong signature is passed.
|
||||
ErrWrongSignature = internal.Error("wrong signature")
|
||||
|
||||
// ErrWrongPublicKey is raised when wrong public key is passed.
|
||||
ErrWrongPublicKey = internal.Error("wrong public key")
|
||||
|
||||
// ErrWrongChequeData is raised when passed bytes cannot not be parsed as valid Cheque.
|
||||
ErrWrongChequeData = internal.Error("wrong cheque data")
|
||||
|
||||
// ErrInvalidLength is raised when passed bytes cannot not be parsed as valid ChequeID.
|
||||
ErrInvalidLength = internal.Error("invalid length")
|
||||
|
||||
u16size = 2
|
||||
u64size = 8
|
||||
|
||||
signaturesOffset = chain.AddressLength + refs.OwnerIDSize + u64size + u64size
|
||||
)
|
||||
|
||||
// NewChequeID generates valid random ChequeID using crypto/rand.Reader.
|
||||
func NewChequeID() (ChequeID, error) {
|
||||
d := make([]byte, chain.AddressLength)
|
||||
if _, err := rand.Read(d); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
id := base58.Encode(d)
|
||||
|
||||
return ChequeID(id), nil
|
||||
}
|
||||
|
||||
// String returns string representation of ChequeID.
|
||||
func (b ChequeID) String() string { return string(b) }
|
||||
|
||||
// Empty returns true, if ChequeID is empty.
|
||||
func (b ChequeID) Empty() bool { return len(b) == 0 }
|
||||
|
||||
// Valid validates ChequeID.
|
||||
func (b ChequeID) Valid() bool {
|
||||
d, err := base58.Decode(string(b))
|
||||
return err == nil && len(d) == chain.AddressLength
|
||||
}
|
||||
|
||||
// Bytes returns bytes representation of ChequeID.
|
||||
func (b ChequeID) Bytes() []byte {
|
||||
d, err := base58.Decode(string(b))
|
||||
if err != nil {
|
||||
return make([]byte, chain.AddressLength)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Equal checks that current ChequeID is equal to passed ChequeID.
|
||||
func (b ChequeID) Equal(b2 ChequeID) bool {
|
||||
return b.Valid() && b2.Valid() && string(b) == string(b2)
|
||||
}
|
||||
|
||||
// Unmarshal tries to parse []byte into valid ChequeID.
|
||||
func (b *ChequeID) Unmarshal(data []byte) error {
|
||||
*b = ChequeID(base58.Encode(data))
|
||||
if !b.Valid() {
|
||||
return ErrInvalidLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Size returns size (chain.AddressLength).
|
||||
func (b ChequeID) Size() int {
|
||||
return chain.AddressLength
|
||||
}
|
||||
|
||||
// MarshalTo tries to marshal ChequeID into passed bytes and returns
|
||||
// count of copied bytes or error, if bytes len is not enough to contain ChequeID.
|
||||
func (b ChequeID) MarshalTo(data []byte) (int, error) {
|
||||
if len(data) < chain.AddressLength {
|
||||
return 0, ErrInvalidLength
|
||||
}
|
||||
return copy(data, b.Bytes()), nil
|
||||
}
|
||||
|
||||
// Equals checks that m and tx are valid and equal Tx values.
|
||||
func (m Tx) Equals(tx Tx) bool {
|
||||
return m.From == tx.From &&
|
||||
m.To == tx.To &&
|
||||
m.Type == tx.Type &&
|
||||
m.Amount == tx.Amount
|
||||
}
|
||||
|
||||
// Verify validates current Cheque and Signatures that are generated for current Cheque.
|
||||
func (b Cheque) Verify() error {
|
||||
data := b.marshalBody()
|
||||
for i, sign := range b.Signatures {
|
||||
if err := crypto.VerifyRFC6979(sign.Key, data, sign.Hash); err != nil {
|
||||
return errors.Wrapf(ErrWrongSignature, "item #%d: %s", i, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign is used to sign current Cheque and stores result inside b.Signatures.
|
||||
func (b *Cheque) Sign(key *ecdsa.PrivateKey) error {
|
||||
hash, err := crypto.SignRFC6979(key, b.marshalBody())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Signatures = append(b.Signatures, ChequeSignature{
|
||||
Key: &key.PublicKey,
|
||||
Hash: hash,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Cheque) marshalBody() []byte {
|
||||
buf := make([]byte, signaturesOffset)
|
||||
|
||||
var offset int
|
||||
|
||||
offset += copy(buf, b.ID.Bytes())
|
||||
offset += copy(buf[offset:], b.Owner.Bytes())
|
||||
|
||||
binary.BigEndian.PutUint64(buf[offset:], uint64(b.Amount.Value))
|
||||
offset += u64size
|
||||
|
||||
binary.BigEndian.PutUint64(buf[offset:], b.Height)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (b *Cheque) unmarshalBody(buf []byte) error {
|
||||
var offset int
|
||||
|
||||
if len(buf) < signaturesOffset {
|
||||
return ErrWrongChequeData
|
||||
}
|
||||
|
||||
{ // unmarshal UUID
|
||||
if err := b.ID.Unmarshal(buf[offset : offset+chain.AddressLength]); err != nil {
|
||||
return err
|
||||
}
|
||||
offset += chain.AddressLength
|
||||
}
|
||||
|
||||
{ // unmarshal OwnerID
|
||||
if err := b.Owner.Unmarshal(buf[offset : offset+refs.OwnerIDSize]); err != nil {
|
||||
return err
|
||||
}
|
||||
offset += refs.OwnerIDSize
|
||||
}
|
||||
|
||||
{ // unmarshal amount
|
||||
amount := int64(binary.BigEndian.Uint64(buf[offset:]))
|
||||
b.Amount = decimal.New(amount)
|
||||
offset += u64size
|
||||
}
|
||||
|
||||
{ // unmarshal height
|
||||
b.Height = binary.BigEndian.Uint64(buf[offset:])
|
||||
offset += u64size
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary is used to marshal Cheque into bytes.
|
||||
func (b Cheque) MarshalBinary() ([]byte, error) {
|
||||
var (
|
||||
count = len(b.Signatures)
|
||||
buf = make([]byte, b.Size())
|
||||
offset = copy(buf, b.marshalBody())
|
||||
)
|
||||
|
||||
binary.BigEndian.PutUint16(buf[offset:], uint16(count))
|
||||
offset += u16size
|
||||
|
||||
for _, sign := range b.Signatures {
|
||||
key := crypto.MarshalPublicKey(sign.Key)
|
||||
offset += copy(buf[offset:], key)
|
||||
offset += copy(buf[offset:], sign.Hash)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Size returns size of Cheque (count of bytes needs to store it).
|
||||
func (b Cheque) Size() int {
|
||||
return signaturesOffset + u16size +
|
||||
len(b.Signatures)*(crypto.PublicKeyCompressedSize+crypto.RFC6979SignatureSize)
|
||||
}
|
||||
|
||||
// UnmarshalBinary tries to parse []byte into valid Cheque.
|
||||
func (b *Cheque) UnmarshalBinary(buf []byte) error {
|
||||
if err := b.unmarshalBody(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := buf[:signaturesOffset]
|
||||
|
||||
count := int64(binary.BigEndian.Uint16(buf[signaturesOffset:]))
|
||||
offset := signaturesOffset + u16size
|
||||
|
||||
if ln := count * int64(crypto.PublicKeyCompressedSize+crypto.RFC6979SignatureSize); ln > int64(len(buf[offset:])) {
|
||||
return ErrWrongChequeData
|
||||
}
|
||||
|
||||
for i := int64(0); i < count; i++ {
|
||||
sign := ChequeSignature{
|
||||
Key: crypto.UnmarshalPublicKey(buf[offset : offset+crypto.PublicKeyCompressedSize]),
|
||||
Hash: make([]byte, crypto.RFC6979SignatureSize),
|
||||
}
|
||||
|
||||
offset += crypto.PublicKeyCompressedSize
|
||||
if sign.Key == nil {
|
||||
return errors.Wrapf(ErrWrongPublicKey, "item #%d", i)
|
||||
}
|
||||
|
||||
offset += copy(sign.Hash, buf[offset:offset+crypto.RFC6979SignatureSize])
|
||||
if err := crypto.VerifyRFC6979(sign.Key, body, sign.Hash); err != nil {
|
||||
return errors.Wrapf(ErrWrongSignature, "item #%d: %s (offset=%d, len=%d)", i, err.Error(), offset, len(sign.Hash))
|
||||
}
|
||||
|
||||
b.Signatures = append(b.Signatures, sign)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrNotEnoughFunds generates error using address and amounts.
|
||||
func ErrNotEnoughFunds(addr string, needed, residue *decimal.Decimal) error {
|
||||
return errors.Errorf("not enough funds (requested=%s, residue=%s, addr=%s", needed, residue, addr)
|
||||
}
|
||||
|
||||
func (m *Account) hasLockAcc(addr string) bool {
|
||||
for i := range m.LockAccounts {
|
||||
if m.LockAccounts[i].Address == addr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateLock checks that account can be locked.
|
||||
func (m *Account) ValidateLock() error {
|
||||
switch {
|
||||
case m.Address == "":
|
||||
return ErrEmptyAddress
|
||||
case m.ParentAddress == "":
|
||||
return ErrEmptyParentAddress
|
||||
case m.LockTarget == nil:
|
||||
return ErrEmptyLockTarget
|
||||
}
|
||||
|
||||
switch v := m.LockTarget.Target.(type) {
|
||||
case *LockTarget_WithdrawTarget:
|
||||
if v.WithdrawTarget.Cheque != m.Address {
|
||||
return errors.Errorf("wrong cheque ID: expected %s, has %s", m.Address, v.WithdrawTarget.Cheque)
|
||||
}
|
||||
case *LockTarget_ContainerCreateTarget:
|
||||
switch {
|
||||
case v.ContainerCreateTarget.CID.Empty():
|
||||
return ErrEmptyContainerID
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanLock checks possibility to lock funds.
|
||||
func (m *Account) CanLock(lockAcc *Account) error {
|
||||
switch {
|
||||
case m.ActiveFunds.LT(lockAcc.ActiveFunds):
|
||||
return ErrNotEnoughFunds(lockAcc.ParentAddress, lockAcc.ActiveFunds, m.ActiveFunds)
|
||||
case m.hasLockAcc(lockAcc.Address):
|
||||
return errors.Errorf("could not lock account(%s) funds: duplicating lock(%s)", m.Address, lockAcc.Address)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// LockForWithdraw checks that account contains locked funds by passed ChequeID.
|
||||
func (m *Account) LockForWithdraw(chequeID string) bool {
|
||||
switch v := m.LockTarget.Target.(type) {
|
||||
case *LockTarget_WithdrawTarget:
|
||||
return v.WithdrawTarget.Cheque == chequeID
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LockForContainerCreate checks that account contains locked funds for container creation.
|
||||
func (m *Account) LockForContainerCreate(cid refs.CID) bool {
|
||||
switch v := m.LockTarget.Target.(type) {
|
||||
case *LockTarget_ContainerCreateTarget:
|
||||
return v.ContainerCreateTarget.CID.Equal(cid)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Equal checks that current Settlement is equal to passed Settlement.
|
||||
func (m *Settlement) Equal(s *Settlement) bool {
|
||||
if s == nil || m.Epoch != s.Epoch || len(m.Transactions) != len(s.Transactions) {
|
||||
return false
|
||||
}
|
||||
return len(m.Transactions) == 0 || reflect.DeepEqual(m.Transactions, s.Transactions)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue