openbazaar-go
openbazaar-go copied to clipboard
Improved Currency Precision
Today, the network assumes a certain precision when reading integer values of currency. OpenBazaar should allow each currency to be represented as an int with varying amounts of precision.
Considerations for BigInt representation (replace instances of json.Number
in the Scope below with whatever solution we pick):
- json.Number
- https://github.com/shopspring/decimal
Scope
Protobuf Schema
- create
pb.CurrencyValue
proto to track high-precision value of a currency- (smallest known precision is 1e-18)
proto CurrencyDefinition {
string code
json.Number divisibility
}
proto CurrencyValue {
CurrencyDefinition currency
json.Number value
}
- restrict prior schema version to BTC, BCH, LTC, ZEC as accepted currencies for transactions (recommend creating non-proto type
pb.Listing
to manage version-specific marshal/unmarshal of JSON contracts and proper mapping of changing fields between currentv4
and futurev5
Listings.... Ex:pb.Listing.Price()
can return a valid price regardless of source being v4 or v5.) - bump Listing to new schema
v5
- update
pb.Listing.Metadata.Coin[Type|Divisibility]
to usepb.CurrencyDefinition
- update
pb.ListingItem.Price
to usepb.CurrencyValue
- merge
pb.Listing.Metadata.PricingCurrency
withpb.Listing.ListingItem.Price
intopb.CurrencyValue
- in
pb.Listing.BuyerOrder
updatepb.Order_Payment.Amount
andpb.Order_Payment.Coin
to usepb.CurrencyValue
- replace
ModeratorInfo.Price
usage withpb.CurrencyValue
- replace
CoinType
andpb.CoinDivisibility
to usepb.CurrencyDefinition
- replace
- update
pb.ListingItem.Skus.Quantity
to usejson.Number
(to support larger inventory requirements in CRYPTOLISTING listings) - update
pb.Listing.BuyerOrder.RefundFee
to usepb.CurrencyValue
- in
pb.Listing.BuyerOrder.Items
collapseQuantity
andQuantity64
itojson.Number
- properly represent that
< v2
schema expectsQuantity
and>= v3
requiresQuantity64
-
>= v5
requiresjson.Number
to support (now) larger inventory requirements for Cryptocurrency listings
- properly represent that
- in
pb.Listing.BuyerOrder
updateAmount
andCoin
to usepb.CurrencyValue
- update BuyerOrder schema version?
- update
pb.VendorOrderConfirmation.RequestedAmount
topb.CurrencyValue
- update
pb.VendorOrderFulfillment.Payout.PayoutFeePerByte
topb.CurrencyValue
- update
pb.Dispute.Outputs[].Value
topb.CurrencyValue
- update
pb.TransactionRecord.Value
topb.CurrencyValue
- in
pb.ShippingOption.Service
updatePrice
andAdditionalItemPrice
topb.CurrencyValue
- update
pb.Coupon.PriceDiscount
topb.CurrencyValue
- update
*Note: I know there is redundant data with the currency type being expressed in every place where a value is represented. This was done intentionally so that knowledge of implicit relationships between fields aren't required to make sense of the data. Denormalizing data here also doesn't hurt us as we can ensure the implicit relationships are intact more easily with tests and validations.
Other Data Models
- Notifications
- New Order Notification
- in
ListingPrice
mergeAmount
,CurrencyCode
, andCoinDivisibility
intopb.CurrencyValue
- in
- New Order Notification
- Profile
- ModeratorInfo needs to be migrated to use the new proto schema
- does prior ModeratorInfo schema needs to be handled for other clients when selecting a Moderator who hasn't migrated?
- ModeratorInfo needs to be migrated to use the new proto schema
SQLite data
- cases
- migrate
buyerOutputs
andvendorOutputs
which contain serialized sets ofvalue
s:[{"hash":"85499ec9b053772fc9ce95b695de1aa1fe83ea7daf44cda52ab27aac13a679d6","value":386091}]
- migrate
- inventory
- migrate
count
from typeinteger
totext
?
- migrate
- notifications
- migrate
serializedNotification
to new model schema
- migrate
- purchases/sales (consider merging these into one table and add a
type
column to differentiate?)- migrate
total
from typeinteger
totext
? - migrate
transactions
which contain serialized sets ofvalue
s:[{"Txid":"85499ec9b053772fc9ce95b695de1aa1fe83ea7daf44cda52ab27aac13a679d6","Index":0,"Value":386091,"Address":"prv2kgx6m8ynestdmvhlndm237hhqgr4yce9q0a090","Spent":true,"Timestamp":"2019-01-23T22:36:37.141428851-05:00"},{"Txid":"dde5a08c860faf7bd1b56d1528640fdbefd659ef798cfddb33e77b8b187fec4b","Index":0,"Value":-386091,"Address":"prv2kgx6m8ynestdmvhlndm237hhqgr4yce9q0a090","Spent":true,"Timestamp":"2019-01-23T22:50:58.659452463-05:00"},{"Txid":"dde5a08c860faf7bd1b56d1528640fdbefd659ef798cfddb33e77b8b187fec4b","Index":0,"Value":-386091,"Address":"prv2kgx6m8ynestdmvhlndm237hhqgr4yce9q0a090","Spent":false,"Timestamp":"2019-01-24T04:07:23.949652851Z"}]
- migrate
- stxos/txns/utxos (consider merging these into one table with a
type
differentiator?)- migrate
value
frominteger
totext
?
- migrate
API changes
The JSON representation of the pb.CurrencyValue
type above should be used in place of int
usages for amount. The typical transformation will look similar to the following other indicated otherwise:
A value of 0.12345678 BTC might be represented like this before:
{
"data": "value",
"amount": 12345678, // this key is inconsistent across calls
"coin": "BTC", // this key is inconsistent across calls
"otherdata": "othervalue"
}
This value would be represented in scientific notification using value
for all significant digits and magnitude
for the exponential. Thus 0.12345678 BTC, or 12345678e-8 BTC would be represented as:
{
"data": "value",
"amount": { // this key would match the replaced key in the BEFORE case
"value": "12345678",
"magnitude": -8,
"currency": {
"code": "BTC", // this key would always be `code`
"divisibility": 8
}
},
"otherdata": "othervalue"
}
-
POST /ob/purchase
(POSTPurchase
) response
wallet-interface changes
Note: Some objects (like pb.CurrencyValue
and pb.CurrencyDefinition
) will need to be defined within the interface's package. Recommend that ob-go abstracts the wallet-interface's version of these type with it's own to cast into as the value returns from a wallet-interface call. wallet.Type
indicates a defined Type
local to the package wallet
.
- create
wallet.CurrencyValue
andwallet.CurrencyDefinition
which mirrors the data of the above protobufs - move
CurrencyCode() string
response into broaderCurrencyDefinition() wallet.CurrencyDefinition
- update
IsDust(amount int64) bool
toIsDust(amount wallet.CurrencyValue) bool
- update
Balance() (unconfirmed, confirmed int64)
toBalance() (unconfirmed, confirmed wallet.CurrencyValue)
- update
ChainTip() (uint32, chainhash.Hash)
toChainTip() (uint64, chainhash.Hash)
- Note: won't overflow until the year 4743 assuming 20-second ETH blocks which is our 'fastest' supported coin today, but would support even faster blocks in the future. None of the above scope considers a change of the ChainTip representation and must be scoped in addition to this.- update
GetConfirmations(txid chainhash.Hash) (confirms, atHeight uint32, err error)
to have same size uint asChainTip
- update
TransactionCallback.Height
to match type
- update
- update
GetFeePerByte(feeLevel FeeLevel) uint64
toGetFeePerByte(feeLevel FeeLevel) wallet.CurrencyValue
- update
Spend(amount int64, addr btc.Address, feeLevel FeeLevel, referenceID string) (*chainhash.Hash, error)
to usewallet.CurrencyValue
- update
EstimateFee([]TransactionInput, []TransactionOutput, feePerByte uint64) uint64
toEstimateFee([]TransactionInput, []TransactionOutput, feePerByte wallet.CurrencyValue) wallet.CurrencyValue
- update
EstimateSpendFee(amount int64, feeLevel FeeLevel) (uint64, error)
toEstimateSpendFee(amount wallet.CurrencyValue, feeLevel FeeLevel) (wallet.CurrencyValue, error)
- update
CreateMultisigSignature(ins []TransactionInput, outs []TransactionOutput, key *hd.ExtendedKey, redeemScript []byte, feePerByte uint64) ([]Signature, error)
to usewallet.CurrencyValue
instead ofuint64
onfeePerByte
- update
Multisign(ins []TransactionInput, outs []TransactionOutput, sigs1 []Signature, sigs2 []Signature, redeemScript []byte, feePerByte uint64, broadcast bool) ([]byte, error)
to usewallet.CurrencyValue
instead ofuint64
onfeePerByte
- change
TransactionCallback
- update
Value
to usewallet.CurrencyValue
- update
- update
TransactionOutput.Value
to usewallet.CurrencyValue
- update
TransactionRecord.Value
to usewallet.CurrencyValue
wallet-interface dependencies
- update each wallet implementation to support new wallet-interface changes
- BTC
- BCH
- LTC
- ZEC
- ETH
auxiliary system dependencies
- update inventory to support ETH precision (whenever we support ETH/ERC-20 cryptolisting trades) (scope?)
- update search crawler/index (scope?)
Thanks for starting this @placer14!
Washington and I have been talking through this scope and this is what we want to recommend:
We're in favor of a lowest LoE change that get us what we need with minimal changes to the protobuf schema and subsequently the codebase itself. Rather than the schema you proposed, we'd like to go for a straightforward change to price amounts from uint64
(for current v4
) to string
(for future v5
). The divisibility of each coin should be configured at the wallet interface level.
The guiding principle is : Use big.Int for computation, use string for storage/representation.
Wallet interface changes
Add Divisibility to wallet-interface#Coin struct in datastore.go
type Coin interface {
String() string
CurrencyCode() string
Divisibility() int
}
func (c *CoinType) Divisibility() int {
switch *c {
case Ethereum:
return 18
case TestnetEthereum:
return 18
default:
return 8
}
}
Change Utxo#Value to be string
in place of current int64
.
Change Txn#Value to be string
in place of current int64
.
Change TransactionCallback#Value to be string
in place of current int64
.
Change TransactionOutput#Value to be string
in place of current int64
.
Change TransactionInput#Value to be string
in place of current int64
.
Change TransactionRecord#Value to be string
in place of current int64
.
Current
// EstimateSpendFee should return the anticipated fee to transfer a given amount of coins
// out of the wallet at the provided fee level. Typically this involves building a
// transaction with enough inputs to cover the request amount and calculating the size
// of the transaction. It is OK, if a transaction comes in after this function is called
// that changes the estimated fee as it's only intended to be an estimate.
//
// All amounts should be in the coin's base unit (for example: satoshis).
EstimateSpendFee(amount int64, feeLevel FeeLevel) (uint64, error)
Changes to
EstimateSpendFee(amount big.Int, feeLevel FeeLevel) (big.Int, error)
Current
// EstimateFee should return the estimate fee that will be required to make a transaction
// spending from the given inputs to the given outputs. FeePerByte is denominated in
// the coin's base unit (for example: satoshis).
EstimateFee(ins []TransactionInput, outs []TransactionOutput, feePerByte uint64) uint64
Changes to
EstimateFee(ins []TransactionInput, outs []TransactionOutput, feePerByte big.Int) big.Int
Current
// Spend transfers the given amount of coins (in the coin's base unit. For example: in
// satoshis) to the given address using the provided fee level. Openbazaar-go calls
// this method in two places. 1) When the user requests a normal transfer from their
// wallet to another address. 2) When clicking 'pay from internal wallet' to fund
// an order the user just placed.
// It also includes a referenceID which basically refers to the order the spend will affect
Spend(amount int64, addr btc.Address, feeLevel FeeLevel, referenceID string) (*chainhash.Hash, error)
Changes to
Spend(amount big.Int, addr btc.Address, feeLevel FeeLevel, referenceID string) (*chainhash.Hash, error)
Current
// Balance returns the confirmed and unconfirmed aggregate balance for the wallet.
// For utxo based wallets, if a spend of confirmed coins is made, the resulting "change"
// should be also counted as confirmed even if the spending transaction is unconfirmed.
// The reason for this that if the spend never confirms, no coins will be lost to the wallet.
//
// The returned balances should be in the coin's base unit (for example: satoshis)
Balance() (confirmed, unconfirmed int64)
Changes to
Balance() (confirmed, unconfirmed big.Int)
Current
// IsDust returns whether the amount passed in is considered dust by network. This
// method is called when building payout transactions from the multisig to the various
// participants. If the amount that is supposed to be sent to a given party is below
// the dust threshold, openbazaar-go will not pay that party to avoid building a transaction
// that never confirms.
IsDust(amount int64) bool
Changes to
IsDust(amount big.Int) bool
Current
// GetFeePerByte returns the current fee per byte for the given fee level. There
// are three fee levels ― priority, normal, and economic.
//
//The returned value should be in the coin's base unit (for example: satoshis).
GetFeePerByte(feeLevel FeeLevel) uint64
Changes to
GetFeePerByte(feeLevel FeeLevel) big.Int
Protobuf Changes
In moderator.proto
,
this L26
message Price {
string currencyCode = 1;
uint64 amount = 2; // Bitcoins must be in satoshi
}
changes to
message Price {
string currencyCode = 1;
string amount = 2; // amount must be in satoshi/wei
}
In contracts.proto
,
this L61
message Item {
string title = 1;
string description = 2;
string processingTime = 3;
uint64 price = 4;
...
changes to
message Item {
string title = 1;
string description = 2;
string processingTime = 3;
string price = 4;
...
this L86
message Sku {
repeated uint32 variantCombo = 1;
string productID = 2;
int64 surcharge = 3;
int64 quantity = 4; // Not saved with listing
}
changes to
message Sku {
repeated uint32 variantCombo = 1;
string productID = 2;
string surcharge = 3;
int64 quantity = 4; // Not saved with listing
}
this L103
message ShippingOption {
string name = 1;
ShippingType type = 2;
repeated CountryCode regions = 3;
repeated Service services = 5;
enum ShippingType {
LOCAL_PICKUP = 0;
FIXED_PRICE = 1;
}
message Service {
string name = 1;
uint64 price = 2;
string estimatedDelivery = 3;
uint64 additionalItemPrice = 4;
}
}
changes to
message ShippingOption {
string name = 1;
ShippingType type = 2;
repeated CountryCode regions = 3;
repeated Service services = 5;
enum ShippingType {
LOCAL_PICKUP = 0;
FIXED_PRICE = 1;
}
message Service {
string name = 1;
string price = 2;
string estimatedDelivery = 3;
string additionalItemPrice = 4;
}
}
this L129
message Coupon {
string title = 1;
oneof code {
string hash = 2;
string discountCode = 3;
}
oneof discount {
float percentDiscount = 5;
uint64 priceDiscount = 6;
}
}
changes to
message Coupon {
string title = 1;
oneof code {
string hash = 2;
string discountCode = 3;
}
oneof discount {
float percentDiscount = 5;
string priceDiscount = 6;
}
}
this L142
message Order {
string refundAddress = 1;
uint64 refundFee = 2;
...
changes to
message Order {
string refundAddress = 1;
string refundFee = 2;
...
this L185
message Payment {
Method method = 1;
string moderator = 2;
uint64 amount = 3; // Satoshis
...
changes to
message Payment {
Method method = 1;
string moderator = 2;
string amount = 3; // Satoshis or Wei
...
this L203
message OrderConfirmation {
string orderID = 1;
google.protobuf.Timestamp timestamp = 2;
// Direct payments only
string paymentAddress = 3;
uint64 requestedAmount = 4;
repeated RatingSignature ratingSignatures = 5;
}
changes to
message OrderConfirmation {
string orderID = 1;
google.protobuf.Timestamp timestamp = 2;
// Direct payments only
string paymentAddress = 3;
string requestedAmount = 4;
repeated RatingSignature ratingSignatures = 5;
}
this L282
message Payout {
repeated BitcoinSignature sigs = 1;
string payoutAddress = 2;
uint64 payoutFeePerByte = 3;
}
changes to
message Payout {
repeated BitcoinSignature sigs = 1;
string payoutAddress = 2;
string payoutFeePerByte = 3;
}
this L329
message DisputeResolution {
google.protobuf.Timestamp timestamp = 1;
string orderId = 2;
string proposedBy = 3;
string resolution = 4;
Payout payout = 5;
repeated bytes moderatorRatingSigs = 6; // Used in ratings
message Payout {
repeated BitcoinSignature sigs = 1;
repeated Outpoint inputs = 2;
Output buyerOutput = 3;
Output vendorOutput = 4;
Output moderatorOutput = 5;
message Output {
oneof scriptOrAddress {
string script = 1;
string address = 3;
}
uint64 amount = 2;
}
}
}
changes to
message DisputeResolution {
google.protobuf.Timestamp timestamp = 1;
string orderId = 2;
string proposedBy = 3;
string resolution = 4;
Payout payout = 5;
repeated bytes moderatorRatingSigs = 6; // Used in ratings
message Payout {
repeated BitcoinSignature sigs = 1;
repeated Outpoint inputs = 2;
Output buyerOutput = 3;
Output vendorOutput = 4;
Output moderatorOutput = 5;
message Output {
oneof scriptOrAddress {
string script = 1;
string address = 3;
}
string amount = 2;
}
}
}
this L365
message Refund {
string orderID = 1;
google.protobuf.Timestamp timestamp = 2;
repeated BitcoinSignature sigs = 3;
TransactionInfo refundTransaction = 4;
string memo = 5;
message TransactionInfo {
string txid = 1;
uint64 value = 2;
}
}
changes to
message Refund {
string orderID = 1;
google.protobuf.Timestamp timestamp = 2;
repeated BitcoinSignature sigs = 3;
TransactionInfo refundTransaction = 4;
string memo = 5;
message TransactionInfo {
string txid = 1;
string value = 2;
}
}
In api.proto
this L45
message TransactionRecord {
string txid = 1;
int64 value = 2;
uint32 confirmations = 3;
uint32 height = 4;
google.protobuf.Timestamp timestamp = 5;
}
changes to
message TransactionRecord {
string txid = 1;
string value = 2;
uint32 confirmations = 3;
uint32 height = 4;
google.protobuf.Timestamp timestamp = 5;
}
API Changes
int => string
type conversions for amounts will need to be changed in several locations. Beyond that, no special additions are required to the existing responses.
The conversion from string
to big.Int
will happen in the core package.
Functions such as core/order.goL76 Purchase
should return string
for amount.
Repo/Sqlite DB changes
Migrate exisiting amount fields from int
to string
.
Sqlite does not support modifying column type as described here: https://www.sqlite.org/lang_altertable.html
The solution given is to create a temp table with the required column changes, start a transaction, insert data from old table to temp table, drop the old table, finally rename the temp table to the old table name.
The proposed wallet.Currency seems to be an overkill for the following reasons:
- The coin does not change for a contract
- There are no intermediate values being stored for a coin. All the amounts are stored in the finest granularity.
- Like the coin, there is no change in the divisibility of a coin in the contract.
- Till the time that Openbazaar does intend to support multi coin contract with varying divisibility, the proposed solution adds a lot more complexity for little mileage.
Also, shopspring/decimal is not being considered because there is no float value storage or processing in ordering.
Overall we believe this is a much simpler and direct path forward to supporting ETH-level precision that minimises schema and codebase changes.
After discussing the suggestion in private, @amangale is on board with the plan as originally described above. For historical purposes, my observations are outlined below:
Some thoughts after reviewing Ashwin's proposal:
(TL;DR)
- Ashwin’s proposal doesn’t reduce scope much compared to the original plan of inlining divisibility within the contract. (See 3.)
- The main delta appears to be rejecting the custom type in favor of big.Int and implicitly defined data. The difference between supporting big.Int and custom types:
- Cons: Minor risk in implementing
MarshalJSON
and accessor functions. More complex structure. - Pros: A type which can answer
IsValid()
no matter what version it is. Access ambiguously defined data in the wire based on version without the consumer knowing how. Cleans up existing debt.
- There are aspects of Ashwin's approach which does increase risk/tech debt. (See 1, 2)
- Does not consider transient or legacy data support.
- Requires a piecemeal logic in specific areas than something more holistic to the app's ecosystem
- If we do the wallet-only fix first as we have suggested doing, Ashwin’s proposal causes bigger tech debt than just inlining the currency divisibility into the contract. (See 1.)
This proposal does not address in-flight contracts and our ability to support them. Providing a proper abstraction (repo.RicardianContract) which is version-aware and can be a place to hang validation/business-logic/etc is going to be needed sooner or later. If we avoid this abstraction, we're simply sprinkling copies of this logic throughout the codebase which we'll need to later reconcile (more debt).
I would personally prefer to see:
(repo.RicardianContract).contract.BuyerPayment().Definition.Divisibility
or
(repo.RicardianContract).contract.BuyerPayment().Divisibility
than having this sprinkled across our codebase:
if contract.Metadata.Version > 5 {
divisibility = wallet.GetWalletForCurrencyCode(coin).CoinDivisibility()
} else {
divisibility = MagicDivisibilityIntThatIsNotLinkedToContractData
}
Considering this will be a two-step approach in which the first step allows ETH contracts containing "satoshi-wei" to be used on the network, there is no explicit divisibility data associated with the amounts to know the correct magnitude when we eventually implement full precision support in step two. Assuming we did this, in step two we'd need a new field with explicit divisibility or a different name with yet more logic in the server. It would seem that adding divisibility in the first place would be prudent.
And if we're already going down the path of including this explicit data, good engineering would be to properly represent this data instead of injecting string arguments throughout our interfaces and requiring the engineer to digest and understand the code body in order to understand what the "string" is. Beyond semantics, it's poor because there's no place to hang your behavior specific to CurrencyValues without writing and creating helpers all over your code. This is not only debt, but it's high-interest.
Responses for your arguments of overkill for a wallet.CurrencyValue
and wallet.CurrencyDefinition
type:
Till the time that Openbazaar does intend to support multi coin contract with varying divisibility, the proposed solution adds a lot more complexity for little mileage.
I'll start here as it's most important. I'm not sure what complexity we're avoiding in your proposal. The only main delta that I can see (forgive me if I'm wrong here as it was hard to compare them) is that you rejected the custom type usage in favor of a string representation throughout the app. All of the same data points are being touched to change the type to a more basic representation (to use big.Int
with implicit data instead of a descriptive type). Objectively, the only added complexity I see is writing MarshalJSON
for the Currency*
types and accessor methods.
The coin does not change for a contract Like the coin, there is no change in the divisibility of a coin in the contract.
If you're suggesting that one expression of the coin metadata is enough for an entire contract, I disagree. When we describe a Cryptocurrency Listing, the coin can be represented as a product of purchase as well as a means of payment. This necessitates independant representation of these values. If we're expressing the same data in both places, a consistant representation of the data would be preferred.
Here's an example listing:
{
"vendorListings": [{
"slug": "bch-eth",
"vendorID": {
"peerID": "QmbweQKGWNqjoPqBjePVqnTCrFsK62oWYeVe8WeqXwwyS8",
"handle": "",
"pubkeys": {
"identity": "CAESIM8hU+iL8iGmlzAIMI1OGqRMWXRwNGcbW8nXOZh0eurL",
"bitcoin": "A/n06c7eeuBo9/mWLgXQGacFI7+gnAXBlLkquCfv0srk"
},
"bitcoinSig": "MEUCIQCftGYX8xF+oGEZ+scMqvEasqLvh0JQ92KO81ZNg3bfMAIgIQNOpyW7eIfR50YefdF/aj9jofL491PRgFvdYHUBT24="
},
"metadata": {
"version": 4,
"contractType": "CRYPTOCURRENCY",
"format": "MARKET_PRICE",
"expiry": "2037-12-31T05:00:00.000Z",
"acceptedCurrencies": ["BCH"],
"pricingCurrency": "",
"language": "",
"escrowTimeoutHours": 1,
"coinType": "ETH",
=> "coinDivisibility": 100000000, // payment or inventory?
"priceModifier": 5
},
"item": {
"title": "BCH-ETH",
"description": "Cheap cryptos!",
"processingTime": "",
"price": 0,
"nsfw": false,
"tags": [],
"images": [{
"filename": "786a.gif",
"original": "zb2rhn3zyAYj17JenXKGpJPBTRn68g8PJ6ZMXWFvRr7BTLLib",
"large": "zb2rhh2BLagyb4yG8kSCTtrChHS45JAE2rLp92Zi3RwdZKK1B",
"medium": "zb2rhnppMGkZYp6Zg7Qf2irDH9z1ZM5jc2VcAXfy6mEnifoEy",
"small": "zb2rhfMZFaaWZxZGvkqAPCMUbmdxNHhAaCby5XCkRrV13bew8",
"tiny": "zb2rhfN4RQyNP6eZszvEBfwBRMZxaysoqF72MYWPKoofV5AQr"
}],
"categories": [],
"grams": 0,
"condition": "",
"options": [],
"skus": [{
"productID": "",
"surcharge": 0,
"quantity": 0
}]
},
"shippingOptions": [],
"coupons": [],
"moderators": [],
"termsAndConditions": "",
"refundPolicy": ""
}],
"buyerOrder": {
"refundAddress": "qreeeu24nq4f3pc5wel4ucwaxpg3ug35lg5t33566e",
"refundFee": 0,
"shipping": {
"shipTo": "",
"address": "",
"city": "",
"state": "",
"postalCode": "",
"country": "NA",
"addressNotes": ""
},
"buyerID": {
"peerID": "Qmb8CvXLDChhU5YBApLKbs8ixfWh9X6dmPHbp6B5tjFqB1",
"handle": "",
"pubkeys": {
"identity": "CAESIHh9uLUPanFQVVIFkYsypxCezy/JYAx/pqu2cjUYIDZo",
"bitcoin": "A0/wPzw14M6i/l+INxQY3JdvVOkV2lLNev+TxVjGBxS6"
},
"bitcoinSig": "MEQCIEx4s0UxxMK44+6KZhJAyQE64DtH9IJg0u38bkXbvfJxAiAGy6iVzHJwaMB3Njaq73+RrQ91VQnhqyc26o4GGi9XSg=="
},
"timestamp": "2019-01-31T21:12:20.170965750Z",
"items": [{
"listingHash": "zb2rhgg9mdHRub6DaBKBTu8Akg9UYYdz4azpBpdVKKNhTqMFB",
"quantity": 0,
=> "quantity64": 1, // 1 what?
=> "preciseQuantity": "1", // future field inclusion makes these quantity fields ambiguous
"memo": "",
"paymentAddress": "Addresss"
}],
"payment": {
"method": "ADDRESS_REQUEST",
"moderator": "",
"amount": 0,
"chaincode": "",
"address": "",
"redeemScript": "",
"coin": "BCH"
},
"ratingKeys": ["Ar1GMyRSUfS+rVZ7k8SOktFPISgU1wBu3jJkQ2Sci2eP"],
"alternateContactInfo": "",
"version": 2
},
"vendorOrderConfirmation": {
"orderID": "QmVMf7iXQxvgbo1r6JoiT22Zyz334mV6xoQmN5bLfB5LVY",
"timestamp": "2019-01-31T21:12:21.258215870Z",
"paymentAddress": "qpf0sv9d5q9rurg40z3es9270j7hktv9fsncfegvlk",
"requestedAmount": 0
},
"signatures": [{
"section": "LISTING",
"signatureBytes": "ga81iV5H6UnFQ1d8HVBjLapNCvLkgFSBd2ziNMQ2Bez7DHXcrL5+qynPeT2TbqPIUvqySAUQleNDSRTEEWRWDA=="
}, {
"section": "ORDER",
"signatureBytes": "FzXD5GmxHgdzSL9FkgHeSs4BLApG3t8dOTulJEwCKEdP9tOWIZU8ZWLf5veiuw/Rn9OtSVgBAzz6pHeI8K4pCw=="
}, {
"section": "ORDER_CONFIRMATION",
"signatureBytes": "m/mOeypWiz9epxwrmcENJjSVQF6ITpaGnvCCtNzF7k/YO8eTSMKr45ZqitAznhZsQbKl4O99mgAakqlODGlzAw=="
}]
}
If you're suggesting that the coin will never change over time (as in from the time that contract is created), I disagree for two reasons. Contracts have technically infinite lifetime as new orders may be executed at any time in the future. While there's precedent for us to void old versions of contracts with new versions of the server, this has been in extreme scenarios and is avoided at all costs. (And is really just a cost of not paying the debt outlined in point (1) above.) More importantly, is the case I outlined in point (2) above. If the wallet is accepting satoshi-wei now and later actual-wei and there's no way to differentiate that in the contract, the contract suddenly becomes a lie (for the value spent) because the relationship between the contract and the server API are non-existent and there's no way to resolve that.
Not to mention the cases where a coin would hardfork to create a larger supply (particularly in the case of utility coins).
There are no intermediate values being stored for a coin. All the amounts are stored in the finest granularity.
No argument from me there. I agree less complexity is preferred over more complexity. But (2) describes why we need a more complex representation. This is an obvious technical case we need to support if we decided to take a two-step approach.
Will prepare planning out the above epic into executable steps for planning and discussion.
Additionally, we briefly discussed the plan of attack for this large scope w/ @drwasho and @amangale. The idea was raised to begin in the ETH wallet implementation as it is the highest risk and could be nice to have the chance to abort the plan if something comes up. I agree with this and with this thought in mind, here's my thinking about how to execute the beginning:
- wallet interfaces for CurrencyValue and CurrencyDefinition. Prefer these types to be separate so a wallet-interface could deliver
func Definition() *wallet.CurrencyDefinition
in place offunc CurrencyCode() string
- Support serializing new data into the datastore (for ETH only right now, keeping in mind how this must work for all wallet implementations)
- datastore migrations which support the wallet interface (stxos/txns/utxos contain
balance
whose type needs to be updated) - Update wallet-interface to use new data types
Let's plan to talk through this scope today.